본문

예제로 알아보는 UINavigationController with Monotouch

* 기본
UINavigationController는 컨트롤러스택을 사용하여 설치마법사에서 사용하는 단계별 네비게이션 UI를 제공한다. Microsoft는 윈도우 비스타의 제어판에서 앞으로가기와 뒤로가기 버튼을 사용하는 방식을 선보이기도 했으며, UINavigationController도 이 방식과 별반 다를게 없다.


[동영상 1] UINavigationController의 동작예제


UINavigationController는 윈도우(화면)를 차지하며, 각각의 뷰/컨트롤러를 출력할 영역을 포함하고 있다. ASP.net의 MVC관점에서는, 컨트롤러는 여러 액션을 가지고 있고, 각각의 액션은 서로 다른 뷰를 가지고 있을 수 있었다. 하지만 iPhone과 Cocoa Touch 환경에서는 각각의 컨트롤러는 하나의 뷰만을 가지고 있다. 그리고 뷰는 ASP.net의 컨트롤 트리혹은 부분 뷰처럼 다른 뷰를 포함할 수 있다(서브뷰) 그러므로 컨트롤러에 대해 언급할 경우 이는 상위 계층인 컨테이너 뷰에 대해도 언급하고 있다고 생각하면 된다.

UINavigationController는 Controllers 속성을 가지고 있는데, 이는 보여질 컨트롤러의 배열을 의미한다. 이 배열의 첫번째 아이템은 UINavigationController에서 보여질 가장 처음 컨트롤러이다. 각각의 컨트롤러는 그 다음에 보여지게될 컨트롤러를 가져올(push) 책임이 있다. 다시말하면, UINavigationController의 루트 컨트롤러는 하나의 버튼을 가지고 있어서, 이 버튼이 눌렸을때 다음에 보여지게 될 컨트롤러를 가져올(push) 것이다. UINavigationController내에서 사용하는 컨트롤러들은 대개 UIViewController이거나 UITableViewController일것이다.


* UIViewController의 계층
계층 구조는 다음과 같다.
UIViewController
 - UITableController
 - UINavigationController
 - UITabBarController

보다시피 UINavigationController가 UIViewController를 상속받는 이 모델에서 의구심이 드는 점은, UIViewController에는 NavigationController라는 속성을 가지고 있는데, 이는 부모 클래스가 자식 클래스에 대해서 안다는 의미이며, 이는 객체지향적인 면에서 좋지 않게 느껴질 수 있다는 것이다. 하지만 UIKit이 계속 이런식으로 만들어져왔으니 어쩔 수 없다. 그리고 해보면 알겠지만UINavigationController내에 UINavigationController를 둘 수 없다. (런타임 오류가 발생한다)

컨트롤러를 가져올때마다(push) 부모객체의 NavigationController 속성을 통해서 (있을수도 있고 없을수도 있는) UINavigationController에 접근한다. 만약 컨트롤러가 없을 경우 이 속성은 Null을 가지게된다.


* XIB 없이 개발하기
이 문서와 딸려오는 두개의 문서는 인터페이스 빌더와 NIB/XIB를 사용하지 않은 채 개발하는것을 목표로 한다. 그러나 iPhone 프로젝트는 항상 하나의 XIB를 기본적으로 포함한다(MainWindow.xib) 이 파일은 개발한 애플리케이션을 담게될 부모 UIWindow에 대한 참조를 가진다.

물론 원한다면 XIB파일을 지우고 윈도우를 직접 만들 수 있다. 그리고 디자이너 파일이 없어진 만큼 AppDelegete 클래스도 부분 클래스(partial)일 필요는 없다.

public class Application
{
    static void Main(string[] args)
    {
        // Make sure you use this overload
        UIApplication.Main(args,null,"AppDelegate");
    }
}

[
Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
    private UIWindow window;
    private MyController controller;

    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {    
        var screenBounds = UIScreen.MainScreen.Bounds;

        controller = new MyController();
        window = new UIWindow(screenBounds);

        window.AddSubview(controller.View);
        window.MakeKeyAndVisible ();

        return true;
    }

    // This method is required in iPhoneOS 3.0
    public override void OnActivated (UIApplication application)
    {
    }
}


 
만약 XIB 파일을 지웠을 경우, 프로젝트 설정에서 "Mainwindow.xib"파일을 제거하는것을 잊지 않아야 한다. 이는 iPhone Application섹션의 Main interface file옵션에서 확인할 수 있다. 그러나 이 글을 작성할때는 굳이 이 파일을 지우지 않고, 대신 부분 클래스를 사용하는 방법을 사용할 것이다.


* UINavigationController 추가하기
만약 Monotouch에서 새 iPhone project를 만들었다면 가장 처음으로 해야 할 일은 새로운 클래스와 UINavigationController를 추가하는것이다. 우선 appdelegate 클래스에 두 줄의 코드를 추가함으로서 메인 윈도우가 컨트롤러를 출력할 수 있도록 해야한다.

