Microsoft MVP성태의 닷넷 이야기
Phone: 10. Xamarin.Forms - 1. Forms 시작하기 [링크 복사], [링크+제목 복사]
조회: 17701
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
[Part1.zip]    
(연관된 글이 1개 있습니다.)

Xamarin.Forms - 1. Forms 시작하기

이번에 Xamarin이 버전 3을 발표하면서 물건을 내놨습니다. ^^ 바로 Xamarin.Forms 인데요. 기존의 Xamarin 앱이 C# 문법만을 사용했을 뿐 각각의 플랫폼(iOS, Android)에서 앱을 만들려면 고유의 User Interface 관련 API를 익혀야 했습니다.

하지만, Xamarin.Forms가 나오면서 이런 상황이 바뀝니다. 물론, 앱은 각각의 플랫폼 별로 프로젝트가 있어야 하지만 UI 요소만큼은 Xamarin.Forms를 이용해 공통화했습니다. 게다가 플랫폼도 기존의 iOS, Android와 함께 Windows Phone (8.0 이상)까지 지원을 넓혔습니다. 한마디로, 1타 3피의 성과를 낼 수 있는데 예전 같으면 이런 경우 "Web App"을 이용해야 했지만 Xamarin을 통해 빠른 속도의 앱을 보장하면서도 3가지 주요 모바일 플랫폼을 지원할 수 있게 된 것입니다.

Xamarin.Forms 소개를 어떻게 할까 고민하던 중에 외국 블로거가 이미 쓰고 있어서, 이를 적당히 참고해서 소개하겠습니다. ^^

Learning Xamarin - Part 1: Forms
; http://blog.falafel.com/Blogs/jesse-liberty/2014/06/11/learning-xamarin---part-1-forms




어차피 Xamarin을 구매할 정도의 국내 업체라면 Visual Studio도 충분히 구비하고 있을 것이라 생각됩니다. 따라서 이 글에서는 Xamarin Studio를 이용하기 보다는 Visual Studio를 이용해 실습을 진행하겠습니다. (사실, 어느 걸로 해도 상관없습니다. 단지, Visual Studio의 에디터가 더 훌륭하기 때문에 선택한 것일뿐.)

Xamarin 3을 설치한 다음 Visual Studio 2013에서 새 프로젝트를 만들면 다음과 같이 "Mobile Apps" 영역에서 Xamarin.Forms 관련 프로젝트를 선택할 수 있습니다. 이 중에서 앱을 만들 것이기 때문에 "Blank App (Xamarin.Forms Portable)"로 프로젝트를 시작하겠습니다.

xamarin_forms_part1_1.png

프로젝트를 생성하면 다음과 같이 총 4개의 프로젝트가 만들어집니다.

xamarin_forms_part1_2.png

아하~~~ 이제 알겠습니다. ^^ 각각의 개별 Mobile App 플랫폼 별로 프로젝트가 생성되고, 공통 Form 관련 프로젝트(위에서는 Part1 프로젝트)가 있습니다. 즉, UI 관련 프로젝트를 3개의 모바일 폴랫폼에서 공유할 수 있도록 하는 것입니다. (일단, 제 컴퓨터에서는 iOS를 빌드할 수 있는 맥이 없으므로 Part1.iOS 프로젝트는 제거하겠습니다.)

예제를 위해 다음과 같은 데이터 클래스를 (Portable) 프로젝트에 만듭니다.

public class Monkey
{
    public string Name {get;set;}
    public string Location { get; set; }
    public string Details { get; set; }
}

이것을 Xamarin.Form과 연결해 줄 방법으로 "Learning Xamarin - Part 1: Forms" 글에서는 MVVM을 사용하고 있습니다. 3개의 모바일 플랫폼 유형에서 UI Form만 공유할 것이기 때문에 MVVM 방식은 꽤나 멋진 접근법이 될 수 있습니다.

따라서 MonkeysViewModel.cs 파일을 추가하고 다음의 코드를 추가합니다.

