UIViewRepresentable是一个协议,用于创建一个SwiftUI视图,该视图包装了一个UIKit视图。通过实现UIViewRepresentable协议,我们可以在SwiftUI中使用自定义的UIKit视图,并与SwiftUI进行交互。
实现UIViewRepresentable
创建一个遵循UIViewRepresentable协议的自定义结构体,比如我们用TextField举例,创建一个TextFieldViewRepresentable,并实现该协议最基础的两个协议方法。
- makeUIView(context:):创建并返回UIKit视图。
- updateUIView(_:context:):更新UIKit视图。
struct TextFieldViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let textField = UITextField()
return textField
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
如果在makeUIView方法中明确了要返回的UIKit控件的类型,那么方法的返回值就可以改为指定的类型,而不是some UIView,改完后,把updateUIView方法删了,再重新添加,就会发现updateUIView方法中的uiView的类型不是UIViewType了,而是我们在makeUIView方法中的返回类型。
struct TextFieldViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.backgroundColor = UIColor.gray
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
}
在SwiftUI中调用:
struct UIViewRepresentableDemo: View {
var body: some View {
VStack {
Text("Hello, World!")
TextFieldViewRepresentable()
}
}
}
UIKit向SwiftUI传值
上面的代码中,我们在TextField中输入任何内容都不会显示在SwiftUI的界面上的,也就是SwiftUI拿不到TextField中输入的内容,这里我们就需要绑定一个值来传递了。
要想拿到输入的内容,我们组要一个协调员去处理TextFieldDelegate的方法,这里就需要实现makeCoordinator()方法。makeCoordinator()方法也是UIViewRepresentable的协议方法。下面在TextFieldViewRepresentable结构体里面定义一个协调员Coordinator。
struct TextFieldViewRepresentable: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.backgroundColor = UIColor.gray
textField.delegate = context.coordinator
return textField
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
init(text: Binding<String>) {
self._text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
}
上面代码中我们创建一个类Coordinator,注意是class类型,该类实现了UITextFieldDelegate协议。
在Coordinator类中定义了一个@Binding修饰的text属性,用于绑定输入的值。在textFieldDidChangeSelection协议方法中进行赋值。
在TextFieldViewRepresentable中我们也定义了一个@Binding修饰的text属性,用于绑定SwiftUI中的@State修饰的String类型的值。
在实现的makeCoordinator()方法中,创建并返回Coordinator的具体实例,并绑定text。
在makeUIView方法中,这里我们通过context的coordinator属性能拿到刚才通过makeCoordinator()方法创建的Coordinator实例,并将UITextField的代理设置为该实例。
textField.delegate = context.coordinator
SwiftUI部分调用代码:
struct UIViewRepresentableDemo: View {
@State private var text: String = ""
var body: some View {
VStack {
Text(text)
TextFieldViewRepresentable(text: $text)
.frame(height: 50)
.padding()
}
}
}
SwiftUI向UIKit传值
有些时候SwiftUI界面不断刷新的时候,也需要想UIKit组件传递数据,比如刚才的代码,修改一下增加了一个SwiftUI的TextField组件。
当UIKit的TextField输入的时候,SwiftUI的TextField也显示了输入的字符串,因为我们已经将text值传到SwiftUI界面了。
但是当SwiftUI的TextField在输入的时候,UIKit的TextField确没有任何显示。要实现这个,我们需要用到前面提到的updateUIView方法了,该方法在SwiftUI界面刷新的时候调用。
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
}
text是通过@Binding绑定的值,我们将这个text给UITextField即可。
定义修饰符方法
TextFieldViewRepresentable是我们定义的一个用在SwiftUI中的结构体,我们给它加一些方法,这样在SwiftUI中通过点语法就可以修改TextFieldViewRepresentable的某些属性了。
TextFieldViewRepresentable组件可能用在很多地方,不同的用处可能会设置不同的样式属性等,比如说背景色,如果在SwiftUI中直接调用.background(Color.red)方法,是不起作用的,因为没有直接修改到UITextField。
那么现在在TextFieldViewRepresentable中添加一个属性和一个方法。
var backgroundColor: UIColor?
func backgroundColor(_ color: UIColor) -> TextFieldViewRepresentable {
var clone = self
clone.backgroundColor = color
return clone
}
该方法传入UIColor,并返回TextFieldViewRepresentable实例,即当前的self,这样在SwiftUI中就可以连续调用点语法了。
在makeUIView方法中,我们给UITextField设置backgroundColor。
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = "Please input..."
textField.borderStyle = .roundedRect
textField.backgroundColor = backgroundColor
return textField
}
切记自定义的方法要在SwiftUI中初始化组件后就调用,如果在系统的修饰符方法后调用则会报错的,因为其他修饰符方法返回的是some View类型,该类型下是没有我们定义的方法的。
完整代码
struct UIViewRepresentableDemo: View {
@State private var text: String = ""
var body: some View {
VStack {
Text(text)
HStack {
Text("SwiftUI:")
TextField("Please input...", text: $text)
.textFieldStyle(.roundedBorder)
}
HStack {
Text("UIKit:")
TextFieldViewRepresentable(text: $text)
.backgroundColor(UIColor.cyan)
.frame(height: 50)
.padding()
}
}
}
}
struct TextFieldViewRepresentable: UIViewRepresentable {
@Binding var text: String
var backgroundColor: UIColor?
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = "Please input..."
textField.borderStyle = .roundedRect
textField.backgroundColor = backgroundColor
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
}
func backgroundColor(_ color: UIColor) -> TextFieldViewRepresentable {
var clone = self
clone.backgroundColor = color
return clone
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
init(text: Binding<String>) {
self._text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
}
本文主要介绍了在SwiftUI中如何包装UIKit组件,当SwiftUI组件无法满足我们的App设计需求的时候,可以使用UIKit组件