it-swarm.com.ru

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

Я хочу изменить anchorPoint, но сохранить представление в том же месте . Я пробовал NSLoging 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
124
Kenny Winker

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

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

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

133
Brad Larson

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

-(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;
}

И эквивалент Swift:

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
}
195
Magnus

Ключом к решению этой проблемы было использование свойства 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.

43
Kenny Winker

Для меня понимание 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 также является точкой, вокруг которой произойдет вращение, если вы примените преобразование к слою.

26
2cupsOfTech

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

- (void) setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view {
   CGPoint oldOrigin = view.frame.Origin;
   view.layer.anchorPoint = anchorPoint;
   CGPoint newOrigin = view.frame.Origin;

   CGPoint transition;
   transition.x = newOrigin.x - oldOrigin.x;
   transition.y = newOrigin.y - oldOrigin.y;

   view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}

И версия Swift:

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

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

   view.center = CGPoint(x: view.center.x - transition.x, y: view.center.y - transition.y)
}
15
Fried Rice

Для тех, кто в этом нуждается, вот решение Магнуса в 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
}
9
Corey Davis

Отредактируйте и увидите опорную точку 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.

5
Mark Moeykens

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

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)
}
5
iMaddin

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

position.x == Origin.x + anchorPoint.x;
position.y == Origin.y + anchorPoint.y;
4
user3156083

Для 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
}
2
AJ9

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

-(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;
}
0
Charles Robertson