SwiftUI中UIViewRepresentable如何响应视图Frame变化更新CALayer绘图?
问题描述
我有一个通过CALayer绘制路径的UIView,绘图的正确尺寸依赖于视图的frame。现在我用UIViewRepresentable将其接入SwiftUI,最初通过GeometryReader获取frame解决了初始尺寸问题,但当屏幕旋转等导致视图尺寸变化时,updateUIView(_:)方法不会触发,无法更新绘图内容。
原代码示例
UIViewRepresentable实现
struct MyRepresentable: UIViewRepresentable { var geometry: GeometryProxy func makeUIView(_ view: UIView, context: Context) -> UIView { let view = UIView() let size = geometry.size view.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) context.coordinator.view = view context.coordinator.draw() return view } func updateUIView(_ view: UIView, context: Context) { let size = geometry.size context.coordinator.view.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) context.coordinator.update() } }
绘图逻辑(draw方法)
// locations is [CLLocation] let points = locations.map { $0.cgPoint } let path = CGMutablePath() path.move(to: points[0]) for index in 1..<points.count { path.addLine(to: points[index]) } var transform = getTransformation(for: path) let transformedPath = path.copy(using: &transform) let newLayer = CAShapeLayer() newLayer.path = transformedPath newLayer.strokeColor = UIColor.white.cgColor newLayer.fillColor = UIColor.clear.cgColor newLayer.lineWidth = 6 self.view.layer.addSublayer(newLayer)
解决方案
核心思路是让UIView自行处理尺寸变化,移除对GeometryProxy的依赖,同时优化绘图逻辑避免重复添加图层。
步骤1:自定义UIView子类,封装绘图与尺寸监听
创建专门的DrawableView,重写layoutSubviews方法(视图尺寸变化时自动调用)来触发重绘,同时复用单个CAShapeLayer避免内存问题:
class DrawableView: UIView { // 绘图数据源,数据变化时触发重绘 var locations: [CLLocation] = [] { didSet { setNeedsLayout() } } private let shapeLayer = CAShapeLayer() override init(frame: CGRect) { super.init(frame: frame) setupShapeLayer() } required init?(coder: NSCoder) { super.init(coder: coder) setupShapeLayer() } private func setupShapeLayer() { shapeLayer.strokeColor = UIColor.white.cgColor shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = 6 layer.addSublayer(shapeLayer) } // 视图尺寸变化时自动调用,在此更新绘图 override func layoutSubviews() { super.layoutSubviews() redrawPath() } private func redrawPath() { guard !locations.isEmpty else { return } let points = locations.map { $0.cgPoint } let path = CGMutablePath() path.move(to: points[0]) for index in 1..<points.count { path.addLine(to: points[index]) } var transform = getTransformation(for: path) guard let transformedPath = path.copy(using: &transform) else { return } shapeLayer.path = transformedPath } // 适配视图尺寸的变换逻辑(根据你的需求调整) private func getTransformation(for path: CGPath) -> CGAffineTransform { let bounds = self.bounds let pathBounds = path.boundingBoxOfPath // 计算缩放比例,保证路径完整显示在视图内 let scaleX = bounds.width / pathBounds.width let scaleY = bounds.height / pathBounds.height let scale = min(scaleX, scaleY) // 计算偏移量,让路径居中显示 let translateX = (bounds.width - pathBounds.width * scale) / 2 - pathBounds.minX * scale let translateY = (bounds.height - pathBounds.height * scale) / 2 - pathBounds.minY * scale return CGAffineTransform(scaleX: scale, y: scale).translatedBy(x: translateX, y: translateY) } }
步骤2:简化UIViewRepresentable实现
去掉GeometryProxy依赖,只负责传递数据源和更新视图:
struct DrawableRepresentable: UIViewRepresentable { var locations: [CLLocation] func makeUIView(context: Context) -> DrawableView { let view = DrawableView() view.locations = locations return view } func updateUIView(_ view: DrawableView, context: Context) { // 数据源变化时更新视图 if view.locations != locations { view.locations = locations } } }
方案优势
- 自动响应尺寸变化:
layoutSubviews会在屏幕旋转、视图布局调整时自动调用,无需额外监听。 - 避免内存泄漏:复用单个
CAShapeLayer,不会每次重绘都添加新图层。 - 符合SwiftUI布局逻辑:移除GeometryProxy依赖,让SwiftUI自动管理视图尺寸,无需手动设置frame。
内容的提问来源于stack exchange,提问作者Krisztian
相关产品推荐
相关产品推荐

