Изменение моего привязки CALayer перемещает вид

Я хочу изменить anchorPoint, но сохраните представление в том же месте. Я пробовал NSLog -ing self.layer.position и self.center, и оба они остаются неизменными независимо от изменений в anchorPoint. Но мое представление движется!

Любые советы о том, как это сделать?

self.layer.anchorPoint = CGPointMake(0.5, 0.5);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);
self.layer.anchorPoint = CGPointMake(1, 1);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);

Вывод:

2009-12-27 20:43:24.161 Type[11289:207] center point: 272.500000 242.500000
2009-12-27 20:43:24.162 Type[11289:207] center point: 272.500000 242.500000

Ответ 1

Раздел Layer Geometry and Transforms в Руководстве по программированию основной анимации объясняет взаимосвязь между позицией CALayer и свойствами anchorPoint. В принципе, положение слоя определяется с точки зрения местоположения слоя anchorPoint. По умолчанию уровень anchorPoint слоя (0.5, 0.5), который лежит в центре слоя. Когда вы устанавливаете положение слоя, вы затем устанавливаете расположение центра слоя в своей системе координат суперслоя.

Поскольку позиция относительно привязанного уровня слоя, изменение этого anchorPoint при сохранении одной и той же позиции перемещает слой. Чтобы предотвратить это движение, вам нужно будет отрегулировать позицию слоя для учета нового anchorPoint. Один из способов, которым я это сделал, - захватить границы слоев, умножить ширину и высоту границ на старые и новые нормализованные значения anchorPoint, различить два якорных точки и применить эту разницу к положению слоя.

Возможно, вы даже сможете учитывать ротацию таким образом, используя CGPointApplyAffineTransform() с вашим UIView CGAffineTransform.

Ответ 2

У меня такая же проблема. Решение Брэда Ларсона отлично работало даже при повороте вида. Вот его решение, переведенное в код.

-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view
{
    CGPoint newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, 
                                   view.bounds.size.height * anchorPoint.y);
    CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, 
                                   view.bounds.size.height * view.layer.anchorPoint.y);

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);

    CGPoint position = view.layer.position;

    position.x -= oldPoint.x;
    position.x += newPoint.x;

    position.y -= oldPoint.y;
    position.y += newPoint.y;

    view.layer.position = position;
    view.layer.anchorPoint = anchorPoint;
}

И быстрый эквивалент:

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

SWIFT 4.x

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x,
                           y: view.bounds.size.height * anchorPoint.y)


    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x,
                           y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

Ответ 3

Ключом к решению этой проблемы было использование свойства frame, которое, как ни странно, единственное, что меняется.

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(1, 1)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 1, y: 1)
self.frame = oldFrame

Затем я делаю мой размер, где он масштабируется от anchorPoint. Затем я должен восстановить старую anchorPoint;

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(0.5,0.5)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.frame = oldFrame

ОБНОВЛЕНИЕ: это исчезает, если представление поворачивается, поскольку свойство кадра не определено, если применено преобразование CGAffineTransform.

Ответ 4

Для меня понимание position и anchorPoint было проще всего, когда я начал сравнивать его с моим пониманием frame.origin в UIView. UIView с frame.origin = (20,30) означает, что UIView составляет 20 точек слева и 30 точек от верхней части его родительского вида. Это расстояние рассчитывается из какой точки UIView? Его вычисляли из верхнего левого угла UIView.

В слое anchorPoint отмечается точка (в нормализованной форме, т.е. от 0 до 1), откуда это расстояние вычисляется так, например, layer.position = (20, 30) означает, что слой anchorPoint составляет 20 точек слева и 30 точек от вершины его родительского слоя. По умолчанию слой anchorPoint равен (0,5, 0,5), поэтому точка вычисления расстояния находится прямо в центре слоя. Следующий рисунок поможет прояснить мою мысль:

enter image description here

anchorPoint также оказывается точкой, вокруг которой произойдет поворот, если вы примените преобразование к слою.

Ответ 5

Есть такое простое решение. Это основано на ответе Кенни. Но вместо применения старого фрейма используйте его origin и новый для вычисления перевода, а затем примените этот перевод к центру. Работает с повернутым видом тоже! Вот код, намного проще, чем другие решения:

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
   let oldOrigin = view.frame.origin
   view.layer.anchorPoint = anchorPoint
   let newOrigin = view.frame.origin

   let translation = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)

   view.center = CGPoint(x: view.center.x - translation.x, y: view.center.y - translation.y)
}

