UIViewControllerRepresentable
是一个协议,用于创建一个SwiftUI
视图,该视图包装了一个UIKit
中的UIViewController
。通过实现UIViewControllerRepresentable
协议,我们可以在SwiftUI
中使用自定义的UIViewController
,并与SwiftUI
的生命周期进行交互。
实现UIViewControllerRepresentable
创建一个遵循UIViewControllerRepresentable
协议的自定义结构体,并实现下面两个基础的方法:
makeUIViewController(context:)
:创建并返回UIViewController
。updateUIViewController(_:context:)
:更新UIViewController
。
这里创建一个自定义结构体MyViewControllerRepresentable
,并继承UIViewControllerRepresentable
协议。该结构体里面包装一个MyViewController
。
struct MyViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MyViewController {
let vc = MyViewController()
return vc
}
func updateUIViewController(_ uiViewController: MyViewController, context: Context) {
}
}
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
}
}
调用代码如下:
struct UIViewControllerRepresentableDemo: View {
@State private var isShow: Bool = false
var body: some View {
Button(action: {
isShow = true
}, label: {
Text("Enter next page")
.padding()
.foregroundColor(.white)
.background(Color.blue)
.clipShape(Capsule())
})
.sheet(isPresented: $isShow) {
MyViewControllerRepresentable()
}
}
}
SwiftUI向UIKit传值
从SwiftUI
跳转到下一个界面的时候,我们经常会传入一些数据过去,不像UIViewRepresentable
协议在UI刷新的时候调用updateUIView
传递参数。在这里下一个界面不会根据上一个界面的状态去刷新的,更多的是一次性传值,既然是这样,那在初始化的时候直接把数值传过来。updateUIViewController
方法基本上就用不上了。
比如要在MyViewController
中显示一段文字,修改MyViewController
如下:
class MyViewController: UIViewController {
var text: String = ""
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
var label = UILabel()
label.font = .systemFont(ofSize: 30)
label.text = text
label.textColor = .white
label.textAlignment = .center
label.frame = view.bounds
view.addSubview(label)
}
}
那么在MyViewControllerRepresentable
创建的时候需要从SwiftUI
界面接收数值,然后再传递给MyViewController
。
struct MyViewControllerRepresentable: UIViewControllerRepresentable {
let text: String
func makeUIViewController(context: Context) -> MyViewController {
let vc = MyViewController()
vc.text = text
return vc
}
func updateUIViewController(_ uiViewController: MyViewController, context: Context) {
}
}
完成上面两步后在SwiftUI
界面初始化MyViewControllerRepresentable
的时候直接传入参数。
struct UIViewControllerRepresentableDemo: View {
@State private var isShow: Bool = false
var body: some View {
Button(action: {
isShow = true
}, label: {
Text("Enter next page")
.padding()
.foregroundColor(.white)
.background(Color.blue)
.clipShape(Capsule())
})
.sheet(isPresented: $isShow) {
MyViewControllerRepresentable(text: "This is a text")
}
}
}
在调用MyViewControllerRepresentable
的时候,直接传入参数,效果如下:
UIKit向SwiftUI传值
在UIKit
中,我们从第二个界面想第一个界面传值一般通过代理或者block的方式回传,下面这个示例采用block方式将数值从UIKit
传到SwiftUI
。
class MyViewController: UIViewController, UITextFieldDelegate {
var text: String = ""
var editResult: ((String?) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
let label = UILabel()
label.font = .systemFont(ofSize: 30)
label.text = text
label.textColor = .white
label.textAlignment = .center
label.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.width - 20, height: 50)
view.addSubview(label)
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.frame = CGRect(x: 10, y: 200, width: UIScreen.main.bounds.width - 20, height: 50)
view.addSubview(textField)
textField.delegate = self
}
func textFieldDidChangeSelection(_ textField: UITextField) {
editResult?(textField.text)
}
}
首先修改MyViewController
中的代码,添加一个TextField
,输入文字后在SwiftUI
界面显示出来。这里定义了一个var editResult: ((String?) -> Void)?
属性,在UITextFieldDelegate
的方法中调用传值,具体见上面代码。
修改MyViewControllerRepresentable
如下:
struct MyViewControllerRepresentable: UIViewControllerRepresentable {
let text: String
@Binding var editText: String?
func makeUIViewController(context: Context) -> MyViewController {
let vc = MyViewController()
vc.text = text
vc.editResult = { text in
editText = text
}
return vc
}
func updateUIViewController(_ uiViewController: MyViewController, context: Context) {
}
}
MyViewControllerRepresentable
结构体中,定义一个@Binding
修饰的editText
属性,用来绑定SwiftUI
界面的属性,并且记录MyViewController
传回来的值。
SwiftUI
界面调用部分:
struct UIViewControllerRepresentableDemo: View {
@State private var isShow: Bool = false
@State var editText: String?
var body: some View {
VStack {
if let editText {
Text(editText)
.padding()
}
Button(action: {
isShow = true
}, label: {
Text("Enter next page")
.padding()
.foregroundColor(.white)
.background(Color.blue)
.clipShape(Capsule())
})
.sheet(isPresented: $isShow) {
MyViewControllerRepresentable(text: "This is a text", editText: $editText)
}
}
}
}
效果如下:
上面的反向传值太过简单了,都没有用上Coordinate
,在上一篇文章中,我们用Coordinate
将UITextField
的输入内容传到了SwiftUI
界面。下面看一下在UIViewControllerRepresentable
中采用Coordinate
向SwiftUI
传值,这个和上一篇文章中的传值方法一样,这里就不过多阐述了,直接上完整代码。如果对具体步骤感兴趣的朋友可以看一下上一篇文章。
struct UIViewControllerRepresentableDemo1: View {
@State private var isShow: Bool = false
@State var image: UIImage?
var body: some View {
VStack {
if let image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 200)
.clipped()
}
Button(action: {
isShow = true
}, label: {
Text("Show image picker")
.padding()
.foregroundColor(.white)
.background(Color.blue)
.clipShape(Capsule())
})
.sheet(isPresented: $isShow) {
ImagePickerViewControllerRepresentable(isShow: $isShow, image: $image)
}
}
}
}
struct ImagePickerViewControllerRepresentable: UIViewControllerRepresentable {
@Binding var isShow: Bool
@Binding var image: UIImage?
func makeUIViewController(context: Context) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.allowsEditing = false
imagePicker.delegate = context.coordinator
return imagePicker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(isShow: $isShow, image: $image)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
@Binding var isShow: Bool
@Binding var image: UIImage?
init(isShow: Binding<Bool>, image: Binding<UIImage?>) {
self._isShow = isShow
self._image = image
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let newImage = info[.originalImage] as? UIImage else { return }
image = newImage
isShow = false
}
}
}
上面代码中点击后弹出一个ImagePicker
界面,选择图片后传给SwiftUI
界面显示。
写在最后
通过UIViewControllerRepresentable
,我们可以在SwiftUI
应用程序中轻松地集成UIKit
中的UIViewController
,实现更加灵活和强大的界面。掌握UIViewControllerRepresentable
的使用方法可以让我们更好地利用SwiftUI
和UIKit
的优势,为应用程序提供更好的用户体验。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。