์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ .NET MAUI๋กœ ํ•œ ๋ฒˆ์— ๋๋‚ด๋Š” ๋ฉ€ํ‹ฐํ”Œ๋žซํผ ์•ฑ ๊ฐœ๋ฐœ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๐Ÿš€ .NET MAUI๋กœ ํ•œ ๋ฒˆ์— ๋๋‚ด๋Š” ๋ฉ€ํ‹ฐํ”Œ๋žซํผ ์•ฑ ๊ฐœ๋ฐœ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

iOS, Android, Windows, macOS๋ฅผ ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋กœ ์ •๋ณตํ•˜๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ์—ฌ์ • โœจ

๐Ÿ’ก ์—ฌ๋Ÿฌ๋ถ„, ์ด๋Ÿฐ ๊ณ ๋ฏผ ํ•ด๋ณด์‹  ์  ์žˆ๋‚˜์š”?

"์•ฑ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์€๋ฐ... iOS๋Š” Swift๋กœ, Android๋Š” Kotlin์œผ๋กœ, Windows๋Š” ๋˜ ๋”ฐ๋กœ? ์ด๊ฑฐ ์–ธ์ œ ๋‹ค ๋ฐฐ์šฐ๊ณ  ๊ฐœ๋ฐœํ•˜์ง€?" ๐Ÿ˜ฑ

๋ฐ”๋กœ ์ด๋Ÿฐ ๊ณ ๋ฏผ์„ ํ•ด๊ฒฐํ•ด์ฃผ๋Š” ๊ฒŒ .NET MAUI์ž…๋‹ˆ๋‹ค! ํ•œ ๋ฒˆ์˜ ์ฝ”๋“œ ์ž‘์„ฑ์œผ๋กœ ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ์—์„œ ๋™์ž‘ํ•˜๋Š” ์•ฑ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋‹ˆ, ์ •๋ง ๊ฟˆ๋งŒ ๊ฐ™์ง€ ์•Š๋‚˜์š”? ์˜ค๋Š˜์€ ์ด ๋†€๋ผ์šด ๊ธฐ์ˆ ์„ ์นœ๊ตฌ์ฒ˜๋Ÿผ ํŽธํ•˜๊ฒŒ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”! ๐ŸŽ‰

.NET MAUI Single Codebase ๐ŸŽ iOS ๐Ÿค– Android ๐ŸชŸ Windows ๐Ÿ’ป macOS One Code, Multiple Platforms

๐ŸŽฏ .NET MAUI๊ฐ€ ๋ญ๊ธธ๋ž˜ ์ด๋ ‡๊ฒŒ ๋‚œ๋ฆฌ์•ผ?

์ž, ๋จผ์ € .NET MAUI๊ฐ€ ์ •ํ™•ํžˆ ๋ญ”์ง€๋ถ€ํ„ฐ ์•Œ์•„๋ณผ๊นŒ์š”? MAUI๋Š” Multi-platform App UI์˜ ์•ฝ์ž์˜ˆ์š”. ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๊ฐ€ 2022๋…„ 5์›”์— ์ •์‹์œผ๋กœ ์ถœ์‹œํ•œ ํฌ๋กœ์Šค ํ”Œ๋žซํผ ํ”„๋ ˆ์ž„์›Œํฌ์ฃ . ๐ŸŽŠ

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, C#์ด๋ผ๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด, ๊ทธ ์ฝ”๋“œ๊ฐ€ iOS, Android, Windows, macOS์—์„œ ๋ชจ๋‘ ๋Œ์•„๊ฐ€๋Š” ์•ฑ์œผ๋กœ ๋ณ€์‹ ํ•œ๋‹ค๋Š” ๊ฑฐ์˜ˆ์š”! ๋งˆ์น˜ ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ์ง€ํŒก์ด ํ•œ ๋ฒˆ ํœ˜๋‘๋ฅด๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ† ๋ผ๊ฐ€ ๋‚˜์˜ค๋Š” ๊ฒƒ์ฒ˜๋Ÿผ์š”. ๐Ÿฐ๐Ÿฐ๐Ÿฐ

๐Ÿ“Œ .NET MAUI์˜ ์ „์‹ , Xamarin์„ ์•„์‹œ๋‚˜์š”?

์‚ฌ์‹ค .NET MAUI๋Š” ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ๊ธฐ์ˆ ์€ ์•„๋‹ˆ์—์š”. Xamarin.Forms๋ผ๋Š” ์„ ๋ฐฐ ๊ธฐ์ˆ ์˜ ์ง„ํ™” ๋ฒ„์ „์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์ฃ . Xamarin์ด ์ข‹๊ธด ํ–ˆ์ง€๋งŒ ๋ช‡ ๊ฐ€์ง€ ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ์–ด์š”. ์„ฑ๋Šฅ ์ด์Šˆ๋„ ์žˆ์—ˆ๊ณ , ๊ฐœ๋ฐœ ๊ฒฝํ—˜๋„ ์กฐ๊ธˆ ๋ณต์žกํ–ˆ๊ฑฐ๋“ ์š”. ๊ทธ๋ž˜์„œ ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๊ฐ€ "์ด๊ฑฐ ์™„์ „ํžˆ ์ƒˆ๋กœ ๋งŒ๋“ค์ž!"๋ผ๊ณ  ๊ฒฐ์‹ฌํ•˜๊ณ  ํƒ„์ƒํ•œ ๊ฒŒ ๋ฐ”๋กœ MAUI์ž…๋‹ˆ๋‹ค. ๋” ๋น ๋ฅด๊ณ , ๋” ๊ฐ„๋‹จํ•˜๊ณ , ๋” ํ˜„๋Œ€์ ์œผ๋กœ ๋ง์ด์ฃ ! ๐Ÿ’ช

๐ŸŒŸ ์™œ ๊ฐœ๋ฐœ์ž๋“ค์ด .NET MAUI์— ์—ด๊ด‘ํ• ๊นŒ?

์†”์งํžˆ ๋งํ•ด์„œ, ํฌ๋กœ์Šค ํ”Œ๋žซํผ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” .NET MAUI๋งŒ ์žˆ๋Š” ๊ฒŒ ์•„๋‹ˆ์—์š”. React Native๋„ ์žˆ๊ณ , Flutter๋„ ์žˆ์ฃ . ๊ทธ๋Ÿฐ๋ฐ ์™œ .NET MAUI๋ฅผ ์„ ํƒํ•ด์•ผ ํ• ๊นŒ์š”? ๐Ÿค”

1๏ธโƒฃ ์ง„์งœ ๋„ค์ดํ‹ฐ๋ธŒ ์„ฑ๋Šฅ

.NET MAUI๋Š” ๊ฐ ํ”Œ๋žซํผ์˜ ๋„ค์ดํ‹ฐ๋ธŒ ์ปจํŠธ๋กค์„ ์ง์ ‘ ์‚ฌ์šฉํ•ด์š”. ์ฆ‰, iOS์—์„œ๋Š” ์ง„์งœ iOS ๋ฒ„ํŠผ์ด ๋‚˜์˜ค๊ณ , Android์—์„œ๋Š” ์ง„์งœ Android ๋ฒ„ํŠผ์ด ๋‚˜์˜จ๋‹ค๋Š” ๊ฑฐ์ฃ . ์›น๋ทฐ๋‚˜ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆผ ๊ทธ๋ฆฌ๋Š” ๋ฐฉ์‹์ด ์•„๋‹ˆ๋ผ, ์ง„์งœ๋ฐฐ๊ธฐ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ์ด์—์š”! ๊ทธ๋ž˜์„œ ์„ฑ๋Šฅ๋„ ์ข‹๊ณ , ์‚ฌ์šฉ์ž ๊ฒฝํ—˜๋„ ๊ฐ ํ”Œ๋žซํผ์— ์ตœ์ ํ™”๋˜์–ด ์žˆ๋‹ต๋‹ˆ๋‹ค. ๐Ÿš€

2๏ธโƒฃ C#์˜ ๊ฐ•๋ ฅํ•จ

C#์€ ์ •๋ง ๋ฉ‹์ง„ ์–ธ์–ด์˜ˆ์š”. ํƒ€์ž… ์•ˆ์ •์„ฑ๋„ ๋›ฐ์–ด๋‚˜๊ณ , LINQ ๊ฐ™์€ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋„ ์žˆ๊ณ , async/await๋กœ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ๋„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์ฃ . JavaScript๋‚˜ Dart๋ฅผ ์ƒˆ๋กœ ๋ฐฐ์šธ ํ•„์š” ์—†์ด, ์ด๋ฏธ C#์„ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด ๋ฐ”๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์–ด์š”! ๐Ÿ˜Ž

3๏ธโƒฃ .NET ์ƒํƒœ๊ณ„์˜ ํ˜œํƒ

.NET MAUI๋Š” ๊ฑฐ๋Œ€ํ•œ .NET ์ƒํƒœ๊ณ„์˜ ์ผ๋ถ€์˜ˆ์š”. NuGet์— ์žˆ๋Š” ์ˆ˜๋งŽ์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , Entity Framework๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋„ ์‰ฝ๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์ฃ . ๋ฐฑ์—”๋“œ๊ฐ€ ASP.NET Core๋กœ ๋˜์–ด ์žˆ๋‹ค๋ฉด? ์™„๋ฒฝํ•œ ๊ถํ•ฉ์ด์—์š”! ๐ŸŽฏ

4๏ธโƒฃ Hot Reload๋กœ ๋น ๋ฅธ ๊ฐœ๋ฐœ

์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์•ฑ์„ ๋‹ค์‹œ ๋นŒ๋“œํ•˜์ง€ ์•Š์•„๋„ ๋ฐ”๋กœ๋ฐ”๋กœ ํ™”๋ฉด์— ๋ฐ˜์˜๋ผ์š”. ๋ฒ„ํŠผ ์ƒ‰๊น” ๋ฐ”๊พธ๊ณ  ์‹ถ์–ด์š”? ์ฝ”๋“œ ์ˆ˜์ •ํ•˜๋ฉด 1์ดˆ ๋งŒ์— ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚˜์š”. ์ด๊ฒŒ ์–ผ๋งˆ๋‚˜ ๊ฐœ๋ฐœ ์‹œ๊ฐ„์„ ๋‹จ์ถ•์‹œ์ผœ์ฃผ๋Š”์ง€ ๋ชฐ๋ผ์š”! โšก

