您无法访问UIViewRepresentable 的内部结构,如果您坚持使用UIView 变量,您将开始收到非常流行的“不允许更新视图时更新视图错误”。 Apple 只是不允许使用 SwiftUI 访问内部结构。
创建一个在 UIKit 和 SwiftUI 之间共享的通用“ViewModel”/Controller 是最简单的方法。 UIView 将存在于 UIViewController 中,因此您可以获得 UIKit 的所有好处。
import SwiftUI
import MapKit
///Source of truth for both SwiftUI and UIKit
class MapCompassViewModel: ObservableObject, MapController{
var provider: (any MapProvider)!
func toggleCompassVisibility(){
provider.toggleCompassVisibility()
}
func addCompass(){
provider.addCompass()
}
}
您可以使用protocols 隐藏内部实现。
protocol MapProvider{
var map : MKMapView {get set}
func toggleCompassVisibility()
func addCompass()
}
protocol MapController{
var provider: (any MapProvider)! {get set}
}
UI 部分只是一个View、UIViewControllerRepresentable 和一个UiViewController。
///Plain SwiftUI View
struct MapCompassView: View {
@StateObject var vm: MapCompassViewModel = .init()
var body: some View {
VStack{
//This is needed to for the very first frame,
//when we are waiting for the provider
//to be set for the UIViewController
if vm.provider != nil{
Compass_UI(vm: vm)
.frame(width: 20, height: 20)
}
Button("Show/Hide compass", action: vm.toggleCompassVisibility)
MapCompass_UI(vm: vm)
}
}
}
///Convers UIKit `UIViewController` to a `UIViewControllerRepresentable`
struct Compass_UI: UIViewRepresentable{
let vm: any MapController
func makeUIView(context: Context) -> some UIView {
let m = MKCompassButton(mapView: vm.provider.map)
m.frame.origin = .init(x: 40, y: 20)
m.compassVisibility = .visible
return m
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
///Converts UIKit `UIViewController` to a `UIViewControllerRepresentable`
struct MapCompass_UI: UIViewControllerRepresentable{
let vm: any MapController
func makeUIViewController(context: Context) -> some UIViewController {
MapCompassViewController(vm: vm)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
///Regular `UIViewController` that uses `MapCompassViewModel`
///This can be as complex as needed.
class MapCompassViewController: UIViewController, MapProvider{
var vm: any MapController
lazy var map: MKMapView = {
let m = MKMapView(frame: .zero)
m.showsCompass = false
return m
}()
lazy var compass: MKCompassButton = {
let m = MKCompassButton(mapView: map)
m.frame.origin = .init(x: 20, y: 20)
m.compassVisibility = .visible
return m
}()
init(vm: any MapController) {
self.vm = vm
super.init(nibName: nil, bundle: nil)
//Critical connection between SwiftUI and UIKit
self.vm.provider = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
//Add map
view.addSubview(map)
//Pin map to edges
map.translatesAutoresizingMaskIntoConstraints = false
map.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
map.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
map.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
map.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
//Add compass
map.addSubview(compass)
}
func toggleCompassVisibility(){
compass.compassVisibility = compass.compassVisibility == .visible ? .hidden : .visible
}
func addCompass() {
print("(#function) :: add your compass code")
}
}
struct MapCompassView_Previews: PreviewProvider {
static var previews: some View {
MapCompassView()
}
}