.NET MAUI ์ตํžˆ๊ธฐ - slog

์ด ํŽ˜์ด์ง€๋ฅผ ํ†ตํ•ด ํ•˜๋‚˜์”ฉ MAUI์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์„ ์ตํ˜€๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๋Š” ์ƒ๋‹นํžˆ ์ž์„ธํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋จผ์ € MAUI๋Š” ๋ฌด์—‡์ผ๊นŒ์š”? ๋ฌธ์„œ์—์„œ๋Š” .NET ๋‹ค์ค‘ ํ”Œ๋žซํผ ์•ฑ UI๋กœ ์ •์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. WPF๋˜๋Š” UWP๋˜๋Š” Xamarin.Forms ์ฒ˜๋Ÿผ XAML์„ ์ด์šฉํ•ด์„œ ๋ชจ๋ฐ”์ผ ๋ฐ ๋ฐ์Šคํฌํ†ฑ ์•ฑ์„ ๋‹ค์ค‘ ํ”Œ๋žซํผ์œผ๋กœ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

image

์ € ๊ทธ๋ฆผ ๋ณด๋ฉด Xamarin.Forms๋ž‘ ๊ฐ™์€๋ฐ? ์ƒ๊ฐ์ด ๋“œ๋Š”๋ฐ ๋งž์Šต๋‹ˆ๋‹ค. Xamarin.Forms๋ฅผ ์„ฑ๋Šฅ ๋ฐ ํ™•์žฅ์„ฑ์„ ์œ„ํ•ด ๋‹ค์‹œ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๊ธฐ๋Šฅ์œผ๋กœ ๊ตฌํ˜„๋œ ๋™์ผํ•œ ์•ฑ์„ Xamarin.Forms ๋ฐ MAUI ์„ฑ๋Šฅ๋น„๊ต ์ •๋ณด๊ฐ€ ์žˆ์œผ๋ฉด ์ข‹๊ฒ ๋„ค์š” ^^;

MAUI๋Š” ๋ˆ„๊ตฌ๋ฅผ ์œ„ํ•œ ๊ฒƒ์ผ๊นŒ์š”? .NET ๋ฐ C#์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํฌ๋กœ์Šคํ”Œ๋žซํผ ๋ชจ๋ฐ”์ผ ๋ฐ ๋ฐ์Šคํฌํ†ฑ ์•ฑ์„ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•œ ์„œ๋น„์Šค ๊ฐœ๋ฐœ ํšŒ์‚ฌ ๋˜๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

ํฌ๋กœ์Šคํ”Œ๋žซํผ ๊ฐœ๋ฐœ ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ Xamarin.Forms์˜ ์ง€๋ถ„์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

image
(How large is Flutter vs React Native community in 2021 - DEV Community)

GitHub ๋ณ„์€ ๊ฐœ๋ฐœ์ž์˜ ์ง€์ง€๋ฅผ ์˜๋ฏธํ•˜๋ฏ€๋กœ Flutter๊ฐ€ ๊ฐ•๋ ฅํ•œ ์ง€์ง€๋ฅผ ๋ฐ›๊ณ  ์žˆ๋Š”๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4๊ฐœ์˜ ์ข‹์•„์š”

Visual Studio 2022 Preview๋ฅผ ํ†ตํ•ด ๋ชจ๋ฐ”์ผ ๊ฐœ๋ฐœ ์›Œํฌ๋กœ๋“œ๋ฅผ ์„ ํƒํ•˜๋ฉด .NET MAUI ๊ฐœ๋ฐœํ™˜๊ฒฝ์ด ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

image

์ƒˆ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ์—์„œ .NET MAUI ์•ฑ์œผ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด MAUI ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์ด ์ƒ์„ฑ๋˜๋Š”๋ฐ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋‹จ์ผ ํ”„๋กœ์ ํŠธ๋กœ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—

image

ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ๊ฐ€ ์ข€ ๋” ํŽธํ•ด์กŒ์Šต๋‹ˆ๋‹ค.

