티스토리 뷰

Xamarin.Forms Book/요약

7장 요약. XAML 및 코드

풍요로운 해구름 2018. 8. 15. 23:42
이 포스트의 내용은 Summary of Chapter 7. XAML vs. code (https://docs.microsoft.com/en-us/xamarin/xamarin-forms/creating-mobile-apps-xamarin-forms/summaries/chapter07) 을 번역한 자료입니다. 한글로 표현하기 어색하거나 모호한 문장은 임의로 조정하거나 삭제하였으므로 필요한 경우 원문을 참고하시기 바랍니다. 또한 언제든지 원저자의 요청에 의해 게시물이 내려지거나 수정될 수 있음을 알려드립니다. 마지막으로 이 글은 개인적인 학습목적으로 번역한 자료이며, 따라서 오역이나 잘못된 해석이 포함되어 있을 수 있으며 이로인한 어떠한 일체의 보장을 하지 않습니다.

이 문서에서는 다음 주제를 다룹니다.

참고
이 페이지에서  참고 영역은  Xamarin.Forms 책의 내용에서 달라진 부분이 무엇인지 기술하고 있습니다.

Xamarin.Forms는 XAML이라는 XML 기반 마크업 언어를 지원합니다. (XAML: Extensible Application Markup Language, zammel이라 발음됨) XAML은 Xamarin.Forms에서 레이아웃이나 UI를 개발할 때 C#을 대신할 수 있습니다. XAML을 사용하면 UI를 직관적으로 표현할 수 있으며 UI와 ViewModel 사이의 바인딩을 지원할 수 있습니다.

Property와 Attribute

Xamarin.Forms의 Class와 Struct들은 XAML에서 XML Element로 표현합니다. Class와 Struct의 Property들을 표현할 때는 XML Attribute를 사용합니다.

//C#으로 작성된 Label은 XAML로 변환될 수 있음
new Label
{
    Text = "Hello from Code!",
    IsVisible = true,
    Opacity = 0.75,
    TextColor = Color.Blue,
    BackgroundColor = Color.FromRgb(255, 128, 128),
    HorizontalTextAlignment = TextAlignment.Center,
    FontAttributes = FontAttributes.Bold | FontAttributes.Italic
};
<!-- XAML에서는 XML Element, XML Attribute로 C#코드를 표현함 -->
<Label Text="Hello from XAML!"
    IsVisible="True"
    Opacity="0.75"
    TextColor="Blue"
    BackgroundColor="#FF8080"
    HorizontalTextAlignment="Center"
    FontAttributes="Bold,Italic" />

XAML 파서가 XAML을 C# 인스턴스로 변환하기 위해서 C# Class들은 매개변수 없는 public 생성자를 포함하고 있어야 합니다. XAML에 정의된 Attribute를 C# Property로 변환하려면 해당 Property는 public setter를 가지고 있어야 합니다.

XAML 파서가 기본적인 변수유형(string, double, bool 등)을 변환할 때는 TryParse 메서드를 사용합니다. XAML 파서는 Enum 값도 해석할 수 있으며, Flags Attribute로 선언된 Enum의 Bitwise 조합 값도 해석할 수 있습니다.

//Flag Attribute 선언
[Flags]
public enum FontAttributes
{
    None = 0,
    Bold = 1,
    Italic = 2
}
<!-- XAML에서 Bitwise 조합 값을 사용 -->
<Label Text="Hello XAML!!" FontAttributes="Bold,Italic" />

XAML에서 모든 유니코드 문자를 XML Entity 문자로 표현할 수 있습니다.

<!-- Unicode가 포함된 문자열 "Cost — € 123.45" -->
<Label Text="Cost &#x2014; &#x20AC;123.45" />

대표적으로 자주 사용되는 XML Entity 문자는 아래와 같습니다.

  • &lt;는 <
  • &gt;는 >
  • &amp;는 &
  • &apos;는 '
  • &quot;는 "
  • &#10는 줄바꿈-캐리지리턴(\r)
  • &#xA (혹은 &#x000A)는 줄바꿈-라인피드(\n)

HTML Entity문자 &nbsp;는 지원하지 않으며 대신 &#xA0;을 사용해야 합니다.

복잡한 Type(또는 해당 Type의 Property)은 TypeConverterAttribute를 선언하여 XAML변환을 지원할 수 있습니다. TypeConverterAttributeTypeConverter를 상속한 Class를 지정하게 됩니다. TypeConverter는 문자열을 특정 Type으로 변환하는 Class로서 XAML에 작성된 문자열을 Type으로 변환할 수 있게 합니다.

public class Label : View, IFontElement
{
    //Property가 Type(Class나 Struct)인 경우 TypeConverterAttribute를 사용하여 XAML변환을 지원
    //ColorTypeConverter는 TypeConverter를 상속하여 구현된 Class
    [TypeConverter (typeof(ColorTypeConverter))]
    public Color TextColor
    {
        ...
    }
}

예를들어 ColorTypeConverter는 "Red" 혹은 "#ff0000"과 같은 문자열을 Color 인스턴스로 변환할 수 있습니다.

참고
TypeConverter, TypeConverterAttribute에 대한 자세한 설명은 How to: Implement a Type Converter를 참고해주세요.

Property-element 표현

XAML에서 Class와 Object들은 XML Element로 표현됩니다. XAML에서는 Object Elements라고 부릅니다. Object들의 Property들은 XML Attribute로 표현합니다. XAML에서는 Property Attribute라고 합니다.

Property에 Class 인스턴스를 할당해야 하는 경우가 있습니다. 예를들어 아래와 같이 FrameLabel을 할당하는 경우가 있습니다.

//Content Property에 Label을 할당하는 코드는 Property Attribute로 표현하기 힘들다
new Frame
{
    Content = new Label
    {
        Text = "Greetings, Xamarin.Forms!"
    }
};

이를 위해 XAML에서는 Property Element를 제공합니다. Property Element는 Class 명과 Property 명을 조합한 태그명를 사용합니다. Property Element 태그 사이에는 다른 Object Element가 위치할 수 있습니다.

<Frame>
    <!-- 
        Content Property에 Label 인스턴스를 할당하기 위해 Property Element를 사용함
        Property Element는 <Class명.Property명>형태의 태그명을 사용함
     -->
    <Frame.Content>
        <Label Text="Greetings, Xamarin.Forms!" />
    </Frame.Content>
</Frame>

참고로 모든 Property 값은 Property Element 형태로 표현할 수 있습니다.

<Frame HorizontalOptions="Center">
    <Frame.HorizontalOptions>
        <LayoutOptions Alignment="Center" Expands="False" />
    </Frame.HorizontalOptions>
    <Frame.VerticalOptions>
        Center
    </Frame.VerticalOptions>
    <Frame.Content>
        <Label>
            <Label.Text>
                Greetings, Xamarin.Forms!
            </Label.Text>
        </Label>
    </Frame.Content>
</Frame>

IList<View>와 같은 Children Property 값도 표현가능합니다.

<!-- IList<View> Type의 Children 표현 -->
<StackLayout Orientation="Horizontal">
    <StackLayout.Children>
        <Label Text="Greeting! " />
        <Label Text="Xamarin!" />
    </StackLayout.Children>
</StackLayout>

프로젝트에 XAML 페이지 추가

Portable Class Library 프로젝트를 생성하면 하나의 XAML페이지를 기본적으로 포함하고 있습니다. 새로운 XAML 페이지를 추가하려면 Add New Item 다일로그에서 Xamarin.Forms의 ContentPage을 선택하시면 됩니다. (ContentView가 아님)

참고
이 챕터가 쓰여진 이후 Visual Studio 업데이트로 인해 ContentPage를 추가하는 방법에 미묘한 차이가 있을 수 있습니다.

페이지를 추가하면 .xaml 확장자를 가진 XML 파일과 .xaml.cs 확장자를 가진 C#파일이 생성됩니다. C#파일은 Code-behind 파일이라고 부릅니다. Code-behind 파일은 ContentPage를 상속하는 partial Class로 선언되어 있습니다.

//Code-behind 파일
namespace CodePlusXaml
{
    public partial class CodePlusXamlPage : ContentPage
    {
        public CodePlusXamlPage()
        {
            InitializeComponent();
        }
    }
}

XAML파일의 XML 코드를 분석해보면 최상위 Root Element는 ContentPage입니다. ContentPage에 포함된 Xml Namespace에서 xmlns="http://xamarin.com/schemas/2014/forms"는 이 문서가 Xamarin.Forms문서라는 것을 알리는 선언입니다. xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"는 이 문서가 XAML파일임을 알리는 선언입니다. 마지막으로 x:Class="CodePlusXaml.CodePlusXamlPage"는 Code-behind파일의 namespace, Class 명을 정의하고 있습니다. x:Class정보를 통해 컴파일러는 Code-behind와 XAML이 서로 쌍을 이루고 있음을 식별하게 됩니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="CodePlusXaml.CodePlusXamlPage">
    <ContentPage.Content>
    </ContentPage.Content>
</ContentPage>

XAML 파일은 빌드가 진행될 때 C# Partial Class 파일로 변환되며 프로젝트의 obj/Debug 폴더에 xaml.g.cs 파일로 저장됩니다. xaml.g.cs파일을 열어보면 아래와 같이 Code-behind와 동일한 이름을 가진 Partial Class가 생성되어 있음을 알수 있습니다.

//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CodePlusXaml {
    using System;
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;

    public partial class CodePlusXamlPage : global::Xamarin.Forms.ContentPage {

        [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG",
                                                        "0.0.0.0")]
        private void InitializeComponent() {
            this.LoadFromXaml(typeof(CodePlusXamlPage));
        }
    }
}

