DEV Community

Ingun 전인건
Ingun 전인건

Posted on

UIView 는 File’s Owner 가 될 수 없어요

재사용 가능한 .xib를 만들때 UIView 를 File’s owner 로 설정하는 방법이 널리 퍼져있어요. 사실 이게 잘못된 방법인데 잘 작동하니깐 사람들이 모르고 쓰는것 같아서 이걸 바로잡고자 이 글을 써봤어요.

애플 개발자 문서 를 보시면 File’s owner를 다음과같이 설명하고 있어요

Typically, you use outlets in the File’s Owner object to store references to the top-level objects of a nib file.

top-level object 가 뭐냐고요? 같은 문서 밑에 보시면 top-level object에 대해 다음과같이 또 써있어요:

The top-level objects are the subset of these objects that do not have a parent object. The top-level objects typically include only the windows, menu bars, and custom controller objects that you add to the nib file.

설명대로라면 UIView 가 File’s owner 로써 알맞는 후보가 아니라는걸 알 수 있어요. UIView 는 부모 오브젝트가 없다는 점은 만족하죠(.xib 파일에는 UIView 가 최상위 오브젝트니까요) 하지만 windows 도 아니고 menu bars 도 아니고 controller object 도 아니에요.

문서상으로는 그렇다고 쳐도 잘 작동 하니깐 장땡 아니냐고요?

진짜 잘 작동 하는걸까요?

절대 아니에요. UIView 를 File’s owner 로 설정하면 어떤 문제점들이 생기는지 설명드릴게요

UIView 를 File’s owner 로 쓴다는 아이디어 자체가 구조적으로 모순이에요. .xib 파일은 원래 다양한 UIView 타입의 오브젝트들을 가지고 있을 수 있게끔 만들어졌어요. 근데 생각해보면 .xib 파일은 단 하나의 File’s owner 만을 가질 수 있잖아요? 그렇다면 .xib 파일은 하나의 UIView 타입이면서 동시에 다수의 UIView 타입일 수 있다는 말이되잖아요? 모순이죠.

이 모순은 악마를 하나 낳아요. 바로 contentView 에요. 첫째로 contentView는 프로그래머가 모든 뷰를 자기 아래로 정렬시키는 노가다를 하게 만들어요. 결과적으로 뷰의 어떠한 컴포넌트를 접근하든 contentView.-를 써서 자기 자신을 거치게 만들어요. 이놈만 없었다면 편하고 자연스러운self 를 쓸 수 있었을텐데 말이죠. 그뿐만이 아니에요. 이 악마때문에 UI 구조랑 일치하는 자료구조를 정의할 수가 없어요. Declarative UI 로발전하고있는 이 시점에 시대를 역행하게 만들죠.

재사용 가능한 .xib를 만드는 옳은 방법

샘플 프로젝트를 만들어 볼게요

이게 기존의 잘못된 구조에요:

Alt Text

우리는 다음과같은 새로운, 옳은 구조를 쓸거에요:

Alt Text

차이점으로는

  • File’s owner는 공석으로 냅둘거에요
  • MyView 는 File’s owner 가 아닌 object class 로써 outlet 들을 제공할거에요
  • contentView 는 사라지리거에요
  • 여전히 storyboard 에서 쓸 수 있을거고, 코드에서 인스턴스화 할 수 있을거에요
  • .xib파일에 여러개의 view 오브젝트들을 정의할 수 있을거에요.

샘플프로젝트는 깃헙에 올려놓았어요:

MyView 라는 클래스를 하나 정의해보아요

class MyView: UIView {
    @IBOutlet weak var subView1:UIView!
    @IBOutlet weak var subView2:UIView!

    //This is helper function so you can instantiate MyView like
    //let view = MyView.loadViewFromNib()
    static func loadViewFromNib() -> MyView {
        let bundle = Bundle(for: self)
        let nib = UINib(nibName: "MyViews", bundle: bundle) //“MyViews” is name of xib file
        return nib.instantiate(withOwner: nil, options: nil).first {
            ($0 as? UIView)?.restorationIdentifier == "1"
        }! as! MyView //You will set restorationIdentifier later
    }
}
Enter fullscreen mode Exit fullscreen mode

outlet 들이랑 .xib파일 도 하나 만들고, 자식 뷰도 몇개 만들고 MyView 로 세팅 해봅시다

Alt Text

Outlet 을 연결하고

Alt Text

Restoration id 도 설정하세요. 이건 xib 에 있는 뷰들을 구별하기위한 id 에요. 뷰 오브젝트가 하나만 존재한다면 굳이 안써도 돼요.

Alt Text

이제 코드에서 다음과같이 인스턴스화 할 수 있어요.

let view = MyView.loadViewFromNib()
Enter fullscreen mode Exit fullscreen mode

이제 스토리보드에서 쓰는 방법을 알아볼건데 그전에 먼저 말하고싶은게 있어요. contentView 를 쓸거에요. 아까 contentView 를 악마라고 까놓고선 이제와서 또 쓰는건 무슨 위선 이냐고 생각할 수 있는데 그렇지 않아요. 이번에 쓰는 contentView 는 기존의것과 많이 다른거에요. 이번거는 기존의 hack 이나 ad-hoc 같은 용도의 contentVew 가 아니에요. 바로 modularization of storyboard adaptation 라는 사명을 가진 뷰랍니다. 말 그대로 스토리보드 어댑터의 역할을 하는 모듈이죠. 그리고 이번거는 프로그래머의 앞길을 가로막지 않을거에요. 다음과같은 특징이 있기 때문이죠

  • 이번 contentView 는 오로지 코드상으로만 존재해요. UI 빌더 상의 정의를 필요로 하지 않죠. 다른말로 .xib 파일이나 .storyboard파일을 건들지 않아요.
  • 더 나아가서 이번 contentView 는 MyView 클래스에서 완전히 모듈화 돼서 떨어져 나가요. 위에서 말한 스토리보드 어댑터 역할을 하는 클래스로 따로 정의돼요. 이해가 안간다면 이따가 보여드릴 샘플을 보면 이해가 가실거에요
  • 다른 클래스로 모듈화가 되었으니, 기존 contentView 처럼 자료구조를 방해할 일이 없겠죠? 이제 뷰의 property 에 접근할때마다contentView.- 를 쓸 일이 없을거에요

이제 스토리보드에서 쓰는 방법을 알아봅시다

어댑터 클래스를 정의해요

class StoryMyView1:UIView {
    var contentView:MyView
    required init?(coder aDecoder: NSCoder) {
        contentView = MyView.loadViewFromNib()
        super.init(coder: aDecoder)
        contentView.frame = bounds
        addSubview(contentView)
    }
}
Enter fullscreen mode Exit fullscreen mode

끝났어요 이제 .storyboard UI 빌더에서 다음과 같이 쓰면 돼요.

Alt Text

다음과같이 한 xib 파일에 여러개의 view 를 정의할 수 있다는 점도 잊지말고요

Alt Text

Alt Text

Latest comments (0)