๐Ÿ› ๏ธ .NET MAUI ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์…‹์—…ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”? ๋จผ์ € ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๋ถ€ํ„ฐ ์ค€๋น„ํ•ด์•ผ๊ฒ ์ฃ ? ๊ฑฑ์ • ๋งˆ์„ธ์š”, ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์•„์š”! ๐Ÿ˜Š

Step 1: Visual Studio ์„ค์น˜

.NET MAUI ๊ฐœ๋ฐœ์˜ ๊ฐ€์žฅ ์ข‹์€ ์นœ๊ตฌ๋Š” ๋ฐ”๋กœ Visual Studio 2022์˜ˆ์š”. (Visual Studio Code๊ฐ€ ์•„๋‹ˆ๋ผ Visual Studio์ž…๋‹ˆ๋‹ค!) Windows์—์„œ๋Š” Community ์—๋””์…˜์„ ๋ฌด๋ฃŒ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , Mac์—์„œ๋Š” Visual Studio for Mac์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

Step 2: ์›Œํฌ๋กœ๋“œ ์„ ํƒ

Visual Studio๋ฅผ ์„ค์น˜ํ•  ๋•Œ ".NET Multi-platform App UI development" ์›Œํฌ๋กœ๋“œ๋ฅผ ๊ผญ ์„ ํƒํ•ด์•ผ ํ•ด์š”. ์ด๊ฒŒ ์„ ํƒ๋˜์–ด์•ผ MAUI ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ๋„๊ตฌ๋“ค์ด ํ•จ๊ป˜ ์„ค์น˜๋ฉ๋‹ˆ๋‹ค. Android SDK, iOS ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์—ฐ๊ฒฐ ๋„๊ตฌ ๋“ฑ๋“ฑ์ด ๋‹ค ํฌํ•จ๋˜์–ด ์žˆ์–ด์š”! ๐Ÿ“ฆ

Step 3: ์ถ”๊ฐ€ ์„ค์ •

iOS ์•ฑ์„ ๊ฐœ๋ฐœํ•˜๋ ค๋ฉด Mac์ด ํ•„์š”ํ•ด์š”. (์ด๊ฑด ์• ํ”Œ์˜ ์ •์ฑ…์ด๋ผ ์–ด์ฉ” ์ˆ˜ ์—†์–ด์š” ๐Ÿ˜…) Windows์—์„œ ๊ฐœ๋ฐœํ•˜๋”๋ผ๋„ ๋„คํŠธ์›Œํฌ๋กœ Mac์— ์—ฐ๊ฒฐํ•ด์„œ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค. Android๋Š” Windows์—์„œ๋„ Mac์—์„œ๋„ ์ž์œ ๋กญ๊ฒŒ ๊ฐœ๋ฐœ ๊ฐ€๋Šฅํ•˜๊ณ ์š”!

๐Ÿ’ก ๊ฟ€ํŒ!

์ฒ˜์Œ ์‹œ์ž‘ํ•œ๋‹ค๋ฉด Android ๊ฐœ๋ฐœ๋ถ€ํ„ฐ ํ•ด๋ณด๋Š” ๊ฑธ ์ถ”์ฒœํ•ด์š”. ์—๋ฎฌ๋ ˆ์ดํ„ฐ๋„ ๋น ๋ฅด๊ณ , ์„ค์ •๋„ ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฑฐ๋“ ์š”. iOS๋Š” ์กฐ๊ธˆ ์ต์ˆ™ํ•ด์ง„ ๋‹ค์Œ์— ๋„์ „ํ•ด๋„ ๋Šฆ์ง€ ์•Š์•„์š”!

๐ŸŽจ ์ฒซ ๋ฒˆ์งธ MAUI ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ

์ž, ์ด์ œ ์ง„์งœ ์ฝ”๋”ฉ์„ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”? Visual Studio๋ฅผ ์—ด๊ณ  ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค! ๐ŸŽ‰

// ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ํ›„ ๊ธฐ๋ณธ ๊ตฌ์กฐ
MyMauiApp/
โ”œโ”€โ”€ Platforms/
โ”‚   โ”œโ”€โ”€ Android/
โ”‚   โ”œโ”€โ”€ iOS/
โ”‚   โ”œโ”€โ”€ MacCatalyst/
โ”‚   โ””โ”€โ”€ Windows/
โ”œโ”€โ”€ Resources/
โ”‚   โ”œโ”€โ”€ Images/
โ”‚   โ”œโ”€โ”€ Fonts/
โ”‚   โ””โ”€โ”€ Styles/
โ”œโ”€โ”€ App.xaml
โ”œโ”€โ”€ AppShell.xaml
โ””โ”€โ”€ MainPage.xaml

ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์ด๋Ÿฐ ๊ตฌ์กฐ๊ฐ€ ๋‚˜ํƒ€๋‚˜์š”. ์‹ ๊ธฐํ•˜์ฃ ? Platforms ํด๋”์—๋Š” ๊ฐ ํ”Œ๋žซํผ๋ณ„ ํŠนํ™” ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ€๊ณ , ๋‚˜๋จธ์ง€๋Š” ๋ชจ๋“  ํ”Œ๋žซํผ์—์„œ ๊ณต์œ ํ•˜๋Š” ์ฝ”๋“œ์˜ˆ์š”. ๋ณดํ†ต์€ ๊ณต์œ  ์ฝ”๋“œ๋งŒ์œผ๋กœ๋„ 90% ์ด์ƒ์˜ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค! ๐ŸŽฏ

MainPage.xaml ํŒŒ์ผ์„ ์—ด์–ด๋ณผ๊นŒ์š”? ์—ฌ๊ธฐ๊ฐ€ ๋ฐ”๋กœ ์šฐ๋ฆฌ ์•ฑ์˜ ์ฒซ ํ™”๋ฉด์ด์—์š”.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">

    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0"
            VerticalOptions="Center">

            <Label 
                Text="์•ˆ๋…•ํ•˜์„ธ์š”! MAUI ์„ธ๊ณ„์— ์˜ค์‹  ๊ฑธ ํ™˜์˜ํ•ด์š”! ๐ŸŽ‰"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />

            <Button 
                x:Name="CounterBtn"
                Text="ํด๋ฆญํ•ด๋ณด์„ธ์š”!"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

XAML์ด๋ผ๋Š” ๋งˆํฌ์—… ์–ธ์–ด๋กœ UI๋ฅผ ์ •์˜ํ•ด์š”. HTML๊ณผ ๋น„์Šทํ•˜๊ฒŒ ์ƒ๊ฒผ์ฃ ? ๊ทผ๋ฐ ํ›จ์”ฌ ๋” ๊ฐ•๋ ฅํ•ด์š”! ์ด ์ฝ”๋“œ๋Š” ๋ผ๋ฒจ ํ•˜๋‚˜์™€ ๋ฒ„ํŠผ ํ•˜๋‚˜๋ฅผ ์„ธ๋กœ๋กœ ๋ฐฐ์น˜ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ํ™”๋ฉด์ด์—์š”. ๐Ÿ˜Š

์ด์ œ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ณผ๊นŒ์š”? MainPage.xaml.cs ํŒŒ์ผ์„ ์—ด์–ด๋ณด์„ธ์š”.

namespace MyMauiApp;

public partial class MainPage : ContentPage
{
    int count = 0;

    public MainPage()
    {
        InitializeComponent();
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;

        if (count == 1)
            CounterBtn.Text = $"1๋ฒˆ ํด๋ฆญํ–ˆ์–ด์š”!";
        else
            CounterBtn.Text = $"{count}๋ฒˆ ํด๋ฆญํ–ˆ์–ด์š”!";

        SemanticScreenReader.Announce(CounterBtn.Text);
    }
}

๋ณด์„ธ์š”! ์™„์ „ ์ผ๋ฐ˜์ ์ธ C# ์ฝ”๋“œ์ฃ ? ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ์นด์šดํŠธ๊ฐ€ ์˜ฌ๋ผ๊ฐ€๊ณ , ๋ฒ„ํŠผ์˜ ํ…์ŠคํŠธ๊ฐ€ ๋ฐ”๋€Œ์–ด์š”. ์ด ์ฝ”๋“œ ํ•˜๋‚˜๋กœ iOS, Android, Windows, macOS ๋ชจ๋‘์—์„œ ๋˜‘๊ฐ™์ด ๋™์ž‘ํ•œ๋‹ต๋‹ˆ๋‹ค! ๐ŸŒˆ

๐ŸŽจ XAML๊ณผ MVVM ํŒจํ„ด ๋งˆ์Šคํ„ฐํ•˜๊ธฐ

.NET MAUI์—์„œ UI๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€์˜ˆ์š”. XAML์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ C# ์ฝ”๋“œ๋กœ ์ง์ ‘ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด์ฃ . ๊ทผ๋ฐ ๋Œ€๋ถ€๋ถ„์˜ ๊ฐœ๋ฐœ์ž๋“ค์€ XAML์„ ์„ ํ˜ธํ•ด์š”. ์™œ๋ƒ๊ณ ์š”? ํ›จ์”ฌ ์ง๊ด€์ ์ด๊ณ  ๋””์ž์ด๋„ˆ์™€ ํ˜‘์—…ํ•˜๊ธฐ๋„ ์ข‹๊ฑฐ๋“ ์š”! ๐ŸŽจ

๐Ÿ“ XAML ๋ ˆ์ด์•„์›ƒ์˜ ๊ธฐ๋ณธ

MAUI๋Š” ๋‹ค์–‘ํ•œ ๋ ˆ์ด์•„์›ƒ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ œ๊ณตํ•ด์š”. ๊ฐ๊ฐ์˜ ํŠน์ง•์„ ์•Œ์•„๋ณผ๊นŒ์š”?

StackLayout (VerticalStackLayout / HorizontalStackLayout)