xaml.g.cs파일에는 InitializeComponent 메서드와 XAML에서 변환된 C# 코드를 포함하게 됩니다. LoadFromXaml 메서드는 Xamarin.Forms.Xaml 어셈블리에 정의된 Extension 메서드로 이 메서드의 역할은 XAML 컴파일러를 설명할 때 같이 설명합니다. 프로젝트가 실행되면 Code-behind의 생성자는 xaml.g.cs파일에 존재하는 InitializeComponent 메서드를 호출하여 ContentPage 인스턴스를 생성하고 초기화한 후 실행하게 됩니다.

XAML파일과 Code에 대한 예제는 CodePlusXaml 샘플에서 확인하실 수 있습니다.

XAML 컴파일러

Xamarin.Forms는 XAML 컴파일러를 포함하고 있습니다. XAML 컴파일러는 빌드가 진행될 때 XAML 유효성검사를 진행하고, 실행파일의 크기를 줄이며, 로드속도를 향상시킵니다. 하지만 XAML 컴파일러는 새롭게 추가된 기능이기 때문에, XAML 컴파일러를 사용했을 때 호환성 문제가 발생할 수 도 있습니다. 따라서 XamlCompilationAttribute를 통해 XAML 컴파일러의 사용여부를 결정할 수 있습니다.

