捏合手势调整高度后网格视图消失(图层相关问题)
问题
容器视图内包含两个子视图:可正常显示的按钮视图,以及绘制网格的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
相关产品推荐
相关产品推荐

