La modifica del punto di ancoraggio del mio CALayer sposta la vista


131

Voglio modificare il anchorPoint, ma mantenere la vista nello stesso posto. Ho provato NSLog-ing self.layer.positioned self.centerentrambi rimangono gli stessi indipendentemente dalle modifiche all'ancoraggio. Eppure la mia visione si muove!

Qualche consiglio su come farlo?

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

L'output è:

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

Risposte:


139

La sezione Geometria e trasformazioni dei livelli della Guida alla programmazione di animazioni di base spiega la relazione tra la posizione di un CALayer e le proprietà anchorPoint. Fondamentalmente, la posizione di un livello è specificata in termini di posizione del punto di ancoraggio del livello. Per impostazione predefinita, anchorPoint di un livello è (0,5, 0,5), che si trova al centro del livello. Quando impostate la posizione del livello, state quindi impostando la posizione del centro del livello nel suo sistema di coordinate del superlayer.

Poiché la posizione è relativa al punto di ancoraggio del livello, la modifica di quel punto di ancoraggio mantenendo la stessa posizione sposta il livello. Per evitare questo movimento, è necessario regolare la posizione del livello per tenere conto del nuovo anchorPoint. Un modo in cui l'ho fatto è quello di afferrare i limiti del livello, moltiplicare la larghezza e l'altezza dei limiti per i valori normalizzati del vecchio e del nuovo anchorPoint, prendere la differenza dei due punti di ancoraggio e applicare quella differenza alla posizione del livello.

Potresti anche essere in grado di rendere conto della rotazione in questo modo utilizzando CGPointApplyAffineTransform()con CGAffineTransform di UIView.


4
Vedi la funzione di esempio di Magnus per vedere come la risposta di Brad può essere implementata in Objective-C.
Jim Jeffers,

Grazie, ma non capisco come l' anchorPointe positionsono legati insieme?
Dumbfingers,

6
@ ss1271 - Come ho descritto sopra, l'ancoraggio di un livello è la base del suo sistema di coordinate. Se è impostato su (0,5, 0,5), quando si imposta la posizione per un livello, si imposta dove si troverà il suo centro nel sistema di coordinate del suo superlayer. Se si imposta anchorPoint su (0.0, 0.5), impostando la posizione verrà impostato il centro del bordo sinistro. Ancora una volta, Apple ha alcune belle immagini nella documentazione sopra collegata (a cui ho aggiornato il riferimento).
Brad Larson

Ho usato il metodo fornito da Magnus. Quando I NSLog posizione e frame sembrano corretti ma la mia vista è ancora spostata. Qualche idea sul perché? È come se la nuova posizione non fosse presa in considerazione.
Alexis,

Nel mio caso, voglio aggiungere un'animazione sul layer usando il metodo videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer della classe AVVideoCompositionCoreAnimationTool. Quello che succede è che metà del mio strato continua a sparire mentre lo sto ruotando attorno all'asse y.
nr5

205

Ho avuto lo stesso problema. La soluzione di Brad Larson ha funzionato benissimo anche quando la vista è ruotata. Ecco la sua soluzione tradotta in codice.

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

E l'equivalente veloce:

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
Perfetto e ha perfettamente senso una volta che sono stato in grado di vedere il tuo esempio qui. Grazie per questo!
Jim Jeffers,

2
Uso questo codice nei miei metodi awakeFromNib / initWithFrame, ma non funziona ancora, devo aggiornare il display? modifica: setNeedsDisplay non funziona
Adam Carter

2
Perché stai usando view.bounds? Non dovresti usare layer.bounds?
zakdances,

1
nel mio caso l'impostazione del valore su: view.layer.position non cambia nulla
AndrewK

5
FWIW, ho dovuto avvolgere il metodo setAnchor in un blocco dispatch_async per farlo funzionare. Non so perché come lo chiamo viewDidLoad. ViewDidLoad non è più sulla coda dei thread principali?
John Fowler,

44

La chiave per risolverlo era usare la proprietà frame, che è stranamente l'unica cosa che cambia.

Swift 2

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

Swift 3

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

Quindi eseguo il ridimensionamento, dove viene ridimensionato dal punto di ancoraggio. Quindi devo ripristinare il vecchio anchorPoint;

Swift 2

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

Swift 3

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

EDIT: questo si sfalda se la vista viene ruotata, poiché la proprietà frame non è definita se è stata applicata una CGAffineTransform.


9
Volevo solo aggiungere perché questo funziona e sembra essere il modo più semplice: secondo i documenti, la proprietà frame è una proprietà "composta": "Il valore di frame è derivato dai limiti, anchorPoint e proprietà position". Questo è anche il motivo per cui la proprietà "frame" non è animabile.
DarkDust,

