如何正确渲染CALayer子图层位置并遵循zPosition层级顺序
问题原因
你重写render(in:)方法时跳过了系统默认实现中对子图层坐标变换的处理逻辑:
- 系统默认的
CALayer.render(in:)会自动将上下文坐标系转换到对应子图层的局部空间,应用子图层的position、transform、anchorPoint等几何属性后再执行渲染 - 你直接在父层的全局上下文中调用子图层的
render(in:),所有子图层的几何配置都没有被应用,因此出现位置偏移、旋转丢失的问题
方案1:调整子图层数组顺序(更简单,无需重写render)
CALayer的sublayers数组顺序直接决定渲染顺序,你可以在生成PDF前临时调整子图层顺序,渲染完成后恢复原顺序即可,完全不需要重写render(in:)方法,避免处理坐标变换的复杂度:
func createPdfData()->Data?{ DebugLog("") let scale: CGFloat = 1 let mWidth = drawingLayer.frame.width * scale let mHeight = drawingLayer.frame.height * scale var cgRect = CGRect(x: 0, y: 0, width: mWidth, height: mHeight) let documentInfo = [kCGPDFContextCreator as String:"MakeSpace(www.xxxx.com)", kCGPDFContextTitle as String:"Layout Image", kCGPDFContextAuthor as String:GlobalVars.shared.appUser?.username ?? "", kCGPDFContextSubject as String:self.level?.imageCode ?? "", kCGPDFContextKeywords as String:"XXXX, Layout"] let data = NSMutableData() guard let pdfData = CGDataConsumer(data: data), let ctx = CGContext.init(consumer: pdfData, mediaBox: &cgRect, documentInfo as CFDictionary) else { return nil} ctx.beginPDFPage(nil) ctx.saveGState() ctx.scaleBy(x: scale, y: scale) // 新增:临时调整子图层顺序 let originalSublayers = drawingLayer.sublayers ?? [] let sortedSublayers = originalSublayers.sorted { $0.zPosition < $1.zPosition } drawingLayer.sublayers = sortedSublayers self.drawingLayer.render(in: ctx) // 新增:恢复原始子图层顺序,不影响屏幕显示 drawingLayer.sublayers = originalSublayers ctx.restoreGState() ctx.endPDFPage() ctx.closePDF() return data as Data }
方案2:修正重写的render方法
如果你一定要通过重写render(in:)实现层级控制,需要在渲染每个子图层前手动处理上下文的坐标变换,渲染后恢复上下文状态:
override func render(in ctx: CGContext) { // 先渲染当前层自身的内容(背景色、边框等,不需要可删除) super.render(in: ctx) guard let layers = self.sublayers else { return } let orderedLayers = layers.sorted(by: { $0.zPosition < $1.zPosition }) for layer in orderedLayers { // 保存当前上下文状态 ctx.saveGState() // 平移到子图层的origin位置 ctx.translateBy(x: layer.frame.origin.x, y: layer.frame.origin.y) // 应用子图层的变换(旋转、缩放等) ctx.concatenate(layer.transform) // 处理anchorPoint带来的偏移 ctx.translateBy( x: -layer.bounds.width * layer.anchorPoint.x, y: -layer.bounds.height * layer.anchorPoint.y ) // 渲染子图层 layer.render(in: ctx) // 恢复上下文状态,避免变换累加影响后续子图层 ctx.restoreGState() } }
补充注意
如果渲染出来的PDF内容出现上下颠倒的情况,是因为Core Graphics PDF上下文默认以左下角为原点,和CALayer的左上角原点坐标系不一致,在ctx.scaleBy(x: scale, y: scale)后补充两行代码翻转坐标系即可:
ctx.translateBy(x: 0, y: drawingLayer.bounds.height) ctx.scaleBy(x: 1, y: -1)
内容的提问来源于stack exchange,提问作者Duncan Groenewald
相关产品推荐
相关产品推荐

