전체 페이지뷰

2017년 2월 21일 화요일

Chapter 4. Scrolling the stack, part 4

A ScrollView in a StackLayout?


일반적으로는 ScrollView 내에 StackLayout을 두는 것이 정석입니다. 반대로 StackLayout내에 ScrollView를 둘 수도 있을까요? 만약 된다면 무엇을 위한 것일까요?

스택 내에 스크롤을 넣을 수 없다는 것은 Xamarin.Forms와 같은 레이아웃 시스템의 일반적 룰입니다. ScrollView는 내부의 내용 높이와 자기 자신의 높이 차이를 계산할 수 있도록 정해진 높이를 필요로 합니다. 그 차이가 바로 ScrollView가 스크롤 할 수 있는 양이 됩니다. 그런데 만약 StackLayout 내에 ScrollView가 있다면 높이가 특정 값으로 정해지지 않습니다. StackLayoutScrollView의 높이를 가능한 한 짧게 하려고 할 것이고, 결과적으로 높이는 0이 되어서 프로그램이 작동하지 않게 됩니다.

그런데 왜 StackLayout 내에 ScrollView를 넣으려고 하는 걸까요?

때로 그런 것이 필요한 때가 있습니다. 스크롤링을 구현하는 원시 e-book 리더를 생각해보십시오. 페이지의 가장 위쪽에 언제나 책제목을 보여주는 Label이 있고 그 아래에 책 내용을 담고 있는 StackLayout을 가지는 ScrollView가 있습니다. LabelScrollView가 화면 전체를 채우는 StackLayout의 children이면 편리할 것입니다.

Xamarin.Forms에서는 그런 일이 가능합니다. ScrollViewLayoutOptions.FillAndExpandVerticalOptions 설정으로 지정하면 실제로 ScrollViewStackLayout의 자식이 될 수 있습니다. StackLayout은 다른 자식에 의해 필요없는 모든 추가 공간을 ScrollView에 제공하므로 높이가 정해집니다. 흥미롭게도, Xamarin.Forms은 VerticalOptions 속성의 다른 설정을 보호하므로, 어떤 값으로 설정하던지 관계없이 작동합니다.

BlackCat 프로젝트는 TheBlackCat.txt라는 텍스트 파일에 단 한 줄짜리 형식으로 저장되어있는 Edgar Allan Poe의 단편 소설 "The Black Cat"의 텍스트를 표시합니다.

BlackCat 프로그램이 어떻게 이 단편소설의  파일에 접근할까요? 아마도 가장 쉬운 방법은 실행 파일 또는 (Xamarin.Forms 응용 프로그램의 경우) Portable Class Library DLL에 텍스트 파일을 embed시키는 것일 겁니다. 이 방법을 embeded resource라고 부릅니다.

Visual Studio 또는 Xamarin Studio에서 임베디드 리소스를 만들려면 먼저 프로젝트 이름에 우클릭하고 추가> 새 폴더 옵션을 선택하여 프로젝트에서 폴더를 만듭니다. 예를 들어 Texts라고 폴더명을 정합시다. 반드시 폴더를 만들어야 하는 것은 아니지만 애셋 구성에 도움이 될 것입니다. 그런 다음 해당 폴더에 우클릭, Visual Studio에서 추가> 기존 항목을 선택하거나 Xamarin Studio에서 추가> 파일 추가를 선택할 수 있습니다. 파일로 이동하여 파일을 선택하고 Visual Studio에서 추가를 클릭하거나 Xamarin Studio에서 열기를 클릭합니다.

아직 중요한 것이 남아 있습니다. 일단 파일이 프로젝트의 일부가 되면, 해당 파일을 우클릭한 후 속성 창을 불러옵니다. 빌드작업(Build Action)을 포함리소스(EmbeddedResource)로 지정하십시오. 필수적이지만 잊기 쉬운 단계이므로 주의를 요합니다.


이렇게 해서 TheBlackCat.txt 파일이 BlackCat.dll에 포함됩니다.

코드 상에서 System.Reflection 네임스페이스의 Assembly 클래스에 정의된 GetManifestResourceStream 메서드를 호출하면 파일을 검색할 수 있습니다. PCL의 어셈블리를 얻으려면 어셈블리에 정의된 클래스의 Type을 가져와야합니다. 그러기 위해서는 해당 클래스의 인스턴스에서 ContentPage로부터 파생된 페이지에 typeof를 사용하거나, GetType 메소드를 사용하면 됩니다. 그런 다음 이 Type 객체에 대해 GetTypeInfo를 호출합니다. Assembly는 그 결과로 나오는 TypeInfo 객체의 속성입니다.

Assembly assembly = GetType().GetTypeInfo().Assembly;
cs

이제, AssemblyGetManifestResourceStream 메서드에서 리소스의 이름을 지정해야합니다. embeded resource의 경우 해당 이름은 resource 파일 이름이 아니라 resource ID입니다. resource ID는 파일명과 비슷하게 보일 수 있으므로 혼동하기 쉽습니다.

