Установка рамки преобразованного CALayer

#swift #transform #calayer

#быстрый #трансформировать #калайер

Вопрос:

В настоящее время я немного играю с CALayer s. Для демонстрационных целей я создаю пользовательский 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 настройке I, но это не дало ожидаемых результатов — возможно, я сделал это неправильно (побочный вопрос: как повернуть заданное 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)
        }
    }
}
 

Если вы добавите это на игровую площадку, затем запустите и коснитесь элемента управления, вы поймете, что я имею в виду. Наблюдайте за красным «квадратом».

Ответ №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 и все остальное в нем.

Комментарии:

1. Привет, спасибо за ваши усилия и ваш ответ. Я отредактирую свой вопрос и предоставлю скриншоты для демонстрации, но сейчас я не могу, потому что я на работе.

2. Извините, что так долго — я попробовал ваш код на игровой площадке, и он действительно работает. Я углублюсь в свой код и посмотрю, что смогу узнать.

3. Что ж, я отредактировал свой вопрос и добавил незначительные изменения в ваш код, чтобы на самом деле продемонстрировать проблему, с которой я столкнулся.