[Swift]UIPickerViewが急にパカパカしだした話
概要
UIPickerView
を使っているときに、急にパカパカしだしたので、調査した内容をまとめる。
下の動画の例だと、"怪しいボタン"をタップすると、Pickerがパカパカしだす。
(Pickerをスクロールすると、Picker自身が閉じられてしまう)
背景
SwiftUIで作成しているアプリで、Picker
による入力を実現するために、UIRepresentable
を使用してUITextView
のinputView
として、UIPickerView
を設定していた。
正常なケースでは以下のように、textViewをタップすると、下部からUIPickerView
が表示される。
PickerをScrollすることで、Pickerの要素を選択しtextを変更できる。
問題
しかし、PickerをScrollしていると、パカパカする現象に遭遇した。
Pickerをスクロールすると、Picker自身が閉じられてしまう。
何度も試してみたが、再現はめったにせず、原因も分らなかった。
原因 & 解決策
調査を進めると、とある画面を開いたあとに、PickerをScrollすると、Pickerが急にパカパカしだすことが判明した。
その画面の実装を見ると、以下のコードを実行していることがわかった。
UIScrollView.appearance().keyboardDismissMode = .interactive
おそらく、iOS15をサポートしていたときに、ScrollViewをScrollしたときにKeyboardを閉じるために実装していたと考えられる。
そのコードをコメントアウトすると、Pickerがパカパカしなくなった。
上記のコードは、SwiftUIでは変更できない(iOS15ではできなかった)要素を、その内部で使用しているUIKitのプロパティを使用してを変更しているものである。
その変更は、UIKitのグローバルな動作を変更するため、アプリケーション全体のUIScrollView
に影響を与える。
今回のバグも、別の画面で実行したコードの影響で、Pickerがパカパカしだしたと考えられる。
サンプルコード
struct ContentView: View {
@State private var text: String = ""
@FocusState private var isFocused: Bool
var body: some View {
ScrollView(.vertical) {
VStack {
Color.clear.frame(height: 200)
UITextFieldContainer(text: $text)
.focused($isFocused)
Button("怪しいボタン") {
UIScrollView.appearance().keyboardDismissMode = .interactive
}
}
}
.padding(.horizontal, 40)
.onTapGesture {
isFocused = false
}
}
}
struct UITextFieldContainer: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: UIViewRepresentableContext<Self>) -> UITextField {
let view = UITextField()
view.clearButtonMode = .whileEditing
view.delegate = context.coordinator
view.backgroundColor = .green
let picker = UIPickerView()
picker.delegate = context.coordinator
picker.dataSource = context.coordinator
view.inputView = picker
return view
}
func updateUIView(_ uiView: UITextField, context _: UIViewRepresentableContext<Self>) {
uiView.text = text
}
final class Coordinator: NSObject, UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource {
private var textView: UITextFieldContainer
init(_ textView: UITextFieldContainer) {
self.textView = textView
super.init()
}
// Picker View
func numberOfComponents(in pickerView: UIPickerView) -> Int {
1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
10
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
"\(row)"
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
textView.text = "\(row)"
}
// TextView
func textFieldDidChangeSelection(_ textField: UITextField) {
textView.text = textField.text ?? ""
}
func textFieldShouldClear(_: UITextField) -> Bool {
self.textView.text = ""
return true
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
"怪しいボタン"をタップすると、Pickerがパカパカしだす。
タップ前
タップ後
まとめ
SwiftUIの足りない部分をUIKitのプロパティを変更して補うことがあるが、UIKitのプロパティを変更することで、SwiftUIの挙動が変わることがあるので注意が必要。
このようなバグは、原因となるコードがバグの箇所と関係のない場所にあるため、調査が難航することがある。
UIKitのグローバルなプロパティを変更するときは、そのリスクを考慮する必要がある。
Discussion