【问题标题】:Setting the frame of a transformed CALayer设置转换后的 CALayer 的框架
【发布时间】:2020-12-07 08:08:48
【问题描述】:

我目前正在玩CALayers。出于演示目的,我正在创建一个自定义 UIView 来呈现电量计。该视图有两个子层:

  • 一个用于背景
  • 手用一个

然后,代表手的图层会简单地相应旋转以指向正确的值。到目前为止,一切都很好。现在我希望视图在视图大小发生变化时调整其图层的大小。为此,我创建了 layoutSubviews 方法的覆盖,如下所示:

public override func layoutSubviews()
{
    super.layoutSubviews()

    if previousBounds == nil || !previousBounds!.equalTo(self.bounds)
    {
        previousBounds = self.bounds
        self.updateLayers(self.bounds)
    }
}

由于该方法被多次调用,我使用previousBounds 来确保仅在大小实际发生变化时才对图层执行更新。

一开始,我在updateLayers方法中只有以下代码来设置子层的框架:

backgroundLayer.frame = bounds.insetBy(dx: 5, dy: 5)
handLayer.frame = bounds.insetBy(dx: 5, dy: 5)

效果很好 - 直到 handLayer 被旋转。在那种情况下,它的大小会发生一些奇怪的事情。我想这是因为框架是在旋转之后应用的,当然,旋转后的图层实际上并不适合边界,因此会调整大小以适应。

我目前的解决方案是临时创建一个新的CATransaction 来抑制动画,将转换恢复为身份,设置框架,然后像这样重新应用转换:

CATransaction.begin()
CATransaction.setDisableActions(true)

let oldTransform = scaleLayer.transform
handLayer.transform = CATransform3DIdentity
handLayer.frame = bounds.insetBy(dx: 5, dy: 5)
handLayer.transform = oldTransform

CATransaction.commit()

我已经尝试省略CATransaction,而是将handLayer.affineTransform 应用于我正在设置的bounds,但这并没有产生预期的结果——也许我做错了(附带问题:如何旋转一个给定的CGRect 在它的中心周围而不自己做所有的数学)?

我的问题很简单:是否有推荐的方法是设置转换层的框架,或者我找到的解决方案已经是“解决方案”了?

编辑
Kevvv 提供了一些示例代码,我对其进行了修改以演示我的问题:

class ViewController: UIViewController {
    let customView = CustomView(frame: .init(origin: .init(x: 200, y: 200), size: .init(width: 200, height: 200)))
    let backgroundLayer = CALayer()
    let handLayer = CAShapeLayer()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(customView)
        
        customView.backgroundColor = UIColor.blue
        
        backgroundLayer.backgroundColor = UIColor.yellow.cgColor
        backgroundLayer.frame = customView.bounds
        
        let handPath = UIBezierPath()
        handPath.move(to: backgroundLayer.position)
        handPath.addLine(to: .init(x: 0, y: backgroundLayer.position.y))
        
        handLayer.frame = customView.bounds
        handLayer.path = handPath.cgPath
        handLayer.strokeColor = UIColor.black.cgColor
        handLayer.backgroundColor = UIColor.red.cgColor
        
        customView.layer.addSublayer(backgroundLayer)
        customView.layer.addSublayer(handLayer)

        handLayer.transform = CATransform3DMakeRotation(5, 0, 0, 1)

        let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
        customView.addGestureRecognizer(tap)
    }
    
    @objc func tapped(_ sender: UITapGestureRecognizer) {
        customView.frame = customView.frame.insetBy(dx:10, dy:10)
        /*let animation = CABasicAnimation(keyPath: #keyPath(CALayer.transform))
        let fromValue = self.handLayer.transform
        let toValue = CGFloat.pi * 2
        animation.duration = 2
        animation.fromValue = fromValue
        animation.toValue = toValue
        animation.valueFunction = CAValueFunction(name: .rotateZ)
        self.handLayer.add(animation, forKey: nil)*/
    }
}