Ответ 6

Для тех, кому это нужно, вот решение Магнуса в Swift:

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
    var newPoint: CGPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
    var oldPoint: CGPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)

    var position: CGPoint = view.layer.position

    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.setTranslatesAutoresizingMaskIntoConstraints(true)     // Added to deal with auto layout constraints
    view.layer.anchorPoint = anchorPoint
    view.layer.position = position
}

Ответ 7

Вот user945711 ответ, настроенный для NSView на OS X. Помимо NSView, не имеющего свойства .center, кадр NSView не изменяется (вероятно, потому что NSViews не поставляются с CALayer по умолчанию), но кадр CALayer Происхождение изменяется при изменении anchorPoint.

func setAnchorPoint(anchorPoint: NSPoint, view: NSView) {
    guard let layer = view.layer else { return }

    let oldOrigin = layer.frame.origin
    layer.anchorPoint = anchorPoint
    let newOrigin = layer.frame.origin

    let transition = NSMakePoint(newOrigin.x - oldOrigin.x, newOrigin.y - oldOrigin.y)
    layer.frame.origin = NSMakePoint(layer.frame.origin.x - transition.x, layer.frame.origin.y - transition.y)
}

Ответ 8

Отредактируйте и увидите опорную точку UIView прямо на раскадровке (Swift 3)

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

Создать новый файл для включения в ваш проект

import UIKit

@IBDesignable
class UIViewAnchorPoint: UIView {

    @IBInspectable var showAnchorPoint: Bool = false
    @IBInspectable var anchorPoint: CGPoint = CGPoint(x: 0.5, y: 0.5) {
        didSet {
            setAnchorPoint(anchorPoint: anchorPoint)
        }
    }

    override func draw(_ rect: CGRect) {
        if showAnchorPoint {
            let anchorPointlayer = CALayer()
            anchorPointlayer.backgroundColor = UIColor.red.cgColor
            anchorPointlayer.bounds = CGRect(x: 0, y: 0, width: 6, height: 6)
            anchorPointlayer.cornerRadius = 3

            let anchor = layer.anchorPoint
            let size = layer.bounds.size

            anchorPointlayer.position = CGPoint(x: anchor.x * size.width, y: anchor.y * size.height)
            layer.addSublayer(anchorPointlayer)
        }
    }

    func setAnchorPoint(anchorPoint: CGPoint) {
        var newPoint = CGPoint(x: bounds.size.width * anchorPoint.x, y: bounds.size.height * anchorPoint.y)
        var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y)

        newPoint = newPoint.applying(transform)
        oldPoint = oldPoint.applying(transform)

        var position = layer.position
        position.x -= oldPoint.x
        position.x += newPoint.x

        position.y -= oldPoint.y
        position.y += newPoint.y

        layer.position = position
        layer.anchorPoint = anchorPoint
    }
}

Добавить представление в раскадровку и установить пользовательский класс

Custom Class

Теперь установите новую опорную точку для UIView

Demonstration

Включение "Показать опорную точку" покажет красную точку, чтобы вы могли лучше видеть, где опорная точка будет визуально. Вы всегда можете выключить его позже.

Это действительно помогло мне при планировании преобразований в UIViews.

Ответ 9

Если вы измените anchorPoint, его позиция тоже изменится, ЕСЛИ вы не являетесь нулевой точкой CGPointZero.

position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;

Ответ 10

Для Swift 3:

func setAnchorPoint(_ anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, y: view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

Ответ 11

Развернувшись на большом и тщательном ответе Магнуса, я создал версию, которая работает на подслоях:

-(void)setAnchorPoint:(CGPoint)anchorPoint forLayer:(CALayer *)layer
{
    CGPoint newPoint = CGPointMake(layer.bounds.size.width * anchorPoint.x, layer.bounds.size.height * anchorPoint.y);
    CGPoint oldPoint = CGPointMake(layer.bounds.size.width * layer.anchorPoint.x, layer.bounds.size.height * layer.anchorPoint.y);
    CGPoint position = layer.position;
    position.x -= oldPoint.x;
    position.x += newPoint.x;
    position.y -= oldPoint.y;
    position.y += newPoint.y;
    layer.position = position;
    layer.anchorPoint = anchorPoint;
}