FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

XcodeでInterfaceBuilderの型を消し去ってみると色々得られて少し失った

こんにちは。

GANMA! チームで開発をしている、寺坂です。

GANMA!のiOSアプリでは、
XcodeInterfaceBuilder を活用してUI開発をしています。

今回は、InterfaceBuilderをより活用できようになる、かもしれない内容を書いていきます。

モチベーション

UITableViewのdalegateやdataSourceのように
自分で作ったViewのdelegateもInterfaceBulder(以下、IB)上で Outlet接続したい。

前提

  • Xcode8系

既知の問題

@IBOutletをつけても、delegateのようなprotocolはOutletで接続できない。

f:id:i_terasaka:20170726114727g:plain

問題を回避する方法

型情報を一度AnyObjectにしてしまい、IBを騙す。

  1. CustomViewクラスを作る
  2. @objc付きのprotocoldelegateを定義
  3. 定義したdelegateをどこかで実装する
  4. @IBOutlet付きvar delegateをAnyObject型で定義する
  5. IBでdelegateが接続できるようになっているので好きな場所へ接続する
  6. var delegateの型をCustomViewDelegateにする

具体的な手順説明

1. CustomViewクラスを作る

自作Viewを用意します。
これをIBに配置します。

class CustomView: UIView { ... }

2. @objc付きのprotocoldelegateを定義

@objcをつけないと、@IBOutletをつけられません。
一方@objcをつけたので、いつもの:class は不要です。

@objc protocol CustomViewDelegate { }

3. 定義したdelegateをどこかで実装する

delegateを繋げたい対象を用意します。
ViewControllerを肥大化させたくない場合は、NSObject継承のクラスを用意しても構いません。

class CustomObject: NSObject { ... }
extension CustomObject: CustomViewDelegate { ... }

これをStoryboardなどに配置しておく。

4. @IBOutlet付きvar delegateをAnyObject型で定義する

CustomViewDelegate型をそのまま指定しても、IB上ではどのObjectも反応しません。

AnyObjectにすると、IB上にあるObjectが反応するようになります。

@objc protocol CustomViewDelegate { }
class CustomView: UIView {
    @IBOutlet private weak var delegate: AnyObject?
    ...
}

[注意点]
AnyObjectにすると、どのObjectにも反応するので、
間違って別のObjectに対して接続してしまわぬよう注意が必要です。

CustomViewDelegateを実装しているObjectだけに
接続できるようにする方法は今の所なさそうです。

5. IBでdelegateが接続できるようになっているので好きな場所へ接続する

楽しい時間ですね。

f:id:i_terasaka:20170726114650g:plain

6. var delegateの型をCustomViewDelegateにする

一度接続してしまえば、AnyObjectをCustomViewDelegateに変えても維持されます。

@objc protocol CustomViewDelegate { }
class CustomView: UIView {
    @IBOutlet private weak var delegate: CustomViewDelegate?
    ...
}

得られたものと失ったもの

[得られたもの]

  • わざわざコードでdelegate = self とかしなくてもOutlet接続で済ませられるようになった
  • delegateprivateにできるようになった
  • NSObjectを継承したクラスを用意してdelegateを分離したい時、このクラスのインスタンスを保持しておかなくてもStoryboard上に配置して接続するだけでよくなった

[失ったもの]

  • 型安全。CustomViewDelegateを実装していないObjectに接続してしまうと実行時に死ぬ

Xcode9ではどうなるのか

Xcode9 Beta3時点から、一時的にAnyObject型にする手順が不要となっています。
ただし、どのObjectにも接続できてしまう問題は依然として残っていますので
リリースまでに解決されることを期待しています。

サンプル

github.com