Comprendi convertRect: toView :, convertRect: FromView :, convertPoint: toView: e convertPoint: fromView: metodi


128

Sto cercando di capire le funzionalità di questi metodi. Potresti fornirmi un semplice esempio per comprendere la loro semantica?

Dalla documentazione, ad esempio, convertPoint: fromView: il metodo è descritto come segue:

Converte un punto dal sistema di coordinate di una determinata vista a quello del ricevitore.

Cosa significa il sistema di coordinate ? E il ricevitore ?

Ad esempio, ha senso usare convertPoint: fromView: come il seguente?

CGPoint p = [view1 convertPoint:view1.center fromView:view1];

Utilizzando l'utilità NSLog, ho verificato che il valore p coincide con il centro di view1.

Grazie in anticipo.

EDIT: per chi è interessato, ho creato un semplice frammento di codice per comprendere questi metodi.

UIView* view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 200)];
view1.backgroundColor = [UIColor redColor];

NSLog(@"view1 frame: %@", NSStringFromCGRect(view1.frame));        
NSLog(@"view1 center: %@", NSStringFromCGPoint(view1.center));   

CGPoint originInWindowCoordinates = [self.window convertPoint:view1.bounds.origin fromView:view1];        
NSLog(@"convertPoint:fromView: %@", NSStringFromCGPoint(originInWindowCoordinates));

CGPoint originInView1Coordinates = [self.window convertPoint:view1.frame.origin toView:view1];        
NSLog(@"convertPoint:toView: %@", NSStringFromCGPoint(originInView1Coordinates));

In entrambi i casi self.window è il ricevitore. Ma c'è una differenza. Nel primo caso il parametro convertPoint è espresso in coordinate view1. L'output è il seguente:

convertPoint: fromView: {100, 100}

Nella seconda invece, convertPoint è espresso in coordinate superview (finestra self.window). L'output è il seguente:

convertPoint: toView: {0, 0}

Risposte:


184

Ogni vista ha il suo sistema di coordinate - con un'origine a 0,0 e una larghezza e altezza. Questo è descritto nel boundsrettangolo della vista. La framevista, tuttavia, avrà la sua origine nel punto all'interno del rettangolo dei limiti della sua superview.

La vista più esterna della gerarchia della vista ha origine a 0,0 che corrisponde alla parte superiore sinistra dello schermo in iOS.

Se si aggiunge una sottoview a 20,30 a questa vista, un punto a 0,0 nella sottoview corrisponde a un punto a 20,30 nella superview. Questa conversione è ciò che stanno facendo quei metodi.

Il tuo esempio sopra è inutile (nessun gioco di parole previsto) poiché converte un punto da una vista a se stesso, quindi non accadrà nulla. Più comunemente scopriresti dove si trovava un punto di vista in relazione alla sua superview - per verificare se una vista si stava spostando dallo schermo, ad esempio:

CGPoint originInSuperview = [superview convertPoint:CGPointZero fromView:subview];

Il "destinatario" è un termine obiettivo-c standard per l'oggetto che riceve il messaggio (i metodi sono anche noti come messaggi), quindi nel mio esempio qui è il destinatario superview.


3
Grazie per la tua risposta, jrturton. Spiegazione molto utile. convertPointe convertRectdifferiscono nel tipo di ritorno. CGPointoppure CGRect. Ma che dire di frome to? Esiste una regola empirica che potrei usare? Grazie.
Lorenzo B,

da quando vuoi convertire da, a quando vuoi convertirti?
jrturton,

3
Che cosa succede se superview non è la vista genitore diretta di subview, funzionerà comunque?
Van Du Tran,

