在这篇博客中,将介绍如何结合使用 SwiftUI 结合 UIKit 来实现一个自定义TextField,当按下退格键的时, 触发指定的方法。
目录
- 文章背景
- 集成SwiftUI和UIkit
- UIViewRepresentable
- Coordinator
- 完整代码
1. 背景
最近帮别人做APP,为了钱,什么都学,后端有的是人。。。 于是看了一个下午的官方demo
加gpt
就搞起来了。现在弄得差不多了,回来写点东西,不然博客上啥都没有。下面是正文
SwiftUI
写个简单的界面确实挺方便的,几个Vstack
、Image
、Button
堆一下就有了 ,但在处理某些复杂的交互时就有点不太方便了。这个时候就要用UIKit
了,例如,SwiftUI
中的 TextField
目前不支持直接检测退格键的按下事件。而在UIKit
中,我们可以通过重写 UITextField
的 deleteBackward
方法来实现这一功能,这玩意在哪里用得上,在验证码界面上。。。为啥不用开源的?时间足,就自己写个,出问题了好定位,且容易改。
2. 集成 SwiftUI 和 UIKit
SwiftUI 的视图(View
)必须是遵循 SwiftUI 的 View
协议的类型,而 UIKit 的 UITextField
是 UIKit 的类,并不符合 SwiftUI 的 View
协议。SwiftUI 和 UIKit 是两个独立的 UI 框架,因此直接在 SwiftUI 中使用 UIKit 的视图(如 UITextField
)是不允许的。
因此我们需要使用 UIViewRepresentable
协议来创建和管理 UITextField
,才能够在SwiftUI中使用UIKit
的UITextField
CustomTextField是我自己定义的继承了UITextField的组件
3. UIViewRepresentable
UIViewRepresentable
是 SwiftUI 提供的一个协议,用Java的语言来说,我简单地理解为接口。我们要做的就是自定义一个结构体,然后实现协议中几个方法,然后就可以在SwiftUI中用到UIKit的东西了
-
makeUIView(context:)
:用于创建并返回一个 UIKit 视图实例,该方法只会在初始化的时候执行一次 -
updateUIView(_:context:)
:用于更新 UIKit 视图的状态 -
makeCoordinator()
:用于创建并返回一个协调器实例,当你在界面交互,触发UIKit 视图的方法和事件时,就会调用该协调器中的对应的方法。
当我们ContentView实例化的时候,遇到
// 自定义的文本视图,绑定 text 和 onBackspace 回调
CustomTextView(...)
就会去创建我们的 UIViewRepresentable,实例化过程中会先调用makeCoordinator()
创建一个coordinator对象放到context对象中。
然后调用makeUIView()
创建UIKit组件,并context中取出coordinator对象交给UITextField,
当用户在界面上与UITextField交互的时候,UITextField就会调用Coordinator的方法,在Coordinator中可以修改我们在SwiftUI视图中的一些变量或者属性,例如@Binding的变量。这样子就完成了UIKit对象同步信息到SwiftUI对象。
这里可以看出UIKit中自己玩自己的(数据和行为和以前一样),只不过在自己玩的时候, 多做一步同步信息给你SwiftUI.
Coordinator使得UIkit对象可以同步信息给SwiftUI,那么SwiftUI如何同步信息给UIkit呢?答案是updateUIView方法。
当你通过其他的SwiftUI组件的交互导致text变化了,就要通过updateUIView方法来同步给UITextField,否则UITextField的值并不会变化。
例如我们可以通过一个按钮来改变text属性,因为UIViewRepresentable是View的一个子类,text是@State属性,就会触发View的重新绘制。在重新绘制的过程中,会调用updateUIView来同步UITextField中的值。当然这里也可以去让UITextField不可编辑之类的操作。
4. Coordinator
// Coordinator 类,用于处理 UITextField 的委托方法
class Coordinator: NSObject, UITextFieldDelegate {
var parent: CustomTextView
// 初始化方法,传入 CustomTextView 实例
init(parent: CustomTextView) {
self.parent = parent
}
// 当 UITextField 的文本发生变化时调用
func textFieldDidChangeSelection(_ textField: UITextField) {
// 更新 CustomTextView 的 text 属性
parent.text = textField.text ?? ""
}
}
在 Coordinator
类中,如果你希望处理 UITextField
的某些事件(例如文本输入变化),就需要实现对应的 UITextFieldDelegate
方法。如果没有特定需求,协议中的方法都可以省略。
这里我咋知道有哪些方法可以代理呢? 问GPT。。。。等要用了再研究官方文档
5. 完整代码示例
代码
import SwiftUI
import UIKit
struct ContentView: View {
@State private var text = ""
@State private var isBackspacePressed = false
var body: some View {
VStack {
Text("用户按下了回退键?")
.font(.title)
.padding()
Text("\(isBackspacePressed ? "Yes" : "No")").font(.title)
// 自定义的文本视图,绑定 text 和 onBackspace 回调
CustomTextView(text: $text, onBackspace: {
// 当按下删除键时,将 isBackspacePressed 设置为 true
isBackspacePressed = true
// 1 秒后将 isBackspacePressed 重置为 false
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
isBackspacePressed = false
}
})
.frame(height: 50) // 设置视图高度
.background(Color.gray.opacity(0.2)) // 设置背景颜色和透明度
.cornerRadius(8) // 设置圆角
.padding() // 设置内边距
}
.padding()
}
}
struct CustomTextView: UIViewRepresentable {
@Binding var text: String // 绑定的文本属性
let onBackspace: () -> Void // 删除键回调
func makeUIView(context: Context) -> CustomTextField {
let textField = CustomTextField() // 创建自定义的 UITextField
textField.delegate = context.coordinator // 设置委托
textField.onBackspace = onBackspace // 设置删除键回调
textField.placeholder = "Enter text" // 设置占位符
textField.borderStyle = .roundedRect // 设置边框样式
return textField
}
func updateUIView(_ uiView: CustomTextField, context: Context) {
uiView.text = text // 更新 UITextField 的文本
//uiView.isHidden = true
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self) // 创建并返回 Coordinator 实例
}
// Coordinator 类,用于处理 UITextField 的委托方法
class Coordinator: NSObject, UITextFieldDelegate {
var parent: CustomTextView
// 初始化方法,传入 CustomTextView 实例
init(parent: CustomTextView) {
self.parent = parent
}
// 当 UITextField 的文本发生变化时调用
func textFieldDidChangeSelection(_ textField: UITextField) {
// 更新 CustomTextView 的 text 属性
parent.text = textField.text ?? ""
}
}
}
class CustomTextField: UITextField {
// 定义一个回调,当用户按下删除��时调用
var onBackspace: (() -> Void)?
// 重写 deleteBackward 方法
override func deleteBackward() {
// 调用外部回调
onBackspace?()
// 打印日志信息
print("用户按下了删除键,但是我不删除,哈哈")
}
}
// 预览 ContentView
#Preview {
ContentView()
}