CALayer'ımın çapa noktasını değiştirmek görünümü hareket ettiriyor


131

anchorPointGörünümü değiştirmek ama aynı yerde tutmak istiyorum . Ben denedim NSLog-ing self.layer.positionve self.centerve her ikisi de kalmak bakılmaksızın anchorPoint değişikliklerin aynı. Yine de görüşüm hareket ediyor!

Bunun nasıl yapılacağına dair herhangi bir ipucu var mı?

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

Çıktı:

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

Yanıtlar:


139

Katman Geometri ve Dönüşümler Çekirdek Animasyon Programlama Kılavuz bölümü CALayer konumu ve anchorPoint özellikleri arasındaki ilişkiyi açıklar. Temel olarak, bir katmanın konumu, katmanın bağlantı noktasının konumuna göre belirlenir. Varsayılan olarak, katmanın bağlantı noktası, katmanın merkezinde yer alan (0.5, 0.5) şeklindedir. Katmanın konumunu belirlediğinizde, katmanın merkezinin konumunu üst katmanının koordinat sisteminde ayarlamış olursunuz.

Konum, katmanın bağlantı noktasına göreceli olduğundan, aynı konumu korurken bu bağlantı noktasını değiştirmek katmanı hareket ettirir. Bu hareketi önlemek için katmanın konumunu yeni anchorPoint'i hesaba katacak şekilde ayarlamanız gerekir. Bunu yapmamın bir yolu, katmanın sınırlarını yakalamak, sınırların genişliğini ve yüksekliğini eski ve yeni anchorPoint'in normalleştirilmiş değerleriyle çarpmak, iki anchorPoint'in farkını almak ve bu farkı katmanın konumuna uygulamaktır.

CGPointApplyAffineTransform()UIView cihazınızın CGAffineTransform'unu kullanarak bu şekilde rotasyonu hesaba katabilirsiniz.


4
Brad'in yanıtının Objective-C'de nasıl uygulanabileceğini görmek için Magnus'un örnek işlevine bakın.
Jim Jeffers

Teşekkür ama anlamıyorum nasıl anchorPointve positionbirlikte ilişkilidir?
dumbfingers

6
@ ss1271 - Yukarıda açıkladığım gibi, bir katmanın anchorPoint koordinat sisteminin temelidir. (0.5, 0.5) olarak ayarlanmışsa, bir katman için konumu belirlediğinizde, süper katmanının koordinat sisteminde merkezinin nerede olacağını ayarlamış olursunuz. Bağlantı noktasını (0,0, 0,5) olarak ayarlarsanız, konumun ayarlanması sol kenarının merkezinin nerede olacağını belirler. Yine, Apple'ın yukarıdaki bağlantılı belgelerde (referansı güncellediğim) bazı güzel görüntüleri var.
Brad Larson

Magnus tarafından sağlanan yöntemi kullandım. NSLog konumunu ve çerçevesini konumlandırdığımda doğru görünüyorlar ama görüşüm hala hareket ediyor. Herhangi bir fikrin neden? Sanki yeni pozisyon hesaba katılmıyor.
Alexis

Benim durumumda, katmana AVVideoCompositionCoreAnimationTool sınıfının videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer yöntemini kullanarak bir animasyon eklemek istiyorum. Olan şu ki, ben onu y ekseni etrafında döndürürken katmanımın yarısı kaybolmaya devam ediyor.
nr5

205

Ben de aynı sorunu yaşadım. Brad Larson'ın çözümü, görünüm döndürüldüğünde bile harika çalıştı. İşte çözümü koda çevrilmiş.

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

Ve hızlı eşdeğeri:

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
Mükemmel ve burada örneğinizi gördükten sonra tamamen mantıklı geliyor. Bunun için teşekkürler!
Jim Jeffers

2
Bu kodu awakeFromNib / initWithFrame yöntemlerimde kullanıyorum, ancak yine de çalışmıyor, ekranı güncellemem gerekiyor mu? edit: setNeedsDisplay çalışmıyor
Adam Carter

2
View.bounds'u neden kullanıyorsunuz? Layer.bounds kullanmanız gerekmiyor mu?
zakdances

1
benim durumumda değeri şu şekilde ayarlamak: view.layer.position hiçbir şeyi değiştirmez
AndrewK

5
FWIW, çalışması için setAnchor yöntemini dispatch_async bloğuna sarmak zorunda kaldım. ViewDidLoad içinde neden çağırdığımdan emin değilim. ViewDidLoad artık ana iş parçacığı kuyruğunda değil mi?
John Fowler

44

Bunu çözmenin anahtarı, garip bir şekilde değişen tek şey olan frame özelliğini kullanmaktı.

Swift 2

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

Hızlı 3

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

Sonra yeniden boyutlandırmamı anchorPoint'ten ölçeklendirdiği yerde yaparım. O halde eski anchorPoint'i geri yüklemem gerekiyor;

Swift 2

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

Hızlı 3

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

DÜZENLEME: Bir CGAffineTransform uygulanmışsa çerçeve özelliği tanımsız olduğundan, görünüm döndürülürse bu durum ayrışır.


9
Bunun neden işe yaradığını ve en kolay yol gibi göründüğünü eklemek istedim: belgelere göre, çerçeve özelliği " birleşik " bir özelliktir: "Çerçevenin değeri sınırlardan, anchorPoint'ten ve konum özelliklerinden türetilir." Bu yüzden "çerçeve" özelliği canlandırılamaz.
DarkDust