3
@VanDuTran sì, purché si trovino nella stessa finestra (come la maggior parte delle visualizzazioni in un'app iOS)
jrturton

Bella risposta. Mi ha aiutato a chiarire molto. Qualche lettura aggiuntiva?
JaeGeeTee

39

Lo trovo sempre confuso, quindi ho creato un parco giochi in cui è possibile esplorare visivamente ciò che fa la convertfunzione. Questo viene fatto in Swift 3 e Xcode 8.1b:

import UIKit
import PlaygroundSupport

class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Main view
        view.backgroundColor = .black
        view.frame = CGRect(x: 0, y: 0, width: 500, height: 500)

        // Red view
        let redView = UIView(frame: CGRect(x: 20, y: 20, width: 460, height: 460))
        redView.backgroundColor = .red
        view.addSubview(redView)

        // Blue view
        let blueView = UIView(frame: CGRect(x: 20, y: 20, width: 420, height: 420))
        blueView.backgroundColor = .blue
        redView.addSubview(blueView)

        // Orange view
        let orangeView = UIView(frame: CGRect(x: 20, y: 20, width: 380, height: 380))
        orangeView.backgroundColor = .orange
        blueView.addSubview(orangeView)

        // Yellow view
        let yellowView = UIView(frame: CGRect(x: 20, y: 20, width: 340, height: 100))
        yellowView.backgroundColor = .yellow
        orangeView.addSubview(yellowView)


        // Let's try to convert now
        var resultFrame = CGRect.zero
        let randomRect: CGRect = CGRect(x: 0, y: 0, width: 100, height: 50)

        /*
        func convert(CGRect, from: UIView?)
        Converts a rectangle from the coordinate system of another view to that of the receiver.
        */

        // The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view:
        resultFrame = view.convert(randomRect, from: yellowView)

        // Try also one of the following to get a feeling of how it works:
        // resultFrame = view.convert(randomRect, from: orangeView)
        // resultFrame = view.convert(randomRect, from: redView)
        // resultFrame = view.convert(randomRect, from: nil)

        /*
        func convert(CGRect, to: UIView?)
        Converts a rectangle from the receiver’s coordinate system to that of another view.
        */

        // The following line converts a rectangle (randomRect) from the coordinate system of yellowView to that of self.view
        resultFrame = yellowView.convert(randomRect, to: view)
        // Same as what we did above, using "from:"
        // resultFrame = view.convert(randomRect, from: yellowView)

        // Also try:
        // resultFrame = orangeView.convert(randomRect, to: view)
        // resultFrame = redView.convert(randomRect, to: view)
        // resultFrame = orangeView.convert(randomRect, to: nil)


        // Add an overlay with the calculated frame to self.view
        let overlay = UIView(frame: resultFrame)
        overlay.backgroundColor = UIColor(white: 1.0, alpha: 0.9)
        overlay.layer.borderColor = UIColor.black.cgColor
        overlay.layer.borderWidth = 1.0
        view.addSubview(overlay)
    }
}

var ctrl = MyViewController()
PlaygroundPage.current.liveView = ctrl.view

Ricorda di mostrare Assistant Editor ( ) per vedere le viste, dovrebbe apparire così:

inserisci qui la descrizione dell'immagine

Sentiti libero di contribuire con altri esempi qui o in questa sintesi .


Grazie, questo è stato davvero utile per comprendere le conversioni.
Anand,

Ottima panoramica. Tuttavia, penso che self.viewsiano ridondanti e di semplice utilizzo viewse selfnon necessari.
Jakub Truhlář

Grazie @ JakubTruhlář, ho appena rimossoself
phi

Molte grazie! Il tuo parco giochi mi ha aiutato molto a capire come funzionano queste conversioni.
Anvar Azizov,

30

Ecco una spiegazione in inglese semplice.

Quando si desidera convertire il retto di una vista secondaria ( aViewè una vista secondaria di [aView superview]) nello spazio delle coordinate di un'altra vista ( self).

// So here I want to take some subview and put it in my view's coordinate space
_originalFrame = [[aView superview] convertRect: aView.frame toView: self];

3
Questo non è vero. Non sposta la vista, ti dà solo le coordinate della vista in termini di un'altra. Nel tuo caso ti darà le coordinate di aView in termini di dove sarebbero in se stessi.
Carl,

