Comprendre les méthodes convertRect: toView :, convertRect: FromView :, convertPoint: toView: et convertPoint: fromView:

128

J'essaye de comprendre les fonctionnalités de ces méthodes. Pouvez-vous me fournir un cas d'utilisation simple pour comprendre leur sémantique?

Dans la documentation, par exemple, la méthode convertPoint: fromView: est décrite comme suit:

Convertit un point du système de coordonnées d'une vue donnée en celui du récepteur.

Que signifie le système de coordonnées ? Et le récepteur ?

Par exemple, est-il logique d'utiliser convertPoint: fromView: comme ce qui suit?

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

En utilisant l'utilitaire NSLog, j'ai vérifié que la valeur p coïncide avec le centre de view1.

Merci d'avance.

EDIT: pour ceux qui sont intéressés, j'ai créé un simple extrait de code pour comprendre ces méthodes.

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

Dans les deux cas, self.window est le récepteur. Mais il y a une différence. Dans le premier cas, le paramètre convertPoint est exprimé en coordonnées view1. Le résultat est le suivant:

convertPoint: fromView: {100, 100}

Dans le second, à la place, le convertPoint est exprimé en coordonnées superview (self.window). Le résultat est le suivant:

convertPoint: toView: {0, 0}

Lorenzo B
la source

Réponses:

184

Chaque vue a son propre système de coordonnées - avec une origine à 0,0 et une largeur et une hauteur. Ceci est décrit dans le boundsrectangle de la vue. Le framede la vue, cependant, aura son origine au point à l'intérieur du rectangle de limites de sa vue supervisée.

La vue la plus externe de votre hiérarchie de vues a son origine à 0,0 qui correspond au coin supérieur gauche de l'écran dans iOS.

Si vous ajoutez une sous-vue à 20,30 à cette vue, alors un point à 0,0 dans la sous-vue correspond à un point à 20,30 dans la supervision. Cette conversion est ce que font ces méthodes.

Votre exemple ci-dessus est inutile (sans jeu de mots) car il convertit un point d'une vue en lui-même, donc rien ne se passera. Vous découvrirez plus souvent où se trouve un point d'une vue par rapport à sa vue supervisée - pour tester si une vue sort de l'écran, par exemple:

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

Le «récepteur» est un terme objectif-c standard pour l'objet qui reçoit le message (les méthodes sont également appelées messages) donc dans mon exemple ici le récepteur est superview.

jrturton
la source
3
Merci pour votre réponse, jrturton. Explication très utile. convertPointet convertRectdiffèrent par le type de retour. CGPointou CGRect. Mais qu'en est-il fromet to? Y a-t-il une règle de base que je pourrais utiliser? Je vous remercie.
Lorenzo B du
à partir de quand vous voulez convertir, à quand vous voulez convertir?
jrturton du
3
Et si superview n'est pas la vue parent directe de la sous-vue, est-ce que cela fonctionnera toujours?
Van Du Tran
3
@VanDuTran oui, tant qu'ils sont dans la même fenêtre (ce que sont la plupart des vues dans une application iOS)
jrturton
Très bonne réponse. Cela m'a beaucoup aidé. Une lecture supplémentaire?
JaeGeeTee
39

Je trouve toujours cela déroutant, alors j'ai créé un terrain de jeu où vous pouvez explorer visuellement ce que fait la convertfonction. Cela se fait dans Swift 3 et 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

N'oubliez pas d'afficher l'Assistant Editor ( ) pour voir les vues, cela devrait ressembler à ceci:

entrez la description de l'image ici

N'hésitez pas à contribuer plus d'exemples ici ou dans cet essentiel .

phi
la source
Merci, cela a été vraiment utile pour comprendre les conversions.
Anand
Excellent aperçu. Cependant, je pense que ceux-ci self.viewsont redondants, simples à utiliser views'ils selfne sont pas nécessaires.
Jakub Truhlář
Merci @ JakubTruhlář, je viens de supprimerself
phi
Merci beaucoup! Votre terrain de jeu m'a beaucoup aidé à comprendre comment ces conversions fonctionnent.
Anvar Azizov
30

Voici une explication en anglais simple.

Lorsque vous souhaitez convertir le rect d'une sous-vue ( aViewest une sous-vue de [aView superview]) en l'espace de coordonnées d'une autre vue ( 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];
fer à cheval7
la source
3
Ce n'est pas vrai. Cela ne déplace pas la vue, cela vous donne simplement les coordonnées de la vue par rapport à une autre. Dans votre cas, cela vous donnera les coordonnées d'aView en fonction de l'endroit où ils se trouveraient en soi.
Carl
Correct. Cela ne déplace pas la vue. La méthode renvoie un CGRect. Ce que vous choisissez de faire avec ce CGRect est votre entreprise. :-) Dans le cas ci-dessus, il pourrait être utilisé pour déplacer une vue vers la hiérarchie d'une autre tout en conservant sa position visuelle à l'écran.
horseshoe7
14

Chaque vue dans iOS a un système de coordonnées. Un système de coordonnées est comme un graphique, qui a un axe x (ligne horizontale) et un axe y (ligne verticale). Le point d'intersection des lignes est appelé origine. Un point est représenté par (x, y). Par exemple, (2, 1) signifie que le point est à 2 pixels à gauche et à 1 pixel en bas.

