【问题标题】:Changing my CALayer's anchorPoint moves the view更改我的 CALayer 的锚点会移动视图
【发布时间】:2010-12-30 09:28:15
【问题描述】:

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

【问题讨论】:

    标签: iphone cocoa cocoa-touch uiview


    【解决方案1】:

    我遇到了同样的问题。即使视图旋转,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
    }
    

    【讨论】:

    • 完美且完全有意义,一旦我能够在此处看到您的示例。谢谢!
    • 我在我的 awakeFromNib/initWithFrame 方法中使用了这段代码,但它仍然不起作用,我需要更新显示吗?编辑:setNeedsDisplay 不起作用
    • 为什么要使用 view.bounds?你不应该使用 layer.bounds 吗?
    • 在我的情况下将值设置为:view.layer.position 不会改变任何东西
    • FWIW,我必须将 setAnchor 方法包​​装在 dispatch_async 块中才能使其工作。不知道为什么我在 viewDidLoad 中调用它。 viewDidLoad 不再在主线程队列上了吗?
    【解决方案2】:

    核心动画编程指南的Layer Geometry and Transforms 部分解释了 CALayer 的位置和锚点属性之间的关系。基本上,层的位置是根据层的锚点的位置来指定的。默认情况下,图层的锚点为 (0.5, 0.5),位于图层的中心。当您设置图层的位置时,您就是在设置图层中心在其超图层坐标系中的位置。

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

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

    【讨论】:

    • 查看 Magnus 的示例函数,了解如何在 Objective-C 中实现 Brad 的答案。
    • 谢谢,但我不明白anchorPointposition 是如何关联在一起的?
    • @ss1271 - 正如我上面所描述的,图层的锚点是其坐标系的基础。如果将其设置为 (0.5, 0.5),那么当您设置图层的位置时,您将设置其中心在其超图层坐标系中的位置。如果将 anchorPoint 设置为 (0.0, 0.5),则设置位置将设置其左边缘的中心位置。同样,Apple 在上面链接的文档中有一些不错的图片(我已经更新了参考)。
    • 我使用了Magnus提供的方法。当我 NSLog 位置和框架时,它们看起来正确,但我的视图仍然移动。知道为什么吗?好像没有考虑新的位置。
    • 就我而言,我想使用 AVVideoCompositionCoreAnimationTool 类的 videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer 方法在图层上添加动画。发生的情况是,当我围绕 y 轴旋转时,我的一半图层不断消失。
    【解决方案3】:

    解决这个问题的关键是使用 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;

    斯威夫特 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 属性是未定义的。

    【讨论】:

    • 只是想补充一下为什么这样有效并且似乎是最简单的方法:根据文档,框架属性是一个“组合”属性:“框架的值来自bounds、anchorPoint 和 position 属性。” 这也是为什么“frame”属性不是动画的原因。
    • 老实说,这是我的首选方法。如果您不想单独操作边界和位置或为其中任何一个设置动画,那么在更改锚点之前捕获帧通常就足够了。不需要数学。
    【解决方案4】:

    当我开始将 positionanchorPoint 与我对 UIView 中 frame.origin 的理解进行比较时,我最容易理解它。带有 frame.origin = (20,30) 的 UIView 意味着 UIView 距其父视图的左侧 20 点和顶部的 30 点。这个距离是从 UIView 的哪个点计算的?它从 UIView 的左上角计算。

    anchorPoint 层中标记了计算此距离的点(以标准化形式,即 0 到 1),例如layer.position = (20, 30) 表示层anchorPoint 距离其父层左侧20 个点,距离其父层顶部30 个点。默认情况下,层锚点为 (0.5, 0.5),因此距离计算点正好在层的中心。下图将有助于澄清我的观点:

    anchorPoint 也恰好是在您对图层应用变换时发生旋转的点。

    【讨论】:

    • 请你告诉我如何解决这个锚点跳跃的问题,谁的 UIView 是使用自动布局设置的,这意味着使用自动布局我们不应该获取或设置框架和边界属性。
    【解决方案5】:

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

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

    【讨论】:

    • 不错的小方法...通过几个小修复可以正常工作: 第 3 行:anchorPoint 中的大写字母 P。第 8 行:TRANS.Y = 新 - 旧
    • 我仍然很困惑如何使用它。我现在正面临同样的问题,因为用 uigesturerotation 旋转我的视图。我很奇怪我应该在哪里调用这个方法。如果我正在调用手势识别器处理程序。它使视图消失。
    • 这个答案太好了,我现在得了糖尿病。
    【解决方案6】:

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

    【讨论】:

    • view.setTranslatesAutoresizingMaskIntoConstraints(true)点赞。谢谢人
    • view.setTranslatesAutoresizingMaskIntoConstraints(true) 在使用自动布局约束时是必需的。
    • 非常感谢您! translatesAutoresizingMaskIntoConstraints 正是我所缺少的,现在可能已经学会了
    【解决方案7】:

    这是为 OS X 上的 NSView 调整的user945711's answer。除了 NSView 没有.center 属性外,NSView 的框架没有改变(可能是因为 NSViews 默认不带有 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)
    }
    

    【讨论】:

      【解决方案8】:

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

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

      【讨论】:

      • 不能比较位置和anchorPoint,anchorPoint的范围是0到1.0
      【解决方案9】:

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

      将视图添加到情节提要并设置自定义类

      现在为 UIView 设置新的锚点

      打开显示锚点将显示一个红点,以便您更好地查看锚点的视觉位置。您以后可以随时将其关闭。

      这对我在 UIViews 上进行转换计划很有帮助。

      【讨论】:

      • UIView 上没有一个点,我在 Swift 中使用了 Objective-C,从您的代码中附加了 UIView 自定义类并打开了 Show anch...但是没有一个点
      【解决方案10】:

      对于 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
      }
      

      【讨论】:

      • 谢谢你 :) 我只是把它作为一个静态函数粘贴到我的项目中,就像一个魅力:D
      【解决方案11】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-16
        • 2018-08-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多