LoginSignup
6
7

More than 3 years have passed since last update.

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

Posted at

今回試してみたこと

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

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


図にするとこんな感じ

スクリーンショット 2019-10-08 9.11.28.png

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

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

そして、SwiftUIでViewを定義


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が出来ました。


こんな感じ

スクリーンショット 2019-10-08 9.20.26.png

次はUIViewを作る

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")
    }
}

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

(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
}

TextFieldの通知を活用してtextFieldの入力が発生したときのイベントをハンドリングしてみました。
textFieldのtextをdata.nameに代入します。

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

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

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

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

_ = data.$name.receive(on: DispatchQueue.main).sink() {
    textField.text = $0
}

@Publishedのnameの変更をmainスレッドでreceiveして、sinkでクロージャーを登録してデータが変わったことを知らせてもらっています。


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

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

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

とかがあり得るのかな?

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

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