7
5

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 3 years have passed since last update.

SwiftUIView ⇄ UIViewRepresentable 値わたし 🐥

Last updated at Posted at 2021-06-26

#SwiftUI 水の巻 🐥

けっきょくのところ、 UIView も SwiftUIView も View であり、 UI である。
宮本武蔵が「いかにもして、敵をひとへに、 うをつなぎにおひなす心にしかけて、 敵のかさなるとミヘバ、 其まゝ間をすかさず、強くはらひこむべし(五輪書)」というように、View としてひとえに扱ってしまえば、おそるるにたらない

さて、両方 View であり、 今のところUIKit で扱う方が楽なフレームワーク(たとえば ARKit の ARView とか、 ARSCNView とか)もあるからには、SwiftUIView と UIView をならべてつかいたいこともあるだろう。

そんなときつかうのが UIViewRepresentable という UIView のコンテナなのだが、そうするととうぜんながら
SwiftUIView と UIView(Representable) の間で値を反映しあって View の変更を共有したい。
値わたしには方法がある。
その方法を記しておこう。

#本日の獲物

こちらが我々の SwiftUIView である。
ボタンで true false をきりかえて 犬か猫の絵文字を表示するだけ。

SwiftUIView(エモジボタン)
Jun-27-2021 07-47-13.gif

ContentView
struct ContentView: View {
    @State var isDog: Bool = true
        
    var body: some View {
        Button(action: {isDog.toggle()}, label: {
            Text(makeButtonEmojiFromIsDog())
        })
    }
    
    func makeButtonEmojiFromIsDog() -> String { // 絵文字を表示
        if isDog {
            return  "🐶"
        } else {
            return "🐱"
        }
    }
}

こちらが UIKitView である。
ボタンで true false をきりかえて 犬か猫の文字を表示するだけ。

UIView(文字ボタン)
Jun-27-2021 07-48-12.gif

IsDogButtonView
import UIKit

class IsDogButtonView: UIView {

    var isDog:Bool = true {
        didSet {
            button.setTitle(makeButtonStringFromIsDog(), for: .normal)
        }
    }
    var button:UIButton!
    
    required override init(frame frameRect: CGRect) {
        super.init(frame:frameRect)
        button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
        addSubview(button)
        button.setTitle(makeButtonStringFromIsDog(), for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 100, weight: .black)
        button.setTitleColor(.blue, for: .normal)
        button.setBackgroundImage(UIImage(systemName: "hand.point.up"), for: .highlighted)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc func buttonTapped(){
        isDog.toggle()
    }

    func makeButtonStringFromIsDog() -> String { // こっちは文字を表示
        if isDog {
            return "Dog"
        } else {
            return "Cat"
        }
    }
}

ならべて表示するとこんな感じ。
SwiftUIView のなかに UIViewRepresentable コンテナを入れた。

とうぜんながら、まだおたがいの挙動はシンクロしない。

おのおののプロパティを更新しているだけの View たち
Jun-27-2021 07-54-15.gif

ContentView
struct ContentView: View {
    @State var isDog: Bool = true
        
    var body: some View {
        VStack {
        Button(action: {isDog.toggle()}, label: {
            Text(makeStringFromIsDog())
                .font(.system(size: 100))
        })
        IsDogButtonViewContainer()
            .frame(width: 200, height: 200, alignment: .center)
        }
    }
    
    func makeStringFromIsDog() -> String {
        if isDog {
            return  "🐶"
        } else {
            return "🐱"
        }
    }
}

struct IsDogButtonViewContainer: UIViewRepresentable {
    
    func makeUIView(context: Context) -> IsDogButtonView {
        let isDogButtonView = IsDogButtonView(frame: .zero)
        return isDogButtonView
    }
    