๊ธ€๊ผด ๋ฐ ์ด๋ฏธ์ง€์™€ ๊ฐ™์€ ๋ฆฌ์†Œ์Šค๋Š” ๊ณตํ†ต์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ”Œ๋ ›ํผ์— ์˜์กด์ ์ธ ์ž์—ฐ์€ ํ”Œ๋ ›ํผ ๋ณ„๋กœ ๋ณ„๋„๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

ํƒ€๊ฒŸ ํ”Œ๋žซํผ์— ๋”ฐ๋ผ Platforms์˜ ๊ฐ ๋””๋ ‰ํ† ๋ฆฌ๋Š” ์ปดํŒŒ์ผ๋˜๊ฑฐ๋‚˜ ์ปดํŒŒ์ผ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž ์„ค์ •์— ๋”ฐ๋ผ ํ”Œ๋žซํผ๋ณ„ ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-android')) != true">
  <Compile Remove="**\**\*.Android.cs" />
  <None Include="**\**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-ios')) != true AND $(TargetFramework.StartsWith('net6.0-maccatalyst')) != true">
  <Compile Remove="**\**\*.iOS.cs" />
  <None Include="**\**\*.iOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true ">
  <Compile Remove="**\*.Windows.cs" />
  <None Include="**\*.Windows.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

์ผ๋‹จ ์ด๋ถ€๋ถ„์€ ์•„์ง์€ ๊นŠ๊ฒŒ ๋“ค์–ด๊ฐ€์ง„ ์•Š๊ณ  ํ•˜๋‚˜์˜ ํ”„๋กœ์ ํŠธ๋กœ ๋‹ค์ค‘ ํ”Œ๋ ›ํผ์— ๋Œ€ํ•œ ๊ตฌ์„ฑ์ด ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค ์ •๋„๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.

3๊ฐœ์˜ ์ข‹์•„์š”

๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ์—์„œ ์ œ๊ณตํ•˜๋Š” MAUI ๋ฌธ์„œ์˜ ํ’ˆ์งˆ์€ ๊ณผ๊ฑฐ์— ๋น„ํ•ด์„œ ์ƒ๋‹นํžˆ ๋†’์€ ํŽธ์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ๋งŒ์œผ๋กœ๋„ ์–ด๋Š์ •๋„์˜ ํ•™์Šต์ด ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™๋„ค์š”.

XAML์— ๊ด€๋ จ๋œ ๋‚ด์šฉ์€ WPF๋‚˜ UWP๋ฅผ ํ•ด๋ณธ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋„˜์–ด๊ฐ€๋„ ๋ฉ๋‹ˆ๋‹ค.

2๊ฐœ์˜ ์ข‹์•„์š”

์‹œ๋ฉ˜ํ‹ฑ

์‹œ๋ฉ˜ํ‹ฑ ์ •๋ณด๋Š” ์ผ์ข…์˜ ๋ฉ”ํƒ€์ •๋ณด์ด๋ฉฐ ์‚ฌ๋žŒ์ด ์‹๋ณ„ํ•˜๊ฑฐ๋‚˜ ๊ธฐ๊ธฐ์—์„œ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์•ฑ ์ˆ˜๋ช… ์ฃผ๊ธฐ

image

์•ฑ ์ˆ˜๋ช…์ฃผ๊ธฐ๋ฅผ MAUI ๊ด€์ ์—์„œ ๋˜๋Š” ํ”Œ๋ ›ํผ๋ณ„๋กœ ๊ด€์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ฑ ์‹œ์ž‘

MauiProgram.cs์„ ํ†ตํ•ด ์‹œ์ž‘ ์‹œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธ€๊ผด์ด๋‚˜ ํฐํŠธ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๊ณ  ํ•ธ๋“ค๋Ÿฌ๋‚˜ ์„œ๋น„์Šค๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ Œ๋”๋Ÿฌ ์—ญ์‹œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ ๋‹ค์Œ์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๊ฐ ํ”Œ๋ ›ํผ๋ณ„๋กœ ๋ Œ๋”๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