1
Dürüst olmak gerekirse bu benim tercih ettiğim yaklaşım. Sınırları ve konumu bağımsız olarak değiştirmeye veya bunlardan herhangi birine animasyon uygulamaya çalışmıyorsanız, bağlantı noktasını değiştirmeden önce sadece kareyi yakalamak genellikle yeterlidir. Matematiğe gerek yok.
CIFilter

28

Benim için anlamak positionve anchorPointonu UIView'deki frame.origin anlayışımla karşılaştırmaya başladığımda en kolayı oldu. Frame.origin = (20,30) olan bir UIView, UIView'ın üst görünümün solundan 20 nokta ve üstünden 30 nokta olduğu anlamına gelir. Bu mesafe bir UIView'un hangi noktasından hesaplanır? Bir UIView'ın sol üst köşesinden hesaplanır.

Katman anchorPointişaretlerinde, bu mesafenin hesaplandığı noktadan (normalleştirilmiş biçimde, yani 0'dan 1'e), örneğin katman.pozisyonu = (20, 30), katmanın anchorPointüst katmanının solundan 20 nokta ve üst katmanından 30 nokta olduğu anlamına gelir . Varsayılan olarak bir katman bağlantı noktası (0,5, 0,5) 'dir, bu nedenle mesafe hesaplama noktası katmanın tam ortasındadır. Aşağıdaki şekil, amacımı netleştirmeye yardımcı olacaktır:

görüntü açıklamasını buraya girin

anchorPoint ayrıca katmana bir dönüşüm uygulamanız durumunda dönüşün gerçekleşeceği nokta da olur.


1
Lütfen bu bağlantı noktası atlamasını nasıl çözebilirim? Kimin UIView otomatik mizanpaj kullanılarak ayarlandığını, yani otomatik mizanpajda çerçeve ve sınır özelliklerini almamız veya ayarlamamız gerekmediği anlamına gelir.
SJ

17

Çok basit bir çözüm var. Bu Kenny'nin cevabına dayanıyor. Ancak eski çerçeveyi uygulamak yerine, çeviriyi hesaplamak için orijini ve yenisini kullanın, ardından bu çeviriyi merkeze uygulayın. Döndürülmüş görünümde de çalışır! İşte kod, diğer çözümlerden çok daha basit:

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
Güzel küçük yöntem ... birkaç küçük düzeltmeyle sorunsuz çalışır: Satır 3: anchorPoint'teki büyük P. Satır 8: TRANS.Y = NEW - ESKİ
bob

2
hala nasıl kullanabilirim kafam karışık. Şu anda görüşümü uigesturerotation ile döndürmek için aynı problemle karşı karşıyayım. Bu yöntemi nereye çağırmalıyım diye tuhafım. Hareket tanıyıcı işleyicide arıyorum. Görünümü ortadan kaldırır.
JAck

3
Bu cevap çok tatlı, artık şeker hastasıyım.
GeneCode

14

İhtiyaç duyanlar için, işte Magnus'un Swift'deki çözümü:

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
için oy verin view.setTranslatesAutoresizingMaskIntoConstraints(true). teşekkürler dostum
Oscar Yuandinata

1
view.setTranslatesAutoresizingMaskIntoConstraints(true)otomatik düzen kısıtlamaları kullanıldığında gereklidir.
SamirChen

1
Bunun için çok teşekkür ederim! Şu translatesAutoresizingMaskIntoConstraintsana kadar kaçırdığım şey buydu
Ziga

5

Burada user945711'in cevabı OS X'te NSView için ayarlanmıştır. NSView'ın bir .centerözelliğe sahip olmamasının yanı sıra , NSView çerçevesi değişmez (muhtemelen NSViews varsayılan olarak bir CALayer ile gelmediğinden), ancak anchorPoint değiştirildiğinde CALayer çerçeve kaynağı değişir.

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

Eğer anchorPoint'i değiştirirseniz, başlangıç ​​noktanız sıfır noktası OLMADIĞINDA konumu da değişecektir CGPointZero.

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

1
Konum ile anchorPoint'i karşılaştıramazsınız, anchorPoint 0 ile 1.0 arasında ölçeklenir
Edward Anthony

4

UIView'ın Bağlantı Noktasını Storyboard'da Düzenleyin ve Görün (Swift 3)

Bu, bağlantı noktasını Nitelikler Denetçisi aracılığıyla değiştirmenize izin veren ve onay için bağlantı noktasını görüntülemek için başka bir özelliğe sahip olan alternatif bir çözümdür.

Projenize dahil etmek için yeni dosya oluşturun

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'a Görünüm ekleyin ve Özel Sınıfı ayarlayın

Özel Sınıf

Şimdi UIView için Yeni Bağlantı Noktasını ayarlayın

gösteri

Bağlantı Noktasını Göster'i açmak kırmızı bir nokta gösterecektir, böylece bağlantı noktasının görsel olarak nerede olacağını daha iyi görebilirsiniz. Daha sonra istediğiniz zaman kapatabilirsiniz.

Bu, UIViews'ta dönüşümleri planlarken bana gerçekten yardımcı oldu.


UIView'de tek nokta yok, Swift ile Objective-C kullandım, kodunuzdan UIView özel sınıfını ekledim ve Show anch'ı açtım ... ama tek nokta yok
Genevios

1

Swift 3 için:

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
Bunun için teşekkür ederim :) Projeme statik bir işlev olarak yapıştırdım ve bir cazibe gibi çalıştım: D
Kjell

0

Magnus'un harika ve kapsamlı cevabını genişleterek, alt katmanlar üzerinde çalışan bir sürüm oluşturdum:

-(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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.