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

回答:


139

コアアニメーションプログラミングガイドのレイヤージオメトリと変換セクションでは、CALayerの位置とanchorPointプロパティの関係について説明しています。基本的に、レイヤーの位置は、レイヤーのanchorPointの位置で指定されます。デフォルトでは、レイヤーのanchorPointは(0.5、0.5)で、レイヤーの中心にあります。レイヤーの位置を設定するときは、スーパーレイヤーの座標系でレイヤーの中心の位置を設定します。

位置はレイヤーのアンカーポイントを基準にしているため、同じ位置を維持しながらアンカーポイントを変更すると、レイヤーが移動します。この動きを防ぐには、新しいanchorPointを考慮してレイヤーの位置を調整する必要があります。これを行う1つの方法は、レイヤーの境界を取得し、境界の幅と高さに新旧のアンカーポイントの正規化された値を掛け、2つのアンカーポイントの差を取得し、その差をレイヤーの位置に適用することです。

CGPointApplyAffineTransform()UIViewのCGAffineTransform を使用して、この方法で回転を説明することもできます。


4
Magnusのサンプル関数を参照して、Objective-CでBradの答えをどのように実装できるかを確認してください。
ジムジェファーズ

ありがとうございましたが、anchorPointpositionがどのように関連しているかがわかりません。
dumbfingers 2013

6
@ ss1271-上で説明したように、レイヤーのanchorPointはその座標系の基礎です。(0.5、0.5)に設定されている場合、レイヤーの位置を設定すると、その中心がスーパーレイヤーの座標系のどこにあるかが設定されます。anchorPointを(0.0、0.5)に設定した場合、位置を設定すると、左端の中心がどこにあるかが設定されます。繰り返しますが、Appleは上記のリンクされたドキュメント(参照を更新しました)にいくつかの素晴らしい画像を持っています。
ブラッド・ラーソン

Magnusが提供する方法を使用しました。私がNSLogの位置とフレームを設定すると、それらは正しく見えますが、私のビューはまだ移動しています。なぜか?新しいポジションが考慮されていないようです。
Alexis

私の場合、AVVideoCompositionCoreAnimationToolクラスのvideoCompositionCoreAnimationToolWithPostProcessingAsVideoLayerメソッドを使用して、レイヤーにアニメーションを追加したいと思います。何が起きるかというと、レイヤーの半分がy軸を中心に回転している間、消え続けます。
nr5 2014年

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
私がここであなたの例を見ることができたら、完璧で完全に理にかなっています。これをありがとう!
ジムジェファーズ

2
このコードをawakeFromNib / initWithFrameメソッドで使用していますが、それでも機能しません。表示を更新する必要がありますか?編集:setNeedsDisplayが機能しません
アダムカーター

2
なぜview.boundsを使用しているのですか?layer.boundsを使用すべきではありませんか?
ザクダンス2013

1
私の場合、値をview.layer.positionに設定しても何も変更されません
AndrewK

5
FWIW、私はそれが機能するために、setAnchorメソッドをdispatch_asyncブロックにラップする必要がありました。viewDidLoad内で呼び出しているため、なぜかわかりません。viewDidLoadはメインスレッドキューに存在しなくなりましたか?
John Fowler

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が適用されている場合、フレームプロパティは定義されていないため、ビューが回転した場合、これはフレークアウトします。


9
これが機能する理由を追加したかっただけで、最も簡単な方法のようです。ドキュメントによると、frameプロパティは「composed」プロパティです:「frameの値は、bounds、anchorPoint、positionプロパティから派生しています。」これが、「フレーム」プロパティがアニメートできない理由でもあります。
DarkDust、2010年

1
これは正直なところ、私が好むアプローチです。境界と位置を個別に操作したり、どちらかをアニメーション化したりしない場合は、通常、アンカーポイントを変更する前にフレームをキャプチャするだけで十分です。数学は必要ありません。
CIFilter 2016年

28

私にとって理解positionanchorPoint、私は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 2015

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でビューを回転させると、同じ問題が発生します。このメソッドをどこで呼び出せばいいのか不思議です。ジェスチャレコグナイザハンドラを呼び出している場合。視界を消します。
JAck

3
この答えはとても甘いので、今は糖尿病にかかっています。
GeneCode 2017

14

それが必要な人のために、SwiftでのMagnusのソリューションを以下に示します。

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)。おかげで男
オスカーYuandinata 2018年

1
view.setTranslatesAutoresizingMaskIntoConstraints(true)自動レイアウト制約を使用する場合に必要です。
SamirChen

1
本当にありがとうございました!これtranslatesAutoresizingMaskIntoConstraintsは私が欠けていたものであり、今では学べるでしょう
Ziga

5

これは、OS XのNSView に対して調整されたuser945711の回答です。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までの間でスケールします
エドワード・アンソニー

4

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の上の1つのドット、私はあなたのコードからスウィフト、添付のUIViewカスタムクラスでのObjective-Cを使用していないと表示ANCHに切り替え...誰ドットなし
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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.