Vous pouvez en savoir plus sur les systèmes de coordonnées ici - http://en.wikipedia.org/wiki/Coordinate_system

Mais ce que vous devez savoir, c'est que, dans iOS, chaque vue a son propre système de coordonnées, dont le coin supérieur gauche est l'origine. L'axe X continue d'augmenter vers la droite et l'axe y continue d'augmenter vers le bas.

Pour la question de conversion des points, prenez cet exemple.

Il y a une vue, appelée V1, qui mesure 100 pixels de large et 100 pixels de haut. Maintenant à l'intérieur de cela, il y a une autre vue, appelée V2, à (10, 10, 50, 50) ce qui signifie que (10, 10) est le point dans le système de coordonnées de V1 où le coin supérieur gauche de V2 doit être situé, et ( 50, 50) est la largeur et la hauteur de V2. Maintenant, prenez un point à l'intérieur du système de coordonnées de V2, disons (20, 20). Maintenant, quel serait ce point à l'intérieur du système de coordonnées de V1? C'est à cela que servent les méthodes (bien sûr, vous pouvez vous calculer elles-mêmes, mais elles vous font gagner du travail supplémentaire). Pour mémoire, le point en V1 serait (30, 30).

J'espère que cela t'aides.

Saurav Sachidanand
la source
9

Merci à tous d'avoir posté la question et vos réponses: cela m'a aidé à régler le problème.

Mon contrôleur de vue a sa vue normale.

À l'intérieur de cette vue, il existe un certain nombre de vues de regroupement qui ne font guère plus que de donner à leurs vues enfants une interaction claire avec les contraintes de mise en page automatique.

Dans l'une de ces vues de regroupement, j'ai un bouton Ajouter qui présente un contrôleur de vue popover où l'utilisateur entre des informations.

view
--groupingView
----addButton

Pendant la rotation de l'appareil, le contrôleur de vue est alerté via l'appel UIPopoverViewControllerDelegate 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 partie essentielle qui vient de l'explication donnée par les deux premières réponses ci-dessus était que le droit dont j'avais besoin pour convertir était les limites du bouton d'ajout, pas son cadre.

Je n'ai pas essayé cela avec une hiérarchie de vues plus complexe, mais je soupçonne qu'en utilisant la vue fournie dans l'appel de méthode (inView :), nous contournons les complications de la laideur de la vue feuille à plusieurs niveaux.

tobinjim
la source
3
"J'avais besoin de convertir à partir des limites du bouton d'ajout, pas de son cadre." - essentiellement m'a sauvé de beaucoup de code de trame laid pour le faire fonctionner. Merci d'avoir pris le temps de le signaler
latenitecoder
3

J'ai utilisé ce post pour postuler dans mon cas. J'espère que cela aidera un autre lecteur à l'avenir.

Une vue ne peut voir que ses enfants immédiats et ses vues parent. Il ne peut pas voir les vues de ses grands-parents ou de ses petits-enfants.

Donc, dans mon cas, j'ai une vue grand - parent appelé self.view, dans ce que self.viewj'ai ajouté subviews appelé self.child1OfView, self.child2OfView. Dans self.child1OfView, j'ai ajouté subviews appelé self.child1OfView1, self.child2OfView1.
Maintenant, si je me déplace physiquement self.child1OfView1vers une zone en dehors de la limite d'un autre self.child1OfViewendroit self.view, puis pour calculer la nouvelle position self.child1OfView1duself.view:

CGPoint newPoint = [self.view convertPoint:self.child1OfView1.center fromView:self.child1OfView];
user523234
la source
3

Vous pouvez voir le code ci-dessous pour comprendre comment cela fonctionne réellement.

    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)

}
vikas prajapati
la source
1

J'ai lu la réponse et je comprends la mécanique mais je pense que le dernier exemple n'est pas correct. Selon le document API, la propriété center d'une vue contient le point central connu de la vue dans le système de coordonnées de la vue supervisée.

Si tel est le cas, je pense que cela n'aurait pas de sens d'essayer de demander au superview de convertir le centre d'une sous-vue à partir du système de coordonnées de sous-vue car la valeur n'est pas dans le système de coordonnées de sous-vue. Ce qui aurait du sens, c'est de faire le contraire, c'est-à-dire de passer du système de coordonnées de superview à celui d'une sous-vue ...

Vous pouvez le faire de deux manières (les deux doivent donner la même valeur):

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

ou

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

Suis-je loin de comprendre comment cela devrait fonctionner?

Moonwalker
la source
Mon exemple était incorrect, j'ai mis à jour la réponse. Comme vous le dites, la propriété center est déjà dans l'espace de coordonnées de la vue supervisée.
jrturton
1

Un autre point important sur l'utilisation de ces API. Assurez-vous que la chaîne de vues parent est complète entre le recto que vous convertissez et la vue vers / depuis. Par exemple - aView, bView et cView -

  • aView est une sous-vue de bView
  • nous voulons convertir aView.frame en cView

Si nous essayons d'exécuter la méthode avant que bView n'ait été ajouté en tant que sous-vue de cView, nous obtiendrons une réponse de bunk. Malheureusement, il n'y a pas de protection intégrée dans les méthodes pour ce cas. Cela peut sembler évident, mais c'est quelque chose dont il faut être conscient dans les cas où la conversion passe par une longue chaîne de parents.

D-avide
la source