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

131

Я хочу изменить 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
Кенни Винкер
источник

Ответы:

139

Раздел « Геометрия слоя и преобразования » Руководства по программированию базовой анимации объясняет взаимосвязь между положением CALayer и свойствами anchorPoint. В основном положение слоя определяется в терминах местоположения точки привязки слоя. По умолчанию точка привязки слоя равна (0,5, 0,5), которая находится в центре слоя. Когда вы устанавливаете положение слоя, вы затем устанавливаете положение центра слоя в системе координат его надслоя.

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

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

Брэд Ларсон
источник
4
См. Пример функции от Магнуса, чтобы увидеть, как ответ Брэда может быть реализован в Objective-C.
Джим Джефферс,
Спасибо , но я не понимаю , как anchorPointи positionсвязаны друг с другом?
dumbfingers 06
6
@ ss1271 - Как я описал выше, точка привязки слоя является основой его системы координат. Если он установлен на (0,5, 0,5), то, когда вы устанавливаете положение для слоя, вы устанавливаете, где его центр будет находиться в системе координат его суперслоя. Если вы установите для точки привязки значение (0,0, 0,5), установка позиции будет установлена ​​там, где будет центр ее левого края. Опять же, у Apple есть несколько хороших изображений в документации по ссылке выше (ссылка на которую я обновил).
Брэд Ларсон
Я использовал метод, предложенный Магнусом. Когда я NSLog позиция и рамка, они выглядят правильно, но мой взгляд все еще перемещается. Есть идеи почему? Как будто новая позиция не учитывается.
Alexis
В моем случае я хочу добавить анимацию на слой, используя метод videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer класса AVVideoCompositionCoreAnimationTool. Происходит следующее: половина моего слоя продолжает исчезать, пока я вращаю его вокруг оси y.
nr5
205

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

-(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
}
Магнус
источник
2
Идеально и имеет полный смысл, как только я смог увидеть здесь ваш пример. Спасибо за это!
Джим Джефферс
2
Я использую этот код в своих методах awakeFromNib / initWithFrame, но он по-прежнему не работает, нужно ли мне обновлять отображение? изменить: setNeedsDisplay не работает
Адам Картер
2
Почему вы используете view.bounds? Разве вы не должны использовать layer.bounds?
zakdances
1
в моем случае установка значения: view.layer.position ничего не меняет
AndrewK
5
FWIW, мне пришлось заключить метод setAnchor в блок dispatch_async, чтобы он работал. Не уверен, почему, поскольку я называю это внутри viewDidLoad. ViewDidLoad больше не находится в очереди основного потока?
Джон Фаулер
44

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

Swift 2

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

Свифт 3

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

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

Swift 2

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

Свифт 3

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

РЕДАКТИРОВАТЬ: это исчезает, если представление повернуто, поскольку свойство кадра не определено, если был применен CGAffineTransform.

Кенни Винкер
источник
9
Просто хотел добавить, почему это работает и кажется самым простым способом: согласно документам, свойство frame является «составным» свойством: «Значение frame выводится из свойств bounds, anchorPoint и position». Вот почему свойство «frame» не анимируется.
DarkDust
1
Честно говоря, это мой предпочтительный подход. Если вы не пытаетесь управлять границами и положением независимо или анимировать любой из них, обычно достаточно просто сделать снимок кадра перед изменением точки привязки. Нет необходимости в математике.
CIFilter 06
28

Для меня понимание 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 точек сверху от родительского слоя. По умолчанию точка привязки слоя равна (0,5, 0,5), поэтому точка расчета расстояния находится прямо в центре слоя. Следующий рисунок поможет прояснить мою точку зрения:

введите описание изображения здесь

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

2cupsOfTech
источник
1
Скажите, пожалуйста, как я могу решить эту проблему с прыжком точки привязки, для которого UIView настроен с использованием автоматической компоновки, то есть с автоматической компоновкой мы не предполагаем получать или устанавливать свойства фреймов и границ.
SJ
17

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

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)
}
Жареный рис
источник
2
Хороший маленький метод ... работает нормально, с парой мелких исправлений: Строка 3: заглавная P в anchorPoint. Строка 8: TRANS.Y = NEW - OLD
bob
2
все еще я в замешательстве, как я могу его использовать. Сейчас у меня такая же проблема с поворотом моего представления с помощью uigesturerotation. Мне странно, где я должен вызвать этот метод. Если я вызываю обработчик распознавателя жестов. Это заставляет взгляд исчезнуть.
JAck,
3
Этот ответ такой приятный, что у меня диабет.
GeneCode
14

Для тех, кому это нужно, вот решение Магнуса на 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
}
Кори Дэвис
источник
1
проголосовать за view.setTranslatesAutoresizingMaskIntoConstraints(true). спасибо человек
Оскар Юандината
1
view.setTranslatesAutoresizingMaskIntoConstraints(true)необходимо при использовании ограничений автоматической компоновки.
SamirChen 08
1
Спасибо тебе большое за это! Это translatesAutoresizingMaskIntoConstraintsкак раз то, чего мне не хватало, я уже мог узнать
Зига
5

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

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)
}
iMaddin
источник
4

Если вы измените точку привязки, ее положение также изменится, ЕСЛИ ваше начало не является нулевой точкой CGPointZero.

position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;
user3156083
источник
1
Вы не можете сравнивать позицию и точку привязки, точка привязки масштабируется от 0 до 1,0
Эдвард Энтони,
4

Редактируйте и просматривайте точку привязки 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
    }
}

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

Пользовательский класс

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

демонстрация

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

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

Марк Мойкенс
источник
Ни одной точки на UIView, я использовал Objective-C со Swift, прикрепил пользовательский класс UIView из вашего кода и включил Показать привязку ... но ни одной точки
Женевиос
1

Для 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
}
AJ9
источник
1
Спасибо за это :) Я просто вставил это в свой проект как статическую функцию и работает как шарм: D
Kjell
0

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

-(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;
}
Чарльз Робертсон
источник