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

如何让上层ScrollView下的背景MapView可点击拖拽?

解决方案:让透明ScrollView区域下的MapView可交互

问题核心

SwiftUI的ScrollView会默认拦截所有触摸事件,即便设置allowsHitTesting(false),也无法让底层MapView完整接收缩放、滑动这类连续手势。需要通过自定义UIKit包装的ScrollView,精确控制触摸事件的分发逻辑。

修改后的完整代码

import SwiftUI
import MapKit
import CoreLocation

// 原MapView代码保持不变
struct MapView: UIViewRepresentable {
    typealias UIViewType = MKMapView
    var userLatitude: String {
        return "34.052235"
    }
        
    var userLongitude: String {
        return "-118.243683"
    }
    
    func makeCoordinator() -> MapView.Coordinator {
        Coordinator(self, colour:  .blue)
    }

    final class Coordinator: NSObject, MKMapViewDelegate {
        var control: MapView
        var colour: UIColor

        init(_ control: MapView, colour: UIColor) {
            self.control = control
            self.colour = colour
        }

        func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
            if let polyline = overlay as? MKPolyline {
                let polylineRenderer = MKPolylineRenderer(overlay: polyline)
                polylineRenderer.strokeColor = colour
                polylineRenderer.lineWidth = 8
                return polylineRenderer
            }
            return MKOverlayRenderer(overlay: overlay)
        }
    }

    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }
    
    func updateUIView(_ view: MKMapView, context: Context) {
        view.showsUserLocation = true
        
        let location = CLLocationCoordinate2D(
            latitude: CLLocationDegrees(userLatitude)!, 
            longitude: CLLocationDegrees(userLongitude)!
        )
        let span = MKCoordinateSpan(latitudeDelta: 0.009, longitudeDelta: 0.009)
        let region = MKCoordinateRegion(center: location, span: span)
        view.setRegion(region, animated: true)
        view.delegate = context.coordinator
    }
}

// 自定义ScrollView包装器,控制触摸事件分发
struct CustomScrollView<Content: View>: UIViewRepresentable {
    let content: Content
    let transparentHeight: CGFloat // 顶部透明区域高度
    
    init(transparentHeight: CGFloat, @ViewBuilder content: () -> Content) {
        self.transparentHeight = transparentHeight
        self.content = content()
    }
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        scrollView.showsVerticalScrollIndicator = false
        // 将SwiftUI内容嵌入到UIKit的ScrollView中
        let hostingController = UIHostingController(rootView: content)
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(hostingController.view)
        
        NSLayoutConstraint.activate([
            hostingController.view.topAnchor.constraint(equalTo: scrollView.topAnchor),
            hostingController.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            hostingController.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
        ])
        
        scrollView.delegate = context.coordinator
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(transparentHeight: transparentHeight)
    }
    
    class Coordinator: NSObject, UIScrollViewDelegate {
        let transparentHeight: CGFloat
        
        init(transparentHeight: CGFloat) {
            self.transparentHeight = transparentHeight
        }
        
        // 重写hitTest,判断触摸点是否在透明区域,若是则不拦截事件
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            guard let scrollView = self.view as? UIScrollView else { return nil }
            // 计算触摸点相对于ScrollView内容的y坐标
            let relativeY = point.y + scrollView.contentOffset.y
            // 如果触摸点在顶部透明区域内,返回nil让事件向下传递给MapView
            if relativeY < transparentHeight {
                return nil
            }
            // 否则返回ScrollView本身,处理滚动逻辑
            return super.hitTest(point, with: event)
        }
    }
}

struct ContentView: View {
    private let transparentAreaHeight: CGFloat = 500
    
    var body: some View {
        ZStack {
            MapView()
            CustomScrollView(transparentHeight: transparentAreaHeight) {
                // 顶部透明占位区域(无需额外设置allowsHitTesting)
                Color.clear.frame(height: transparentAreaHeight)
                
                VStack(spacing: 16) {
                    ForEach(0..<30, id: \.self) { rowIndex in
                        Text("row \(rowIndex)")
                            .frame(height: 35)
                    }
                }
                .frame(maxWidth: .infinity)
                .background(Color.white)
            }
        }
        .ignoresSafeArea()
    }
}

关键修改说明

  1. 自定义CustomScrollView:用UIKit的UIScrollView包装SwiftUI内容,通过重写hitTest方法实现事件的精准分发
  2. 触摸区域判断:检查触摸点位置,若在顶部透明区域内则返回nil,让事件透传给底层的MapView;否则由ScrollView处理滚动
  3. 移除冗余设置:删除原代码中allowsHitTesting(false),因为自定义ScrollView已处理事件逻辑

修改后,顶部透明区域的所有手势(点击、滑动、缩放)都会传递给MapView,底部白色区域则正常响应ScrollView的滚动操作。

内容的提问来源于stack exchange,提问作者mars

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 02:17:32