[Swift] 커스텀 뷰 xib 연결하기 : File’s Owner vs Custom Class

또또 뭐가 다른건디요~~!🤔🤔

naljin
13 min readOct 20, 2020

시작하기 전에

자 여기 작고 귀여운 제 커스텀 뷰 class가 있습니다

xib는 이렇게 생겼고요?

이제 연결을 해야겠죠? 여기서 우리는 선택지 2개가 있습니다

  1. File’s Owner 세팅

2. Custom Class 세팅

엥.. 어떤 차이가 있는걸까요. 지금부터 알아봅시다.

File’s Owner 세팅

File’s Owner로 xib를 세팅하고 나서 해당 뷰를 가져오기 위해서는 코드에서 nib 형태로 불러와야 합니다.

이때 또 두가지 방식으로 불러올 수 있져.

보시다시피 하나는 loadNibNamed 함수를 사용하는 거고, 다른 하나는 UINib 생성 후에 instantiate 함수 사용하는 건데, 별 차이 없으니까 첫번째거 기준으로 설명해 볼게요.

loadNibNamed(_:owner:options:)

하나씩 살펴보죠

음.. 첫번째 인자는 xib 이름.. ㅇㅋㅇㅋ

두번째 인자는 owner네요? 근데 우리 방금 xib에서 file’s owner 설정해줬잖아요?? 근데 코드에서 왜 다시 owner 를 설정해줘야하는데?!

xib에서 file’s owner 설정

왜냐면 xib에서의 설정은 xib를 소유할 class의 타입을 지정한 것 뿐이기 때문이죠! 이는 이후에 Outlet이나 IBAction을 해당하는 owner에 연결할 수 있도록 해줍니다.

그리고 우리는 코드로 owner의 instance를 설정해야 합니다.

  • Xib’s Owner: xib를 소유할 class의 타입 (IBOutlet 등 연결 가능)
  • Code’s Owner: owner의 instance

모르겠으면 그냥 xib로 file’s owner 설정할때는 코드로도 owner 설정이 필요하다! 하고 넘기도록 합시다 (반 쯤 내 얘기ㅎ)

자자, 한차례 어려운 내용 지나갔으니까 마지막 인자인 옵션은 건너뛰고~~

loadNibNamedfirst 의 마지막에 .first를 사용하는 이유는 해당 함수의 return 값이 array 이기 때문이에요. 아래처럼 하나의 xib가 여러개의 view를 가질 수도 있으니까 array를 리턴하는거겠죠?

마지막으로 ViewController에 내 커스텀 뷰를 설정해준다면? 완성!

사실 설명만 길었다 뿐이지 3 step이 끝입니다.

  1. CustomView xib에서 File’s Owner 설정
  2. CustomView class 초기화에서loadNibNamed로 가져온 viewaddSubview
  3. ViewController에서 Custom Class 세팅

어 잠깐, 근데 우리가 초기화 함수인 init 을 호출한거 같진 않은데..! 어떻게 뷰가 load 된걸까요?

두둥. 그 시점은 ViewController 가 로드 될 때! 해당 처리가 곧장 시작된다고 합니다. 스토리보드에서 회색 뷰에 custom Class(MyCustomView)를 설정했기 때문에 시스템이 nib 파일을 unarchive 하려고하고, 이때 커스텀 뷰 class의 init(coder: NSCoder)를 호출한다네요. 결과적으로 xib file을 로드하는 customInit() 메소드도 호출되고요!

init(coder: NSCoder)
스토리보드에서 회색 뷰에 Custom Class(MyCustomView)를 설정한 상태

근데 위에서 unarchive 라는 말.. 이해하는데 무리 없으셨나요? 저는 잘 몰라서 찾아봤습니다 ㅎㅎ “아카이빙” 에 대해 잠깐 짚고 가시죠! (딴길로 새버리기)

아카이빙 / 언아카이빙

  • 아카이빙은 iOS 에서 모델 객체를 저장하는 가장 흔한 방법 중 하나이다.
  • 객체의 프로퍼티들을 모두 기록하고 파일시스템에 그 내용을 저장하는 것을 포함한다.
  • 언아카이빙(unarchiving)은 아카이브한 데이터로부터 객체를 다시 만든다.
  • 아카이브하고 언아카이브해야 할 클래스들은 NSCoding 프로토콜을 conform 하며, 두 필수 메소드 func encode(with aCoder: NSCoder)required init?(coder aDecoder: NSCoder) 구현이 필요하다.
  • func encode(with aCoder: NSCoder) : NSCoder로 나타나는 encoder (archiver object)를 이용해서 모든 프로퍼티들을 인코딩한다.
  • required init?(coder aDecoder: NSCoder): NSCoder로 나타나는 decoder(unarchiver object)안의 data를 이용해서 초기화 한 객체를 return 한다
  • 아카이빙 Xib파일을 만드는 방법이기도 하다.
  • Xib 파일이 저장되면 View 들을 해당 Xib 파일에 아카이브 하고, 적절한 시점에 Xib 파일에서 View 들을 언아카이브한다.
  • Xib 파일과 표준 아카이브 간에 약간의 차이점이 있지만 전반적인 과정은 비슷하다.
  • UIView 는 NSCoding 프로토콜을 따른다

아 오키오키. 돌고 돌아 왔지만 “nib 파일을 unarchive 한다” 라는 말은 Xib에 이렇게 저렇게 변형되어 저장된 view들을 압축 해제한다! 정도로 이해하면 될 것 같네요ㅎㅎ

어휴 길었다. 이제 두번째 방법으로 넘어가..보기 전에! 어우 피곤해 내일 이어서 쓰도록 하죠ㅋㅎ

Custom Class 세팅

