改变我的 CALayer 的锚点移动视图

我想改变的 anchorPoint,但保持在同一个地方的观点。 我已经尝试了 NSLog-ingself.layer.positionself.center,无论锚点如何变化,它们都保持不变。但我的视野还是移动了!

有什么建议吗?

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
72698 次浏览

解决这个问题的关键是使用框架属性,这是唯一会发生变化的东西。

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

然后调整大小,从锚点开始调整。 然后,我必须恢复旧的锚点;

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,那么框架属性是未定义的。

核心动画编程指南的 图层几何与变换部分解释了 CALayer 的位置和锚点属性之间的关系。基本上,层的位置是根据层的锚点的位置来指定的。默认情况下,图层的锚点是(0.5,0.5) ,位于图层的中心。当你设置图层的位置时,你就设置了图层中心的位置在它的超级图层的坐标系中。

由于该位置相对于层的锚点,因此在保持相同位置的同时更改该锚点将移动该层。为了防止这种移动,您需要调整图层的位置以适应新的锚点。我做到这一点的一个方法是获取图层的边界,将边界的宽度和高度乘以新旧锚点的标准化值,取两个锚点的差值,然后将这个差值应用到图层的位置。

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

我也有同样的问题。即使视图是旋转的,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
}

SWIFT4.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
}

有一个简单的解决办法。这是基于肯尼的回答。但是不要使用旧的框架,而是使用它的起源和新的计算翻译,然后应用到中心的翻译。它与旋转视图也工作!下面是代码,比其他解决方案要简单得多:

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

如果你改变锚点,它的位置也会改变,除非你的原点是零点 CGPointZero

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

对我来说,理解 positionanchorPoint是最容易的,当我开始比较它与我在 UIView 中对 frame.source 的理解。具有 frame.Origin= (20,30)的 UIView 意味着 UIView 从左起20点,从其父视图顶部起30点。这个距离是从 UIView 的哪个点计算出来的?它是从 UIView 的左上角计算的。

在层 anchorPoint标记点(在归一化的形式,即0到1)从那里这个距离是计算如此,例如 layer.position = (20,30)意味着层 anchorPoint是20点从左边和30点从它的父层顶部。默认情况下,层锚点是(0.5,0.5) ,因此距离计算点正好位于层的中心。以下数字将有助于澄清我的观点:

enter image description here

如果您对图层应用转换,anchorPoint也恰好是旋转发生的点。

对于那些需要帮助的人,以下是 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
}

这是为 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)
}

对于斯威夫特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
}

扩展 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;
}

编辑并查看 UIView 在故事板上的锚点(Swift 3)

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

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

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

打开“显示锚点”将显示一个红点,这样你可以更好地看到锚点在视觉上的位置。你可以以后再关掉。

这对我规划 UIView 上的转换非常有帮助。

根据 anchorPoint 坐标(0.5,0.5) ,让 成为该层的中心。如果您熟悉 Metal/Vulkan/OpenGL/DirectX,请考虑单位坐标/或 UV 坐标。 让 N成为新的锚定位置(x,y)。 设 D为图层边界的尺寸(宽度、高度)。

然后,为了在层锚点位置改变后将层移回其原始位置,计算从 a 到 n 的偏移矢量乘以 d: V = D(N-). 然后,为了让你的图层回到原来的位置,向 layer.position 添加偏移矢量: Position + = < strong > v

Swift/iOS

let vx = (layer.anchorPoint.x - 0.5) * layer.bounds.width
let vy = (layer.anchorPoint.y - 0.5) * layer.bounds.height
layer.position.x += vx
layer.position.y += vy

如果图层是视图的背景层,那么当 layer.position 改变时,view.center 会移动(反之亦然)。如果它是一个子层. position,那么它不会移动 view.center。 因此,如果在上面的代码中,层是视图的后台层,我可以用偏移 view.center 代替 layer.position,两者都可以工作。