Зміна якоря точки мого 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. В основному положення шару задається з точки зору розташування anchorPoint шару. За замовчуванням anchorPoint шару дорівнює (0,5, 0,5), який лежить у центрі шару. Встановлюючи положення шару, ви встановлюєте розташування центру шару в системі координат його супершару.

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

Можливо, ви навіть зможете врахувати обертання таким чином, використовуючи CGPointApplyAffineTransform()CGAffineTransform вашого UIView.


4
Дивіться приклад функції від Magnus, щоб побачити, як відповідь Бреда можна реалізувати в Objective-C.
Джим Джефферс

Спасибі , але я не розумію , як anchorPointі positionпов'язані один з одним?
гантелі

6
@ ss1271 - Як я описав вище, anchorPoint шару є основою його системи координат. Якщо він встановлений на (0,5, 0,5), то, встановлюючи положення шару, ви встановлюєте, де його центр буде лежати в системі координат його супершару. Якщо ви встановите anchorPoint на (0,0, 0,5), то в положенні буде встановлено, де буде центр його лівого краю. Знову ж таки, Apple має приємні зображення у зв'язаній вище документації (на яку я оновив посилання).
Бред Ларсон

Я використовував метод, наданий Магнусом. Коли я займаю положення NSLog та кадр, вони виглядають правильними, але мій погляд все одно зміщений. Будь-яка ідея чому? Наче нова посада не враховується.
Олексій

У моєму випадку я хочу додати анімацію на шар за допомогою методу відео AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer. Що відбувається, це те, що половина мого шару зникає, коли я обертаю його навколо осі 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? Ви не повинні використовувати слой.bounds?
zakdances

1
у моєму випадку значення параметра: view.layer.position нічого не змінює
AndrewK

5
FWIW, мені довелося загорнути метод setAnchor у блок dispatch_async, щоб він працював. Не впевнений, чому я називаю це всередині viewDidLoad. Чи viewDidLoad більше не в черзі основного потоку?
Джон Фоулер

44

Ключовим для вирішення цього було використання властивості фрейму, що дивно є єдиним, що змінюється.

Швидкий 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

Потім я змінюю розмір, де він масштабується від anchorPoint. Тоді мені доведеться відновити старий anchorPoint;

Швидкий 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

EDIT: це відшаровується при повороті представлення, оскільки властивість кадру не визначена, якщо застосовано CGAffineTransform.


9
Просто хотілося додати, чому це працює, і, здається, це найпростіший спосіб: згідно з документами, властивість фрейму - це "складене" властивість: "Значення кадру виводиться з властивостей межі, anchorPoint та позиції". Ось чому властивість "frame" не анімаційна.
DarkDust

1
Це, чесно, мій переважний підхід. Якщо ви не намагаєтесь самостійно маніпулювати межами та положенням або анімувати будь-який з них, достатньо лише захоплення кадру перед тим, як змінити точку прив’язки. Не потрібно математики.
CIFilter

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 балів зверху його батьківського шару. За замовчуванням шар anchorPoint дорівнює (0,5, 0,5), тому точка обчислення відстані знаходиться в центрі шару. Наступний малюнок допоможе прояснити мою думку:

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

anchorPoint також трапляється точка, навколо якої відбудеться обертання, якщо застосувати трансформацію до шару.


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 = НОВО - СТАРИЙ
боб

2
все ще я в розгубленості, як я можу ним користуватися. Зараз я зіткнувся з тією самою проблемою, щоб повернути свій погляд з уігестюреротацією. Мені дивно, що куди мені закликати цей метод. Якщо я дзвоню в обробник розпізнавальних жестів. Це змушує погляд зникати.
JAck

3
Ця відповідь така мила, що зараз у мене діабет.
GeneCode

14

Для тих, хто цього потребує, ось рішення Magnus у 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

1
Дуже дякую за це! Я translatesAutoresizingMaskIntoConstraintsтільки те, чого я
бракував

5

Ось відповідь 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)
}

4

Якщо ви зміните anchorPoint, його позиція теж зміниться, ПІДНЯТТЯ, звідки ви походження, дорівнює нулю CGPointZero.

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

1
Ви не можете порівнювати положення та anchorPoint, шкали anchorPoint від 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

Демонстрація

Якщо ввімкнути Show Anchor Point, з’явиться червона крапка, щоб ви могли краще бачити, де буде точка зору прив’язки візуально. Ви завжди можете вимкнути це пізніше.

Це мені справді допомогло при плануванні перетворень на UIViews.


Ніхто не мав крапки на UIView, я використовував Objective-C зі Swift, додав користувальницький клас UIView зі свого коду та увімкнув Показати прив’язку ... але ніхто не крапку
Genevios

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
}

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.