6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Swift]ホットリロードしつつ、UIKitで宣言的にレイアウトを組む

Posted at

はじめに

SwiftUIが徐々に浸透していっている中、まだまだUIKitがメインなのが現状だと思います。

ただStoryboardもコンフリクトが起きやすいなどのデメリットが多いのも事実・・

UIKitでStoryboardを使わず、かつ宣言的で簡単にレイアウトを組むことができれば1番ですよね。

それを実現できる便利なライブラリを今回は紹介したいと思います。

補足

ライブラリとは関係ないですが,ホットリロード機能が使えるようになるツールもご紹介します。

ホットリロードとは簡単にいうと,その都度ビルドしてUIを確認することなくソースコードの変更を即座にシュミレーターで確認できる便利機能です(flutterだと標準装備ですが・・)

まずは下記からツールをダウンロードしておきましょう↓
https://github.com/johnno1962/InjectionIII/releases/tag/4.3.2

ダウンロードしたらApplicationフォルダに入っていることを確認し、一度開きましょう。
上部バーに注射器アイコンが表示されていればOKです。

スクリーンショット 2022-09-03 11.14.43.png

ライブラリ導入

次にSPMかPodsでライブラリを入れていきます。

Swift Package Manager
dependencies: [
    .package(url: "https://github.com/sakiyamaK/DeclarativeUIKit", .upToNextMajor(from: "0.2"))
]
CocoaPods
pod 'DeclarativeUIKit'

プロジェクトを作成したら、AppDelegateに下記を記述していきます。

ツールを入れた場所をpathで指定してます

AppDelegate.swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
#if DEBUG
        Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
#endif
        return true
    }

つぎは から初期画面を立ち上げるための処理です

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        let window = UIWindow(windowScene: windowScene)
        self.window = window
        
        let vc = DeclarativeViewController()
        window.rootViewController = vc
        window.makeKeyAndVisible()
    }

    func sceneDidDisconnect(_ scene: UIScene) {
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
    }

    func sceneWillResignActive(_ scene: UIScene) {
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
    }
}

これで初期画面が立ち上がります。

DeclarativeViewControllerのソースコードを見ていきましょう。

import UIKit
import DeclarativeUIKit


extension Notification.Name {
    static let injection = Notification.Name("INJECTION_BUNDLE_NOTIFICATION")
}

extension NotificationCenter {
    func addInjectionObserver(_ observer: Any, selector: Selector, object: Any?) {
        NotificationCenter.default.addObserver(observer, selector: selector, name: .injection, object: object)
    }
}


final class DeclarativeViewController: UIViewController {
    
    override func loadView() {
            super.loadView()
        NotificationCenter.default.addInjectionObserver(self, selector: #selector(setupLayout), object: nil)
        setupLayout()
    }
}

// ここでレイアウトを組んでいく
@objc private extension DeclarativeViewController {
    func setupLayout() {
        //初期時に画面を白にしておく
        self.view.backgroundColor = .white
        
        self.declarative {
           // ここに記述していく
       
        }
    }
}


loadViewdでレイアウト変更通知を受け取れるようにセットしておいて、レイアウトが変更されたら通知が送られるという感じです。

self.view.backgroundColor = .whiteと記述しないと画面が真っ黒なままになってしまうので注意です。

あとはself.declarative {}としてその中で宣言的に記述していけばいいだけ。

ホットリロードの準備

  • 上部バーの注射器アイコンをタップし、Open Project(1番上)を選択
  • 作成したプロジェクトファイル(podで入れた場合は~.xcworkspaceのファイル)を選択

スクリーンショット 2022-09-03 11.56.16.png

これで対象のプロジェクトのみにホットリロードを適用することができます!

一度ビルドすると、下記のようにデバッグコンソールに表示されたらOK
スクリーンショット 2022-09-03 12.08.37.png

実際のホットリロード画面はこちら↓

ezgif-1-11726da619.gif

感動...

ソースコード

さて、本題であるDeclarativeUIKitの使い方を見ていきましょう。


self.declarative {
                UIStackView.vertical {
                    UIStackView.horizontal {}
                        .backgroundColor(.green)
                    UIStackView.horizontal {}
                        .backgroundColor(.red)
                    UIStackView.horizontal {}
                        .backgroundColor(.brown)
                }
                .distribution(.fillEqually)
        }

上のサンプルみたく、UIStackViewの中にさらにUIStackViewが3つ入ってる状態です。

下記のようにプロパティを並べていけるので直感的に分かりやすいです

self.declarative {
            UIStackView.vertical {
                UIStackView.horizontal {
                    UIView()
                        .size(width: 100, height: 100)
                        .backgroundColor(.red)
                        .cornerRadius(30)
                        .border(color: .blue, width: 10)
                }
            }
        }

また、宣言的に書くなかでも
「今までみたいに命令的に書きたい!」
って時に便利なのがimperative

     self.declarative {
            UIStackView.vertical {
                UILabel()
                    .imperative { label in
                    let label = label as! UILabel
                    label.text = "hogehoge"
                }
                .backgroundColor(.gray)
                .height(100)
                
                UIView()
                    .imperative { view in
                        view.tintColor = .black
                        view.isUserInteractionEnabled = true
                    }
                UIView.spacer()
                    .backgroundColor(.brown)
            }
        }

こんな感じで今まで通りに書くことができます。

レイアウトは宣言的に書いて、その他の処理はimperativeの中にぶち込むなんてこともできちゃいますね

おわりに

他にも色々使い方が満載なので、やりながら覚えていきたいと思います!

ホットリロード+DeclarativeUIKitでレイアウト作りは爆速になりそうな予感ですね👀

一応サンプルのリポジトリを貼っておきます↓

ぜひ試してみてください

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?