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

捏合手势调整高度后网格视图消失(图层相关问题)

问题

容器视图内包含两个子视图:可正常显示的按钮视图,以及绘制网格的PianoRollView。当通过捏合手势将网格视图高度缩放至约5000以上时,PianoRollView会消失,但按钮视图功能正常。经检查视图尺寸存在但无法显示,疑似图层绘制限制导致。

原因分析

iOS中UIView的默认图层(CALayer)存在最大纹理尺寸限制(通常为4096x4096或8192x8192,依设备而定)。当PianoRollView的高度超过该限制时,Core Graphics的绘制操作会失效,导致网格无法显示,尽管视图的布局尺寸依然存在。

解决方案

1. 改用CATiledLayer分块绘制

将PianoRollView的图层替换为CATiledLayer,它会将大视图拆分为多个小瓦片(tile)分别绘制,避免单图层尺寸超出限制。

修改PianoRollView代码:

class PianoRollView: UIView {
    var notewidth:CGFloat = 0
    var noteHeight: CGFloat = 0
  
    var numN = 127
    var numT = 128
 
    // 指定使用CATiledLayer
    override class var layerClass: AnyClass {
        return CATiledLayer.self
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupTiledLayer()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupTiledLayer() {
        guard let tiledLayer = layer as? CATiledLayer else { return }
        // 设置瓦片尺寸,可根据需求调整
        tiledLayer.tileSize = CGSize(width: 300, height: 300)
        // 适配屏幕分辨率
        tiledLayer.contentsScale = UIScreen.main.scale
        // 关闭淡出效果(可选)
        tiledLayer.fadeDuration = 0
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        let context = UIGraphicsGetCurrentContext()
        context?.setStrokeColor(UIColor.black.cgColor)
        context?.setLineWidth(1.0)
     
        // 只绘制当前tile范围内的水平线
        let startRow = max(0, Int(rect.minY / noteHeight))
        let endRow = min(numN, Int(rect.maxY / noteHeight) + 1)
        for i in startRow..<endRow {
            let y = CGFloat(i) * noteHeight
            // 确保线在当前rect范围内
            if y >= rect.minY && y <= rect.maxY {
                context?.move(to: CGPoint(x: rect.minX, y: y))
                context?.addLine(to: CGPoint(x: rect.maxX, y: y))
            }
        }
        
        // 只绘制当前tile范围内的垂直线
        let startCol = max(0, Int(rect.minX / notewidth))
        let endCol = min(numT, Int(rect.maxX / notewidth) + 1)
        for i in startCol..<endCol {
            let x = CGFloat(i) * notewidth
            // 确保线在当前rect范围内
            if x >= rect.minX && x <= rect.maxX {
                context?.move(to: CGPoint(x: x, y: rect.minY))
                context?.addLine(to: CGPoint(x: x, y: rect.maxY))
            }
        }
        
        // 绘制当前tile的边界线(可选,确保边缘完整)
        // 左边界
        if rect.minX == 0 {
            context?.move(to: CGPoint(x: 0, y: rect.minY))
            context?.addLine(to: CGPoint(x: 0, y: rect.maxY))
        }
        // 右边界
        if rect.maxX == bounds.width {
            context?.move(to: CGPoint(x: bounds.width, y: rect.minY))
            context?.addLine(to: CGPoint(x: bounds.width, y: rect.maxY))
        }
        // 上边界
        if rect.minY == 0 {
            context?.move(to: CGPoint(x: rect.minX, y: 0))
            context?.addLine(to: CGPoint(x: rect.maxX, y: 0))
        }
        // 下边界
        if rect.maxY == bounds.height {
            context?.move(to: CGPoint(x: rect.minX, y: bounds.height))
            context?.addLine(to: CGPoint(x: rect.maxX, y: bounds.height))
        }
        
        context?.strokePath() 
    }
}

2. 修复布局更新逻辑

在捏合手势处理方法中,确保布局正确更新,避免视图尺寸计算滞后:

修改handlePinch方法:

@objc func handlePinch(_ g: UIPinchGestureRecognizer) {
    if g.state == .began || g.state == .changed {
        let newHeightright = targetHeight * g.scale
        let newHeightgo = max(newHeightright, 100.0)
       
        g.scale = 1.0
        targetHeight = newHeightgo
    
        noteHeight = calculateNoteHeight(targetHeight: newHeightgo, numN: rightPianoRollView.numN)
       
        rightPianoRollView.noteHeight = noteHeight
        // 重绘整个PianoRollView
        rightPianoRollView.setNeedsDisplay()
        
        // 更新按钮高度约束
        for constraint in buttonHeightConstraints {
            constraint.constant = noteHeight
        }
        // 触发布局更新
        rightButtonsView.setNeedsLayout()
        rightContainerView.layoutIfNeeded()
        
        print("container contain count",rightContainerView.subviews.count)
        print("pianorol height",rightPianoRollView.frame.size.height)
        print("rigth button height",rightButtonsView.frame.size.height)
    }
}

3. 优化滚动视图缩放设置(可选)

确保滚动视图的缩放范围合理,避免极端尺寸导致的问题:

在viewDidAppear中调整最大缩放比例:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    let minZoomScale = rightScrollView.frame.height / rightPianoRollView.bounds.height
    rightScrollView.minimumZoomScale = minZoomScale
    // 设置合理的最大缩放比例,避免过度放大
    rightScrollView.maximumZoomScale = minZoomScale * 10
}
说明
  • CATiledLayer会自动将大视图拆分为多个小瓦片,仅绘制当前可见区域的内容,既解决了尺寸限制问题,也优化了大视图的绘制性能。
  • 调整tileSize可平衡绘制性能和瓦片数量,建议根据网格密度和设备性能选择合适的尺寸。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:27:00