長期間運営・更新をしていくプロジェクトで iOSアプリの開発をしていると、
「使いまわせるViewのxib」を用意しておきたくなってきます。
さらに、以下の条件をすべて満たすものが望ましい、と強く感じてきます。

  • Storyboard、xibのプレビューに対応している
  • コード(Swift)からでも、Storyboard、xibでも作成、使用できる
  • 使い回すために、親や他のビューと依存させない
  • (できれば)SwiftUIからUIKitのViewを使用したい。もちろんプレビューも対応

これらを満たすためのViewを、ここにまとめておきます。

※これからの新規プロジェクトなら素直にSwiftUIで作成していくのがいいと思いますが、UIKitで実装した方がイイ!というビューを作る場合も、この記事が少し役に立つかもしれません。

AutoLayoutなViewのxibを作成

まず、View.xibとViewのカスタムクラスのswiftファイルを新規作成します。(名前はViewAとします)

xibファイルを新規作成

右側の「Simulated Metrics」のSizeを「Freedom」に、Top BarとBottom Barを「None」に設定。

swiftファイルを新規作成

ViewA.swiftにUIViewカスタムクラスの定義を書いてから、ViewA.xibの「File’s Owner」の方に、カスタムクラスとして関連づけます。

注意! Placeholdersの下にある「View」の方に関連づけてはいけません。なぜなら、xibをロードする処理が自分のinit()メソッド内で書けなくなり、脱依存が叶わなくなるからです。

「File’s Owner」の方のCustom Classに関連づける!

ただしく「File’s Owner」の方に関連づけてから、ラベルやその他のビューを配置して、OutletやActionなどを結びづけていけば、完成です。

テキスト行が無限なので高さが可変長。Storyboard上でも初期のテキストの長さを変えてプレビューできるように、親とのbottom制約をゆるくしています。

ちなみにグラデーション背景を表現するためのViewはこんな感じです(コードのみ)

import UIKit

@IBDesignable
class GradationView: UIView {
    var gradientLayer: CAGradientLayer?

    @IBInspectable var topColor: UIColor = UIColor.white {
        didSet {
            setGradation()
        }
    }

    @IBInspectable var bottomColor: UIColor = UIColor.black {
        didSet {
            setGradation()
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        setGradation()
    }

    private func setGradation() {
        gradientLayer?.removeFromSuperlayer()
        gradientLayer = CAGradientLayer()
        gradientLayer!.colors = [topColor.cgColor, bottomColor.cgColor]
        gradientLayer!.frame.size = frame.size
        layer.addSublayer(gradientLayer!)
        layer.masksToBounds = true
    }
}

コード(Swift)から使用する場合

たとえば「TableViewを使うほどの数ではないけど項目が可変長なリスト」を表現したいときは、
StackViewに addArreagedSubview() で済ませたいんじゃないかと思います。

AutoLayoutに任せて、シンプルに定義できます

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        let v1 = ViewA()
        v1.caption.text = "Android端末でWebSocketサーバーアプリを開発する"
        v1.translatesAutoresizingMaskIntoConstraints = false
        
        let v2 = ViewA()
        v2.caption.text = "アルゴリズムマスターへの道 ~【その壱】アルゴリズムを業務で活かすためのプロセス~"
        v2.translatesAutoresizingMaskIntoConstraints = false
        
        stackView.addArrangedSubView(v1)
        stackView.addArrangedSubView(v2)
    }
    
    @IBOutlet weak var stackView: UIStackView!
}

StackView内に追加した実行結果

Storyboardから使用する場合

ViewControllerのサブとしてシンプルに配置するものだとしても、Storyboardに直接たくさんのViewを配置するよりも、xibに分けることで、Storyboardの容量を減らすメリットが期待できます。

Storyboard自体のサイズを減らして、gitのコンフリクトを起こりにくくしたい

表(UITableView)のセルとして使いたい場合も、以下のようにUITableViewCellに付属しているContentViewの子として貼り付けることで、ViewAをシンプルに使用できます。

UITableViewCellと、そのContentViewの子としてViewAを、Storyboard上に配置。

ViewAをAutoLayoutで上下の制約をつけているので、可変長のUITableViewCellもシンプルな記述で扱えます。

TableViewでの実行結果

SwiftUIからUIKitのViewを使用したい

UIViewRepresentableを利用して、SwiftUIからもViewAを使えるか試してみましたが、なんとか使えました。
※現時点(2023.05)ではAutoLayoutのためにひと手間加える必要がありました。

クリックで文字を増やす処理を入れて、高さが変わる動作を確認する。

AutoLayoutのUIKitのViewを、SwiftUIのプレビューにうまく反映してくれる魔法のクラス。詳細は後述。

SwiftUIの実行結果。3番目の項目をクリックして、高さの変化を確認。

まとめ

自分が長年担当しているプロジェクトの開発効率アップのために纏めた記事でありましたが、SwiftUIからも使用できることがわかったのは思わぬ収穫でした。今後のSwiftUIのバージョンアップでこのあたりもスマートに対応してくれることを期待して、UIKit側の資源の再利用の可能性を追っていこうと思いました。

とてもためになった記事のリンク



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる