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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 15:55:00