Corretta. Non sposta la vista. Il metodo restituisce un CGRect. Quello che scegli di fare con quel CGRect è la tua attività. :-) Nel caso sopra, potrebbe essere usato per spostare una vista nella gerarchia di un'altra mantenendo la sua posizione visiva sullo schermo.
ferro di

14

Ogni vista in iOS ha un sistema di coordinate. Un sistema di coordinate è proprio come un grafico, che ha l'asse x (linea orizzontale) e l'asse y (linea verticale). Il punto in cui le linee si intersecano si chiama origine. Un punto è rappresentato da (x, y). Ad esempio, (2, 1) significa che il punto ha 2 pixel a sinistra e 1 pixel in basso.

Puoi leggere di più sui sistemi di coordinate qui: http://en.wikipedia.org/wiki/Coordinate_system

Ma quello che devi sapere è che, in iOS, ogni vista ha il suo sistema di coordinate PROPRIO, dove l'angolo in alto a sinistra è l'origine. L'asse X continua ad aumentare verso destra e l'asse y continua ad aumentare verso il basso.

Per la domanda sui punti di conversione, prendi questo esempio.

C'è una vista, chiamata V1, che è larga 100 pixel e alta 100 pixel. Ora all'interno di questo, c'è un'altra vista, chiamata V2, in (10, 10, 50, 50) che significa che (10, 10) è il punto nel sistema di coordinate di V1 in cui dovrebbe essere situato l'angolo in alto a sinistra di V2, e ( 50, 50) è la larghezza e l'altezza di V2. Ora, prendi un punto del sistema di coordinate INSIDE V2, diciamo (20, 20). Ora, quale sarebbe quel punto all'interno del sistema di coordinate di V1? Ecco a cosa servono i metodi (ovviamente puoi calcolarli da soli, ma ti fanno risparmiare lavoro extra). Per la cronaca, il punto in V1 sarebbe (30, 30).

Spero che questo ti aiuti.


9

Grazie a tutti per aver pubblicato la domanda e le risposte: mi ha aiutato a risolvere questo problema.

Il mio controller di visualizzazione ha una visualizzazione normale.

All'interno di quella vista ci sono un certo numero di viste di raggruppamento che fanno poco più che dare alle loro viste figlio un'interazione chiara con i vincoli di layout automatico.

All'interno di una di quelle viste di raggruppamento ho un pulsante Aggiungi che presenta un controller di vista popover in cui l'utente inserisce alcune informazioni.

view
--groupingView
----addButton

Durante la rotazione del dispositivo, il controller di visualizzazione viene avvisato tramite UIPopoverViewControllerDelegate call popoverController: willRepositionPopoverToRect: inView:

- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
{
    *rect = [self.addButton convertRect:self.addbutton.bounds toView:*view];
}

La parte essenziale che deriva dalla spiegazione data dalle prime due risposte sopra era che il rettangolo da cui avevo bisogno di convertire era i limiti del pulsante Aggiungi, non la sua cornice.

Non ho provato questo con una gerarchia di viste più complessa, ma sospetto che usando la vista fornita nella chiamata del metodo (inView :) aggiriamo le complicazioni dei tipi di bruttezza della vista foglia a più livelli.


3
"Avevo bisogno di convertire da era i limiti del pulsante Aggiungi, non è frame." - essenzialmente mi ha salvato da un sacco di brutti codici frame per farlo funzionare. Grazie per aver
dedicato

3

Ho usato questo post per candidarmi nel mio caso. Spero che questo possa aiutare un altro lettore in futuro.

Una vista può vedere solo le viste figlio e genitore immediate. Non riesce a vedere i suoi nonni o i suoi nipoti.

Così, nel mio caso, ho una vista grandiosa genitore chiamato self.view, in questo self.viewho aggiunto subviews chiamati self.child1OfView, self.child2OfView. In self.child1OfView, ho aggiunto subviews chiamati self.child1OfView1, self.child2OfView1.
Ora, se mi muovo fisicamente self.child1OfView1in un'area al di fuori del confine di un altro self.child1OfViewpunto su self.view, quindi per calcolare la nuova posizione self.child1OfView1all'internoself.view:

CGPoint newPoint = [self.view convertPoint:self.child1OfView1.center fromView:self.child1OfView];

3

Puoi vedere sotto il codice in modo da poter capire come funziona effettivamente.

    let scrollViewTemp = UIScrollView.init(frame: CGRect.init(x: 10, y: 10, width: deviceWidth - 20, height: deviceHeight - 20))

override func viewDidLoad() {
    super.viewDidLoad()

    scrollViewTemp.backgroundColor = UIColor.lightGray
    scrollViewTemp.contentSize = CGSize.init(width: 2000, height: 2000)
    self.view.addSubview(scrollViewTemp)

    let viewTemp = UIView.init(frame: CGRect.init(x: 100, y: 100, width: 150, height: 150))
    viewTemp.backgroundColor = UIColor.green
    self.view.addSubview(viewTemp)

    let viewSecond = UIView.init(frame: CGRect.init(x: 100, y: 700, width: 300, height: 300))
    viewSecond.backgroundColor = UIColor.red
    self.view.addSubview(viewSecond)

    self.view.convert(viewTemp.frame, from: scrollViewTemp)
    print(viewTemp.frame)


    /*  First take one point CGPoint(x: 10, y: 10) of viewTemp frame,then give distance from viewSecond frame to this point.
     */
    let point = viewSecond.convert(CGPoint(x: 10, y: 10), from: viewTemp)
    //output:   (10.0, -190.0)
    print(point)

    /*  First take one point CGPoint(x: 10, y: 10) of viewSecond frame,then give distance from viewTemp frame to this point.
     */
    let point1 = viewSecond.convert(CGPoint(x: 10, y: 10), to: viewTemp)
    //output:  (10.0, 210.0)
    print(point1)

    /*  First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewSecond frame,then give distance from viewTemp frame to this rect.
     */
    let rect1 = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), to: viewTemp)
    //output:  (10.0, 210.0, 20.0, 20.0)
    print(rect1)

    /* First take one rect CGRect(x: 10, y: 10, width: 20, height: 20) of viewTemp frame,then give distance from viewSecond frame to this rect.
     */
    let rect = viewSecond.convert(CGRect(x: 10, y: 10, width: 20, height: 20), from: viewTemp)
    //output:  (10.0, -190.0, 20.0, 20.0)
    print(rect)

}

1

Ho letto la risposta e ho capito la meccanica, ma penso che l'esempio finale non sia corretto. Secondo il documento API, la proprietà centrale di una vista contiene il punto centrale noto della vista nel sistema di coordinate della superview.

Se questo è il caso, allora penso che non avrebbe senso provare a chiedere alla superview di convertire il centro di una sottoview DAL sistema di coordinate della sottoview perché il valore non è nel sistema di coordinate della subview. Ciò che avrebbe senso è fare il contrario, ovvero convertire dal sistema di coordinate di superview a quello di una subview ...

Puoi farlo in due modi (entrambi dovrebbero produrre lo stesso valore):

CGPoint centerInSubview = [subview convertPoint:subview.center fromView:subview.superview];

o

CGPoint centerInSubview = [subview.superview convertPoint:subview.center toView:subview];

Sto andando a capire come dovrebbe funzionare?


Il mio esempio non è corretto, ho aggiornato la risposta. Come dici tu, la proprietà center è già nello spazio delle coordinate della superview.
jrturton,

1

Un altro punto importante sull'utilizzo di queste API. Assicurarsi che la catena della vista padre sia completa tra il rettangolo che si sta convertendo e la vista in / da. Ad esempio - aView, bView e cView -

  • aView è una sottoview di bView
  • vogliamo convertire aView.frame in cView

Se proviamo a eseguire il metodo prima che bView sia stato aggiunto come sottoview di cView, avremo una risposta a castello. Purtroppo non esiste una protezione integrata nei metodi per questo caso. Questo può sembrare ovvio, ma è qualcosa di cui essere consapevoli nei casi in cui la conversione passa attraverso una lunga catena di genitori.

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.