Xcode
iOS
Swift
playground

【iOS】Playgroundで擬似ホットリロードなUI開発

はじめに

iOSアプリ開発において、開発対象のアプリが大きくなり、コードが増えていくにつれて、UI開発において以下のような悩みが発生してくるかと思います。

  • ビルド時間が長い
  • シミュレータの起動が遅い
  • 確認対象の画面まで遷移させるのが面倒

自分は少し前にReactNativeでアプリ開発をしており、その時使っていたReactNativeのホットリロード/ライブリロードが快適だったため、Swiftに戻ってから上記について余計にストレスが溜まるようになっていました。

また、storyboard、xibによるレイアウトもファイルを開くのが遅かったり、再利用性がいまひとつだったりと効率の悪さからSnapKitによるコードでのレイアウト実装を行うようにしています。これにより出来上がりの確認をこまめに行いたいという欲求も生まれていました。

そこで思い立ったのがPlaygroundをプロダクトのUI開発に活かせないかという考えです。

Playgroundについて

この記事ではXcode9.2のPlaygroundを対象としています。

Playgroundは、2014年にリリースされたXcode6から、新しい開発言語として発表された「Swift」と同時に搭載されている機能です。当初はSwiftを学習するための、文字通りの「遊び場」という印象でしたが、年々性能が向上されています。
Xcode9では、「Single View」というテンプレートが追加され、UIViewControllerのビュー表示を簡単に確認することができるようになっています。

Playgroundの問題点

まずPlaygroundの問題点を以下にあげます。

1. 新規作成でワークスペースと別のウィンドウで立ち上がる
→ 以前はプロジェクトへのファイル追加でPlaygroundを選べたようですが、現在は無くなっています。
→ メニューバーからFile -> New -> Playground... で作成できます。
2. Playgroundにプロダクトのコードを追加できない
.Playground内のSourcesディレクトリにswiftファイルを追加してコードを実行できますが、このファイルを開発ターゲットに追加することはできません。

では何ができるのか?

Playgroundではフレームワークをimportして使用することができます。CocoaPodsやCarthageでインストールしたものはもちろん、EmbeddedFrameworkもいけます。先ほど新規作成時にワークスペースとは別になると書きましたが、この記事で問題解決しました。

これにより、Viewに関するファイルだけEmbeddedFramework化し、Playgroundの「Single View」で確認すれば良いのではと思いつきました。

擬似ホットリロードまでの手順まとめ

では、私が考えたPlaygroundの活用方法をまとめていきます。

【前提条件】開発中のプロダクトのワークスペースがすでにあることを想定しています。

  1. 新規Playgroundをテンプレート「Single View」で作成、任意の場所に保存
  2. 保存したxxx.playgroundを開発中のプロダクトのワークスペースにドラッグドロップして追加
  3. View専用のEmbeddedFrameworkをワークスペースに作成
  4. EmbeddedFramework内にカスタムViewやカスタムViewControllerを作成し、ビルド
  5. Playgroundに作成したEmbeddedFrameworkをimport
  6. PlaygroundのLiveViewに確認対象のカスタムViewなどを設定
  7. LiveViewに表示される

一度表示の確認ができれば、あとは4~7の繰り返しですいすい作業ができます。短時間ですが修正後にビルドが入るので記事のタイトルの通り「擬似ホットロード」と呼ばせて頂きました。

カスタムViewが完成したら、開発ターゲットの方にもimportして使うことができるので、Playgroundと開発プロダクト間でコードの共有が可能になります。

サンプル

上記の手順に沿って作成したサンプルです。「View」というEmbeddedFramework内に「HelloView」を作成し、PlaygroundのLiveViewで確認します。

import UIKit
import SnapKit

public class HelloView: UIView {

    let label = UILabel()
    let button = UIButton()

    public override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    func setup() {
        makeConstraints()
        setParams()
    }

    func makeConstraints() {
        addSubview(label)
        label.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.centerY.equalToSuperview().offset(-50)
            make.width.equalTo(200)
            make.height.equalTo(50)
        }
        addSubview(button)
        button.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.centerY.equalToSuperview().offset(50)
            make.width.equalTo(80)
            make.height.equalTo(40)
        }
    }

    func setParams() {
        backgroundColor = .lightGray
        label.text = "Hello"
        label.textAlignment = .center
        label.numberOfLines = 0
        button.setTitle("Hello", for: .normal)
        button.layer.borderWidth = 1
        button.layer.borderColor = UIColor.white.cgColor
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }

    @objc func buttonTapped() {
        label.text = label.text! + " Hello"
    }
}

Screen Shot 2018-03-09 at 1.15.37.png

LiveViewはボタンタップやスクロールなどのアクション操作も行うことができ、シミュレータと同じような動作検証が可能です。

おわりに

日々の業務でずっともやもやしていたものが解決でき、とても快適になりました。Viewをフレームワーク化することで、データを提供するプレゼンターや遷移のルータなどとの連携について工夫が必要となってくるとは思いますが、作業効率としては格段に上がるのではないでしょうか。ぜひ皆さんも一度お試しください!

参考情報

【Xcode 9.2】PlaygroundでLibraryを使う