public partial class AppDelegate : UIApplicationDelegate
{
    private NavigationController controller;
    public override bool FinishedLaunching (UIApplication app, NSDictionary options)
    {
        controller = new NavigationController();
        window.AddSubview (controller.View);
        window.MakeKeyAndVisible ();

        return true;
    }

    // This method is required in iPhoneOS 3.0
    public override void OnActivated (UIApplication application)
    {
    }
}


컨트롤러 선언시에 지역변수를 사용하지 않도록 주의해야 한다. iPhone Simulator를 사용할때는 문제가 없겠지만, 실제로 iPhone에서 운용할경우 가비지 컬렉션이 더욱 철저하고 치밀하게 이루어지므로 런타임오류가 발생할 수 있다, iPhone Simulator는 생성-소멸에 의한 가비지 컬렉션 메카니즘을 사용하지만, iPhone 기기위에서는 AOT로 컴파일되어있는 애플리케이션을 실행하게 되며 객체들은 더욱 철저한 방식으로 역참조 되게 된다. 

이 글은 보면서 참고할점은 사용하는 각각의 컨트롤러마다 새로운 클래스를 만들고 있다는 점이다. 물론 이렇게 하지 않아도 되지만(단순히 UIViewController의 인스턴스를 만들고 UINavigationController내에 컨트롤과 레이아웃 코드를 추가시킬수도 있다) 이방식으로 모든 레이아웃을 관리하는 하나의 통합된 클래스를 만들 수 있다. 각각의 컨트롤러마다 분리된 클래스를 만듦으로서 각각에 대해 책임을 확실히 분리시킬 수 있다. 즉, 스파게티 코드를 미연에 방지할 수있다는 것이다.

아래는 AppDelegate 클래스가 참조하는 NavigtionController이다

public class NavigationController : UINavigationController
{
    HomeViewController _firstController;

    public override void ViewDidLoad ()
    {
        _firstController = new HomeViewController();
        PushViewController(_firstController,true);

        base.ViewDidLoad ();
    }
}


이렇게 하면 UINavigationController가 출력하는 첫번째 컨트롤러/뷰를 추가할 준비가 되었다. 비록 첨부된 솔루션에 있는 하나의 .cs파일에 모든 정의를 다 모아놓았지만, Controllers라는 폴더를 만들고 각각의 컨트롤러 파일로 분리해서 저장하는 편이 더욱 깔끔할 것이다.

public class HomeViewController : UIViewController
{
    public override void ViewDidLoad ()
    {
    }
}
 
run버튼을 누르면 네비게이션 바와 흰 뷰를 출력할 것이다.


* ViewDidLoad, ViewWillAppear, ViewDidAppear
만약 컨트롤(실상은 뷰) 레이아웃을 초기화하는 코드를 어느 메소드에 넣어야할지 헷갈린다면 이곳을 확이해보라


* 맨 처음 컨트롤러에 컨트롤(뷰) 추가하기
참고로 글을 작성하면서 '컨트롤'이라는 단어를 라벨, 버튼, 텍스트 입력상자를 지칭하는데 쓰고 있는데, 애플측에서는 이들을 뷰라고 지칭한다. 아래에 있는 코드는 맨처음 보이는 컨트롤러에 간단한 라벨과 두개의 툴바버튼을 붙이는 방법을 보여준다. 각각의 컨트롤은 가운데로 정렬되어있고, 각각의 버튼은 다음 컨트롤을 가져오게 되고(push) 이로서 원하는 페이지가 출력되게 된다.

툴바와 네비게이션 바를 (부모인) UINavigationController안에 있는 모든 컨트롤러와 함께 원하는대로 숨기거나 보이게 할수 있다. HomeViewController 클래스는 ViewWillAppear 메소드를을 통해서 툴바가 보일 수 있도록 한다. 이렇게 한 이유는 가장 처음에 있는 버튼을 눌러서 다음 컨트롤을 가져올 때(push) 자동적으로 툴바가 숨겨지기 때문이다. 그리고 사용자가 뒤로가기 버튼을 누를 때(HomeViewController로 돌아올때) 툴바는 다시 보여져야 한다.

public class HomeViewController : UIViewController
{
    public override void ViewDidLoad ()
    {
        Title = "Home";

        // A 'view' aka label control for this view
        UILabel label = new UILabel();
        label.Text = "HomeViewController";
        label.Frame = Center(100,100);
        label.BackgroundColor = UIColor.LightGray;
        View.AddSubview(label);

        // 2 toolbar items
        UIBarButtonItem item1 = new UIBarButtonItem();
        item1.Title = "Click me";
        item1.Clicked += delegate(object sender, EventArgs e) {
            Level2ViewController controller = new Level2ViewController();
            NavigationController.PushViewController(controller,true);
        };

        UIBarButtonItem item2 = new UIBarButtonItem();
        item2.Title = "View with no back";
        item2.Clicked += delegate(object sender, EventArgs e) {
            ViewWithNoBackController controller = new ViewWithNoBackController();
            NavigationController.PushViewController(controller,true);
        };

        ToolbarItems = new UIBarButtonItem[] {item1,item2};


        base.ViewDidLoad ();
    }

    public override void ViewWillAppear (bool animated)
    {
        // Re-show the toolbar here for consistency
        NavigationController.SetToolbarHidden(false,true);
        base.ViewWillAppear (animated);
    }

    public RectangleF Center(float width,float height)
    {      
        var rect = new RectangleF((View.Frame.Width / 2) - (width /2),(View.Frame.Height / 2) - (height /2),width,height);

        // Prints:
        // Rect: {X=110,Y=180,Width=100,Height=100}
        // Frame: {X=0,Y=20,Width=320,Height=460}
        Console.WriteLine("Rect: {0}",rect);
        Console.WriteLine("Frame: {0}",View.Frame);

        return rect;
    }
}



* 뒤로가기 버튼 숨기기
틀바에 있는 두번째 버튼은 새로운 컨트롤러를 가져오고 이는 뒤로가기 버튼을 숨겨놓게 한다. 물론 뒤로가기 버튼이 없어지면 사용자가 할 수 있는 일이라곤 어플리케이션을 종료하는수밖에 없을것이다. 하지만 이는 그냥 어떻게 버튼을 숨길 수 있는지에 대한 예시이다. 개발하다보면 로딩중 화면에서 뒤로가기 버튼을 숨기고 싶을지도 모른다.

public class ViewWithNoBackController: UIViewController
{
    public override void ViewDidLoad ()
    {
        Title = "Settings";

        // The label again
        UILabel label = new UILabel();
        label.Text = "ViewWithNoBackController";
        label.Frame = new RectangleF(100,100,100,100);
        View.AddSubview(label);

        // Demonstrates hiding the back and toolbars.
        NavigationItem.SetHidesBackButton(true,true);
        NavigationController.SetToolbarHidden(true,true);

        base.ViewDidLoad ();
    }


}


* NavigationController.PushViewController에 애니메이션 주기
새로운 뷰를 받아올 때 간단한 화면 변환 효과를 주기 위해서 UIView.BeginAnimation을 사용하여 네가지의 효과를 보여줄 수 있다. 아래에 있는것처럼 쉽게 구현할 수 있는데, 좀 더 다양한 애니메이션을 원한다면 CATransition클래스를 사용하는 것이 좋을것이다.

// Make sure you push first or the Title doesn't get animated.

var controller = new MyController();
NavigationController.PushViewController(controller, false);

UIView.BeginAnimations(null,IntPtr.Zero);
UIView.SetAnimationDuration(1);    UIView.SetAnimationTransition(UIViewAnimationTransition.FlipFromLeft,NavigationController.View,true);
UIView.CommitAnimations();

만약 컨트롤러가 나오고 사라질때마다 애니메이션을 보고싶을 경우, 뒤로가기 버튼(혹은 현재 컨트롤러를 숨기도록 할 버튼)을 숨기고 클릭 이벤트를 직접 구현해야 할것이다. 이는 대략 아래와 같이 구현이 가능하다.

UIView.BeginAnimations(null,IntPtr.Zero);
UIView.SetAnimationDuration(1);
UIView.SetAnimationTransition(UIViewAnimationTransition.FlipFromRight,NavigationController.View,true);
NavigationController.PopViewControllerAnimated(false);
UIView.CommitAnimations();


* 툴바 교체하기
새로운 컨트롤러를 불러왔을 때, 툴바도 교체가 된다. 만약 지난 컨트롤러에서 사용된 툴바를 계속 사용하길 원한다면 UIViewController를 상속받는 클래스에 컨트롤러들을 넣고, 이 클래스의 ViewDidLoad 메소드에서 툴바를 추가시키게 하는 방법이 있다. 아래의 코드는 버튼이 눌려져서 다른 컨트롤러를 불러올때 새로운 툴바 아이템을 추가하는 방법을 나타낸다.
public class Level2ViewController: UIViewController
{
    public override void ViewDidLoad ()
    {
        Title = "Level 2";

        UILabel label = new UILabel();
        label.Text = "Level2ViewController";
        label.Frame = new System.Drawing.RectangleF(100,100,100,100);
        View.AddSubview(label);

        // A new toolbar with items
        UIBarButtonItem item = new UIBarButtonItem();
        item.Title = "Another item";
        item.Clicked += delegate(object sender, EventArgs e) {
            Level3ViewController controller = new Level3ViewController();
            NavigationController.PushViewController(controller,true);
        };

        ToolbarItems = new UIBarButtonItem[] {item};

        base.ViewDidLoad ();
    }