1
Questo è onestamente il mio approccio preferito. Se non si sta tentando di manipolare i limiti e posizionarli in modo indipendente o animarli, è sufficiente catturare il fotogramma prima di modificare il punto di ancoraggio. Non è necessaria la matematica.
CIFilter,

28

Per me capire positioned è anchorPointstato più semplice quando ho iniziato a confrontarlo con la mia comprensione di frame.origin in UIView. Una vista UIV con frame.origin = (20,30) significa che la vista UIV è a 20 punti da sinistra e 30 punti dalla parte superiore della vista padre. Questa distanza viene calcolata da quale punto di una UIView? È calcolato dall'angolo in alto a sinistra di una vista UIV.

Nel layer anchorPointsegna il punto (in forma normalizzata, cioè da 0 a 1) da dove viene calcolata questa distanza, ad es. Layer.position = (20, 30) significa che il layer si anchorPointtrova a 20 punti da sinistra e 30 punti dalla parte superiore del layer principale. Per impostazione predefinita, un punto di ancoraggio di livello è (0,5, 0,5), quindi il punto di calcolo della distanza si trova proprio al centro del livello. La seguente figura aiuterà a chiarire il mio punto:

inserisci qui la descrizione dell'immagine

anchorPoint capita anche di essere il punto attorno al quale avverrà la rotazione nel caso in cui applichi una trasformazione al livello.


1
Per favore, puoi dire come posso risolvere questo punto di ancoraggio saltando chi è UIView impostato usando il layout automatico, significa che con il layout automatico non supponiamo di ottenere o impostare proprietà di frame e limiti.
SJ,

17

C'è una soluzione così semplice. Questo si basa sulla risposta di Kenny. Invece di applicare la vecchia cornice, usa la sua origine e quella nuova per calcolare la traduzione, quindi applica quella traduzione al centro. Funziona anche con vista ruotata! Ecco il codice, molto più semplice rispetto ad altre soluzioni:

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
Piccolo metodo carino ... funziona bene con un paio di correzioni minori: Linea 3: P maiuscola in anchorPoint. Linea 8: TRANS.Y = NEW - OLD
bob

2
sono ancora confuso come posso usarlo. Sto affrontando lo stesso problema in questo momento per ruotare la mia vista con uigesturerotation. Sono strano che dove dovrei chiamare questo metodo. Se sto chiamando il gestore del riconoscimento gesti. Fa sparire la vista.
JAck

3
Questa risposta è così dolce che ora ho il diabete.
GeneCode

14

Per chi ne ha bisogno, ecco la soluzione di Magnus in 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
}

1
voto per view.setTranslatesAutoresizingMaskIntoConstraints(true). grazie amico
Oscar Yuandinata il

1
view.setTranslatesAutoresizingMaskIntoConstraints(true)è necessario quando si utilizzano i vincoli di layout automatico.
SamirChen,

1
Grazie mille per questo! L' translatesAutoresizingMaskIntoConstraintsè proprio quello che mi mancava, potrebbe aver imparato da ora
Ziga

5

Ecco la risposta di user945711 modificata per NSView su OS X. Oltre a NSView che non ha una .centerproprietà, il frame di NSView non cambia (probabilmente perché NSViews non viene fornito con un CALayer per impostazione predefinita) ma l'origine del frame di CALayer cambia quando viene cambiato l'ancoraPoint.

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

Se si cambia anchorPoint, anche la sua posizione cambierà, A MENO CHE l'origine non sia pari a zero CGPointZero.

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

1
Non puoi confrontare la posizione e anchorPoint, anchorPoint scala tra 0 e 1.0
Edward Anthony,

4

Modifica e visualizza il punto di ancoraggio di UIView direttamente su Storyboard (Swift 3)

Questa è una soluzione alternativa che consente di modificare il punto di ancoraggio tramite la finestra di ispezione degli attributi e ha un'altra proprietà per visualizzare il punto di ancoraggio per la conferma.

Crea un nuovo file da includere nel tuo progetto

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

Aggiungi Visualizza allo storyboard e imposta la classe personalizzata

Classe personalizzata

Ora imposta il nuovo punto di ancoraggio per l'UIView

Dimostrazione

Attivare Mostra punto di ancoraggio mostrerà un punto rosso in modo da poter vedere meglio dove sarà visivamente il punto di ancoraggio. Puoi sempre disattivarlo in seguito.

Questo mi ha davvero aiutato nella pianificazione delle trasformazioni su UIVview.


Nessun punto su UIView, ho usato Objective-C con Swift, ho collegato la classe personalizzata UIView dal tuo codice e ho attivato Show anch ... ma nessun punto
Genevios

1

Per 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
Grazie per questo :) L'ho appena incollato nel mio progetto come una funzione statica e funziona come un incantesimo: D
Kjell

0

Espandendo la risposta eccellente e approfondita di Magnus, ho creato una versione che funziona su livelli secondari:

-(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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.