자자 돌아왔습니다. 두번째 방법인 xib의 Custom Class 세팅!

File’s owner 설정을 Custom Class로만 변경하고 코드 변경 없이 돌렸을때 문제 없이 실행 될까요? 답은 ㄴㄴ!

이유는 위에서 설명드릴 것에서 유추할 수 있습니다!

ViewController 가 로드되자마자 시스템은 우리 customView class의 init(coder: NSCoder)을 호출한다. 결과적으로 xib file을 로드하는 customInit() 메소드가 불린다.

호출되는 customInit() 에서는 xib file을 로드하고 있었죠?

그리고 해당 xib file에서는 custom class를 설정해놨었구요.

이 때문에 결과적으로 system은 MyCustomView 를 위한 init(coder: NSCoder)을 또 다시 호출하게 될것이고, 무한 루프를 돌면서 앱이 죽게 됩니다.

끝이 없네 끝이 없어! 답이 없네 답이 없어!

흠.. 사실 양심 고백 하자면 왜 무한루프를 도는지는 잘 모르겠어요. 걍 VC에서 트리거 되는거 한번, 자기 자신에 Custom Class 세팅해놓은 것 때문에 트리거 되는거 한번 해서 총 두번만 실행되면 안되나..? 다시 구글링 하고 올게여

구글링 완료. 찬찬히 다시 살펴봅시다.

  1. custom class가 MyCustomView로 설정된 뷰를 스토리보드에 추가
  2. ViewController 로딩 후 MyCustomViewinit(coder: NSCoder)를 호출
  3. init(coder: NSCoder)MyCustomViewcustomInit() 호출
  4. customInitloadNibNamed 호출해서 xib 로드
  5. 여기서 xib의 최상위 view는 custom class인 MyCustomView로 설정된 상태. 따라서 loadNibNamedMyCustomViewinit(coder: NSCoder) 다시 호출
  6. 3번으로 돌아감
  7. 무한 루프

오호라..!

여기까지 잘 따라오셨나요? 그렇다면 Custom Class를 이용해서 커스텀 뷰를 만드는 올바른 방법은 무엇일까요?

  1. 무한 루프를 방지하기 위해 init(coder: NSCoder)나, init(frame: CGRect)와 같은 init 메서드에서 같은 클래스의 xib를 로드하지 마세요.
  2. custom class 대신, 부모 클래스나 다른 곳에서 xib를 로드합니다.

그러면 File’s Owner 코드에서 호출했던 customInit() 함수를 지우고 이런식으로 ViewController에서 xib를 로드할 수 있을거예요

custom view는 이런식으로 outlet이랑 action 만 가질 수 있게 됩니당. init 쪽에서 xib 불러올 생각은 꿈도 꾸지 마시라요..

class MyCustomView: UIView {
@IBOutlet var myLabel: UILabel!
}

그럼 사실 VC의 회색 뷰에서 커스텀 클래스 설정해 줄 필요도 없을거 같은디요.. 프레임은… 잘 조절해줍시다..

자 여기서 잠깐. 방금 xib를 로드할 때 owner를 nil로 설정했다는 거에 주목해보아요.

Bundle.main.loadNibNamed("MyCustomView", owner: nil, options: nil)

만약 owner를 self로 설정(여기서는 parent class가 됨)하면 어떤 일이 일어날까요?

사실 코드에서든, xib에서든 owner를 parent class로 설정하는 것은 아무 문제 없습니다! 완벽하게 잘 작동할거예요. custom view에 outlet을 연결할 수 있음은 물론, parent class에도 outlet을 연결할 수 있게 됩니다.

이게 바로 custom class와 file owner 두가지 모두를 이용해서 custom view를 만드는 케이스인데, 좀 더 딥하게 보고싶다! 하시는 분들은 여기를 참고해주세요.

무엇을 써야할까?

지금까지 xib를 설정하는 두가지 방법으로 file owner와 custom class에 대해 살펴봤습니다. 무엇을 쓸지는 여러분의 자유긴 하지만 File’s owner로 설정하는게 아래와 같은 장점이 있다고 해요.

  • 코드, 스토리보드, xib를 사용하여 앱 전체에서 custom view를 재사용할 수 있습니다. 다른 방법들은 이미 살펴 봤듯이 무한 루프를 돌거예요.
  • 부모 클래스 및 다른 클래스와 같이 바깥에서 xib을 로드하는 번거로움이 없습니다.
  • File owner은 상위 클래스 또는 일부 다른 클래스를 대신하는 클래스일 뿐이므로 더 Clean 한 접근 방식이라고 할 수 있습니다.

전반적으로, 그 자체로 포장된(packed in itself) reusable class를 제공함으로써 다수의 File owner 및 outlet을 갖는데서 오는 혼란을 방지할 수 있습니다!

어떤 곳에서는 xib를 Custom Class로 설정하는 경우, 로드 해올 때 캐스팅을 UIView가 아닌 MyCustomView 로 사용할 수 있어서 좋다지만..

let myCustomView = loadedNib.first as? MyCustomView

안에 속성들이 필요한거라면 xib는 File’s Owner로 설정해놓고, VC에서 Custom Class 설정된 뷰(위에서 예시는 회색 뷰) 연결해서 쓰면 되는거 아니어요?_?

@IBOutlet weak var grayView: MyCustomView!

흠.. 🤔 선택은 여러분의 몫! 상황에 따라서 알아서 쓰십셔!

마무리

후.. 쌓여있는 것들 중 하나를 간신히 했군여.. 뭔데 이렇게 걸렸지..!ㅜ 쩝.. 나머지도 언젠가 할 날이 오겠죠..?ㅎㅎ.. 흠 오늘은 일단 샤따 내립니다 빠이!

--

--