using Part1;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Monkeys.ViewModels
{
    public class MonkeysViewModel
    {
        public ObservableCollection<Monkey> Monkeys { get; set; }

        public MonkeysViewModel()
        {
            Monkeys = new ObservableCollection<Monkey>();
            Monkeys.Add(new Monkey
            {
                Name = "Baboon",
                Location = "Africa & Asia",
                Details = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae."
            });

            Monkeys.Add(new Monkey
            {
                Name = "Capuchin Monkey",
                Location = "Central & South America",
                Details = "The capuchin monkeys are New World monkeys of the subfamily Cebinae. Prior to 2011, the subfamily contained only a single genus, Cebus."
            });

            Monkeys.Add(new Monkey
            {
                Name = "Blue Monkey",
                Location = "Central and East Africa",
                Details = "The blue monkey or diademed monkey is a species of Old World monkey native to Central and East Africa, ranging from the upper Congo River basin east to the East African Rift and south to northern Angola and Zambia"
            });


            Monkeys.Add(new Monkey
            {
                Name = "Squirrel Monkey",
                Location = "Central & South America",
                Details = "The squirrel monkeys are the New World monkeys of the genus Saimiri. They are the only genus in the subfamily Saimirinae. The name of the genus Saimiri is of Tupi origin, and was also used as an English name by early researchers."
            });

            Monkeys.Add(new Monkey
            {
                Name = "Golden Lion Tamarin",
                Location = "Brazil",
                Details = "The golden lion tamarin also known as the golden marmoset, is a small New World monkey of the family Callitrichidae."
            });

            Monkeys.Add(new Monkey
            {
                Name = "Howler Monkey",
                Location = "South America",
                Details = "Howler monkeys are among the largest of the New World monkeys. Fifteen species are currently recognized. Previously classified in the family Cebidae, they are now placed in the family Atelidae."
            });

            Monkeys.Add(new Monkey
            {
                Name = "Japanese Macaque",
                Location = "Japan",
                Details = "The Japanese macaque, is a terrestrial Old World monkey species native to Japan. They are also sometimes known as the snow monkey because they live in areas where snow covers the ground for months each"
            });

            Monkeys.Add(new Monkey
            {
                Name = "Mandrill",
                Location = "Southern Cameroon, Gabon, Equatorial Guinea, and Congo",
                Details = "The mandrill is a primate of the Old World monkey family, closely related to the baboons and even more closely to the drill. It is found in southern Cameroon, Gabon, Equatorial Guinea, and Congo."
            });

            Monkeys.Add(new Monkey
            {
                Name = "Proboscis Monkey",
                Location = "Borneo",
                Details = "The proboscis monkey or long-nosed monkey, known as the bekantan in Malay, is a reddish-brown arboreal Old World monkey that is endemic to the south-east Asian island of Borneo."
            });
        }
    }
}

데이터가 마련되었으니, 이제 3개의 프로젝트(여기서는 iOS제외 2개)에서 공통적으로 사용할 페이지를 만들어 보겠습니다. 역시 (Portable) 프로젝트에 MonkeyPage.cs 파일을 추가하고 코드를 만들어 줍니다.

using Monkeys.ViewModels;
using Xamarin.Forms;

namespace Part1
{
    public class MonkeyPage : ContentPage
    {
        public MonkeyPage()
        {
            Title = "Monkeys";

            var list = new ListView();

            var viewModel = new MonkeysViewModel();
            list.ItemsSource = viewModel.Monkeys;

            var cell = new DataTemplate(typeof(TextCell));

            cell.SetBinding(TextCell.TextProperty, "Name");
            cell.SetBinding(TextCell.DetailProperty, "Location");

            list.ItemTemplate = cell;

            Content = list;
        }
    }
}

위의 코드를 이해하려면 WPF를 알고 있어야 합니다. 왜냐하면 Xamarin.Forms는 이름과는 달리 윈도우 폼이 아닌 WPF 방식을 채택하고 있기 때문입니다. (여기서 WPF에 대한 설명을 하는 것은 어렵기 때문에 넘어가겠습니다. ^^)

자, 이제 이렇게 만들어진 앱 페이지를 각각의 플랫폼 프로젝트에서 사용해야겠지요. ^^

그 방법은 (Portable) 프로젝트의 App.cs에 정의된 GetMainPage가 해결해 줍니다. 즉, 3개의 앱 프로젝트는 (Portable) 프로젝트의 App.GetMainPage가 반환하는 페이지를 화면에 보여주는 것입니다. 따라서, 다음과 같이 App.cs 코드를 (기존 내용은 지우고) 구성하면 됩니다.

using Xamarin.Forms;

namespace Part1
{
    public class App
    {
        public static Page GetMainPage()
        {
            var monkeys = new MonkeyPage();
            return new NavigationPage(monkeys);

        }
    }
}

끝입니다. 여기까지 코드를 구성했으면 이제 "Part1.WinPhone (Windows Phone 8.0)" 프로젝트를 빌드하고 (에뮬레이터로) 실행하면 이렇게 보입니다.