์š”์†Œ๋“ค์„ ์„ธ๋กœ๋‚˜ ๊ฐ€๋กœ๋กœ ์Œ“์•„์˜ฌ๋ ค์š”. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ณ  ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ์ด์—์š”. ๋งˆ์น˜ ๋ธ”๋ก์„ ์Œ“๋“ฏ์ด UI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ฃ ! ๐Ÿงฑ

<VerticalStackLayout Spacing="10">
    <Label Text="์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ" />
    <Label Text="๋‘ ๋ฒˆ์งธ ํ•ญ๋ชฉ" />
    <Label Text="์„ธ ๋ฒˆ์งธ ํ•ญ๋ชฉ" />
</VerticalStackLayout>

Grid

ํ–‰๊ณผ ์—ด๋กœ ๊ตฌ์„ฑ๋œ ํ‘œ ํ˜•ํƒœ์˜ ๋ ˆ์ด์•„์›ƒ์ด์—์š”. ๋ณต์žกํ•œ UI๋ฅผ ๋งŒ๋“ค ๋•Œ ์ •๋ง ์œ ์šฉํ•ด์š”! ์›น์˜ CSS Grid์™€ ๋น„์Šทํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ์š”. ๐Ÿ“Š

<Grid RowDefinitions="Auto,*,Auto" 
      ColumnDefinitions="*,*">
    
    <Label Text="ํ—ค๋”" Grid.Row="0" Grid.ColumnSpan="2" />
    <Label Text="์™ผ์ชฝ ์ปจํ…์ธ " Grid.Row="1" Grid.Column="0" />
    <Label Text="์˜ค๋ฅธ์ชฝ ์ปจํ…์ธ " Grid.Row="1" Grid.Column="1" />
    <Button Text="ํ‘ธํ„ฐ ๋ฒ„ํŠผ" Grid.Row="2" Grid.ColumnSpan="2" />
    
</Grid>

FlexLayout

CSS Flexbox์™€ ๊ฑฐ์˜ ๋˜‘๊ฐ™์•„์š”! ๋ฐ˜์‘ํ˜• UI๋ฅผ ๋งŒ๋“ค ๋•Œ ์ •๋ง ์ข‹์ฃ . ํ™”๋ฉด ํฌ๊ธฐ์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ๋ฐฐ์น˜๊ฐ€ ๋ฐ”๋€Œ์–ด์š”. ๐Ÿ“ฑ๐Ÿ’ป

AbsoluteLayout

์ ˆ๋Œ€ ์ขŒํ‘œ๋กœ ์š”์†Œ๋ฅผ ๋ฐฐ์น˜ํ•ด์š”. ๊ฒŒ์ž„์ด๋‚˜ ํŠน์ˆ˜ํ•œ UI๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” ์ž˜ ์•ˆ ์จ์š”. ๋ฐ˜์‘ํ˜•์ด ์•ˆ ๋˜๊ฑฐ๋“ ์š”! ๐Ÿ˜…

๐Ÿ—๏ธ MVVM ํŒจํ„ด, ์–ด๋ ต์ง€ ์•Š์•„์š”!

MVVM์€ Model-View-ViewModel์˜ ์•ฝ์ž์˜ˆ์š”. ์ฒ˜์Œ ๋“ค์œผ๋ฉด ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, ์‚ฌ์‹ค์€ ์ฝ”๋“œ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ผ ๋ฟ์ด์—์š”. ๐Ÿงน

๐ŸŽญ Model (๋ชจ๋ธ)

๋ฐ์ดํ„ฐ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” User ํด๋ž˜์Šค ๊ฐ™์€ ๊ฑฐ์ฃ .

๐Ÿ‘๏ธ View (๋ทฐ)

์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋Š” ํ™”๋ฉด์ด์—์š”. XAML ํŒŒ์ผ์ด ๋ฐ”๋กœ View์˜ˆ์š”!

๐Ÿ”— ViewModel (๋ทฐ๋ชจ๋ธ)

View์™€ Model ์‚ฌ์ด์˜ ์ค‘๊ฐ„ ๋‹ค๋ฆฌ์˜ˆ์š”. View์— ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์ค€๋น„ํ•˜๊ณ , ์‚ฌ์šฉ์ž์˜ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•ด์š”.

์‹ค์ œ ์ฝ”๋“œ๋กœ ๋ณด๋ฉด ์ดํ•ด๊ฐ€ ์‰ฌ์›Œ์š”. ๊ฐ„๋‹จํ•œ ํ•  ์ผ ๋ชฉ๋ก ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”? ๐Ÿ“

// Model/TodoItem.cs
public class TodoItem
{
    public string Title { get; set; }
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; }
}

// ViewModel/TodoViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;

public class TodoViewModel : INotifyPropertyChanged
{
    public ObservableCollection<TodoItem> TodoItems { get; set; }
    
    private string newTodoText;
    public string NewTodoText
    {
        get => newTodoText;
        set
        {
            newTodoText = value;
            OnPropertyChanged(nameof(NewTodoText));
        }
    }

    public ICommand AddTodoCommand { get; }

    public TodoViewModel()
    {
        TodoItems = new ObservableCollection<TodoItem>();
        AddTodoCommand = new Command(AddTodo);
    }

    private void AddTodo()
    {
        if (!string.IsNullOrWhiteSpace(NewTodoText))
        {
            TodoItems.Add(new TodoItem
            {
                Title = NewTodoText,
                IsCompleted = false,
                CreatedAt = DateTime.Now
            });
            NewTodoText = string.Empty;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, 
            new PropertyChangedEventArgs(propertyName));
    }
}

ViewModel์—์„œ INotifyPropertyChanged๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒŒ ๋ณด์ด์‹œ๋‚˜์š”? ์ด๊ฒŒ MVVM์˜ ํ•ต์‹ฌ์ด์—์š”! ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ž๋™์œผ๋กœ UI๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ์ผ์ด ์ผ์–ด๋‚˜์ฃ . โœจ

์ด์ œ View์—์„œ ์ด ViewModel์„ ์‚ฌ์šฉํ•ด๋ณผ๊นŒ์š”?

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             x:Class="TodoApp.Views.TodoPage">
    
    <ContentPage.BindingContext>
        <viewmodels:TodoViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout Padding="20">
        
        <HorizontalStackLayout Spacing="10">
            <Entry Text="{Binding NewTodoText}" 
                   Placeholder="์ƒˆ๋กœ์šด ํ•  ์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”"
                   HorizontalOptions="FillAndExpand" />
            <Button Text="์ถ”๊ฐ€" 
                    Command="{Binding AddTodoCommand}" />
        </HorizontalStackLayout>

        <CollectionView ItemsSource="{Binding TodoItems}">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <HorizontalStackLayout Padding="10">
                        <CheckBox IsChecked="{Binding IsCompleted}" />
                        <Label Text="{Binding Title}" 
                               VerticalOptions="Center" />
                    </HorizontalStackLayout>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>

    </VerticalStackLayout>

</ContentPage>

๋ณด์„ธ์š”! {Binding ...} ์ด๋Ÿฐ ๋ฌธ๋ฒ•์ด ๋ณด์ด์ฃ ? ์ด๊ฒŒ ๋ฐ”๋กœ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด์—์š”. Entry์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์ž๋™์œผ๋กœ ViewModel์˜ NewTodoText ์†์„ฑ์ด ์—…๋ฐ์ดํŠธ๋˜๊ณ , ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด AddTodoCommand๊ฐ€ ์‹คํ–‰๋ผ์š”. ์ฝ”๋“œ ๋น„ํ•˜์ธ๋“œ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์–ด์š”! ๐ŸŽ‰

๐Ÿ’ก MVVM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”์ฒœ!

INotifyPropertyChanged๋ฅผ ๋งค๋ฒˆ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒŒ ๊ท€์ฐฎ๋‹ค๊ณ ์š”? CommunityToolkit.Mvvm NuGet ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์„ธ์š”! [ObservableProperty] ๊ฐ™์€ ์†์„ฑ๋งŒ ๋ถ™์ด๋ฉด ์ž๋™์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ด์ค˜์š”. ์ •๋ง ํŽธ๋ฆฌํ•˜๋‹ต๋‹ˆ๋‹ค! ๐Ÿš€

๐ŸŒ ๋„ค๋น„๊ฒŒ์ด์…˜๊ณผ Shell ํ™œ์šฉํ•˜๊ธฐ

์•ฑ์—์„œ ํ™”๋ฉด ์ „ํ™˜์€ ์ •๋ง ์ค‘์š”ํ•˜์ฃ ? .NET MAUI๋Š” Shell์ด๋ผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ด์š”. ์ด๊ฒŒ ์ •๋ง ํŽธ๋ฆฌํ•ด์š”! ๐Ÿ—บ๏ธ

๐Ÿš Shell์ด ๋ญ์˜ˆ์š”?

Shell์€ ์•ฑ์˜ ์ „์ฒด์ ์ธ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ์˜ˆ์š”. ํƒญ ๋ฐ”, ํ”Œ๋ผ์ด์•„์›ƒ ๋ฉ”๋‰ด, ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๋“ฑ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ฃ . AppShell.xaml ํŒŒ์ผ์„ ์—ด์–ด๋ณด๋ฉด ์ดํ•ด๊ฐ€ ์‰ฌ์›Œ์š”.

<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:local="clr-namespace:MyApp.Views"
       x:Class="MyApp.AppShell">

    <!-- ํƒญ ๋ฐ” ๊ตฌ์กฐ -->
    <TabBar>
        <ShellContent Title="ํ™ˆ" 
                      Icon="home.png"
                      ContentTemplate="{DataTemplate local:HomePage}" />
        
        <ShellContent Title="๊ฒ€์ƒ‰" 
                      Icon="search.png"
                      ContentTemplate="{DataTemplate local:SearchPage}" />
        
        <ShellContent Title="ํ”„๋กœํ•„" 
                      Icon="profile.png"
                      ContentTemplate="{DataTemplate local:ProfilePage}" />
    </TabBar>

</Shell>

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•˜๋‹จ์— 3๊ฐœ์˜ ํƒญ์ด ์žˆ๋Š” ์•ฑ์ด ๋งŒ๋“ค์–ด์ ธ์š”! iOS์—์„œ๋Š” iOS ์Šคํƒ€์ผ๋กœ, Android์—์„œ๋Š” Android ์Šคํƒ€์ผ๋กœ ์ž๋™์œผ๋กœ ๋ Œ๋”๋ง๋˜์ฃ . ์‹ ๊ธฐํ•˜์ง€ ์•Š๋‚˜์š”? ๐Ÿ˜ฎ