...
                #if ANDROID
                .ConfigureMauiHandlers(handlers =>
                {
                    handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.BoxView),
                        typeof(Microsoft.Maui.Controls.Compatibility.Platform.Android.BoxRenderer));
                    handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.Frame),
                        typeof(Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers.FrameRenderer));
                });
                #elif IOS
                .ConfigureMauiHandlers(handlers =>
                {
                    handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.BoxView),
                        typeof(Microsoft.Maui.Controls.Compatibility.Platform.iOS.BoxRenderer));
                    handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.Frame),
                        typeof(Microsoft.Maui.Controls.Compatibility.Platform.iOS.FrameRenderer));
                });
                #endif  
...

Behavior

์ƒ์†์„ ํ•˜์ง€ ์•Š๊ณ ๋„ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค ์ปจํŠธ๋กค์— ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. WPF ๊ทธ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

...
public static class AttachedNumericValidationBehavior
{
    public static readonly BindableProperty AttachBehaviorProperty =
        BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(AttachedNumericValidationBehavior), false, propertyChanged: OnAttachBehaviorChanged);
...

๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ

๋ณ„๋„ ์ •๋ฆฌ

์ œ์Šค์ฒ˜

Drag&Drop, Pan, Pinch, Swipe, Tap์˜ ๋‹ค์–‘ํ•œ ์ œ์Šค์ฒ˜๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์†์„ฑ

๋ฐ”์ธ๋”ฉ ๊ฐ€๋Šฅ ์†์„ฑ(Bindable property)๊ณผ, ์ฒจ๋ถ€ ์†์„ฑ(Attached property) ๋ชจ๋‘ WPF์˜ ๊ทธ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋ฉ”์‹œ์ง€ ๊ฒŒ์‹œ ๋ฐ ๊ตฌ๋…

๋ฐœํ–‰-๊ตฌ๋… ํŒจํ„ด์€ ๋ฐœํ–‰์ž์™€ ๊ตฌ๋…์ž๋ฅผ ์ง์ ‘ ์ฐธ์กฐํ•˜์ง€ ์•Š๊ณ ๋„ ํ•„์š”ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๊ณ  ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. MAUI์—์„œ๋Š” MessagingCenter๋ฅผ ์ด์šฉํ•ด ์ด ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ด์šฉํ•ด ์ปจํŠธ๋กค์™€ ์ปจํŠธ๋กค๊ฐ„ ๋ฉ”์‹œ์ง€๋ฅผ ์ฃผ๊ณ  ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฆฌ์†Œ์Šค ์‚ฌ์ „

ResourceDictionary๋ฅผ ์ด์šฉํ•ด ๋ฆฌ์†Œ์Šค๋ฅผ ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. WPF์˜ ๊ทธ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

Shell

๋‹จ์ผ ํ”„๋กœ์ ํŠธ

๋‹ค์–‘ํ•œ ํ”„๋กœ์ ํŠธ ์„ค์ •์œผ๋กœ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<ItemGroup>
    <!-- Images -->
    <MauiImage Include="Resources\Images\*" />

    <!-- Fonts -->
    <MauiFont Include="Resources\Fonts\*" />

    <!-- Assets -->
    <MauiAsset Include="Resources\Assets\*" />
</ItemGroup>

ํ…œํ”Œ๋ฆฟ

WPF์˜ ๊ทธ๊ฒƒ๊ณผ ๊ฐ™์ด ControlTemplate, DateTemplate์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

ํŠธ๋ฆฌ๊ฑฐ

ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ํ†ตํ•ด XAML์—์„œ ๋‹ค์–‘ํ•œ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ๋‹ค๋ฅธ ๋™์ž‘์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์„ฑ์€ WPF์˜ ํŠธ๋ฆฌ๊ฑฐ์™€ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ฐฝ

