【SwiftUI】UIViewRepresentableの使い方!Coordinatorクラスとは?

この記事からわかること

  • SwiftUIUIKitを使えるようにするには?
  • UIViewRepresentableプロトコル使い方
  • makeUIViewメソッドとupdateUIViewメソッドの違い
  • UIKitイベントをSwiftUIに伝える
  • UILabelUIButtonをSwiftUIで扱う方法
  • Coordinator(コーディネータ)クラスとは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

iOSアプリを開発するためのプログラミング言語である「Swift」の代表的なフレームワークが「UIKit」と「SwiftUI」です。

今回は「SwiftUI」から「UIKit」を使えるようにできるUIViewRepresentableプロトコルの使い方と概要についてまとめていきたいと思います。

UIViewRepresentableプロトコルとは?

protocol UIViewRepresentable : View where Self.Body == Never

UIViewRepresentableプロトコル」とはUIKitのビューや機能をSwiftUIでも使えるようにするラッパーの役割も持つプロトコルです。そもそもUIKitのビューを管理するUIViewControllerとSwiftUIのViewはベースが異なるので、両者をそのままつなぎ合わせることはできません。

また「UIKit」は古くから使用されてきたフレームワークなので豊富な機能やビューが提供されています。一方比較的新しいフレームワークである「SwiftUI」はまだまだ実装できる機能には制限がありできることは限られてきます。

「互換性のないビュー」「機能の豊富さの差異」を解決するために両フレームワーク間の機能の橋渡しするためのラッパーが用意されています。

両フレームワーク間の機能の橋渡しするためのラッパー

UIViewRepresentableプロトコルの使い方

SwiftUIでUIKitを使用するためのUIViewRepresentableプロトコルは構造体に準拠させて使用します。定義を見てみると以下の2つのメソッドの実装が必須になっています。

定義

public protocol UIViewRepresentable : View where Self.Body == Never {

    func makeUIView(context: Self.Context) -> Self.UIViewType

    func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)
}

ちなみにUIViewRepresentableプロトコルをクラスに準拠させる場合final classにしないと以下のようなエラーを吐きます。

Protocol 'View' requirement '_makeView(view:inputs:)' cannot be satisfied by a non-final class ('LabelView') because it uses 'Self' in a non-parameter, non-result type position
// プロトコル 'ビュー' 要件 '_makeView(view:inputs:)' は非最終クラス ('LabelView') では満たすことができません

とはいえUIKitは主にクラスで定義されているの対し、SwiftUiは主に構造体で定義されているので準拠させていくのは構造体になります。

makeUIViewメソッド

func makeUIView(context: Self.Context) -> Self.UIViewType

makeUIViewメソッドは表示するViewの初期状態のインスタンスを生成するメソッドです。SwiftUIの中で使用したいUIKitのViewを戻り値として返却します。

このメソッドは最初の一回しか呼び出されず、ビューを更新する場合はupdateUIViewメソッドに処理を定義します。

引数contextにはUIKitビューの作成と更新に使用するシステムの状態に関するコンテキスト(文脈)情報が渡されます。つまりビューを構築するために必要な情報のことです。

戻り値には表示させたいUIKitのViewの型(UIViewType)を指定します。ここで戻す型が実際にSwiftUIで表示させたいUIKitの型になります。最初のコード補完では上記のように-> Self.UIViewTypeとなります。この部分を直接指定の型に書き換えるもしくはUIViewTypeを指定の型のタイプエイリアスとして宣言しておく必要があります。

直接指定するパターン

func makeUIView(context: Self.Context) -> UILabel

タイプエイリアスとして宣言するパターン

typealias UIViewType = UILabel 
func makeUIView(context: Self.Context) -> Self.UIViewType

updateUIViewメソッド

func updateUIView(
    _ uiView: Self.UIViewType,
    context: Self.Context
)

updateUIViewメソッドは表示するビューの状態が更新されるたびに呼び出され更新を反映させるためのメソッドです。

例えばUIViewRepresentableプロトコルに準拠させた構造体のプロパティなどが変更された時などに呼び出されます。

UIViewRepresentable〜まとめ〜

使用方法1:SwiftUIの変更をUIKitに伝える

実際にUILabel(UIKitのビュー)をSwiftUIで表示させてみたいと思います。ラベルを表示させるだけではなくSwiftUIのButton構造体で定義したボタンをクリック時にラベルのテキストを変更させます。

  1. UIViewRepresentableに準拠させた構造体を作成
  2. isClickプロパティをバインディングで定義
  3. makeUIViewメソッド内でUILabelを構築しreturn
  4. updateUIViewメソッドではバインディングしたプロパティの値の変化をキャッチできるので値に応じて処理を分岐
  5. LabelView()としてインスタンス化してビューを表示