๐Ÿš€ ํŽ˜์ด์ง€ ์ด๋™ํ•˜๊ธฐ

Shell์„ ์‚ฌ์šฉํ•˜๋ฉด ํŽ˜์ด์ง€ ์ด๋™์ด ์ •๋ง ๊ฐ„๋‹จํ•ด์š”. URI ๊ธฐ๋ฐ˜ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋“ ์š”!

// ๋จผ์ € ๋ผ์šฐํŠธ๋ฅผ ๋“ฑ๋กํ•ด์š”
Routing.RegisterRoute("detailpage", typeof(DetailPage));

// ๊ทธ๋ฆฌ๊ณ  ์ด๋™!
await Shell.Current.GoToAsync("detailpage");

// ํŒŒ๋ผ๋ฏธํ„ฐ๋„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์–ด์š”
await Shell.Current.GoToAsync($"detailpage?id={itemId}");

// ๋’ค๋กœ ๊ฐ€๊ธฐ
await Shell.Current.GoToAsync("..");

ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›๋Š” ํŽ˜์ด์ง€๋Š” ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์š”:

[QueryProperty(nameof(ItemId), "id")]
public partial class DetailPage : ContentPage
{
    private string itemId;
    public string ItemId
    {
        get => itemId;
        set
        {
            itemId = value;
            LoadItemDetails(value);
        }
    }

    private async void LoadItemDetails(string id)
    {
        // ID๋กœ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์™€์š”
        var item = await DataService.GetItemAsync(id);
        BindingContext = item;
    }
}

์ •๋ง ๊น”๋”ํ•˜์ฃ ? ์›น ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ URL๋กœ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”! ๐ŸŒ

๐Ÿ“ฑ ํ”Œ๋žซํผ๋ณ„ ํŠนํ™” ๊ธฐ๋Šฅ ๋‹ค๋ฃจ๊ธฐ

์•„๋ฌด๋ฆฌ ํฌ๋กœ์Šค ํ”Œ๋žซํผ์ด๋ผ๊ณ  ํ•ด๋„, ๋•Œ๋กœ๋Š” ๊ฐ ํ”Œ๋žซํผ์˜ ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, iOS์˜ Face ID๋‚˜ Android์˜ ๋ฐฑ ๋ฒ„ํŠผ ๊ฐ™์€ ๊ฑฐ์ฃ . .NET MAUI๋Š” ์ด๋Ÿฐ ๊ฒƒ๋„ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”! ๐ŸŽญ

๐Ÿ”ง ์กฐ๊ฑด๋ถ€ ์ปดํŒŒ์ผ

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ์กฐ๊ฑด๋ถ€ ์ปดํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์˜ˆ์š”:

public void DoSomethingPlatformSpecific()
{
#if ANDROID
    // Android ์ „์šฉ ์ฝ”๋“œ
    Android.Widget.Toast.MakeText(
        Platform.CurrentActivity, 
        "์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ์‹คํ–‰ ์ค‘!", 
        Android.Widget.ToastLength.Short
    ).Show();
#elif IOS
    // iOS ์ „์šฉ ์ฝ”๋“œ
    var alert = UIAlertController.Create(
        "์•Œ๋ฆผ", 
        "iOS์—์„œ ์‹คํ–‰ ์ค‘!", 
        UIAlertControllerStyle.Alert
    );
    // ... alert ํ‘œ์‹œ ์ฝ”๋“œ
#elif WINDOWS
    // Windows ์ „์šฉ ์ฝ”๋“œ
    var dialog = new Windows.UI.Popups.MessageDialog(
        "์œˆ๋„์šฐ์—์„œ ์‹คํ–‰ ์ค‘!"
    );
    await dialog.ShowAsync();
#endif
}

๐ŸŽฏ Dependency Injection์œผ๋กœ ๋” ์šฐ์•„ํ•˜๊ฒŒ

๊ทผ๋ฐ ์กฐ๊ฑด๋ถ€ ์ปดํŒŒ์ผ์€ ์ฝ”๋“œ๊ฐ€ ์ง€์ €๋ถ„ํ•ด์งˆ ์ˆ˜ ์žˆ์–ด์š”. ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์ธํ„ฐํŽ˜์ด์Šค์™€ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์˜ˆ์š”!

// ๊ณตํ†ต ์ธํ„ฐํŽ˜์ด์Šค
public interface INotificationService
{
    void ShowNotification(string title, string message);
}

// Android ๊ตฌํ˜„ (Platforms/Android ํด๋”์—)
public class NotificationService : INotificationService
{
    public void ShowNotification(string title, string message)
    {
        // Android ์•Œ๋ฆผ ๊ตฌํ˜„
        var builder = new NotificationCompat.Builder(
            Platform.CurrentActivity, 
            "default"
        )
        .SetContentTitle(title)
        .SetContentText(message)
        .SetSmallIcon(Resource.Drawable.notification_icon);
        
        // ์•Œ๋ฆผ ํ‘œ์‹œ
    }
}

// iOS ๊ตฌํ˜„ (Platforms/iOS ํด๋”์—)
public class NotificationService : INotificationService
{
    public void ShowNotification(string title, string message)
    {
        // iOS ์•Œ๋ฆผ ๊ตฌํ˜„
        var content = new UNMutableNotificationContent
        {
            Title = title,
            Body = message
        };
        
        // ์•Œ๋ฆผ ํ‘œ์‹œ
    }
}

// MauiProgram.cs์—์„œ ๋“ฑ๋ก
builder.Services.AddSingleton<INotificationService, NotificationService>();

// ์‚ฌ์šฉํ•  ๋•Œ
public class MyViewModel
{
    private readonly INotificationService notificationService;
    
    public MyViewModel(INotificationService notificationService)
    {
        this.notificationService = notificationService;
    }
    
    public void SendNotification()
    {
        notificationService.ShowNotification(
            "์ƒˆ ๋ฉ”์‹œ์ง€", 
            "์•ˆ๋…•ํ•˜์„ธ์š”!"
        );
    }
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ณตํ†ต ์ฝ”๋“œ๋Š” ๊น”๋”ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ๊ฐ ํ”Œ๋žซํผ์˜ ํŠน์„ฑ์„ ์‚ด๋ฆด ์ˆ˜ ์žˆ์–ด์š”! ๐ŸŽจ

๐Ÿ’ก ํ”Œ๋žซํผ ์ฒดํฌํ•˜๊ธฐ

๋Ÿฐํƒ€์ž„์— ํ˜„์žฌ ํ”Œ๋žซํผ์„ ํ™•์ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด DeviceInfo.Platform์„ ์‚ฌ์šฉํ•˜์„ธ์š”!

if (DeviceInfo.Platform == DevicePlatform.Android)
{
    // Android ์ „์šฉ ๋กœ์ง
}
else if (DeviceInfo.Platform == DevicePlatform.iOS)
{
    // iOS ์ „์šฉ ๋กœ์ง
}

๐ŸŽจ ์Šคํƒ€์ผ๋ง๊ณผ ํ…Œ๋งˆ ์ ์šฉํ•˜๊ธฐ

์˜ˆ์œ ์•ฑ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์ฃ ? .NET MAUI๋Š” ๊ฐ•๋ ฅํ•œ ์Šคํƒ€์ผ๋ง ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ด์š”. CSS์™€ ๋น„์Šทํ•˜์ง€๋งŒ ๋” ํƒ€์ž… ์•ˆ์ „ํ•˜๊ณ  ๊ฐ•๋ ฅํ•˜๋‹ต๋‹ˆ๋‹ค! ๐Ÿ’…

๐ŸŽญ ๋ฆฌ์†Œ์Šค ๋”•์…”๋„ˆ๋ฆฌ

App.xaml ํŒŒ์ผ์— ์ „์—ญ ์Šคํƒ€์ผ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด์š”:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             x:Class="MyApp.App">
    <Application.Resources>
        <ResourceDictionary>
            
            <!-- ์ƒ‰์ƒ ์ •์˜ -->
            <Color x:Key="PrimaryColor">#6200EE</Color>
            <Color x:Key="SecondaryColor">#03DAC6</Color>
            <Color x:Key="BackgroundColor">#FFFFFF</Color>
            <Color x:Key="TextColor">#000000</Color>
            
            <!-- ์Šคํƒ€์ผ ์ •์˜ -->
            <Style TargetType="Button">
                <Setter Property="BackgroundColor" 
                        Value="{StaticResource PrimaryColor}" />
                <Setter Property="TextColor" 
                        Value="White" />
                <Setter Property="CornerRadius" 
                        Value="8" />
                <Setter Property="Padding" 
                        Value="16,8" />
            </Style>
            
            <Style x:Key="TitleLabel" TargetType="Label">
                <Setter Property="FontSize" Value="24" />
                <Setter Property="FontAttributes" Value="Bold" />
                <Setter Property="TextColor" 
                        Value="{StaticResource PrimaryColor}" />
            </Style>
            
        </ResourceDictionary>
    </Application.Resources>
</Application>

์ด์ œ ์•ฑ ์ „์ฒด์—์„œ ์ด ์Šคํƒ€์ผ๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”:

<Label Text="์ œ๋ชฉ์ž…๋‹ˆ๋‹ค" Style="{StaticResource TitleLabel}" />
<Button Text="ํด๋ฆญํ•˜์„ธ์š”" /> <!-- ์ž๋™์œผ๋กœ ์Šคํƒ€์ผ ์ ์šฉ -->

๐ŸŒ“ ๋‹คํฌ ๋ชจ๋“œ ์ง€์›ํ•˜๊ธฐ

์š”์ฆ˜ ์•ฑ์€ ๋‹คํฌ ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒŒ ํ•„์ˆ˜์ฃ ! MAUI์—์„œ๋Š” ์ •๋ง ์‰ฌ์›Œ์š”:

<Application.Resources>
    <ResourceDictionary>
        
