SwiftUIView ⇄ UIViewRepresentable 値わたし 🐥

#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/fc9b731c-1e5d-3622-4ccb-d8f2eeae14ca.gif)


```swift: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/7be62e8d-87fd-e863-8ffc-f30e1232f356.gif)


```swift: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/c439f28e-7205-bdaa-d402-8e37966cb0d4.gif)

```swift: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 に値を渡せる。

```swift:IsDogButtonViewContainer
@Binding var isDog: Bool // +

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

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

```swift:ContentView
IsDogButtonViewContainer(isDog: $isDog) // +
```

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

【**SwiftUI （エモジ）の変更は、 UIView（文字）に反映されるようになった**】

![Jun-27-2021 08-05-47.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3b706ef0-ac09-382e-6a44-1fb721ab0967.gif)

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

#UIViewRepresentable → SwiftUIView

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

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

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

class IsDogButtonView: UIView {

    weak var delegate: IsDogButtonViewDelegate? // +

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

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

```swift: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/292a2057-b0c1-41f8-7c8a-12d19097505c.gif)


全体コード

```swift: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](https://twitter.com/JackdeS11)
[Medium](https://medium.com/@rockyshikoku)