更改CALayer的anchorPoint可移动视图


131

我想更改anchorPoint,但将视图保留在同一位置。我已经尝试过NSLog-ing self.layer.positionself.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

Answers:


139

《核心动画编程指南》的“ 层几何和变换”部分介绍了CALayer的位置和anchorPoint属性之间的关系。基本上,图层的位置是根据图层的anchorPoint的位置指定的。默认情况下,图层的anchorpoint为(0.5,0.5),位于图层的中心。设置图层的位置时,然后要在其上层的坐标系中设置图层中心的位置。

由于位置相对于图层的anchorPoint,因此在保持相同位置的同时更改该anchorPoint将移动图层。为了防止这种移动,您需要调整图层的位置以考虑新的anchorPoint。我执行此操作的一种方法是,获取图层的边界,将边界的宽度和高度乘以新旧的anchorPoint的归一化值,取两个anchorPoints的差,并将该差应用于图层的位置。

您甚至可以通过CGPointApplyAffineTransform()与UIView的CGAffineTransform一起使用来解决这种轮换问题。


4
请参阅Magnus的示例函数,以了解如何在Objective-C中实现Brad的答案。
Jim Jeffers

谢谢,但我不了解anchorPoint和和position的关系如何?
dumbfingers

6
@ ss1271-如上所述,图层的anchorPoint是其坐标系的基础。如果将其设置为(0.5,0.5),则在设置图层的位置时,将设置其中心位于其上层坐标系中的位置。如果将anchorPoint设置为(0.0,0.5),则设置位置将设置其左边缘的中心。同样,Apple在上述链接的文档中提供了一些不错的图像(我已经更新了对其的引用)。
布拉德·拉尔森

我使用了Magnus提供的方法。当我NSLog位置和框架时,它们看起来正确,但是我的视图仍然移动。知道为什么吗?好像没有考虑到新职位。
亚历克西斯(Alexis)2014年

就我而言,我想使用AVVideoCompositionCoreAnimationTool类的videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer方法在图层上添加动画。发生的事情是,当我绕y轴旋转时,我的图层的一半会消失。
nr5

205

我有同样的问题。即使旋转视图,布拉德·拉尔森(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;
}

和快速等效:

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
能够在这里看到您的示例后,就可以说是十全十美。谢谢你!
Jim Jeffers

2
我在awakeFromNib / initWithFrame方法中使用了此代码,但仍然无法使用,是否需要更新显示?编辑:setNeedsDisplay不起作用
亚当·卡特

2
为什么要使用view.bounds?您不应该使用layer.bounds吗?
zakdances

1
在我的情况下,将值设置为:view.layer.position不会更改任何内容
AndrewK

5
FWIW,我必须将setAnchor方法包装在dispatch_async块中才能起作用。不知道为什么在viewDidLoad中调用它。viewDidLoad不再在主线程队列上吗?
约翰·福勒

44

解决此问题的关键是使用frame属性,这是唯一奇怪的更改。

迅捷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

编辑:如果旋转视图,此效果会消失,因为如果已应用CGAffineTransform,则frame属性未定义。


9
只是想补充一下它为什么可行并且似乎是最简单的方法:根据文档,frame属性是“ composited”属性:“ frame的值是从bounds,anchorPoint和position属性派生的。” 这也是为什么无法设置“框架”属性的原因。
DarkDust

1
老实说,这是我的首选方法。如果您不想尝试独立地操作边界和位置或为其中任何一个设置动画,那么通常只需在更改锚点之前捕获帧即可。无需数学。
CIFilter

28

对我来说position,这anchorPoint是最容易的,当我将其与我对UIView中frame.origin的理解进行比较时,这是最简单的。一个带有frame.origin =(20,30)的UIView意味着该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行:anchorPoint中的大写字母P。8号线:TRANS.Y = NEW-OLD
bob

2
我仍然很困惑我该如何使用它。我现在通过uigesturerotation旋转视图也面临着同样的问题。我很奇怪应该在哪里调用该方法。如果我正在调用手势识别器处理程序。它使视图消失。
2016年

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)。谢谢男人
Oscar Yuandinata 18-3-22

1
view.setTranslatesAutoresizingMaskIntoConstraints(true)当使用自动布局约束时,此参数是必需的。
SamirChen

1
非常感谢你做的这些!这translatesAutoresizingMaskIntoConstraints正是我现在所缺少的,现在可以知道
Ziga

5

这是user945711的答案,已针对OS X上的NSView进行了调整。除了NSView没有.center属性外,NSView的框架没有更改(可能是因为NSViews默认不带有CALayer),但是当更改了anchorPoint时,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)
}

4

如果更改anchorPoint,其位置也会改变,除非您的原点为零点CGPointZero

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

1
您无法比较位置和anchorPoint,anchorPoint的范围介于0到1.0之间
Edward Anthony

4

在情节提要板上编辑并查看UIView的锚点(快速3)

这是一种替代解决方案,它允许您通过Attributes Inspector更改锚点,并具有另一个属性来查看锚点以进行确认。

创建新文件以包含在您的项目中

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上没有一个点,我使用了带有Swift的Objective-C,从您的代码中附加了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

扩展了Magnus的详尽解答,我创建了一个可用于子层的版本:

-(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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.