        <!-- ๋ผ์ดํŠธ ํ…Œ๋งˆ -->
        <Color x:Key="PageBackgroundColor">White</Color>
        <Color x:Key="TextColor">Black</Color>
        
        <!-- ๋‹คํฌ ํ…Œ๋งˆ (AppThemeBinding ์‚ฌ์šฉ) -->
        <Style TargetType="ContentPage" ApplyToDerivedTypes="True">
            <Setter Property="BackgroundColor" 
                    Value="{AppThemeBinding Light=White, Dark=#1E1E1E}" />
        </Style>
        
        <Style TargetType="Label">
            <Setter Property="TextColor" 
                    Value="{AppThemeBinding Light=Black, Dark=White}" />
        </Style>
        
    </ResourceDictionary>
</Application.Resources>

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‚ฌ์šฉ์ž์˜ ์‹œ์Šคํ…œ ์„ค์ •์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ํ…Œ๋งˆ๊ฐ€ ๋ฐ”๋€Œ์–ด์š”! ์‚ฌ์šฉ์ž๊ฐ€ ๋‹คํฌ ๋ชจ๋“œ๋กœ ์ „ํ™˜ํ•˜๋ฉด ์•ฑ๋„ ์ฆ‰์‹œ ๋‹คํฌ ๋ชจ๋“œ๋กœ ๋ฐ”๋€Œ์ฃ . ์ •๋ง ๋ฉ‹์ง€์ง€ ์•Š๋‚˜์š”? ๐ŸŒ™

์ฝ”๋“œ์—์„œ ํ˜„์žฌ ํ…Œ๋งˆ๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ์ˆ˜๋„ ์žˆ์–ด์š”:

// ํ˜„์žฌ ํ…Œ๋งˆ ํ™•์ธ
var currentTheme = Application.Current.RequestedTheme;

// ํ…Œ๋งˆ ๋ณ€๊ฒฝ
Application.Current.UserAppTheme = AppTheme.Dark;

๐Ÿ’พ ๋ฐ์ดํ„ฐ ์ €์žฅ๊ณผ ๊ด€๋ฆฌ

์•ฑ์„ ๋งŒ๋“ค๋‹ค ๋ณด๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด์•ผ ํ•  ์ผ์ด ์ •๋ง ๋งŽ์•„์š”. ์‚ฌ์šฉ์ž ์„ค์ •, ๋กœ๊ทธ์ธ ์ •๋ณด, ์บ์‹œ ๋ฐ์ดํ„ฐ ๋“ฑ๋“ฑ... .NET MAUI๋Š” ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ด์š”! ๐Ÿ“ฆ

๐Ÿ”‘ Preferences - ๊ฐ„๋‹จํ•œ ํ‚ค-๊ฐ’ ์ €์žฅ

๊ฐ„๋‹จํ•œ ์„ค์ •๊ฐ’์„ ์ €์žฅํ•  ๋•Œ๋Š” Preferences๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ •๋ง ์‰ฌ์›Œ์š”!

// ์ €์žฅํ•˜๊ธฐ
Preferences.Set("username", "ํ™๊ธธ๋™");
Preferences.Set("is_logged_in", true);
Preferences.Set("font_size", 16);

// ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
string username = Preferences.Get("username", "๊ธฐ๋ณธ๊ฐ’");
bool isLoggedIn = Preferences.Get("is_logged_in", false);
int fontSize = Preferences.Get("font_size", 14);

// ์‚ญ์ œํ•˜๊ธฐ
Preferences.Remove("username");

// ์ „์ฒด ์‚ญ์ œ
Preferences.Clear();

๋‚ด๋ถ€์ ์œผ๋กœ iOS๋Š” NSUserDefaults, Android๋Š” SharedPreferences, Windows๋Š” ApplicationDataContainer๋ฅผ ์‚ฌ์šฉํ•ด์š”. ์šฐ๋ฆฌ๋Š” ๊ทธ๋ƒฅ ๊ฐ„๋‹จํ•œ API๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋˜์ฃ ! ๐Ÿ˜Š

๐Ÿ” Secure Storage - ๋ฏผ๊ฐํ•œ ์ •๋ณด ์ €์žฅ

๋น„๋ฐ€๋ฒˆํ˜ธ๋‚˜ ํ† ํฐ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” SecureStorage๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์•”ํ˜ธํ™”๋˜์–ด ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅ๋ผ์š”!

// ์ €์žฅํ•˜๊ธฐ
await SecureStorage.SetAsync("oauth_token", "abc123xyz");

// ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
string token = await SecureStorage.GetAsync("oauth_token");

// ์‚ญ์ œํ•˜๊ธฐ
SecureStorage.Remove("oauth_token");

๐Ÿ—„๏ธ SQLite - ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋ ค๋ฉด SQLite๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. .NET MAUI์—์„œ๋Š” sqlite-net-pcl NuGet ํŒจํ‚ค์ง€๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด์š”.

// ๋ชจ๋ธ ์ •์˜
public class TodoItem
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }
    
    public string Title { get; set; }
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; }
}

// ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋น„์Šค
public class TodoDatabase
{
    private SQLiteAsyncConnection database;

    public TodoDatabase(string dbPath)
    {
        database = new SQLiteAsyncConnection(dbPath);
        database.CreateTableAsync<TodoItem>().Wait();
    }

    // ์ „์ฒด ์กฐํšŒ
    public Task<List<TodoItem>> GetItemsAsync()
    {
        return database.Table<TodoItem>().ToListAsync();
    }

    // ๋‹จ์ผ ์กฐํšŒ
    public Task<TodoItem> GetItemAsync(int id)
    {
        return database.Table<TodoItem>()
                      .Where(i => i.Id == id)
                      .FirstOrDefaultAsync();
    }

    // ์ €์žฅ
    public Task<int> SaveItemAsync(TodoItem item)
    {
        if (item.Id != 0)
            return database.UpdateAsync(item);
        else
            return database.InsertAsync(item);
    }

    // ์‚ญ์ œ
    public Task<int> DeleteItemAsync(TodoItem item)
    {
        return database.DeleteAsync(item);
    }
}

// ์‚ฌ์šฉํ•˜๊ธฐ
var dbPath = Path.Combine(
    FileSystem.AppDataDirectory, 
    "todos.db3"
);
var database = new TodoDatabase(dbPath);

// ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
await database.SaveItemAsync(new TodoItem
{
    Title = "MAUI ๊ณต๋ถ€ํ•˜๊ธฐ",
    IsCompleted = false,
    CreatedAt = DateTime.Now
});

// ๋ฐ์ดํ„ฐ ์กฐํšŒ
var items = await database.GetItemsAsync();

SQLite๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•ด์š”! ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋„ ๊ฐ€๋Šฅํ•˜๊ณ , ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋„ ์ €์žฅํ•  ์ˆ˜ ์žˆ์ฃ . ์˜คํ”„๋ผ์ธ ์•ฑ์„ ๋งŒ๋“ค ๋•Œ ํ•„์ˆ˜์˜ˆ์š”! ๐Ÿ’ช

๐Ÿ’ก Entity Framework Core๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!

๋” ๊ฐ•๋ ฅํ•œ ORM์„ ์›ํ•œ๋‹ค๋ฉด Entity Framework Core๋„ .NET MAUI์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ๋ณต์žกํ•œ ๊ด€๊ณ„ ์„ค์ • ๋“ฑ ๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ฃ . ๋‹ค๋งŒ ์•ฑ ํฌ๊ธฐ๊ฐ€ ์กฐ๊ธˆ ์ปค์งˆ ์ˆ˜ ์žˆ์–ด์š”!

๐ŸŒ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ๊ณผ REST API

์š”์ฆ˜ ์•ฑ์€ ๋Œ€๋ถ€๋ถ„ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜์ฃ ? .NET MAUI์—์„œ HTTP ํ†ต์‹ ์€ ์ •๋ง ๊ฐ„๋‹จํ•ด์š”. HttpClient๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๊ฑฐ๋“ ์š”! ๐Ÿš€

๐Ÿ“ก ๊ธฐ๋ณธ HTTP ์š”์ฒญ

public class ApiService
{
    private readonly HttpClient httpClient;
    private const string BaseUrl = "https://api.example.com";

    public ApiService()
    {
        httpClient = new HttpClient
        {
            BaseAddress = new Uri(BaseUrl)
        };
    }

    // GET ์š”์ฒญ
    public async Task<List<Product>> GetProductsAsync()
    {
        try
        {
            var response = await httpClient.GetAsync("/products");
            response.EnsureSuccessStatusCode();
            
            var json = await response.Content.ReadAsStringAsync();
            var products = JsonSerializer.Deserialize<List<Product>>(json);
            
            return products;
        }
        catch (Exception ex)
        {
            // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
            Debug.WriteLine($"Error: {ex.Message}");
            return new List<Product>();
        }
    }

    // POST ์š”์ฒญ
    public async Task<bool> CreateProductAsync(Product product)
    {
        try
        {
            var json = JsonSerializer.Serialize(product);
            var content = new StringContent(
                json, 
                Encoding.UTF8, 
                "application/json"
            );
            
            var response = await httpClient.PostAsync("/products", content);
            return response.IsSuccessStatusCode;
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Error: {ex.Message}");
            return false;
        }
    }

    // ์ธ์ฆ ํ† ํฐ ์ถ”๊ฐ€
    public void SetAuthToken(string token)
    {
        httpClient.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", token);
    }
}

๐Ÿ”„ Refit์œผ๋กœ ๋” ์‰ฝ๊ฒŒ

Refit์ด๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด REST API ํ˜ธ์ถœ์ด ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด์ ธ์š”!

// API ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜
public interface IProductApi
{
    [Get("/products")]
    Task<List<Product>> GetProducts();

    [Get("/products/{id}")]
    Task<Product> GetProduct(int id);

    [Post("/products")]
    Task<Product> CreateProduct([Body] Product product);

    [Put("/products/{id}")]
    Task<Product> UpdateProduct(int id, [Body] Product product);

    [Delete("/products/{id}")]
    Task DeleteProduct(int id);
}

// ์‚ฌ์šฉํ•˜๊ธฐ
var api = RestService.For<IProductApi>("https://api.example.com");
var products = await api.GetProducts();

