Mengubah anchorPoint CALayer saya menggerakkan tampilan

131

Saya ingin mengubah anchorPoint, tetapi menyimpan tampilan di tempat yang sama. Saya sudah mencoba NSLog-ing self.layer.positiondan self.centerdan mereka berdua tinggal sama terlepas dari perubahan anchorPoint tersebut. Namun pandangan saya bergerak!

Ada tips tentang cara melakukan ini?

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

Outputnya adalah:

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
Kenny Winker
sumber

Jawaban:

139

Bagian Lapisan Geometri dan Transformasi dari Panduan Pemrograman Animasi Inti menjelaskan hubungan antara posisi CALayer dan properti anchorPoint. Pada dasarnya, posisi layer ditentukan dalam hal lokasi anchorPoint layer. Secara default, anchorPoint layer adalah (0,5, 0,5), yang terletak di tengah-tengah layer. Ketika Anda mengatur posisi layer, Anda kemudian mengatur lokasi pusat lapisan dalam sistem koordinat superlayer-nya.

Karena posisinya relatif terhadap anchorPoint pada layer, mengubah anchorPoint itu dengan tetap mempertahankan posisi yang sama menggerakkan layer. Untuk mencegah gerakan ini, Anda harus menyesuaikan posisi layer untuk memperhitungkan anchorPoint baru. Salah satu cara saya telah melakukan ini adalah untuk mengambil batas-batas lapisan, kalikan lebar dan tinggi batas dengan nilai normalisasi anchorPoint lama dan baru, ambil perbedaan dari dua anchorPoints, dan terapkan perbedaan itu pada posisi layer.

Anda bahkan dapat menghitung rotasi dengan cara ini dengan menggunakan CGPointApplyAffineTransform()CGAffineTransform UIView Anda.

Brad Larson
sumber
4
Lihat contoh fungsi dari Magnus untuk melihat bagaimana jawaban Brad dapat diimplementasikan di Objective-C.
Jim Jeffers
Terima kasih, tetapi saya tidak mengerti bagaimana anchorPointdan positionterkait bersama?
dumbfingers
6
@ ss1271 - Seperti yang saya jelaskan di atas, anchorPoint dari sebuah layer adalah dasar dari sistem koordinatnya. Jika diatur ke (0,5, 0,5), maka ketika Anda mengatur posisi untuk layer, Anda mengatur di mana pusatnya terletak pada sistem koordinat pemain supernya. Jika Anda mengatur anchorPoint ke (0,0, 0,5), pengaturan posisi akan menetapkan di mana pusat tepi kiri berada. Sekali lagi, Apple memiliki beberapa gambar yang bagus dalam dokumentasi yang ditautkan di atas (yang saya perbarui rujukannya).
Brad Larson
Saya menggunakan metode yang disediakan oleh Magnus. Ketika saya posisi dan bingkai NSLog mereka terlihat benar tetapi pandangan saya masih bergerak. Ada yang tahu kenapa? Sepertinya posisi baru tidak diperhitungkan.
Alexis
Dalam kasus saya, saya ingin menambahkan animasi pada layer menggunakan AVVideoCompositionCoreAnimationTool video classCompositionCoreAnimationToolWithPostProcessingAsVideoLayer metode. Apa yang terjadi adalah, setengah dari lapisan saya terus menghilang sementara saya memutarnya di sekitar sumbu y.
nr5
205

Saya memiliki masalah yang sama. Solusi Brad Larson bekerja sangat baik bahkan ketika pandangan diputar. Ini solusinya diterjemahkan ke dalam kode.

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

Dan setara dengan cepat:

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
}
Magnus
sumber
2
Sempurna dan masuk akal begitu saya bisa melihat contoh Anda di sini. Terima kasih untuk ini!
Jim Jeffers
2
Saya menggunakan kode ini dalam metode awakeFromNib / initWithFrame saya, tetapi masih tidak berfungsi, apakah saya perlu memperbarui tampilan? sunting: setNeedsDisplay tidak berfungsi
Adam Carter
2
Mengapa Anda menggunakan view.bounds? Bukankah Anda seharusnya menggunakan layer.bounds?
zakdances
1
dalam nilai pengaturan kasus saya ke: view.layer.position tidak mengubah apa pun
AndrewK
5
FWIW, saya harus membungkus metode setAnchor di blok dispatch_async agar bisa berfungsi. Tidak yakin mengapa saya menyebutnya di dalam viewDidLoad. Apakah viewDidLoad tidak lagi di antrean utas?
John Fowler
44

Kunci untuk menyelesaikan ini adalah dengan menggunakan properti frame, yang anehnya adalah satu-satunya hal yang berubah.

Cepat 2

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

Cepat 3

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

Lalu saya melakukan resize saya, di mana ia mengukur dari anchorPoint. Maka saya harus mengembalikan anchorPoint lama;

Cepat 2

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

Cepat 3

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

EDIT: ini serpihan jika tampilan diputar, karena properti bingkai tidak terdefinisi jika CGAffineTransform telah diterapkan.

Kenny Winker
sumber
9
Hanya ingin menambahkan mengapa ini bekerja dan tampaknya menjadi cara termudah: menurut dokumen, properti frame adalah properti "terdiri": "Nilai frame berasal dari properti bounds, anchorPoint, dan position." Itu juga mengapa properti "bingkai" tidak dapat digerakkan.
DarkDust
1
Ini sejujurnya pendekatan yang saya sukai. Jika Anda tidak mencoba untuk memanipulasi batas dan posisi secara independen atau menghidupkan keduanya, cukup ambil bingkai sebelum Anda mengubah titik jangkar biasanya sudah cukup. Tidak perlu matematika.
CIFilter
28