์˜ค๋Š˜ ๊ธฐ์ดˆ๋ถ€๋ถ„์„ ์ „๋ฐ˜์ ์œผ๋กœ ์‚ดํŽด๋ดค๋Š”๋ฐ WPF ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋น ๋ฅด๊ฒŒ MAUI์— ์ต์ˆ™ํ•ด์งˆ ์ˆ˜ ์žˆ๋„๋ก ๊ธฐ์กด ๊ธฐ์ˆ ์„ ๊ฑฐ์˜ ๋Œ€๋ถ€๋ถ„ ๊ณ„์Šนํ•ด์„œ ์‚ฌ์šฉํ–ˆ๋„ค์š”. ๋‹ค์Œ ์‹œ๊ฐ„์—๋Š” MAUI ์ปจํŠธ๋กค์„ ์ค‘์ ์ ์œผ๋กœ ๋ฐฐ์›Œ๋ณผ ์ƒ๊ฐ์ž…๋‹ˆ๋‹ค.

2๊ฐœ์˜ ์ข‹์•„์š”

Shell (1)

Shell์€ ํŠนํžˆ ๋ชจ๋ฐ”์ผ ๊ตฌ์„ฑ์— ํ•„์ˆ˜ ๊ตฌ์„ฑ ๋ฐ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์„œ ์•ฑ ๊ฐœ๋ฐœ์„ ์‰ฝ๊ฒŒ ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

  • ์•ฑ์˜ ์‹œ๊ฐ์  ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„
  • ํƒ์ƒ‰ ๊ธฐ๋Šฅ์„ ์ œ๊ณต
  • ํŽ˜์ด์ง€์— URI ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๊ฐ€๋Šฅ
  • ํ†ตํ•ฉ ๊ฒ€์ƒ‰ ์ฒ˜๋ฆฌ

image

Shell์€ ๋‹ค์Œ์˜ ์ƒ์† ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

Shell โ†’ Page โ†’ VisualElement โ†’ NavigableElement โ†’ Element โ†’ BindableObject

Shell์€ ์ผ๋ฐ˜์ ์œผ๋กœ App์˜ MainPage๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

| App.xaml.cs

	public App()
	{
		InitializeComponent();

		Application.Current.UserAppTheme = AppTheme.Light;

		MainPage = new AppShell();
	}
3๊ฐœ์˜ ์ข‹์•„์š”

๊ตฌ์‚ผ๋‹˜์ด MAUI Shell์— ๋Œ€ํ•ด ์ •๋ฆฌํ•œ ๊ธ€์ด ์žˆ์Šต๋‹ˆ๋‹ค!

ํ”Œ๋ ›ํผ ๊ธฐ๋Šฅ์€ Microsoft.Maui.Essentials๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šคํ”Œ๋ž˜์‰ฌ ํ™”๋ฉด์€ ํ”„๋กœ์ ํŠธ ์„ค์ •์œผ๋กœ ๋งค์šฐ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @honeyhead ๋‹˜์ด ์†Œ๊ฐœํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค.

.NET MAUI๋กœ ์ง„ํ™”ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ์— ๋Œ€ํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค. ์‚ฌ์‹ค ๋‹ค์–‘ํ•œ (๊ด‘๊ณ ์„ฑ) ์ด์œ ๊ฐ€ ์žˆ์ง€๋งŒ ์‚ฌ์‹ค์€ ๋‹ค์Œ์ฒ˜๋Ÿผ๋งŒ ์ค€๋น„๋œ๋‹ค๋ฉด .NET ๊ฐœ๋ฐœ์ž๋กœ์„  ๋Œ€ํ™˜์˜์ž…๋‹ˆ๋‹ค.

  • ์ž‘์€ ํŒจํ‚ค์ง€ ์‚ฌ์ด์ฆˆ
  • ๋น ๋ฅธ ์‹œ์ž‘ ์‹œ๊ฐ„
  • ๋น ๋ฅธ ๋™์ž‘ ์‹œ๊ฐ„
  • ํ”Œ๋žซํผ์˜ ๊ธฐ๋Šฅ ์ œ์•ฝ ์—†๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ ์ œ๊ณต
  • ๋””๋ฒ„๊ทธ๋ฅผ ์ •ํ™•ํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ
  • ํ”Œ๋žซํผ์— ๋งž๋Š” ์ด์งˆ๊ฐ ์—†๋Š” ์•ฑ ์ž‘์„ฑ
4๊ฐœ์˜ ์ข‹์•„์š”