UIView 는 File’s Owner 가 될 수 없어요
Tags: ios, xib, nib, 재사용
Published: 2020-03-22 15:22:08

---
title: UIView 는 File’s Owner 가 될 수 없어요
published: true
description: 
tags: iOS,xib,nib,재사용
---

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

[애플 개발자 문서](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html) 를 보시면 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](https://dev-to-uploads.s3.amazonaws.com/i/vsm98i65qvus6p3fnhfb.jpeg)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d34u0efxrhm96e6v0vs8.jpeg)

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

샘플프로젝트는 깃헙에 올려놓았어요: 
{% github ingun37/xib4StoryboardSample %}

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

```swift
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
    }
}
```

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/orht1ke9sbdb72tr0zb2.jpeg)

Outlet 을 연결하고

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/80nclqyszn1m8zeieqst.jpeg)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g1xos9t2dunu15qxlq3q.jpeg)

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

```swift
let view = MyView.loadViewFromNib()
```

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

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

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

어댑터 클래스를 정의해요

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

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gapkuse9yyvrhv7gj14k.jpeg)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/12coxhyu5lopdcv5r50v.jpeg)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8hdhnux9taeeaoqy6ks1.jpeg)