【问题标题】:How can I change the phase of UIColor patternImage in Swift?如何在 Swift 中更改 UIColor patternImage 的相位?
【发布时间】:2017-01-23 07:51:56
【问题描述】:

我正在使用非常方便的 UIColor(patternImage:) 在带有 Xcode 8.2 的 iOS 10 应用程序中创建一些带有平铺图案的 CAShapeLayers。平铺总是从视图的原点开始,如果您希望它从其他地方开始,这可能会很不方便。为了说明,下面是模拟器的截图(代码如下):

左边的CAShapeLayer 从 (0,0) 开始,所以一切都很好。右边的那个在 (110,50),所以它在中间分开。代码如下:

let firstBox = CAShapeLayer()
firstBox.fillColor = UIColor(patternImage: UIImage(named: "test-image")!).cgColor
view.layer.addSublayer(firstBox)
firstBox.path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100)).cgPath

let secondBox = CAShapeLayer()
secondBox.fillColor = UIColor(patternImage: UIImage(named: "test-image")!).cgColor
view.layer.addSublayer(secondBox)
secondBox.path = UIBezierPath(rect: CGRect(x: 110, y: 50, width: 100, height: 100)).cgPath

我想为右边的CAShapeLayer 调整图案的相位,以便两个图块都显示完整的脸。 Apple 为 UIColor(patternImage:) 提供的 documentation 有用地指代了用于此目的的函数:

要更改相位,请将颜色设为当前颜色,然后使用 setPatternPhase(_:) 改变相位的函数。

听起来很简单!但我很难实现它。我不太确定“使颜色成为当前颜色”是什么意思。我尝试获取当前上下文并在其上调用setPatternPhase,在将填充颜色分配给图层之前和之后:

UIGraphicsGetCurrentContext()?.setPatternPhase(CGSize(width: 25, height: 25))

没有明显的效果。我尝试将包含的UIView 子类化并在其drawRect: 方法中设置相位,如this answer 中所建议的那样。但是drawRect: 在 Swift 中不存在,所以我尝试了draw(_ rect:)draw(_ layer:, in:)。两个函数都被调用,但没有明显的效果。

class PatternView: UIView {
    override func draw(_ rect: CGRect) {
        UIGraphicsGetCurrentContext()?.setPatternPhase(CGSize(width: 25, height: 25))
        super.draw(rect)
    }
    override func draw(_ layer: CALayer, in ctx: CGContext) {
        ctx.setPatternPhase(CGSize(width: 25, height: 25))
        super.draw(layer, in: ctx)
    }
}

在 Dave Weston 的建议下,在调用 setPatternPhase 之前,我使用 UIImage.set() 为当前上下文设置当前笔画和填充。不幸的是,输出不受影响。这是我尝试过的代码:

let secondBoxColor = UIColor(patternImage: UIImage(named: "test-image")!)
secondBoxColor.set()
UIGraphicsGetCurrentContext()?.setPatternPhase(CGSize(width: 50, height: 50))

let secondBox = CAShapeLayer()
secondBox.fillColor = secondBoxColor.cgColor
view.layer.addSublayer(secondBox)
secondBox.path = UIBezierPath(rect: CGRect(x: 110, y: 50, width: 100, height: 100)).cgPath

如何将绘制到CAShapeLayer 的模式的相位转换?

【问题讨论】:

    标签: ios swift calayer uicolor cashapelayer


    【解决方案1】:

    要使您的图案成为当前颜色,您应该在包含您的图案的UIColor 实例上调用set() 实例方法。这会将颜色配置为当前上下文的当前笔触和填充颜色。

    然后,根据 Apple 的文档,setPatternPhase 应该可以工作。

    【讨论】:

    • 感谢您的帮助。我尝试在setPatternPhase() 之前使用set(),但它对输出没有影响。我编辑了我的问题以包含新代码(最后)。我做错了什么?
    【解决方案2】:

    我无法解决这个问题,但我想我会分享我正在使用的解决方法,以防它对任何人有用。

    据我所知,CAShapeLayer 在一个秘密、隐藏的地方进行渲染,并忽略了CALayerDelegates 应该使用的正常display()draw() 函数。结果,您永远无法访问它用于渲染的CGContext,因此无法调用setPatternPhase()

    我对@9​​87654329@ 的具体用例是让图案与它所绘制的 CAShapeLayer 的左上角对齐,所以我找到了另一种方法来做到这一点。 它不允许您设置任意阶段。

    我所做的是创建一个名为 CAPatternLayer 的新 CALayer 子类,它需要一个 UIImage 平铺和一个 CGPath 来填充。它委托给名为CAPatternLayerDelegateCALayerDelegate 类,该类提供draw(layer: in ctx:) 函数。当请求绘制时,代理会创建一个临时的UIImageView,用平铺图像填充它,然后将其渲染到CALayer 的上下文中。

    这样做的一个很好的副作用是您可以使用带有帽插入的UIImage,这允许在平铺中心切片的情况下进行 9 切片缩放。

    这是PatternLayerPatternLayerDelegate 的代码:

    class CAPatternLayer: CALayer {
        var image: UIImage?
        var path: CGPath? {
            didSet {
                if let path = self.path {
                    self.frame = path.boundingBoxOfPath
                    // shift the path to 0,0 since you built position into the frame
                    var translation = CGAffineTransform(translationX: -path.boundingBoxOfPath.origin.x, y: -path.boundingBoxOfPath.origin.y)
                    let shiftedPath = path.copy(using: &translation)
                    // use the shifted version
                    self.path = shiftedPath
                    self.maskLayer.path = shiftedPath
                }
            }
        }
        let maskLayer: CAShapeLayer = CAShapeLayer()
    
        override init() {
            super.init()
            self.delegate = CAPatternLayerDelegate.sharedInstance
            self.setNeedsDisplay()
            self.mask = self.maskLayer
        }
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        convenience init(path: CGPath, image: UIImage) {
            self.init()
            defer {
                self.image = image
                self.path = path
            }
        }
    }
    
    class CAPatternLayerDelegate: NSObject, CALayerDelegate {
        static let sharedInstance = CAPatternLayerDelegate()
        func draw(_ layer: CALayer, in ctx: CGContext) {
            // cast layer to a CAPatternLayer so you can access properties
            if let layer = layer as? CAPatternLayer, let image = layer.image, let path = layer.path {
                // create a UIImageView to display the image, then render it to the context
                let imageView = UIImageView()
                // if a path bounding box was set, use it, otherwise draw over the whole layer
                imageView.bounds = path.boundingBoxOfPath
                imageView.image = image
                imageView.layer.render(in: ctx)
            }
        }
    }
    

    下面是一个使用示例:

    // create a path to fill
    let myMaskPath = UIBezierPath(rect: CGRect(x: 50, y: 25, width: 200, height: 100))
    // pull the image and set it up as a resizeableImage with cap insets
    let patternImage = UIImage(named: "ground")!.resizableImage(withCapInsets: .init(top: 16, left: 16, bottom: 0, right: 16), resizingMode: .tile)
    // create the CAPatternLayer and add it to the view
    let myPatternLayer = CAPatternLayer(path: myMaskPath.cgPath, image: patternImage)
    view.layer.addSublayer(myPatternLayer)
    

    输出:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-04
      • 1970-01-01
      • 1970-01-01
      • 2015-06-21
      • 2015-09-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多