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
组件。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。