改变我的CALayer的anchorPoint会移动视图。

136

我想改变anchorPoint,但保持视图在同一位置。 我尝试过使用NSLog记录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
13个回答

211

我有相同的问题。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
}

2
我在我的awakeFromNib/initWithFrame方法中使用此代码,但仍然不起作用,我需要更新显示吗?编辑:setNeedsDisplay无效。 - Adam Carter
2
你为什么使用view.bounds?难道不应该使用layer.bounds吗? - zakdances
1
在我的情况下,将值设置为:view.layer.position 不会改变任何东西。 - AndrewK
5
需要翻译的内容:FWIW, I had to wrap the setAnchor method in a dispatch_async block for it to work. Not sure why as I'm calling it inside viewDidLoad. Is viewDidLoad no longer on the main thread queue?翻译后的结果:就我所知,我必须将setAnchor方法包装在dispatch_async块中才能使其正常工作。不确定为什么,因为我是在viewDidLoad内调用它的。viewDidLoad是否不再位于主线程队列上? - John Fowler
1
@Magnus,你是个天才。这个方案完美地运行了100%。我唯一改变的是UIView参数为CALayer参数,并修改了所有引用。这使得你的解决方案可以适用于子层。我还必须删除CGPointApplyAffineTransform行。我已经点赞了,为什么这还没有被接受为官方答案?而且你提供了Swift/Objective C的解决方案。太棒了。谢谢... - Charles Robertson
显示剩余4条评论

143
Core Animation编程指南的图层几何和变换部分解释了CALayer的position和anchorPoint属性之间的关系。基本上,一个图层的位置是以其anchorPoint的位置为基础来指定的。默认情况下,一个图层的anchorPoint是(0.5, 0.5),位于图层的中心。当您设置图层的位置时,您实际上是在设置该图层在其父图层坐标系统中的中心位置。
由于位置是相对于图层的anchorPoint而言的,因此改变anchorPoint的同时保持相同的位置会移动图层。为了防止这种移动,您需要调整图层的位置以适应新的anchorPoint。我做过的一种方法是获取图层的边界,将边界的宽度和高度乘以旧的和新的anchorPoint的归一化值,取两个anchorPoints的差,并将该差应用到图层的位置上。

通过使用UIView的CGAffineTransform和CGPointApplyAffineTransform(),您甚至可以计算旋转。


4
请看Magnus提供的示例函数,了解Brad的答案如何在Objective-C中实现。 - Jim Jeffers
谢谢,但我不明白anchorPointposition之间的关系是什么? - dumbfingers
7
如我所述,图层的锚点是其坐标系的基础。如果将其设置为(0.5, 0.5),那么当你为一个图层设置位置时,你就是在设置它在其父图层坐标系中心的位置。如果将锚点设置为(0.0, 0.5),那么设置位置将决定其左边缘中心的位置。苹果在上面链接的文档中提供了一些不错的图片(我已经更新了参考资料)。 - Brad Larson
在我的情况下,我想使用AVVideoCompositionCoreAnimationTool类的videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer方法在图层上添加动画。问题是,当我围绕y轴旋转图层时,有一半的图层会不断消失。 - nr5
@BradLarson,请问您能告诉我如何解决使用自动布局设置的UIView中锚点跳动的问题吗?这意味着我们不应该获取或设置框架和边界属性。 - S.J
显示剩余4条评论

47
解决这个问题的关键是使用frame属性,奇怪的是只有它会发生变化。 Swift 2
let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(1, 1)
self.frame = oldFrame

Swift 3

->

Swift 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

Swift 3

Swift 3

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

编辑:如果视图被旋转,这会失败,因为如果已应用CGAffineTransform,则框架属性未定义。


10
只是想补充一下为什么这个方法可行,看起来也是最简单的:根据文档,frame属性是一个“组合”属性:“frame的值是由bounds、anchorPoint和position属性派生而来的。” 这也是为什么“frame”属性不能进行动画处理的原因。 - DarkDust
2
这实际上是我的首选方法。如果你不想独立操纵边界和位置,也不需要对它们进行动画处理,那么在改变锚点之前捕获帧通常就足够了。不需要数学计算。 - CIFilter

28
对于我来说理解 positionanchorPoint 是最容易的,当我开始将它与我对 UIView 中的 frame.origin 的理解进行比较时。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 还是应用变换旋转的点。

1
请问如何解决使用自动布局设置了UIView的锚点跳动问题?因为使用自动布局时,我们不应该获取或设置frame和bounds属性。 - S.J

18

有一个非常简单的解决方案。这是基于Kenny的答案。但是,不要应用旧框架,而是使用它的原点和新框架来计算平移,然后将该平移应用于中心。它也适用于旋转视图!以下是代码,比其他解决方案简单得多:

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旋转我的视图。我很奇怪应该在哪里调用这个方法。如果我在手势识别处理程序中调用它,它会使视图消失。 - JAck
3
这个回答太甜蜜了,现在我得了糖尿病。 - GeneCode

16

对于需要的人,这里是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
}

2
点赞支持 view.setTranslatesAutoresizingMaskIntoConstraints(true)。谢谢! - Oscar Yuandinata
2
当使用自动布局约束时,需要使用view.setTranslatesAutoresizingMaskIntoConstraints(true) - SamirChen
2
非常感谢这个! translatesAutoresizingMaskIntoConstraints 正是我缺少的,本应该学会的。 - Ziga

6
这里是针对OS X上的NSView进行调整的user945711的答案。除了NSView没有.center属性外,NSView的框架不会改变(可能是因为默认情况下NSView没有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)
}

4
如果您更改了锚点,它的位置也会随之改变,除非您的原点是零点 CGPointZero
position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;

1
你不能比较position和anchorPoint,anchorPoint的缩放范围在0到1.0之间。 - Edward Anthony

4

在Storyboard中编辑和查看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
    }
}

将视图添加到Storyboard并设置自定义类

Custom Class

现在为UIView设置新的锚点

Demonstration

打开“显示锚点”选项可以显示一个红点,以便您更好地看到锚点的位置。稍后您始终可以关闭它。

这对我在计划UIView上的变换时非常有帮助。


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接