๋ณด์„ธ์š”! ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ์ •์˜ํ•˜๋ฉด Refit์ด ์ž๋™์œผ๋กœ ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด์ค˜์š”. ์ •๋ง ๋งˆ๋ฒ• ๊ฐ™์ฃ ? โœจ

๐Ÿ“ถ ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธํ•˜๊ธฐ

๋„คํŠธ์›Œํฌ ์š”์ฒญ ์ „์— ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๋Š” ๊ฒŒ ์ข‹์•„์š”:

public async Task<List<Product>> GetProductsAsync()
{
    // ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ
    if (Connectivity.NetworkAccess != NetworkAccess.Internet)
    {
        await App.Current.MainPage.DisplayAlert(
            "์˜ค๋ฅ˜", 
            "์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.", 
            "ํ™•์ธ"
        );
        return new List<Product>();
    }

    // ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์ง„ํ–‰
    var products = await apiService.GetProductsAsync();
    return products;
}

// ์—ฐ๊ฒฐ ์ƒํƒœ ๋ณ€ํ™” ๊ฐ์ง€
Connectivity.ConnectivityChanged += (sender, e) =>
{
    if (e.NetworkAccess == NetworkAccess.Internet)
    {
        Debug.WriteLine("์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ๋จ!");
    }
    else
    {
        Debug.WriteLine("์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ ๋Š๊น€!");
    }
};

๐ŸŽฌ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค ์‚ดํŽด๋ณด๊ธฐ

์ด์ œ ์ข€ ๋” ์žฌ๋ฏธ์žˆ๋Š” ๊ธฐ๋Šฅ๋“ค์„ ์•Œ์•„๋ณผ๊นŒ์š”? .NET MAUI๋Š” ์ •๋ง ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์š”! ๐ŸŽช

๐Ÿ“ธ ์นด๋ฉ”๋ผ์™€ ๋ฏธ๋””์–ด

์‚ฌ์ง„ ์ฐ๊ธฐ๋‚˜ ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ์ด๋ฏธ์ง€ ์„ ํƒํ•˜๊ธฐ๋Š” MediaPicker๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ์š”:

// ์‚ฌ์ง„ ์ฐ๊ธฐ
public async Task<FileResult> TakePhotoAsync()
{
    try
    {
        var photo = await MediaPicker.CapturePhotoAsync();
        return photo;
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Error: {ex.Message}");
        return null;
    }
}

// ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ์„ ํƒ
public async Task<FileResult> PickPhotoAsync()
{
    try
    {
        var photo = await MediaPicker.PickPhotoAsync(new MediaPickerOptions
        {
            Title = "์‚ฌ์ง„์„ ์„ ํƒํ•˜์„ธ์š”"
        });
        return photo;
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Error: {ex.Message}");
        return null;
    }
}

// ์ด๋ฏธ์ง€ ํ‘œ์‹œํ•˜๊ธฐ
var photo = await PickPhotoAsync();
if (photo != null)
{
    var stream = await photo.OpenReadAsync();
    MyImage.Source = ImageSource.FromStream(() => stream);
}

๐Ÿ“ ์œ„์น˜ ์ •๋ณด

GPS ์œ„์น˜๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๋„ ๊ฐ„๋‹จํ•ด์š”:

public async Task<Location> GetCurrentLocationAsync()
{
    try
    {
        var location = await Geolocation.GetLastKnownLocationAsync();
        
        if (location == null)
        {
            var request = new GeolocationRequest(
                GeolocationAccuracy.Medium, 
                TimeSpan.FromSeconds(10)
            );
            location = await Geolocation.GetLocationAsync(request);
        }

        if (location != null)
        {
            Debug.WriteLine($"์œ„๋„: {location.Latitude}");
            Debug.WriteLine($"๊ฒฝ๋„: {location.Longitude}");
        }

        return location;
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Error: {ex.Message}");
        return null;
    }
}

๊ถŒํ•œ ์š”์ฒญ๋„ ์žŠ์ง€ ๋งˆ์„ธ์š”! Android์˜ ๊ฒฝ์šฐ AndroidManifest.xml์—, iOS์˜ ๊ฒฝ์šฐ Info.plist์— ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ด์š”.

๐Ÿ”” ๋กœ์ปฌ ์•Œ๋ฆผ

ํ‘ธ์‹œ ์•Œ๋ฆผ์ด ์•„๋‹Œ ๋กœ์ปฌ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๊ณ  ์‹ถ๋‹ค๋ฉด Plugin.LocalNotification ๊ฐ™์€ ์ปค๋ฎค๋‹ˆํ‹ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”:

// ์ฆ‰์‹œ ์•Œ๋ฆผ
await LocalNotificationCenter.Current.Show(new NotificationRequest
{
    NotificationId = 1,
    Title = "์•ˆ๋…•ํ•˜์„ธ์š”!",
    Description = "MAUI์—์„œ ๋ณด๋‚ด๋Š” ์•Œ๋ฆผ์ด์—์š” ๐Ÿ˜Š",
    BadgeNumber = 1
});

// ์˜ˆ์•ฝ ์•Œ๋ฆผ
await LocalNotificationCenter.Current.Show(new NotificationRequest
{
    NotificationId = 2,
    Title = "๋ฆฌ๋งˆ์ธ๋”",
    Description = "30๋ถ„ ํ›„์— ํšŒ์˜๊ฐ€ ์žˆ์–ด์š”!",
    Schedule = new NotificationRequestSchedule
    {
        NotifyTime = DateTime.Now.AddMinutes(30)
    }
});

๐ŸŽจ ์• ๋‹ˆ๋ฉ”์ด์…˜

UI ์š”์†Œ์— ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์•ฑ์ด ํ›จ์”ฌ ์ƒ๋™๊ฐ ์žˆ์–ด์ ธ์š”!

// ํŽ˜์ด๋“œ ์ธ/์•„์›ƒ
await MyLabel.FadeTo(0, 1000); // 1์ดˆ ๋™์•ˆ ํˆฌ๋ช…ํ•˜๊ฒŒ
await MyLabel.FadeTo(1, 1000); // 1์ดˆ ๋™์•ˆ ๋ถˆํˆฌ๋ช…ํ•˜๊ฒŒ

// ํฌ๊ธฐ ๋ณ€ํ™”
await MyButton.ScaleTo(1.5, 500); // 0.5์ดˆ ๋™์•ˆ 1.5๋ฐฐ ํ™•๋Œ€
await MyButton.ScaleTo(1, 500);   // ์›๋ž˜ ํฌ๊ธฐ๋กœ

// ํšŒ์ „
await MyImage.RotateTo(360, 2000); // 2์ดˆ ๋™์•ˆ 360๋„ ํšŒ์ „

// ์ด๋™
await MyView.TranslateTo(100, 100, 1000); // 1์ดˆ ๋™์•ˆ (100,100)์œผ๋กœ ์ด๋™

// ๋ณตํ•ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜
var animation = new Animation();
animation.Add(0, 0.5, new Animation(v => MyView.Scale = v, 1, 1.5));
animation.Add(0.5, 1, new Animation(v => MyView.Rotation = v, 0, 360));
animation.Commit(this, "ComplexAnimation", length: 2000);

๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

์•ฑ์„ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ ๋А๋ฆฌ๋‹ค๋ฉด? ์‚ฌ์šฉ์ž๋“ค์ด ๊ธˆ๋ฐฉ ๋– ๋‚˜๋ฒ„๋ฆด ๊ฑฐ์˜ˆ์š”. ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ์ •๋ง ์ค‘์š”ํ•ด์š”! ๐Ÿ’จ

1๏ธโƒฃ CollectionView ๊ฐ€์ƒํ™” ํ™œ์šฉ

๊ธด ๋ฆฌ์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•  ๋•Œ๋Š” ListView ๋Œ€์‹  CollectionView๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ž๋™์œผ๋กœ ๊ฐ€์ƒํ™”๋˜์–ด ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ›จ์”ฌ ์ ๊ฒŒ ์‚ฌ์šฉํ•ด์š”!

<CollectionView ItemsSource="{Binding Items}">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <!-- ์•„์ดํ…œ ํ…œํ”Œ๋ฆฟ -->
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

2๏ธโƒฃ ์ด๋ฏธ์ง€ ์ตœ์ ํ™”

ํฐ ์ด๋ฏธ์ง€๋Š” ์•ฑ์„ ๋А๋ฆฌ๊ฒŒ ๋งŒ๋“ค์–ด์š”. ์ ์ ˆํ•œ ํฌ๊ธฐ๋กœ ๋ฆฌ์‚ฌ์ด์ง•ํ•˜๊ณ , ์บ์‹ฑ์„ ํ™œ์šฉํ•˜์„ธ์š”!

<Image Source="photo.jpg" 
       Aspect="AspectFill"
       HeightRequest="200"
       WidthRequest="200" />

<!-- URL ์ด๋ฏธ์ง€๋Š” ์ž๋™ ์บ์‹ฑ -->
<Image Source="https://example.com/image.jpg" />

3๏ธโƒฃ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ

๋ฌด๊ฑฐ์šด ์ž‘์—…์€ ๋ฐ˜๋“œ์‹œ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜์„ธ์š”. UI ์Šค๋ ˆ๋“œ๋ฅผ ๋ธ”๋กํ•˜๋ฉด ์•ฑ์ด ๋ฉˆ์ถ˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์—ฌ์š”!

// โŒ ๋‚˜์œ ์˜ˆ
public void LoadData()
{
    var data = database.GetItems(); // UI ๋ธ”๋ก!
    Items = new ObservableCollection<Item>(data);
}

// โœ… ์ข‹์€ ์˜ˆ
public async Task LoadDataAsync()
{
    var data = await database.GetItemsAsync();
    Items = new ObservableCollection<Item>(data);
}

4๏ธโƒฃ ์ปดํŒŒ์ผ ๋ชจ๋“œ ์ตœ์ ํ™”

๋ฆด๋ฆฌ์Šค ๋นŒ๋“œ์—์„œ๋Š” XAML ์ปดํŒŒ์ผ๊ณผ ๋ง์ปค๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. ์•ฑ ํฌ๊ธฐ๋„ ์ค„๊ณ  ์„ฑ๋Šฅ๋„ ์ข‹์•„์ ธ์š”!

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
    <XamlCompilation>Compile</XamlCompilation>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>Link</TrimMode>