resource ID는 어셈블리의 default namespace로 시작됩니다(.NET namespace가 아닙니다).
Visual Studio에서 어셈블리의 default namespace를 가져 오려면 프로젝트를 우클릭해서 속성(Properties)을 선택하고 속성 대화창 왼쪽의 라이브러리를 선택하고 기본 네임스페이스 필드를 확인합니다. Xamarin Studio의 프로젝트 메뉴에서 옵션을 선택하고 Project Options 대화 상자의 왼쪽에있는 Main Settings를 선택하고 Default Namespace 레이블이 붙은 필드를 찾습니다.

BlackCat 프로젝트의 경우 기본 네임스페이스는 어셈블리와 동일한 "BlackCat"입니다. 그러나 실제로 기본 네임 스페이스를 원하는대로 설정할 수 있습니다.

리소스 ID는 기본 네임스페이스, 마침표, 폴더명, 마침표, 파일명 순으로 정해집니다. 이 예제의 경우, resource ID는 "BlackCat.Texts.TheBlackCat.txt"이며, 이것을 GetManifestResourceStream 메서드로 전달합니다. 이 메서드는 .NET Stream object를 반환하고, StreamReader를 통해 텍스트를 읽을 수 있습니다. StreamReader 객체와 Stream 객체에는 using 문을 사용하는 것이 좋습니다.  using을 사용하면 객체가 더 이상 필요하지 않거나 예외가 발생할 경우 사용 해제해 주기 때문입니다.

BlackCatPage 생성자는 두 개의 StackLayout 객체(mainStacktextStack)를 만듭니다. 파일의 첫 번째 줄 (스토리의 제목과 작성자)은 mainStack에서 bold체로 표시되고 가운데 레이블이 되며 나머지 후속 줄은 textStack에 들어갑니다. mainStack 인스턴스내에 textStack이있는 ScrollView가 들어가게 됩니다.

using System.Reflection;
using System.IO;
 
using Xamarin.Forms;
 
namespace BlackCat
{
    public class BlackCatPage : ContentPage
    {
        public BlackCatPage()
        {
            StackLayout mainStack = new StackLayout();
            StackLayout textStack = new StackLayout
            {
                Padding = new Thickness(5),
                Spacing = 10
            };
 
            // text resource에 access
            Assembly assembly = GetType().GetTypeInfo().Assembly;
            string resource = "BlackCat.Texts.TheBlackCat.txt";
 
            using (Stream stream = assembly.GetManifestResourceStream(resource))
            {
                using (StreamReader reader=new StreamReader(stream))
                {
                    bool gotTitle = false;
                    string line;
 
                    // 한줄씩 읽어들임
                    while (null != (line = reader.ReadLine()))
                    {
                        Label label = new Label
                        {
                            Text = line,
 
                            // e-book용 검은 텍스트
                            TextColor = Color.Black
                        };
 
                        if (!gotTitle)
                        {
                            // 첫 줄(제목)을 mainStack으로
                            label.HorizontalOptions = LayoutOptions.Center;
                            label.FontSize = Device.GetNamedSize(NamedSize.Medium, label);
                            label.FontAttributes = FontAttributes.Bold;
                            mainStack.Children.Add(label);
                            gotTitle = true;
                        }
                        else
                        {
                            //나머지 줄은 textStack으로
                            textStack.Children.Add(label);
                        }
                    }
                }
            }
 
            // textStack을 FillAndExpand 옵션으로 ScrollView에 넣음
            ScrollView scrollView = new ScrollView
            {
                Content = textStack,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Padding = new Thickness(50),
            };
 
            // ScrollView를 mainStack의 두번째 child로 추가
            mainStack.Children.Add(scrollView);
 
            // page content를 mainStack으로 지정
            Content = mainStack;
 
            // e-book용 하얀 배경
            BackgroundColor = Color.White;
 
            // iOS용 padding 추가
            Padding = new Thickness(0, Device.OnPlatform(2000), 00);
        }
    }
}
cs

이것은 e-book이므로, 흰 바탕에 검은 텍스트라는 전통적 책의 색상을 따라했습니다.

이 프로그램은 PCL로 작성되었지만 Shared Asset Project로도 작성 가능합니다. SAP 프로젝트에서는 Embedded Resource가 각 애플리케이션 프로젝트의 일부가 되므로 default namespace도 프로젝트마다 다르게 사용해야 합니다. SAP 프로젝트에서 Resource Id를 사용하는 부분은 아래와 같습니다.

#if __IOS__ 
            string resource = "BlackCatSap.iOS.Texts.TheBlackCat.txt"
#elif __ANDROID__ 
            string resource = "BlackCatSap.Droid.Texts.TheBlackCat.txt"
#elif WINDOWS_UWP 
            string resource = "BlackCatSap.UWP.Texts.TheBlackCat.txt"
#elif WINDOWS_APP 
            string resource = "BlackCatSap.Windows.Texts.TheBlackCat.txt"
#elif WINDOWS_PHONE_APP
            string resource = "BlackCatSap.WinPhone.Texts.TheBlackCat.txt"
#endif
cs

만약 embeded resource를 참조하는데 문제가 있다면 잘못된 이름을 사용하고 잇어서일 가능성이 있습니다. Assembly 개체에 GetManifestResourceNames를 호출하여 모든 포함 리소스(embeded resource)의 resource ID 목록을 체크해 보시기 바랍니다.

댓글 없음:

댓글 쓰기