SwiftUI(View)とUIKit(UIView)と同じ画面にだしてsinkさせてみる

## 今回試してみたこと
今回はUIKitで作成したViewControllorとSwiftUIで作成したViewを一緒に表示させることに挑戦してみたいと思います。

おそらく新規プロジェクトでなければ、SwiftUIに移行することになったときに必ずやることになると思ったからです。

----
### 図にするとこんな感じ
<img width="310" alt="スクリーンショット 2019-10-08 9.11.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184634/a3f08efa-292c-f1de-e65b-8d0c408bc7f4.png">



## とりあえず共有するデータを定義

``` swift
final class MyData: ObservableObject {
    @Published var name: String = ""
}
```

## そして、SwiftUIでViewを定義
``` swift

struct TestView: View {
    @EnvironmentObject var myData: MyData
    var body: some View {
        HStack {
            Text(myData.name)
                .frame(minWidth: 100)
            Button(action: {
                self.myData.name = Int(arc4random_uniform(50000)).description
            }) {
                Text("タップして乱数生成")
            }.frame(minWidth: 100)
        }
    }
}
```
frameのminWidthやminHeightはよく使うことになりそうです。

これでボタンをタップしたら乱数を生成してTextで表示させるシンプルなViewが出来ました。

----
### こんな感じ
<img width="389" alt="スクリーンショット 2019-10-08 9.20.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184634/d574d00f-92b4-0597-92ba-2a010f741aa0.png">

## 次はUIViewを作る

``` swift
class MyTestUIView: UIView {
    /// 共有するデータ
    var data: MyData  

    /// 今回はコードでviewを生成します。MyDataをこのときに渡してます。
    init(frame: CGRect, data: MyData) {
        self.data = data
        super.init(frame: frame)
        // UIKitで生成したViewをわかりやすくするためにyellowにしました
        backgroundColor = UIColor.yellow

        // textFieldに値を反映し、修正も行います
        let textField = UITextField(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        addSubview(textField)
        textField.borderStyle = .roundedRect

        // (1) textFieldからのデータInputを処理する
        NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification,
                                               object: nil,
                                               queue: OperationQueue.main) { notification in
                                                guard let text = (notification.object as? UITextField)?.text else { return }
                                                data.name = text
        }
        // (2) SwiftUIからInputが発生したときにTextFieldにも反映させる
        _ = data.$name.receive(on: DispatchQueue.main).sink() {
            textField.text = $0
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

```

確認するところは２点あります

### (1) textFieldからのデータInputを処理する
``` swift
NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification,
                                       object: nil,
                                       queue: OperationQueue.main) { notification in
    guard let text = (notification.object as? UITextField)?.text else { return }
    data.name = text
}
```
TextFieldの通知を活用してtextFieldの入力が発生したときのイベントをハンドリングしてみました。
textFieldのtextをdata.nameに代入します。

もうこれでSwiftUIで定義したViewに反映されます。

``` swift
HStack {
    Text(myData.name)
    .frame(minWidth: 100)
```

こちらの`myData.name`は`@Published`のおかげてデータが変更されたらTextが即時に更新されます

### (2) SwiftUIからInputが発生したときにTextFieldにも反映させる

``` swift
_ = data.$name.receive(on: DispatchQueue.main).sink() {
    textField.text = $0
}
```
`@Published`のnameの変更をmainスレッドでreceiveして、sinkでクロージャーを登録してデータが変わったことを知らせてもらっています。

----

とりあえずやってみたいことは出来ました。

シンプルにしたかったのでtextFieldの変更が入るたびにデータが更新されtextFieldもまた更新されるといった、無駄なことをやってるソースコードにも見えますがｗ

例えばメモ帳アプリで
上部はUIKItのカレンダーライブラリーで暦の表示
下部はSwiftUIで選択された日付のデータを表示
presentされた画面でイベントが登録されたら両方に反映

とかがあり得るのかな？

他の書き方でも似たようなことは出来ると思うのでなにかあればぜひコメントもお願いします！