はじめに
タイトルのように、皆んさんこんな画面を作るような時がありますか?
例えば下記のような場面。
- TextFieldをタップして、キーボードではなく、SwiftUIで書いたカスタムのPickerを出したい
SwiftUIにはUIKitにあるinputViewのような仕組みがないようです。
ここでタイトルのような作りを考えました。
アプローチ
- SwiftUIでカスタムのPicker(CustomPicker_SwiftUI)を作成
- UITextFieldを継承したクラス(CustomTextField_UIKit)を作成
- UIHostingControllerでCustomPickerをラップする(
let wrapperView = UIHostingController(rootView: CustomPicker_SwiftUI)
) - CustomTextFieldのinputViewにwrapperView.viewを設定する
- UIViewRepresentableでCustomTextFieldをラップして、SwiftUIで呼び出す
実際のコード
import SwiftUI
import UIKit
// Step1
struct CustomViewPicker: View {
var tapHandler: (String) -> Void
var body: some View {
Button {
tapHandler("Hello World")
} label: {
Text("Hello World")
}
}
}
// Step2
class CustomTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
initSubView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initSubView()
}
private func initSubView() {
self.placeholder = "Tap here!"
// Step3
let wrapperView = UIHostingController(rootView: CustomViewPicker(tapHandler: setText)).view
if let wrapperView {
wrapperView.translatesAutoresizingMaskIntoConstraints = false
// Step4
self.inputView = wrapperView
self.inputAccessoryView = createToolbar()
}
}
private func createToolbar() -> UIToolbar {
let toolbar = UIToolbar()
toolbar.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: 44)
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(didTapDone))
let toolbarItems = [flexibleSpace, doneButton]
toolbar.items = toolbarItems
return toolbar
}
@objc private func didTapDone(_ sender: UIButton) {
resignFirstResponder()
}
private func setText(text: String) {
self.text = text
}
}
struct CustomTextFieldRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
return CustomTextField()
}
func updateUIView(_ uiView: UIViewType, context: Context) {}
}
struct ContentView: View {
var body: some View {
// Step5
CustomTextFieldRepresentable()
.frame(height: 44)
.background(.red)
}
}
動き
終わり
今SwiftUIではinputViewのような仕組みがまだないですが、今後Appleの対応に期待しています。