xamarin_forms_part1_3.png

이어서 "Part1.Android" 프로젝트를 빌드하고 (에뮬레이터로) 실행하면 이렇게 보입니다.

xamarin_forms_part1_4.png

오... 그렇군요. 안타깝지만 아직 UI를 똑같이 보여주지는 않습니다. ^^ 따지고 보면, 윈폰에서 Detail 속성의 텍스트가 파란색으로 나오는 것이 오히려 이상합니다. 위의 코드에 보면 우리는 파란색이 나오라고 지정한 것이 없기 때문입니다. 아마도 Xamarin 측에서 TextCell.DetailProperty를 자동으로 파란색으로 바인딩하는 것 같은데, 그 처리를 안드로이드에서는 아직 구현을 못한 것 같습니다.




첫 페이지는 그렇게 했고, 그럼 다른 페이지로의 네비게이션은 어떻게 할 수 있을까요? 이를 위해 ListView 컨트롤의 항목을 눌렀을 때 상세 페이지로 넘어가는 것을 "Learning Xamarin - Part 1: Forms"글에 설명된 데로 구현해 보겠습니다.

우선, MonkeyPage 만들때처럼 ContentPage를 상속받는 DetailPage.cs를 (Portable) 프로젝트에 추가합니다.

using Xamarin.Forms;

namespace Part1
{
    public class DetailPage : ContentPage
    {
        public DetailPage(Monkey monkey)
        {
            this.Title = monkey.Name;

            var details = new Label
            {
                Text = monkey.Details
            };

            Content = new ScrollView
            {
                Padding = 20,
                Content = details
            };
        }
    }
}

그리곤 MonkeyPage.cs에 포함된 ListView 항목이 선택되는 것에 대한 이벤트 처리기를 만들고 DetailPage에 네비게이션하는 코드를 넣어줍니다.


using Monkeys.ViewModels;
using Xamarin.Forms;

namespace Part1
{
    public class MonkeyPage : ContentPage
    {
        public MonkeyPage()
        {
            // ... [생략] ...

            list.ItemTapped += (sender, args) =&gt;
            {
              var monkey = args.Item as Monkey;
              if (monkey == null)
                return;

              Navigation.PushAsync(new DetailsPage(monkey));
              list.SelectedItem = null;
            };

            Content = list;
        }
    }
}

보시는 바와 같이 Navigation 객체의 PushAsync를 이용해 다른 페이지로 네비게이션하고 있습니다. 역시 이번에도 실행해 보면 윈도우폰/안드로이드폰 모두 잘 동작합니다.

이 정도면, Xamarin.Forms에 대해서 대강 감이 오시겠죠? ^^

(첨부 파일은 위의 예제 코드를 구현한 Xamarin 프로젝트입니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/10/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2014-07-08 02시03분
멋지군요. Xamarin.Forms 관련 라이브러리들이 등장하는군요.

XForms/Xamarin-Forms-Labs
; https://github.com/XForms/Xamarin-Forms-Labs

--------------------------------------------------------

Microsoft Graveyard
; https://microsoftgraveyard.com/
정성태
2021-01-26 07시53분
[.NET Conf 2021 x Seoul] 닷넷으로 하는 크로스 플랫폼 앱 개발의 현재와 미래 (동영상 57분)
; https://www.youtube.com/watch?v=WdssztHlPDI
정성태

1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13497정성태12/22/20232276오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20232281Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20232273Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232469Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232521닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232247개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20232200Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232296개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232057개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20231992오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20232292개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20232092개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
13485정성태12/15/20231986오류 유형: 883. dotnet build/restore - error : Root element is missing
13484정성태12/14/20232058개발 환경 구성: 694. Windows 디렉터리 경로를 WSL의 /mnt 포맷으로 구하는 방법
13483정성태12/14/20232193닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법파일 다운로드1
13482정성태12/13/20232725닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20232173개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20232480개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20232206개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20232409닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20232140닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
13475정성태12/7/20232200닷넷: 2180. .NET 8 - 함수 포인터에 대한 Reflection 정보 조회파일 다운로드1
13474정성태12/6/20232068개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
13473정성태12/5/20232261닷넷: 2179. C# - 값 형식(Blittable)을 메모리 복사를 이용해 바이트 배열로 직렬화/역직렬화파일 다운로드1
13472정성태12/4/20232080C/C++: 164. Visual C++ - InterlockedCompareExchange128 사용 방법
13471정성태12/4/20232129Copilot - To enable GitHub Copilot, authorize this extension using GitHub's device flow
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...