Bagi saya pengertian positiondan anchorPointtermudah ketika saya mulai membandingkannya dengan pemahaman saya tentang frame.origin di UIView. UIView dengan frame.origin = (20,30) berarti bahwa UIView adalah 20 poin dari kiri dan 30 poin dari atas tampilan induknya. Jarak ini dihitung dari titik mana dari UIView? Ini dihitung dari sudut kiri atas UIView.

Dalam lapisan anchorPointmenandai titik (dalam bentuk normal yaitu 0 sampai 1) dari mana jarak ini dihitung jadi misal layer.position = (20, 30) berarti bahwa lapisan tersebut anchorPointadalah 20 titik dari kiri dan 30 poin dari atas lapisan induknya. Secara default, anchorPoint layer adalah (0,5, 0,5) sehingga titik kalkulasi jarak tepat di tengah-tengah layer. Gambar berikut akan membantu memperjelas poin saya:

masukkan deskripsi gambar di sini

anchorPoint juga merupakan titik di mana rotasi akan terjadi jika Anda menerapkan transformasi ke layer.

2cupsOfTech
sumber
1
Tolong bisakah Anda memberi tahu bagaimana cara mengatasi lompatan titik jangkar ini yang UIView disetel menggunakan tata letak otomatis, artinya dengan tata letak otomatis kami tidak seharusnya mendapatkan atau menyetel properti bingkai dan batas.
SJ
17

Ada solusi yang sangat sederhana. Ini berdasarkan jawaban Kenny. Tetapi alih-alih menerapkan bingkai yang lama, gunakan yang asli dan yang baru untuk menghitung terjemahan, lalu terapkan terjemahan itu ke tengah. Ini bekerja dengan tampilan yang diputar juga! Inilah kodenya, jauh lebih sederhana daripada solusi lain:

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)
}
Nasi goreng
sumber
2
Metode kecil yang bagus ... berfungsi dengan baik dengan beberapa perbaikan kecil: Baris 3: huruf P di anchorPoint. Baris 8: TRANS.Y = BARU - TUA
bob
2
masih saya bingung bagaimana saya bisa menggunakannya. Saya menghadapi masalah yang sama sekarang untuk memutar pandangan saya dengan uigesturerotation. Saya aneh di mana saya harus memanggil metode ini. Jika saya memanggil penangan isyarat gesture. Itu membuat tampilan menghilang.
JAck
3
Jawaban ini sangat manis saya sekarang menderita diabetes.
GeneCode
14

Bagi mereka yang membutuhkannya, inilah solusi Magnus di 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
}
Corey Davis
sumber
1
pilih untuk view.setTranslatesAutoresizingMaskIntoConstraints(true). Terima kasih
kawan
1
view.setTranslatesAutoresizingMaskIntoConstraints(true)diperlukan saat menggunakan batasan tata letak otomatis.
SamirChen
1
Terima kasih banyak atas ini! The translatesAutoresizingMaskIntoConstraintsadalah hanya apa yang saya hilang, bisa belajar sekarang
Ziga
5

Berikut adalah jawaban pengguna945711 yang disesuaikan untuk NSView pada OS X. Selain NSView tidak memiliki .centerproperti, bingkai NSView tidak berubah (mungkin karena NSViews tidak datang dengan CALayer secara default) tetapi asal bingkai CALayer berubah ketika anchorPoint diubah.

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)
}
iMaddin
sumber
4

Jika Anda mengubah anchorPoint, posisinya juga akan berubah, KECUALI asalnya adalah titik nol CGPointZero.

position.x == origin.x + anchorPoint.x;
position.y == origin.y + anchorPoint.y;
pengguna3156083
sumber
1
Anda tidak dapat membandingkan posisi dan anchorPoint, skala anchorPoint antara 0 hingga 1.0
Edward Anthony
4

Edit dan Lihat Anchor Point UIView Tepat di Storyboard (Swift 3)

Ini adalah solusi alternatif yang memungkinkan Anda untuk mengubah titik jangkar melalui Inspektur Atribut dan memiliki properti lain untuk melihat titik jangkar untuk konfirmasi.

Buat file baru untuk dimasukkan dalam proyek Anda

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

Tambahkan View ke Storyboard dan atur Kelas Kustom

Kelas Khusus

Sekarang atur New Anchor Point untuk UIView

Demonstrasi

Mengaktifkan Show Anchor Point akan menampilkan titik merah sehingga Anda bisa lebih baik melihat di mana anchor point akan terlihat secara visual. Anda selalu dapat mematikannya nanti.

Ini sangat membantu saya ketika merencanakan transformasi pada UIViews.

Mark Moeykens
sumber
Tidak ada satu titik pun di UIView, saya menggunakan Objective-C dengan Swift, melampirkan kelas khusus UIView dari kode Anda dan mengaktifkan Show anch ... tetapi tidak ada satu titik
Genevios
1

Untuk 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
}
AJ9
sumber
1
Terima kasih untuk ini :) Saya baru saja menempelkannya ke proyek saya sebagai fungsi statis dan berfungsi seperti pesona: D
Kjell
0

Memperluas jawaban Magnus yang hebat & menyeluruh, saya telah membuat versi yang berfungsi pada sub lapisan:

-(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;
}
Charles Robertson
sumber