今回試してみたこと
今回はUIKitで作成したViewControllorとSwiftUIで作成したViewを一緒に表示させることに挑戦してみたいと思います。
おそらく新規プロジェクトでなければ、SwiftUIに移行することになったときに必ずやることになると思ったからです。
図にするとこんな感じ
とりあえず共有するデータを定義
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が出来ました。
こんな感じ
次は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された画面でイベントが登録されたら両方に反映
とかがあり得るのかな?
他の書き方でも似たようなことは出来ると思うのでなにかあればぜひコメントもお願いします!