SwiftUI/UIKit:使用UIScrollView实现仅水平方向缩放滚动视图问题
问题原因分析
移除.flexibleHeight后图片无法显示的原因
- 原代码中
hostedView依赖autoresizingMask适配UIScrollView的尺寸,移除.flexibleHeight后,hostedView高度不会跟随UIScrollView的布局变化自动调整,初始设置的frame会在后续布局刷新后失效。 - 你使用了
.scaledToFit()修饰的图片,会优先按照宽高比适配容器尺寸,当hostedView高度约束缺失或冲突时,SwiftUI布局系统会将内容压缩至0高度,导致图片不可见。
垂直方向同步缩放的原因
UIScrollView默认的缩放逻辑是对可缩放视图应用均匀的transform变换,X/Y轴使用相同的缩放系数,所以缩放时会同步修改垂直方向尺寸。
解决方案
我们需要修改默认缩放逻辑,仅对水平方向应用缩放系数,垂直方向固定为原始高度,同时限制滚动仅在水平方向生效:
- 用自动布局固定
hostedView的高度和UIScrollView高度一致,移除垂直方向的自适应规则 - 在
UIScrollViewDelegate的scrollViewDidZoom回调中,手动设置hostedView的变换,仅应用X轴缩放 - 手动调整
UIScrollView的contentSize,高度固定和滚动视图一致,仅宽度随缩放比例变化
修改后的完整代码
struct ContentView: View { var body: some View { ZoomableScrollView { Image(systemName: "star") .resizable() .scaledToFit() .frame(maxHeight: .infinity) // 垂直方向填充满容器 } } } struct ZoomableScrollView<Content: View>: UIViewRepresentable { private var content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } func makeUIView(context: Context) -> UIScrollView { let scrollView = UIScrollView() scrollView.delegate = context.coordinator scrollView.maximumZoomScale = 20 scrollView.minimumZoomScale = 1 scrollView.bouncesZoom = true // 禁止垂直方向滚动和指示器 scrollView.alwaysBounceVertical = false scrollView.showsVerticalScrollIndicator = false let hostedView = context.coordinator.hostingController.view! hostedView.translatesAutoresizingMaskIntoConstraints = false hostedView.backgroundColor = .clear // 避免背景遮挡 scrollView.addSubview(hostedView) // 固定高度和ScrollView一致 NSLayoutConstraint.activate([ hostedView.topAnchor.constraint(equalTo: scrollView.topAnchor), hostedView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), hostedView.heightAnchor.constraint(equalTo: scrollView.heightAnchor), ]) return scrollView } func makeCoordinator() -> Coordinator { return Coordinator(hostingController: UIHostingController(rootView: self.content)) } func updateUIView(_ uiView: UIScrollView, context: Context) { context.coordinator.hostingController.rootView = self.content assert(context.coordinator.hostingController.view.superview == uiView) } class Coordinator: NSObject, UIScrollViewDelegate { var hostingController: UIHostingController<Content> init(hostingController: UIHostingController<Content>) { self.hostingController = hostingController } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return hostingController.view } func scrollViewDidZoom(_ scrollView: UIScrollView) { let scale = scrollView.zoomScale // 仅应用水平方向缩放,垂直方向保持原始比例 hostingController.view.transform = CGAffineTransform(scaleX: scale, y: 1) // 调整内容尺寸,仅宽度随缩放变化 let contentWidth = hostingController.view.intrinsicContentSize.width * scale scrollView.contentSize = CGSize( width: contentWidth, height: scrollView.bounds.height ) } } }
内容的提问来源于stack exchange,提问作者Isaak
相关产品推荐
相关产品推荐