</PropertyGroup>

5๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ–ˆ๋‹ค๋ฉด ๋ฐ˜๋“œ์‹œ ํ•ด์ œํ•˜์„ธ์š”!

protected override void OnAppearing()
{
    base.OnAppearing();
    MessagingCenter.Subscribe<string>(this, "UpdateData", (sender) =>
    {
        // ์ฒ˜๋ฆฌ
    });
}

protected override void OnDisappearing()
{
    base.OnDisappearing();
    MessagingCenter.Unsubscribe<string>(this, "UpdateData");
}

๐Ÿงช ํ…Œ์ŠคํŒ…๊ณผ ๋””๋ฒ„๊น…

๋ฒ„๊ทธ ์—†๋Š” ์•ฑ์€ ์—†์–ด์š”. ํ•˜์ง€๋งŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ž˜ํ•˜๋ฉด ๋ฒ„๊ทธ๋ฅผ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์ฃ ! ๐Ÿ›

๐Ÿ” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

ViewModel์ด๋‚˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆํ•˜์„ธ์š”:

// xUnit ์‚ฌ์šฉ ์˜ˆ์‹œ
public class TodoViewModelTests
{
    [Fact]
    public void AddTodo_ShouldAddItemToCollection()
    {
        // Arrange
        var viewModel = new TodoViewModel();
        viewModel.NewTodoText = "ํ…Œ์ŠคํŠธ ํ•ญ๋ชฉ";

        // Act
        viewModel.AddTodoCommand.Execute(null);

        // Assert
        Assert.Single(viewModel.TodoItems);
        Assert.Equal("ํ…Œ์ŠคํŠธ ํ•ญ๋ชฉ", viewModel.TodoItems[0].Title);
    }

    [Fact]
    public void AddTodo_WithEmptyText_ShouldNotAddItem()
    {
        // Arrange
        var viewModel = new TodoViewModel();
        viewModel.NewTodoText = "";

        // Act
        viewModel.AddTodoCommand.Execute(null);

        // Assert
        Assert.Empty(viewModel.TodoItems);
    }
}

๐ŸŽฏ UI ํ…Œ์ŠคํŠธ

UI ํ…Œ์ŠคํŠธ๋Š” Appium์ด๋‚˜ Xamarin.UITest๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”. ์‹ค์ œ ์‚ฌ์šฉ์ž์ฒ˜๋Ÿผ ์•ฑ์„ ์กฐ์ž‘ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธํ•˜์ฃ !

๐Ÿž ๋””๋ฒ„๊น… ํŒ

Hot Reload ํ™œ์šฉ

XAML์„ ์ˆ˜์ •ํ•˜๋ฉด ์•ฑ์„ ์žฌ์‹œ์ž‘ํ•˜์ง€ ์•Š์•„๋„ ๋ฐ”๋กœ ๋ฐ˜์˜๋ผ์š”. ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ์—„์ฒญ ๋นจ๋ผ์ ธ์š”! โšก

Live Visual Tree

Visual Studio์˜ Live Visual Tree๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‹คํ–‰ ์ค‘์ธ ์•ฑ์˜ UI ๊ตฌ์กฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”. ๋ ˆ์ด์•„์›ƒ ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ๋•Œ ์ •๋ง ์œ ์šฉํ•ด์š”!

๋กœ๊น…

Debug.WriteLine()์ด๋‚˜ ๋กœ๊น… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜์„ธ์š”:

Debug.WriteLine($"๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ: {items.Count}๊ฐœ ํ•ญ๋ชฉ");

// ๋˜๋Š” ILogger ์‚ฌ์šฉ
logger.LogInformation("์‚ฌ์šฉ์ž {UserId}๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ์Šต๋‹ˆ๋‹ค", userId);

๐Ÿ“ฆ ๋ฐฐํฌํ•˜๊ธฐ

๋“œ๋””์–ด ์•ฑ์„ ์™„์„ฑํ–ˆ์–ด์š”! ์ด์ œ ์„ธ์ƒ์— ๊ณต๊ฐœํ•  ์‹œ๊ฐ„์ด์—์š”! ๐ŸŽ‰

๐ŸŽ iOS ์•ฑ ๋ฐฐํฌ

1. Apple Developer ๊ณ„์ • ํ•„์š”

์—ฐ๊ฐ„ $99๋ฅผ ๋‚ด๊ณ  Apple Developer Program์— ๊ฐ€์ž…ํ•ด์•ผ ํ•ด์š”. (๊ฐœ์ธ ๊ฐœ๋ฐœ์ž ๊ธฐ์ค€)

2. ์ธ์ฆ์„œ์™€ ํ”„๋กœ๋น„์ €๋‹ ํ”„๋กœํŒŒ์ผ ์ƒ์„ฑ

Apple Developer ์‚ฌ์ดํŠธ์—์„œ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, Visual Studio์—์„œ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”.

3. App Store Connect์— ์•ฑ ๋“ฑ๋ก

์•ฑ ์ •๋ณด, ์Šคํฌ๋ฆฐ์ƒท, ์„ค๋ช… ๋“ฑ์„ ์ž…๋ ฅํ•˜๊ณ  ์‹ฌ์‚ฌ๋ฅผ ์š”์ฒญํ•ด์š”. ๋ณดํ†ต 1-3์ผ ์ •๋„ ๊ฑธ๋ ค์š”.

๐Ÿค– Android ์•ฑ ๋ฐฐํฌ

1. Google Play Console ๊ณ„์ •

์ผํšŒ์„ฑ $25๋ฅผ ๋‚ด๋ฉด ํ‰์ƒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”!

2. ์„œ๋ช… ํ‚ค ์ƒ์„ฑ

Visual Studio์—์„œ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, keytool๋กœ ์ง์ ‘ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”.

keytool -genkey -v -keystore myapp.keystore 
        -alias myapp -keyalg RSA -keysize 2048 
        -validity 10000

3. AAB ํŒŒ์ผ ์ƒ์„ฑ ๋ฐ ์—…๋กœ๋“œ

Android App Bundle(.aab) ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์„œ Play Console์— ์—…๋กœ๋“œํ•˜๋ฉด ๋ผ์š”!

๐ŸชŸ Windows ์•ฑ ๋ฐฐํฌ

Microsoft Store๋ฅผ ํ†ตํ•ด ๋ฐฐํฌํ•˜๊ฑฐ๋‚˜, ์ง์ ‘ ์„ค์น˜ ํŒŒ์ผ(.msix)์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด์š”. Store๋ฅผ ํ†ตํ•˜๋ฉด ์ž๋™ ์—…๋ฐ์ดํŠธ ๊ฐ™์€ ํ˜œํƒ์ด ์žˆ์ฃ !

๐Ÿ’ก ๋ฒ ํƒ€ ํ…Œ์ŠคํŠธ ํ™œ์šฉํ•˜๊ธฐ

์ •์‹ ์ถœ์‹œ ์ „์— TestFlight(iOS)๋‚˜ Google Play ๋‚ด๋ถ€ ํ…Œ์ŠคํŠธ(Android)๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ฒ ํƒ€ ํ…Œ์Šคํ„ฐ๋“ค์—๊ฒŒ ๋จผ์ € ๊ณต๊ฐœํ•ด๋ณด์„ธ์š”. ๋ฒ„๊ทธ๋ฅผ ๋ฏธ๋ฆฌ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด์š”!

๐ŸŽ“ ํ•™์Šต ๋ฆฌ์†Œ์Šค์™€ ์ปค๋ฎค๋‹ˆํ‹ฐ

.NET MAUI๋ฅผ ๋ฐฐ์šฐ๋Š” ์—ฌ์ •์—์„œ ํ˜ผ์ž๊ฐ€ ์•„๋‹ˆ์—์š”! ์ •๋ง ๋งŽ์€ ๋ฆฌ์†Œ์Šค์™€ ํ™œ๋ฐœํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ์žˆ๋‹ต๋‹ˆ๋‹ค. ๐Ÿค

๐Ÿ“š ๊ณต์‹ ๋ฌธ์„œ

Microsoft์˜ ๊ณต์‹ ๋ฌธ์„œ๋Š” ์ •๋ง ์ž˜ ๋˜์–ด ์žˆ์–ด์š”. learn.microsoft.com์—์„œ .NET MAUI๋ฅผ ๊ฒ€์ƒ‰ํ•ด๋ณด์„ธ์š”!

๐ŸŽฅ ์œ ํŠœ๋ธŒ ์ฑ„๋„

- dotNET ๊ณต์‹ ์ฑ„๋„
- James Montemagno (MAUI ํŒ€ ๋ฉค๋ฒ„!)
- Gerald Versluis

๐Ÿ’ฌ ์ปค๋ฎค๋‹ˆํ‹ฐ

