전체 페이지뷰

2017년 4월 10일 월요일

Chapter 7. XAML vs. code, part 2

Platform specificity in the XAML file

여기 ScaryColorList라는 이미 본 스니펫과 유사한 XAML 파일이 있습니다. 다른 점은 모든 색상 아이템이 Frame으로 둘러싸여져서 반복과정이 훨씬 무섭다(?)는 점입니다.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ScaryColorList"
             x:Class="ScaryColorList.MainPage">
    <ContentPage.Content>
        <StackLayout>
            <StackLayout.Children>
                <Frame OutlineColor="Accent">
                    <Frame.Content>
                        <StackLayout Orientation="Horizontal">
                            <StackLayout.Children>
                                <BoxView Color="Red" />
                                <Label Text="Red" VerticalOptions="Center" />
                            </StackLayout.Children>
                        </StackLayout>
                    </Frame.Content>
                </Frame>
                <Frame OutlineColor="Accent">
                    <Frame.Content>
                        <StackLayout Orientation="Horizontal">
                            <StackLayout.Children>
                                <BoxView Color="Green" />
                                <Label Text="Green" VerticalOptions="Center" />
                            </StackLayout.Children>
                        </StackLayout>
                    </Frame.Content>
                </Frame>
                <Frame OutlineColor="Accent">
                    <Frame.Content>
                        <StackLayout Orientation="Horizontal">
                            <StackLayout.Children>
                                <BoxView Color="Blue" />
                                <Label Text="Blue" VerticalOptions="Center" />
                            </StackLayout.Children>
                        </StackLayout>
                    </Frame.Content>
                </Frame>
            </StackLayout.Children>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
cs


code-behind 파일에는 오로지 InitializeComponent 메소드 호출만 들어 있습니다.

반복적인 마크업이 나오는 것은 논외로 치더라도 이 프로그램에는 실용적 측면에서 문제가 있습니다. iOS에서 돌아갈 때 최상단 아이템이 상태바와 겹쳐집니다. 이 문제는 페이지 생성자에서 Device.OnPlatform을 호출하면 고쳐집니다(2장에서 공부했습니다). Device.OnPlatform은 페이지의 Padding 프로퍼티를 설정할 뿐 XAML 파일에 아무 것도 필요로 하지 않기 때문에 InitializeComponent 호출 전이나 후 아무 때나 올 수 있습니다.

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        Padding = Device.OnPlatform(new Thickness(02000), 
            new Thickness(0), new Thickness(0));
           
            InitializeComponent();
    }
}
cs

Device.OnPlatform 사용시 obsolete attribute가 더 이상 사용하지 말 것을 권고하고 있습니다. 대신 switch(RuntimePlatform)을 사용하라는군요. switch나 if 같은 분기문을 사용하라는 뜻인가 봅니다. 아래처럼 바꾸어 보니 똑같이 동작합니다. (제가 뭔가 공지를 잘 못 보고 있나 봅니다. 며칠 사이에 뭐가 이렇게 바뀌어 버렸는데 모르고 있다니...)
public partial class MainPage : ContentPage
{
    public MainPage()
    { 
        if (Device.RuntimePlatform == "iOS")
        {
            Padding = new Thickness(02000);
        }
        else
        {
            Padding = new Thickness(0);
        }
            
        InitializeComponent();
    }
}
cs
계속해서 책의 내용을 진행합니다.

또는 XAML 파일의 루트 요소에서 세 가지 플랫폼 모두에 대해 균일 한 패딩 값을 설정해 버릴 수도 있습니다.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ScaryColorList"
             x:Class="ScaryColorList.MainPage"
             Padding="0,20,0,0">
    <ContentPage.Content>
        ...
    </ContentPage.Content>
</ContentPage>
cs

이렇게 하면 페이지의 Padding 프로퍼티가 설정됩니다. ThicknessTypeConverter 클래스는 값을 쉼표로 구분합니다만 Thickness 생성자와 같은 유연성을 갖습니다. 네 개의 값을 좌, 상, 우, 하 순서로 지정할 수도 있고, 두 개의 값을 좌우, 상하로 지정할 수도 있으며, 하나로 지정할 수도 있습니다.

