のほほん停留所

びぼうろく

Interface Builderでの生成時のClassがレイアウトに与える影響について

f:id:nonchalant0303:20211027175016p:plain

Interface Builder

XcodeのInterface Builder (IB)エディタはMacOS, iOSアプリ開発でレイアウトを組み際に直感的に操作できるレイアウト生成ツールです。

コードからデザインに関する処理を分離することができるので、活用している人も多いと思います。

今回はIBでデザインを組む際に、Object生成時のClassが与える影響について書いていきます。

f:id:nonchalant0303:20211027175113p:plain
Object生成時のClass

サンプルコード

今回の説明に使うコードは全て以下のレポジトリに含まれています。

github.com

Object生成時のClassが与える影響

f:id:nonchalant0303:20211027175331p:plain
Object生成時のクラスが異なる2画面

Object生成時のクラスが異なる2画面上の2画面はクラス名などを除いてコードは全て同じもので実行しています。しかし、左の画面はCellの背景色がありますが、右の画面ではCellの背景色がありません。

// 背景色がついているCell
class ContentTableViewCell: UITableViewCell {
    @IBOutlet private(set) weak var label: UILabel!
}

// 背景色がついていないCell
class NonContentTableViewCell: UITableViewCell {
    @IBOutlet private(set) weak var label: UILabel!
}

IB上で違う点はそれぞれのTableViewで使用しているCellのデザインを組んでいるxibでのObject生成時のクラスが異なることです。

正しく背景色が表示されている左の画面ではObject生成時のクラス(ContentTableViewCell)をUITableViewCellにしており、背景色が表示されていない右の画面ではObject生成時のクラス(NonContentTableViewCell)をUIViewにしています。

f:id:nonchalant0303:20211027175439p:plain

Object生成時のクラスによってIB上でUITableViewCellのデフォルトプロパティのContent Viewの有無が違います。

developer.apple.com

しかし、コード上ではどちらもUITableViewCellを継承しているので、コンパイルエラーがなく参照できます

ただ、Debug View Hierarchyで確認するとNonContentTableViewCellにはContentViewが存在しません。

f:id:nonchalant0303:20211027175555p:plain

この2種類のCellのObject情報を確認するとopaque = NO;という要素が異なります。

print(cell.contentView) // ContentTableViewCell

<UITableViewCellContentView: 0x7f8a11414ff0; frame = (0 0; 414 43.6667); opaque = NO; gestureRecognizers = <NSArray: 0x6000004344b0>; layer = <CALayer: 0x600000a1eac0>>

---

print(cell.contentView) // NonContentTableViewCell

<UITableViewCellContentView: 0x7f8a1150ebd0; frame = (0 0; 414 43.6667); gestureRecognizers = <NSArray: 0x60000043cb10>; layer = <CALayer: 0x600000a02b60>>

opaque = NO;

opaque要素はisOpaqueプロパティで確認できます。

developer.apple.com

If set to true, the drawing system treats the view as fully opaque, which allows the drawing system to optimize some drawing operations and improve performance.

isOpaqueはtrueになっているときはdrawing systemが描画しないので、Debug View Hierarchy上で表示されません。

The default value of this property is true.

NonContentTableViewCellではcontentViewがIB上では存在しないので、UITableViewCellのデフォルトプロパティのcontentViewがレイアウトされずにisOpaqueでデフォルト値のtrueのままになっていると推測できます。

xmlの違い

ContentTableViewCell.xibとNonContentTableViewCell.xibのxmlでは、Object生成時のClass情報が保持されています。

また、ContentTableViewCellではtableViewCellContentViewが存在しますが、NonContentTableViewCellではsubviewsになっています。

https://github.com/Nonchalant/IBInitialClassTrap/blob/a4a65a5bc0bb4ee3f1a5eaee1c089fde2401bfd1/IBInitialClassTrap/ContentView/ContentTableViewCell.xib#L14

https://github.com/Nonchalant/IBInitialClassTrap/blob/a4a65a5bc0bb4ee3f1a5eaee1c089fde2401bfd1/IBInitialClassTrap/Non-ContentView/NonContentTableViewCell.xib#L14

解決策

isOpaqueはSetterが提供されているプロパティなので、drawRectメソッド内でisOpaque = falseにすると解決できると考えたのですが、system-provided classesでは作用しないと公式ドキュメントに書いていました。

You only need to set a value for the opaque property in subclasses of UIView that draw their own content using the drawRect: method. The opaque property has no effect in system-provided classes such as UIButton, UILabel, UITableViewCell, and so on.

ですので、xib側でObjectをUITableViewCellを選択して作り直すことで解決しました。

最後に

この現象は元々StackViewで組んでいたViewをUITableViewに置き換える際に、SubviewをUIViewからUITableViewCellに変更する作業で発見しました。

コード上では継承元のクラスをUIViewからUITableViewCellに変更したのですが、xibはそのまま使用していました。

今回はUIView → UITableViewCellの変換を例に挙げて説明をしましたが、他のクラス間の変換でも同様の現象が発生しますので、クラスを変更する場合はIB側の確認もする必要があります。

個人的にはIB上でCustom Classを特定Classを継承しているClassを適用したら、Constraintsの情報を保持したままIB上でサポートしているClassに変換されてほしいので、Bug Reporterから意見として送信しました。

あまり期待はしていないですが、反応があったらTwitterでお知らせします。

twitter.com