import SwiftUI

struct LabelView: UIViewRepresentable {
    @Binding  var isClick:Bool
    func makeUIView(context: Context) -> UILabel {
        let labelView:UILabel = UILabel()       // UIKitのビュー
        labelView.text = "UIKitから作成したView" // テキストを格納
        labelView.textAlignment = NSTextAlignment.center
        return labelView // ビューを返す
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
        if isClick {
            uiView.text = "変更しました。"
        }else{
            uiView.text = "UIKitから作成したView"
        }
    }
}
struct ContentView: View {
    @State  var isClick:Bool = false
    
    var body: some View {
        VStack {
            LabelView(isClick: $isClick).padding()
            Button("ボタン"){
                isClick.toggle()
            }
        }
    }
}

これでボタン(SwiftUI)をクリックした時にUILabel(UIKit)のテキストを変更する機能が完了しました。

使用方法2:UIKitのイベントをSwiftUIに伝える

ではUIKitのUIButtonをクリックされた時にUILabelを変更する方法を考えてみます。まずはUIButtonをSwiftUIで使えるように先ほどと同じ手順で定義していきます。今回は特にボタンは変更しないのでupdateUIViewは空にしておきます


struct ButtonView: UIViewRepresentable {
    @Binding  var isClick:Bool

    func makeUIView(context: Context) -> UIButton {
        let control = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
        control.setTitle("ボタン", for: .normal)
        control.setTitleColor(.red, for: .normal)
        return control
        }
    
    func updateUIView(_ uiView: UIButton, context: Context) {
      // 今回は定義しない
    }
}

これでUIButtonをSwiftUIで表示できるようになりましたが、クリック時(UIKit側のイベント)の処理はまだ記述できていません。

UIKit側のイベントを取得するにはCoordinator(コーディネータ)クラスを構造体の中に定義して実装していきます。

Coordinator(コーディネータ)クラス

Coordinator(コーディネータ)クラスとはUIKitでのイベントをSwiftUIで管理するために作成するクラスのことです。このクラスはデフォルトで存在するわけではなく、自分で実装します。そのためクラス名に決まりはないですが構造体の中で定義するため名前の衝突を気にする必要がないので分かりやすくCoordinatorとしておきます。


struct ButtonView: UIViewRepresentable {
    @Binding  var isClick:Bool
    func makeUIView(context: Context) -> UIButton {
        let control = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
        control.setTitle("ボタン", for: .normal)
        control.setTitleColor(.red, for: .normal)
        control.addTarget(context.coordinator,action: #selector(Coordinator.clickButton(sender:)),for: .touchUpInside)
        return control
    }
    
    func updateUIView(_ uiView: UIButton, context: Context) {
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    class Coordinator {
        var control: ButtonView
        init(_ control: ButtonView){
            self.control = control
        }
        @objc  func clickButton(sender : Any){
            control.isClick.toggle()
        }
    }
}

Coordinatorクラスの中には「観測対象プロパティの宣言」と「イベント発生時の処理」を実装していきます。

  1. Coordinatorクラスを構造体の中に作成
  2. プロパティに定義したビューをセット
  3. イニシャライザで引数を自身にセット
  4. イベント発生時の処理(clickButton)を記述する
  5. インスタンス化用の専用メソッド(makeCoordinator)を構造体内に定義
  6. makeUIViewメソッドにアクション部分を定義

まずはクラスを定義します。@objcSwift前身のObjective-Cのコードとして扱うためのコードです。これは後述する#selectorと関係しています。その中にアクションで行う処理を記述しておきます。

class Coordinator {
        var control: ButtonView
        init(_ control: ButtonView){
            self.control = control
        }
        @objc  func clickButton(sender : Any){
            control.isClick.toggle()
        }
}

続いてCoordinatorクラスをインスタンス化するためのmakeCoordinatorメソッドを準備します。これはUIViewRepresentableにあらかじめ用意されているメソッドでmakeUIViewメソッドが呼び出される前に一度だけ呼びだされインスタンス化されます。

func makeCoordinator() -> Coordinator {
        Coordinator(self)
}

インスタンス化されるとmakeUIViewメソッドの引数contextの中から参照できるようになります。UIButtonのアクション部分を実装するaddTargetメソッドにCoordinatorを渡していきます。#selectorを使ってメソッドを指定します。

func makeUIView(context: Context) -> UIButton {
    let control = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
    control.setTitle("ボタン", for: .normal)
    control.setTitleColor(.red, for: .normal)
    control.addTarget(context.coordinator,action: #selector(Coordinator.clickButton(sender:)),for: .touchUpInside)
    return control
}

私がSwift UI学習に使用した参考書

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index