    func updateUIView(_ uiView: IsDogButtonView, context: Context) {
    }
}

#プロパティをシンクロさせよう

SwiftUIView も UIView も isDog という変数をもっているので、これを値わたしでシンクロさせて、 おたがいのボタンタップによる View の変更を反映できるようにしていこう

#SwiftUIView → UIViewRepresentable

UIViewRepresentable に Binding var で isDog の変更を反映させて、 updateUIView で UIView に値をわたす。

State と Binding は反映しあう。だから、 ContentView で State isDog に変更があったときは、 UIViewRepresentable の Biding isDog にも変更が降りてくる。 で、
UIViewRepresentable でアップデートがあった時には updateUIView が呼ばれるので、組み合わせれば UIView に値を渡せる。

IsDogButtonViewContainer
@Binding var isDog: Bool // +

func updateUIView(_ uiView: IsDogButtonView, context: Context) {
    uiView.isDog = isDog // +
}

SwiftUIView で UIViewRepresentable を初期化する時に、 isDog をドルマークつけて渡して、バインディング(結びつける)させる。

ContentView
IsDogButtonViewContainer(isDog: $isDog) // +

これで SwiftUI の値を UIView に渡して変更を反映できる。

SwiftUI (エモジ)の変更は、 UIView(文字)に反映されるようになった

Jun-27-2021 08-05-47.gif

逆方向の変更はまだ反映されない。

#UIViewRepresentable → SwiftUIView

デリゲートメソッドを作って、 Coordinator にデリゲートを継承させて、UIViewRepresentable に値を反映させて、 SwiftUIView にバインディングで渡す。

1、デリゲートメソッドに UIView の変更を渡す

IsDogButtonView
protocol IsDogButtonViewDelegate:NSObjectProtocol { // +
    func isDogChanged(isDog:Bool)
}

class IsDogButtonView: UIView {

    weak var delegate: IsDogButtonViewDelegate? // +

    @objc func buttonTapped(){
        isDog.toggle()
        delegate?.isDogChanged(isDog:isDog) // +
    }

2、コーディネーターにデリゲートを継承させる

IsDogButtonViewContainer
    func makeUIView(context: Context) -> IsDogButtonView {
        let isDogButtonView = IsDogButtonView(frame: .zero)
        isDogButtonView.delegate = context.coordinator
        return isDogButtonView
    }

    func makeCoordinator() -> Coordinator { // +
        return Coordinator(isDog: $isDog)
    }

    class Coordinator:NSObject, IsDogButtonViewDelegate { // + コーディネーターにデリゲートを継承させる。
        @Binding var isDog:Bool
        
        init(isDog: Binding<Bool>){
            _isDog = isDog
        }
        func isDogChanged(isDog: Bool) {
            self.isDog = isDog
        }        
    }

これで、 UIView での変更も SwiftUI に反映される。

【UIView から SwiftUIView にも値が反映されている様】
Jun-27-2021 08-10-47.gif

全体コード

ContentView
struct ContentView: View {
    @State var isDog: Bool = true
        
    var body: some View {
        VStack {
        Button(action: {isDog.toggle()}, label: {
            Text(makeStringFromIsDog())
                .font(.system(size: 100))
        })
        IsDogButtonViewContainer(isDog: $isDog)
            .frame(width: 200, height: 200, alignment: .center)
        }
    }
    
    func makeStringFromIsDog() -> String {
        if isDog {
            return  "🐶"
        } else {
            return "🐱"
        }
    }
}

struct IsDogButtonViewContainer: UIViewRepresentable {
    @Binding var isDog: Bool
    
    func makeUIView(context: Context) -> IsDogButtonView {
        let isDogButtonView = IsDogButtonView(frame: .zero)
        isDogButtonView.delegate = context.coordinator
        return isDogButtonView
    }
    
    func updateUIView(_ uiView: IsDogButtonView, context: Context) {
        uiView.isDog = isDog
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(isDog: $isDog)
    }

    class Coordinator:NSObject, IsDogButtonViewDelegate {
        @Binding var isDog:Bool
        
        init(isDog: Binding<Bool>){
            _isDog = isDog
        }
        func isDogChanged(isDog: Bool) {
            self.isDog = isDog
        }
    }
}

🐣


フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

7
5
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
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?