You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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传递布局信息,让地图视图在布局周期的合适时机更新,避免实时响应拖动事件:

  1. 定义PreferenceKey:
struct BottomHeightPreference: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}
  1. 在底部视图中注入高度值:
Color.red
    .frame(height: bottomHeight)
    .preference(key: BottomHeightPreference.self, value: bottomHeight)
  1. 在父视图中接收偏好值,仅在布局稳定后更新地图:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:27:49