    public override void ViewWillAppear (bool animated)
    {
        // Re-show the toolbar here for consistency
        NavigationController.SetToolbarHidden(false,true);
        base.ViewWillAppear (animated);
    }
}



* 툴바 숨기기
이는 가장 마지막에 나타날 컨트롤러로서, 로드되면 툴바를 숨긴다.

public class Level3ViewController: UIViewController
{
    public override void ViewDidLoad ()
    {
        Title = "Level 3";

        // The label again
        UILabel label = new UILabel();
        label.Text = "Level3ViewController";
        label.Frame = new System.Drawing.RectangleF(100,100,100,100);
        View.AddSubview(label);

        // Hide the toolbar for this view
        NavigationController.SetToolbarHidden(true,true);

        base.ViewDidLoad ();
    }
}



* 특정 컨트롤러로 바로 접근하기
만약 어플리케이션이 막 시작했을 경우, 아니면 어떠한 이유에서라도 가장 마지막 컨트롤러로 접근하고 싶다면(이 예제에서는 Level3ViewController) 이는 NavigationController.ViewControllers을 직접 입력함으로서 해결될 수 있다.

...
using System.Linq;

public class NavigationController : UINavigationController
{
    public override void ViewDidLoad ()
    {
        var firstController = new HomeViewController();
        var secondController = new Level2ViewController();
        var thirdController = new Level3ViewController();

        var viewControllers = ViewControllers.ToList();
        viewControllers.Add(firstController);
        viewControllers.Add(secondController);
        viewControllers.Add(thirdController);
        ViewControllers = viewControllers.ToArray();

        base.ViewDidLoad ();
    }
}


* 스택에서 컨트롤러 삭제하기
컨트롤러를 삭제하하는것은 위에서 나왔던것과 동일한 방법을 사용한다 (대신 추가하는것이 아닌 삭제하는방법으로) 이는 현재에 사용되고 있는 컨트롤러를 삭제하는것도 가능하게 할 수 있을것이다(예를 들어 RemoveAt(controllers.Length-1, 실행은 해보지 않았다)

public class Level3ViewController: UIViewController
{
    public override void ViewDidLoad ()
    {
        var controllers = NavigationController.ViewControllers.ToList();
        for (int i = 0; i < controllers.Count; i++)
        {
            if (controllers[i] is Level2ViewController)
                controllers.RemoveAt(i);
        }
        NavigationController.ViewControllers = controllers.ToArray();
    }
}




===============================================
HomeViewController ViewWithNoBackController Level2ViewController Level3ViewController 가 상호 운용으로 차례차례 화면이 바뀌는 예제이다. 초반에 있는 설명대로, 설치마법사와 같이(앱에서는 사용자 정보입력 정도가 되겠지) 화면을 변환시키는데 사용이 되는 예제이다. 하지만 이것을 중점적으로 설명하기보다는 여기서 사용된 c#.net으로 앱개발이 가능하도록 하는 프로젝트인 Monotouch에 대해서 끄적여서 기억해 놓으려고 글을 올렸다,

Mono~ 라하면 Novell사에서 주관하는 *NIX 에서 마이크로소프트 .net 기술을 사용할 수 있도록하는 프로젝트를 의미한다. 사실 Object-C에 대해서 반감이 있는데 이런것도 가능하구나 생각하니 사용해보고 싶은 마음이 굴뚝같다. 하지만 유료라서 차마 사용은 못하겠고, 그래서 마침 UINavigationController 에 대해 알아볼 참에 번역이나 해봤다. 오랜만에 하는 번역이라 어색한 부분도 많은데다가 감기때문에 머리가 아파서 뒷부분은 대충 넘어갔기 때문에 이 글을 올릴까 말까 고민도 했었다. 게다가 글쓴이가 .net 개념으로 계속 프로그래밍을 진행하려고 해서 번역하는데 어떻게 설명해야 할지 약간 고민이 된 부분도 있었다(용어도 계속 헷갈리고)

세상에는 많은 (프로그래밍)언어가 있다. 물론 다양성은 존중되어져야겠지만 시대의 요구에 따라 주로 사용되는 언어가 있다. 첫눈에 봐을때 이해하기 쉬운가, 동일한 내용을 표현하더라도 문장이 복잡해 지지는 않는가 많은 표현을 지원하는가 어디서든 사용가능한가 다른 언어와 호환이 되는가. 가 훌륭한 언어를 결정짓는 요소인것 같고, 이를 생각한다면 c#[.net] 은 그 훌륭한 언어중에 하나일 것이다.

원문출처 : http://www.shrinkrays.net/articles/monotouch-controllers-by-example/uinavigationcontroller-by-example.aspx

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.