class CustomView: UIView {
    var previousBounds: CGRect!
    override func layoutSubviews() {
        super.layoutSubviews()
        if previousBounds == nil || !previousBounds!.equalTo(self.bounds) {
            previousBounds = self.bounds
            self.updateLayers(self.bounds)
        }
    }

    func updateLayers(_ bounds: CGRect) {
        guard let sublayers = self.layer.sublayers else { return }
        for sublayer in sublayers {
            sublayer.frame = bounds.insetBy(dx: 5, dy: 5)
        }
    }
}

如果您将它添加到游乐场,然后运行并点击控件,您就会明白我的意思了。注意红色的“正方形”。

【问题讨论】:

    标签: swift transform calayer


    【解决方案1】:

    您介意解释一下“尺寸发生奇怪的事情”是什么意思吗?我试图复制它,但找不到意想不到的效果:

    class ViewController: UIViewController {
        let customView = CustomView(frame: .init(origin: .init(x: 200, y: 200), size: .init(width: 200, height: 200)))
        let backgroundLayer = CALayer()
        let handLayer = CAShapeLayer()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.view.addSubview(customView)
            
            backgroundLayer.backgroundColor = UIColor.yellow.cgColor
            backgroundLayer.frame = customView.bounds
            
            let handPath = UIBezierPath()
            handPath.move(to: backgroundLayer.position)
            handPath.addLine(to: .init(x: 0, y: backgroundLayer.position.y))
            
            handLayer.frame = customView.bounds
            handLayer.path = handPath.cgPath
            handLayer.strokeColor = UIColor.black.cgColor
            
            customView.layer.addSublayer(backgroundLayer)
            customView.layer.addSublayer(handLayer)
            
            let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
            customView.addGestureRecognizer(tap)
        }
        
        @objc func tapped(_ sender: UITapGestureRecognizer) {
            let animation = CABasicAnimation(keyPath: #keyPath(CALayer.transform))
            let fromValue = self.handLayer.transform
            let toValue = CGFloat.pi * 2
            animation.duration = 2
            animation.fromValue = fromValue
            animation.toValue = toValue
            animation.valueFunction = CAValueFunction(name: .rotateZ)
            self.handLayer.add(animation, forKey: nil)
        }
    }
    
    class CustomView: UIView {
        var previousBounds: CGRect!
        override func layoutSubviews() {
            super.layoutSubviews()
            if previousBounds == nil || !previousBounds!.equalTo(self.bounds) {
                previousBounds = self.bounds
                self.updateLayers(self.bounds)
            }
        }
    
        func updateLayers(_ bounds: CGRect) {
            guard let sublayers = self.layer.sublayers else { return }
            for sublayer in sublayers {
                sublayer.frame = bounds.insetBy(dx: 5, dy: 5)
            }
        }
    }
    

    编辑

    我认为问题在于用框架调整了红色框的大小。由于即使旋转框架也始终是直立的,因此如果您要从框架中插入,它看起来像这样:

    但是,如果您要调整带边界的红色框的大小:

    sublayer.bounds = bounds.insetBy(dx: 5, dy: 5)
    sublayer.position = self.convert(self.center, from: self.superview)
    

    代替:

    sublayer.frame = bounds.insetBy(dx: 5, dy: 5)
    

    您可能必须将handPath 以及其中的其他所有内容重新居中。

    【讨论】:

    • 您好,感谢您的努力和回答。我将编辑我的问题并提供屏幕截图来演示,但我现在不能,因为我在工作。
    • 抱歉花了这么长时间 - 我在操场上尝试了你的代码,它确实有效。我将深入研究我的代码,看看我能找到什么。
    • 好吧,我已经编辑了我的问题,并对您的代码进行了少量修改,以实际演示我面临的问题。
    猜你喜欢
    • 2012-04-10
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多