SwiftUI中调整MKMapView尺寸时出现性能卡顿问题
解决SwiftUI中MKMapView分栏调整卡顿问题
针对你在iPhone 11 Pro Max真机上遇到的MKMapView分栏拖动卡顿问题,以下是几个可行的优化方案:
1. 限制MKMapView的不必要更新
UIViewRepresentable默认会在状态变化时频繁调用updateUIView,而MKMapView的frame实时更新会触发地图重绘,消耗大量资源。可以通过以下方式优化:
- 增加拖动状态判断,仅在拖动结束后更新地图frame
- 对比前后frame差异,仅当变化超过阈值时才执行更新
示例代码:
struct MapView: UIViewRepresentable { var mapHeight: CGFloat @Binding var isDragging: Bool func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView() return mapView } func updateUIView(_ uiView: MKMapView, context: Context) { // 拖动时不更新地图frame,结束后再同步 guard !isDragging else { return } let currentFrame = uiView.frame let newFrame = CGRect(x: currentFrame.origin.x, y: currentFrame.origin.y, width: currentFrame.width, height: mapHeight) // 仅当高度变化超过1pt时更新 if abs(newFrame.height - currentFrame.height) > 1 { uiView.frame = newFrame } } }
2. 利用PreferenceKey延迟布局更新
通过PreferenceKey传递布局信息,让地图视图在布局周期的合适时机更新,避免实时响应拖动事件:
- 定义PreferenceKey:
struct BottomHeightPreference: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } }
- 在底部视图中注入高度值:
Color.red .frame(height: bottomHeight) .preference(key: BottomHeightPreference.self, value: bottomHeight)
- 在父视图中接收偏好值,仅在布局稳定后更新地图:
struct ContentView: View { @State private var bottomHeight: CGFloat = UIScreen.main.bounds.height * 0.3 @State private var mapHeight: CGFloat = 0 @State private var isDragging = false var body: some View { VStack(spacing: 0) { MapView(mapHeight: mapHeight, isDragging: $isDragging) Divider() .frame(height: 6) .background(Color.gray) .gesture( DragGesture() .onChanged { _ in isDragging = true } .onEnded { value in isDragging = false let newHeight = bottomHeight + value.translation.height bottomHeight = max(UIScreen.main.bounds.height * 0.1, min(UIScreen.main.bounds.height * 0.9, newHeight)) } ) Color.red .frame(height: bottomHeight) .preference(key: BottomHeightPreference.self, value: bottomHeight) } .onPreferenceChange(BottomHeightPreference.self) { newValue in // 延迟更新地图高度,等待布局稳定 DispatchQueue.main.async { mapHeight = UIScreen.main.bounds.height - newValue - 6 // 减去Divider高度 } } } }
3. 拖动时临时禁用MKMapView的高消耗功能
拖动过程中暂时关闭地图的交互和渲染功能,减少资源占用:
func updateUIView(_ uiView: MKMapView, context: Context) { if isDragging { uiView.isUserInteractionEnabled = false uiView.showsUserLocation = false uiView.mapType = .standard // 切换到更轻量的地图类型 } else { uiView.isUserInteractionEnabled = true uiView.showsUserLocation = true uiView.mapType = .hybrid // 恢复原地图类型 } // 其他frame更新逻辑... }
4. 改用UIKit布局实现分栏逻辑
如果SwiftUI的布局更新性能无法满足需求,可以将分栏逻辑放在UIKit中实现,再封装到SwiftUI:
class SplitViewController: UIViewController { let mapView = MKMapView() let bottomView = UIView() let handleView = UIView() var bottomHeightConstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() setupViews() setupConstraints() setupGesture() } private func setupViews() { bottomView.backgroundColor = .red handleView.backgroundColor = .gray handleView.heightAnchor.constraint(equalToConstant: 6).isActive = true [mapView, handleView, bottomView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false view.addSubview($0) } } private func setupConstraints() { bottomHeightConstraint = bottomView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3) NSLayoutConstraint.activate([ mapView.topAnchor.constraint(equalTo: view.topAnchor), mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor), mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor), mapView.bottomAnchor.constraint(equalTo: handleView.topAnchor), handleView.leadingAnchor.constraint(equalTo: view.leadingAnchor), handleView.trailingAnchor.constraint(equalTo: view.trailingAnchor), handleView.bottomAnchor.constraint(equalTo: bottomView.topAnchor), bottomView.leadingAnchor.constraint(equalTo: view.leadingAnchor), bottomView.trailingAnchor.constraint(equalTo: view.trailingAnchor), bottomView.bottomAnchor.constraint(equalTo: view.bottomAnchor), bottomHeightConstraint ]) } private func setupGesture() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) handleView.addGestureRecognizer(panGesture) } @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) let newHeight = bottomHeightConstraint.constant - translation.y // 限制高度范围 let minHeight = view.frame.height * 0.1 let maxHeight = view.frame.height * 0.9 bottomHeightConstraint.constant = max(minHeight, min(maxHeight, newHeight)) gesture.setTranslation(.zero, in: view) if gesture.state == .ended { // 添加弹簧动画优化体验 UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() } } } } struct SplitView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> SplitViewController { SplitViewController() } func updateUIViewController(_ uiViewController: SplitViewController, context: Context) {} } // 在SwiftUI中使用 struct ContentView: View { var body: some View { SplitView() .edgesIgnoringSafeArea(.all) } }
内容的提问来源于stack exchange,提问作者daniel
相关产品推荐
相关产品推荐