namespace CodePlusXaml
{
    //XamlCompilationOptions를 통해 XAML 컴파일러를 사용할 지 결정할 수 있다
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class CodePlusXamlPage : ContentPage
    {
        public CodePlusXamlPage()
        {
            InitializeComponent();
        }
    }
}

XamlCompilationOptions Enum은 CompileSkip를 포함하고 있습니다. 만약 Skip을 선택하면 XAML 컴파일러를 사용하지 않게 됩니다. 이 경우 XAML파일은 빌드 과정에서 Embedded Resource로 파싱되며 실행 중에 LoadFromXaml 메서드를 호출하여 Embedded Resource파일을 로드하고 약간의 시간을 들여 인스턴스화와 초기화를 마무리하게 됩니다.

만약 Compile 선택하면 XAML 컴파일러는 빌드과정이 진행될 때 XAML을 컴파일하고 바이너리를 생성하며, 실행 중에는 바이너리 파일을 로드하여 빠른속도로 실행하게 됩니다.

XAML 파일에서 플랫폼 특이성

플렛폼 별로 서로다른 XAML 코드를 실행하기 위해 OnPlatform Class를 사용합니다. OnPlatform은 Generic Class이며 x:TypeArguments Attribute와 함께 선언되어야 합니다. x:TypeArguments Attribute을 통해 플렛폼 별로 실행할 값을 지정할 수 있습니다. OnIdiom Class도 이와 유사하게 사용될 수 있습니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ScaryColorList.ScaryColorListPage">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
</ContentPage>

