web-dev-qa-db-ja.com

CALayerのanchorPointを変更するとビューが移動します

anchorPointを変更したいが、ビューを同じ場所に保持したい。 NSLog- ing self.layer.positionおよびself.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
126
Kenny Winker

『Core Animation Programming Guide』の Layer Geometry and Transforms セクションでは、CALayerの位置とanchorPointプロパティの関係について説明しています。基本的に、レイヤーの位置は、レイヤーのanchorPointの場所に関して指定されます。デフォルトでは、レイヤーのanchorPointは(0.5、0.5)で、レイヤーの中心にあります。レイヤーの位置を設定すると、スーパーレイヤーの座標系でレイヤーの中心の位置が設定されます。

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

UIViewのCGAffineTransformでCGPointApplyAffineTransform()を使用することで、この方法で回転を考慮することもできます。

135
Brad Larson

同じ問題がありました。 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
}
202
Magnus

これを解決するための鍵は、変わった唯一のフレームプロパティを使用することでした。

スイフト2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(1, 1)
self.frame = oldFrame

Swift

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

Swift

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

編集:CGAffineTransformが適用されている場合、フレームプロパティは未定義であるため、ビューが回転するとこれはフレークアウトします。

44
Kenny Winker

私にとって、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)であるため、距離計算ポイントはレイヤーの中心にあります。次の図は、私のポイントを明確にするのに役立ちます。

enter image description here

anchorPointは、レイヤーに変換を適用する場合に回転が発生するポイントでもあります。

27
2cupsOfTech

このような簡単な解決策があります。これは、ケニーの答えに基づいています。ただし、古いフレームを適用する代わりに、Originと新しいフレームを使用してトランジションを計算し、そのトランジションを中央に適用します。回転したビューでも機能します!コードは次のとおりです。他のソリューションよりもはるかに簡単です。

- (void) setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view {
   CGPoint oldOrigin = view.frame.Origin;
   view.layer.anchorPoint = anchorPoint;
   CGPoint newOrigin = view.frame.Origin;

   CGPoint transition;
   transition.x = newOrigin.x - oldOrigin.x;
   transition.y = newOrigin.y - oldOrigin.y;

   view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}

そしてSwiftバージョン:

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
   let oldOrigin = view.frame.Origin
   view.layer.anchorPoint = anchorPoint
   let newOrigin = view.frame.Origin

   let transition = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)

   view.center = CGPoint(x: view.center.x - transition.x, y: view.center.y - transition.y)
}
16
Fried Rice

それが必要な人のために、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
}
12
Corey Davis

ストーリーボード上で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
    }
}

ストーリーボードにビューを追加し、カスタムクラスを設定します

Custom Class

次に、UIViewの新しいアンカーポイントを設定します

Demonstration

アンカーポイントの表示をオンにすると、赤い点が表示されるので、アンカーポイントが視覚的にどこにあるのかがわかりやすくなります。後でいつでもオフにすることができます。

これは、UIViewでの変換を計画するときに本当に役立ちました。

5
Mark Moeykens

ここに ser945711's answer OS XのNSView用に調整されています。NSViewには.centerプロパティがないため、NSViewのフレームは変更されません(おそらくNSViewsにはデフォルトでCALayerが付属しないため)ただし、anchorPointが変更されると、CALayerフレームのOriginも変更されます。

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

AnchorPointを変更すると、その位置も変わります。OriginがゼロポイントCGPointZeroでない限り。

position.x == Origin.x + anchorPoint.x;
position.y == Origin.y + anchorPoint.y;
4
user3156083

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
}
2
AJ9

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;
}
1