또는 OnPlatform 메소드를 사용하여 XAML에서 플랫폼 고유 값을 지정할 수도 있는데, 이름으로 보아 알 수 있듯이 Device.OnPlatform 메소드와 유사합니다.

OnPlatform은 매우 흥미로운 클래스여서, 자세히 알아둘 가치가 있습니다. 이 클래스는 generic이며, 내부에 T type의 세 가지 프로퍼티와 Device.OS 값에 의거해 해당 플랫폼의 프로퍼티를 반환해 주는 메소드를 가집니다.

public class OnPlatform<T>
{
    public T iOS { get; set; }
    public T Android { get; set; }
    public T WinPhone { get; set; }
    public static implicit operator T(OnPlatform<T> onPlatform)
    {
        // returns one of the three properties based on Device.OS 
    }
}
cs

이론 상으로는 ContentPage에서 파생된 클래스의 생성자에서 사용하는 것처럼 OnPlatform<T> 클래스를 사용할 수 있습니다.
Padding = new OnPlatform<Thickness> 
{
    iOS = new Thickness(02000),
    Android = new Thickness(0),
    WinPhone = new Thickness(0
};
cs

OnPlatform 클래스가 자체 인수를 generic 인수(이 경우 Thickness)로 변환하므로 이 OnPlatform 클래스의 인스턴스를 Padding 프로퍼티에 직접 설정할 수 있습니다.

그러나 코드에서 OnPlatform을 사용하지 마시고, 대신 Device.OnPlatform을 사용하십시오. OnPlatform은 XAML 용으로 설계 되었으며 제네릭 형식 인수를 지정하는 방법만 알면 어려울 것은 없습니다.

다행히 XAML 2009 사양에는 TypeArguments라는 제네릭 클래스 용으로 디자인된 attribute가 포함되어 있습니다. XAML 자체의 일부이기 때문에 x 접두사와 함께 사용되며, 따라서 x:TypeArguments로 표시됩니다. 다음은 XAML에서 OnPlatform을 사용하여 세 가지 Thickness를 선택하는 방법입니다.

<OnPlatform x:TypeArguments="Thickness" 
            iOS="0, 20, 0, 0" 
            Android="0" 
            WinPhone="0" />
cs

위의 예에서 AndroidWinPhone 세팅은 생략되어도 디폴트값과 같으므로 상관없습니다. XAML 파서가 ThicknessTypeConverter를 사용하여 문자열을 변환해줄 것이므로 Thickness 문자열을 직접 프로퍼티에 설정할 수 있다는 점에 유의 바랍니다.

이제 이 OnPlatform 마크업을 페이지의 Padding 속성에 어떻게 설정합니까? 물론 Property-Element 구문을 사용하면 되겠지요.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ScaryColorList"
             x:Class="ScaryColorList.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" 
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <ContentPage.Content>
        ...        
    </ContentPage.Content>
</ContentPage>
cs

실행한 결과는 다음과 같습니다.


OnDevice와 마찬가지로 OnIdiomPhoneTablet을 구별합니다. 다음 장에서 명백 한 이유를 알게 될 테지만, OnDevice OnIdiom의 사용을 큰 블록이 아닌 작은 덩어리의 마크업으로 제한해야 합니다. 이들의 사용은 XAML 파일의 구조적 element가 되어서는 안됩니다.


The content property attribute

ScaryColorList의 XAML 파일은 필요 이상으로 긴 감이 있습니다. ContentPage.Content 태그, 모든 StackLayout.Children 태그 및 모든 Frame.Content 태그를 삭제해도 프로그램이 동일하게 작동합니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ScaryColorList"
             x:Class="ScaryColorList.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" 
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <StackLayout>
        <Frame OutlineColor="Accent">
            <StackLayout Orientation="Horizontal">
                <BoxView Color="Red" />
                <Label Text="Red" VerticalOptions="Center" />
            </StackLayout>
        </Frame>
        <Frame OutlineColor="Accent">
            <StackLayout Orientation="Horizontal">
                <BoxView Color="Green" />
                <Label Text="Green" VerticalOptions="Center" />
            </StackLayout>
        </Frame>
        <Frame OutlineColor="Accent">
            <StackLayout Orientation="Horizontal">
                <BoxView Color="Blue" />
                <Label Text="Blue" VerticalOptions="Center" />
            </StackLayout>
        </Frame>
    </StackLayout>
</ContentPage>
cs

좀 더 명료해 졌습니다. 이제 남은 property element는 Padding 뿐입니다.

XAML 문법 상, 일부 property element의 제거는 배후 클래스에서 지원됩니다. XAML에서 사용되는 모든 클래스는 하나의 프로퍼티를 content property로(default property라고도 부름)으로 정의할 수 있습니다. 이 content property의 경우 property-element 태그는 필요하지 않으며, start와 end 태그 사이의 모든 XML 내용이 이 property에 자동으로 할당됩니다. 편리하게도 ContentPage의 content property는 Content이고, StackLayoutChildren, FrameContent입니다.

이 콘텐트 프로퍼티는 문서화 되어 있는데, 어디서 찾아볼 수 있는지 알아 봅시다. 클래스는 ContentPropertyAttribute를 사용하여 content property를 지정합니다. 이 attribute를 클래스에 연결하면, 클래스 선언과 함께 온라인 Xamarin.Forms API 문서에 나타납니다. ContentPage의 문서에는 다음과 같이 표시됩니다.

[Xamarin.Forms.ContentProperty("Content")] 
public class ContentPage : TemplatedPage

Content 속성은 ContentPage의 content 속성입니다.
Frame 클래스 선언도 유사합니다.

[Xamarin.Forms.ContentProperty("Content")] 
public class Frame : ContentView

StackLayout에는 ContentProperty 특성이 없지만 StackLayoutLayout<View>에서 파생되며 Layout<T>에는 ContentProperty attribute가 있습니다.

[Xamarin.Forms.ContentProperty("Children")] 
public abstract class Layout<T> : Layout, IViewContainer<T> 
where T : View

ContentProperty attribute는 Layout<T>에서 파생된 클래스에 상속됩니다. 따라서 ChildrenStackLayout의 content property입니다.

필요가 없는 property element를 쓴다고 해도 아무런 문제가 없지만, 앞으로 이 책에서는 필요없는 경우엔 모두 생략하겠습니다.


Formatted text

XAML 파일로 출력된 텍스트가 한 두 단어가 아니라 한 단락 이상이 되어 문자 서식을 내부에 필요로 하는 경우가 있습니다. XAML에서의 문자 서식 지정은 HTML만큼 쉽지는 않습니다.

TextVariations 솔루션에는 스크롤 가능한 StackLayout에 7 개의 Label 뷰가 포함 된 XAML 파일이 있습니다.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TextVariations.TextVariationsPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <ScrollView>
        <StackLayout>
            ...
        </StackLayout>
    </ScrollView>
</ContentPage>
cs

7 개의 Label 뷰 각각은 표시된 텍스트를 정의하는 다소 다른 방법을 보여줍니다. 세 플랫폼에서 실행된 프로그램의 모습을 참고하시기 바랍니다.


가장 간단한 방법은 Label element의 Text attribute에 몇 단어를 설정하는 것입니다.
<Label VerticalOptions="CenterAndExpand" 
       Text="Single lines of text are easy." />
cs

Text property를 property element로 분리하여 설정할 수도 있습니다.
<Label VerticalOptions="CenterAndExpand">
    <Label.Text>
        Text can also be content of the Text property.
    </Label.Text>
</Label>
cs

TextLabel의 content property이므로 Label.Text 태그가 필요 없습니다.
<Label VerticalOptions="CenterAndExpand">
    Text is the content property of Label.
</Label>
cs

텍스트를 Label의 content로 설정하면 (Label.Text 태그 사용 여부와 관계없이) 텍스트가 트리밍(trimming) 됩니다: carriage return을 포함한 모든 공백은 텍스트의 앞 뒤에서 잘려 나가고 텍스트 내의 공백(end-of-line 문자 포함)은 유지됩니다.

Text property를 property attribute로 설정하면 따옴표 내의 모든 공백은 유지됩니다. 그러나 XAML 파일 내에서 문장이 여러 줄로 되어 있다면 각각의 end-of-line 문자는 공백 한칸으로 변환됩니다.

결과적으로, 텍스트의 전체 단락을 균일한 포맷으로 표시하는 것은 좀 문제가 있습니다. 가장 확실한 방법은 Text를 property attribute으로 설정하는 것입니다. 물론 한 단락 전체를 한 줄로 쓸 수도 있습니다만 여러 줄로 쓰고 싶다면  아래 처럼 따옴표로 묶어서 써야 합니다.
<Label VerticalOptions="CenterAndExpand" 
       Text= 
"Perhaps the best way to define a paragraph of 
uniformly formatted text is by setting the Text 
property as an attribute and left justifying 
the block of text in the XAML file. End-of-line 
characters are converted to a space character." />
cs

end-of-line 문자는 공백으로 치환되어 합쳐집니다. 그러나 개별 줄 앞 뒤에 구분을 위한  stray 문자(\)를 남겨두지 말도록 하시기 바랍니다. 그 문자 역시 출력될 것입니다.

여러 줄의 텍스트가 Label의 content로 지정될 때에는 시작과 끝에 있는 공백만 잘려 나갑니다. 모든 내부 공백(end-of-line 문자 포함)은 유지됩니다.
<Label VerticalOptions="CenterAndExpand">
Text as content has the curse 
Of breaks at each line's close. 
That's a format great for verse 
But not the best for prose.
</Label>
cs

이 텍스트는 네 개의 별도 라인으로 렌더링됩니다. 앱에서 목록이나 시를 표시하는 경우 바람직할 겁니다.

불규칙적인 서식이 필요한 경우 FormattedText property를 사용하면 좋습니다. 먼저 이것을 FormattedString 객체로 설정하고, 여러 Span 객체를 FormattedStringSpans 컬렉션으로 설정합니다. Label.FormattedString에 대한 property-element 태그가 필요하지만 SpansFormattedString의 content property라서 생략 가능합니다
<Label VerticalOptions="CenterAndExpand">
    <Label.FormattedText>
        <FormattedString>
            <Span Text="A single line with " />
            <Span Text="bold" FontAttributes="Bold" />
            <Span Text=" and " />
            <Span Text="italic" FontAttributes="Italic" />
            <Span Text=" and " />
            <Span Text="large" FontSize="Large" />
            <Span Text=" text." />
        </FormattedString>
    </Label.FormattedText>
</Label>
cs

이 불규칙한 서식의 Text property가 시작 부분이나 끝 부분에 공백을 가져야 아이템간에 겹치지 않는다는 것을 기억하기 바랍니다.

하지만 보통의 경우 문단 전체 단위에서 작업하게 마련입니다. SpanText attribute를 긴 줄로 설정하거나 여러 줄로 할 수 있습니다. Label과 마찬가지로 문단 전체를 왼쪽 정렬해 줍니다.
            <Label VerticalOptions="CenterAndExpand">
                <Label.FormattedText>
                    <FormattedString>
                        <Span Text= 
"A paragraph of formatted text requires left justifying 
it within the XAML file. But the text can include multiple 
kinds of character formatting, including " />
                        <Span Text="bold" FontAttributes="Bold" />
                        <Span Text=" and " />
                        <Span Text="italic" FontAttributes="Italic" />
                        <Span Text=" and " />
                        <Span Text="large" FontSize="Large" />
                        <Span Text= 
" and whatever combinations you might desire to adorn 
your glorious prose." />
                    </FormattedString>
                </Label.FormattedText>
            </Label>
cs

스크린 샷에서 보면, large 사이즈의 텍스트가 regular 텍스트와 정렬되고 줄 간격이 조정되어 큰 텍스트를 수용할 수 있게 바뀝니다.

대부분의 자마린 프로그램에서 XAML과 코드는 독립적인 것이 아니라 함께 작동합니다. XAML의 element는 코드에서 처리되는 이벤트를 발생시킬 수 있으며, 코드는 XAML의 element를 수정할 수 있습니다. 다음 장에서 그런 작업이 어떻게 수행되는지 보여 드릴 겁니다.

댓글 없음:

댓글 쓰기