이 책이 쓰여진 이후로 OnPlatform의 작성방법이 변경되었습니다. 과거에는 OnPlatform과 iOS, Android, WinPhone Attribute를 조합하여 사용하였습니다. 새로운 작성방법은 On Class를 사용합니다. Platform Property에는 Device Class에 정의된 const 문자열을 지정하여 플렛폼을 지정합니다. Value Property에는 OnPlatform태그의 x:TypeArguments Type에 해당하는 값을 지정해주세요.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ScaryColorList.ScaryColorListPage">
    
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>
</ContentPage>

ScaryColorList 샘플에는 OnPlatform에 대한 예제를 포함하고 있습니다. 프로젝트 이름에 Scary(끔찍한, 무서운)가 포함된 이유는 이 프로젝트는 거의 동일한 XAML 블록들을 반복하고 있기 때문입니다. ScaryColorList 샘플은 3개 색상을 표현하기 위해 3번의 중복 XAML 코드를 작성했지만, 만약 3개가 아니라 256개의 색을 표현해야 했다면 어땠을까요? 중복 코드를 256회에 걸쳐 반복해서 작성해야 했을 것입니다. 이러한 반복을 줄이기 위해서는 C# 코드를 활용하거나, 추후 챕터에서 다루게 될 ContentView를 활용해야 합니다.

Content Property Attribute

XAML에서 특정 코드는 상당히 자주 반복됩니다. 예를들어 ContentPage<ContentPage.Content>나, StackLayout<StackLayout.Children>이 있습니다.

<ContentPage.Content><StackLayout.Children> Property는 ContentPropertyAttribute를 통해 Type을 식별합니다. 다행이 ContentPropertyAttribute는 Property Element 태그를 생략할 수 있게 허용합니다. 따라서 ContentPageContent Property나 Layout<T>(StackLayout이 상속하는 Class)가 정의하는 Children Property는 생략할 수 있습니다.

이와 동일하게 LabelText Property Element를 생략할 수 있습니다.

<StackLayout Orientation="Horizontal">
    <!-- StackLayout.Children은 생략할 수 있음 -->
    <!-- <StackLayout.Children> -->
    <Label VerticalOptions="Center">
        <!-- Label.Text는 생략할 수 있음 -->
        <!-- <Label.Text> -->
            Greetings!!
        <!-- </Label.Text> -->
    </Label>
    <!-- </StackLayout.Children> -->
</StackLayout>

서식 있는 텍스트

TextVariations 샘플은 LabelTextFormattedText Property를 사용하는 몇가지 예제를 포함하고 있습니다.

만약 줄바꿈 문자가 포함된 문자열을 Text에 지정하면 줄바꿈 문자는 공백으로 출력되고 줄바꿈이 일어나지 않을 것입니다. 그러나 몇가지 트릭이나 XAML 작성 방법을 달리하여 줄바꿈을 유지할 수 있는 방법을 샘플에서 확인할 수 있습니다.
Formatted Text

댓글
댓글쓰기 폼