- GitHub Discussions
- Stack Overflow (#maui ํƒœ๊ทธ)
- Discord ์„œ๋ฒ„๋“ค
- Reddit r/dotnet

๐Ÿ“ฑ ์ƒ˜ํ”Œ ์•ฑ

GitHub์— ์žˆ๋Š” microsoft/dotnet-maui-samples ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ๊ผญ ํ™•์ธํ•ด๋ณด์„ธ์š”. ์‹ค์ œ ๋™์ž‘ํ•˜๋Š” ์˜ˆ์ œ๋“ค์ด ๊ฐ€๋“ํ•ด์š”!

๊ทธ๋ฆฌ๊ณ  ์žฌ๋Šฅ๋„ท์—์„œ๋„ .NET MAUI ๊ด€๋ จ ๋ฉ˜ํ† ๋ง์ด๋‚˜ ํ”„๋กœ์ ํŠธ ์˜๋ขฐ๋ฅผ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ์–ด์š”. ์‹ค์ „ ๊ฒฝํ—˜์ด ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ์ง์ ‘ ๋ฐฐ์šธ ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๊ธฐํšŒ์ฃ ! ๐Ÿ˜Š

๐ŸŒŸ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด

์ด๋ก ๋งŒ ๊ณต๋ถ€ํ•˜๋ฉด ์žฌ๋ฏธ์—†์ฃ ? ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๋ฉด์„œ ๋ฐฐ์›Œ์•ผ ์ง„์งœ ์‹ค๋ ฅ์ด ๋Š˜์–ด์š”! ์—ฌ๊ธฐ ๋ช‡ ๊ฐ€์ง€ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด๋ฅผ ๋“œ๋ฆด๊ฒŒ์š”. ๐ŸŽจ

1๏ธโƒฃ ๋‚ ์”จ ์•ฑ

OpenWeatherMap API๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ˜„์žฌ ์œ„์น˜์˜ ๋‚ ์”จ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. ์œ„์น˜ ์ •๋ณด, HTTP ํ†ต์‹ , UI ๋””์ž์ธ์„ ๋ชจ๋‘ ์—ฐ์Šตํ•  ์ˆ˜ ์žˆ์–ด์š”! โ˜€๏ธ

2๏ธโƒฃ ํ•  ์ผ ๊ด€๋ฆฌ ์•ฑ

SQLite๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ , MVVM ํŒจํ„ด์„ ์ ์šฉํ•ด๋ณด์„ธ์š”. ์•Œ๋ฆผ ๊ธฐ๋Šฅ๋„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋” ์ข‹๊ณ ์š”! โœ…

3๏ธโƒฃ ์†Œ์…œ ๋ฏธ๋””์–ด ํด๋ผ์ด์–ธํŠธ

Twitter๋‚˜ Reddit API๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ”ผ๋“œ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. CollectionView, ์ด๋ฏธ์ง€ ๋กœ๋”ฉ, ๋ฌดํ•œ ์Šคํฌ๋กค ๋“ฑ์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด์š”! ๐Ÿ“ฑ

4๏ธโƒฃ ๊ฐ€๊ณ„๋ถ€ ์•ฑ

์ˆ˜์ž…๊ณผ ์ง€์ถœ์„ ๊ธฐ๋กํ•˜๊ณ , ์ฐจํŠธ๋กœ ์‹œ๊ฐํ™”ํ•ด๋ณด์„ธ์š”. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„์™€ ๋ณต์žกํ•œ UI๋ฅผ ๋‹ค๋ฃจ๋Š” ์—ฐ์Šต์ด ๋ผ์š”! ๐Ÿ’ฐ

5๏ธโƒฃ ์Œ์•… ํ”Œ๋ ˆ์ด์–ด

๋กœ์ปฌ ์Œ์•… ํŒŒ์ผ์„ ์žฌ์ƒํ•˜๋Š” ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. ๋ฏธ๋””์–ด ์žฌ์ƒ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…, ํ”Œ๋žซํผ๋ณ„ ๊ธฐ๋Šฅ ๋“ฑ์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด์š”! ๐ŸŽต

์ด๋Ÿฐ ํ”„๋กœ์ ํŠธ๋“ค์„ ๋งŒ๋“ค๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ .NET MAUI ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด ์žˆ์„ ๊ฑฐ์˜ˆ์š”! ๊ทธ๋ฆฌ๊ณ  ์™„์„ฑํ•œ ํ”„๋กœ์ ํŠธ๋Š” ํฌํŠธํด๋ฆฌ์˜ค๋กœ ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ๋น„์Šทํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ์˜๋ขฐ๋ฐ›์„ ๋•Œ ํฐ ๋„์›€์ด ๋˜์ฃ ! ๐Ÿ’ช

Success! You are now a .NET MAUI Developer ๐ŸŽ‰๐Ÿš€โœจ

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์˜ค์‹œ๋А๋ผ ์ •๋ง ์ˆ˜๊ณ  ๋งŽ์œผ์…จ์–ด์š”! ๐ŸŽŠ

.NET MAUI๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์˜ˆ์š”. ํ•œ ๋ฒˆ์˜ ์ฝ”๋“œ ์ž‘์„ฑ์œผ๋กœ iOS, Android, Windows, macOS ์•ฑ์„ ๋ชจ๋‘ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑด ์ •๋ง ๋†€๋ผ์šด ์ผ์ด์ฃ . ๋ฌผ๋ก  ์ฒ˜์Œ์—๋Š” ๋ฐฐ์šธ ๊ฒŒ ๋งŽ์•„ ๋ณด์ผ ์ˆ˜ ์žˆ์–ด์š”. XAML๋„ ์ต์ˆ™ํ•ด์ ธ์•ผ ํ•˜๊ณ , MVVM ํŒจํ„ด๋„ ์ดํ•ดํ•ด์•ผ ํ•˜๊ณ , ๊ฐ ํ”Œ๋žซํผ์˜ ํŠน์„ฑ๋„ ์•Œ์•„์•ผ ํ•˜๋‹ˆ๊นŒ์š”. ๐Ÿ˜…

ํ•˜์ง€๋งŒ ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ์„ธ์š”! ํ•œ ๊ฑธ์Œ์”ฉ ์ฒœ์ฒœํžˆ ๋‚˜์•„๊ฐ€๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ ๋ฉ‹์ง„ ์•ฑ์„ ๋งŒ๋“ค๊ณ  ์žˆ๋Š” ์ž์‹ ์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋  ๊ฑฐ์˜ˆ์š”. ์ €๋„ ์ฒ˜์Œ์—๋Š” "์ด๊ฒŒ ๋˜๋‚˜?" ์‹ถ์—ˆ๋Š”๋ฐ, ์ง€๊ธˆ์€ MAUI ์—†์ด๋Š” ๋ชป ์‚ด ๊ฒƒ ๊ฐ™์•„์š”. ๐Ÿ˜„

๐ŸŒˆ ๊ธฐ์–ตํ•ด์•ผ ํ•  ํ•ต์‹ฌ ํฌ์ธํŠธ

โœ… .NET MAUI๋Š” ์ง„์งœ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ์„ ๋งŒ๋“ค์–ด์š”
โœ… C# ํ•˜๋‚˜๋กœ ๋ชจ๋“  ํ”Œ๋žซํผ์„ ์ปค๋ฒ„ํ•  ์ˆ˜ ์žˆ์–ด์š”
โœ… MVVM ํŒจํ„ด์œผ๋กœ ๊น”๋”ํ•œ ์ฝ”๋“œ๋ฅผ ์œ ์ง€ํ•˜์„ธ์š”
โœ… ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ํ™œ๋ฐœํ•˜๋‹ˆ ํ˜ผ์ž๊ฐ€ ์•„๋‹ˆ์—์š”
โœ… ์‹ค์ „ ํ”„๋กœ์ ํŠธ๋กœ ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋นจ๋ผ์š”

๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœ์˜ ์„ธ๊ณ„๋Š” ์ •๋ง ๋„“๊ณ  ์žฌ๋ฏธ์žˆ์–ด์š”. ์—ฌ๋Ÿฌ๋ถ„์˜ ์•„์ด๋””์–ด๋ฅผ ์‹ค์ œ ๋™์ž‘ํ•˜๋Š” ์•ฑ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑด ์ •๋ง ๋ฉ‹์ง„ ์ผ์ด์ฃ . ๊ทธ๋ฆฌ๊ณ  .NET MAUI๋Š” ๊ทธ ์—ฌ์ •์„ ํ›จ์”ฌ ์‰ฝ๊ณ  ์ฆ๊ฒ๊ฒŒ ๋งŒ๋“ค์–ด์ค„ ๊ฑฐ์˜ˆ์š”. ๐Ÿš€

ํ˜น์‹œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ฑฐ๋‚˜ ๋„์›€์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐพ์•„๋ณด๊ฑฐ๋‚˜ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•ด๋ณด์„ธ์š”. ๊ทธ๋ฆฌ๊ณ  ์žฌ๋Šฅ๋„ท์—์„œ ๊ฒฝํ—˜ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์—์š”. ๋•Œ๋กœ๋Š” ์ „๋ฌธ๊ฐ€์˜ ์กฐ์–ธ ํ•œ๋งˆ๋””๊ฐ€ ๋ฉฐ์น ๊ฐ„์˜ ์‚ฝ์งˆ์„ ์ค„์—ฌ์ฃผ๊ธฐ๋„ ํ•˜๊ฑฐ๋“ ์š”! ๐Ÿ˜Š

์ž, ์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฒซ .NET MAUI ์•ฑ์„ ๋งŒ๋“ค์–ด๋ณผ ์‹œ๊ฐ„์ด์—์š”! Visual Studio๋ฅผ ์—ด๊ณ , ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ์ฝ”๋”ฉ์„ ์‹œ์ž‘ํ•ด๋ณด์„ธ์š”. ์ฒ˜์Œ์—๋Š” ๊ฐ„๋‹จํ•œ ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋ฉด ๋ผ์š”. "Hello, MAUI!" ์•ฑ๋„ ์ถฉ๋ถ„ํžˆ ๋ฉ‹์ง„ ์‹œ์ž‘์ด๋ž๋‹ˆ๋‹ค! ๐Ÿ’ป

์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฐœ๋ฐœ ์—ฌ์ •์— ํ–‰์šด์ด ๊ฐ€๋“ํ•˜๊ธธ ๋ฐ”๋ผ์š”! ์–ธ์  ๊ฐ€ ์•ฑ์Šคํ† ์–ด๋‚˜ ํ”Œ๋ ˆ์ด์Šคํ† ์–ด์—์„œ ์—ฌ๋Ÿฌ๋ถ„์ด ๋งŒ๋“  ๋ฉ‹์ง„ ์•ฑ์„ ๋งŒ๋‚˜๊ฒŒ ๋˜๊ธธ ๊ธฐ๋Œ€ํ• ๊ฒŒ์š”! ๐ŸŒŸ

Happy Coding! ๐ŸŽ‰

๐Ÿš€ .NET MAUI๋กœ ๋‹น์‹ ์˜ ์•„์ด๋””์–ด๋ฅผ ํ˜„์‹ค๋กœ!

์ด ๊ธ€์ด ๋„์›€์ด ๋˜์—ˆ๋‹ค๋ฉด, ์ง์ ‘ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ด๋ณด์„ธ์š”.
๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์—์š”! ๐Ÿ˜Š

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.