web-dev-qa-db-fra.com

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

J'essaie de comprendre les fonctionnalités de ces méthodes. Pourriez-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? Qu'en est-il du récepteur?

Par exemple, est-il judicieux d’utiliser convertPoint: fromView: comme suit?

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

À l'aide de l'utilitaire NSLog, j'ai vérifié que la valeur p coïncidait avec le centre de view1.

Merci d'avance.

EDIT: Pour ceux que cela intéresse, j'ai créé un extrait de code simple 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. La sortie est la suivante:

convertPoint: fromView: {100, 100}

Dans le second, convertPoint est exprimé en coordonnées superview (self.window). La sortie est la suivante:

convertPoint: toView: {0, 0}

124
Lorenzo B

Chaque vue a son propre système de coordonnées - avec une origine à 0,0, une largeur et une hauteur. Ceci est décrit dans le rectangle bounds de la vue. Le frame de la vue, cependant, aura son origine au point situé dans le rectangle de limites de son aperçu.

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

Si vous ajoutez une sous-vue à 20,30 à cette vue, un point à 0,0 dans la sous-vue correspond à un point à 20,30 dans l'aperçu supérieur. Cette conversion est ce que ces méthodes font.

Votre exemple ci-dessus est inutile (sans jeu de mots) puisqu'il convertit un point d'une vue en lui-même, de sorte que rien ne se passera. Vous feriez plus souvent savoir où se trouvait un point de vue par rapport à sa vue d'ensemble - pour vérifier si une vue sortait de l'écran, par exemple:

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

Le "destinataire" est un terme objectif-c standard pour l'objet qui reçoit le message (les méthodes sont également appelées messages). Dans mon exemple, le destinataire est donc superview.

178
jrturton

Je trouve toujours cela déroutant et j'ai donc créé un terrain de jeu où vous pouvez explorer visuellement ce que fait la fonction convert. Ceci est 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 de montrer le rédacteur adjoint () afin de voir les points de vue, cela devrait ressembler à ceci:

enter image description here

N'hésitez pas à donner plus d'exemples ici ou dans this Gist .

34
phi

Voici une explication en anglais simple.

Lorsque vous voulez convertir le rect d'une sous-vue (aView est une sous-vue de [aView superview]) à 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];
30
horseshoe7

Chaque vue dans iOS possède un système de coordonnées. Un système de coordonnées est semblable à un graphique, qui a un axe x (ligne horizontale) et un axe y (ligne verticale). Le point d'intersection des lignes s'appelle l'origine. Un point est représenté par (x, y). Par exemple, (2, 1) signifie que le point a 2 pixels restants et 1 pixel vers le 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 système de coordonnées OWN, où le coin supérieur gauche est l’origine. L'axe des X augmente à droite et l'axe des Y augmente.

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

Il existe une vue, appelée V1, d’une largeur de 100 pixels et d’une hauteur de 100 pixels. Maintenant à l'intérieur de cela, il y a une autre vue, appelée V2, en (10, 10, 50, 50) 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 devrait être situé, et ( 50, 50) est la largeur et la hauteur de V2. Maintenant, prenons un point dans le système de coordonnées de V2, disons (20, 20). Maintenant, quel serait ce point dans le système de coordonnées de V1? C’est à cela que servent les méthodes (bien sûr, vous pouvez calculer vous-même, mais elles vous épargnent du travail supplémentaire). Pour mémoire, le point dans V1 serait (30, 30).

J'espère que cela t'aides.

14
Saurav Sachidanand

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

Mon contrôleur de vue a sa vue normale.

Dans cette vue, un certain nombre de vues de regroupement ne font que donner à leurs vues enfants une interaction propre avec des contraintes de mise en page automatique.

À l'intérieur de l'une de ces vues de regroupement, j'ai un bouton Ajouter qui présente un contrôleur de vue popover dans lequel l'utilisateur entre certaines informations.

view
--groupingView
----addButton

Pendant la rotation du périphérique, 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 découle de l’explication donnée par les deux premières réponses ci-dessus est que le recto à partir duquel je devais convertir était les bornes 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 évitons les complications liées aux sortes de laideurs avec des vues à plusieurs niveaux.

8
tobinjim

Vous pouvez voir le code ci-dessous afin que vous puissiez comprendre que 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)

}
3
vikas prajapati

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 ses grands-parents ou ses petits-enfants.

Donc, dans mon cas, j'ai une vue grand parent appelée self.view, Dans ce self.view, J'ai ajouté des sous-vues nommées self.child1OfView, self.child2OfView. Dans self.child1OfView, J'ai ajouté des sous-vues appelées self.child1OfView1, self.child2OfView1.
Maintenant, si je déplace physiquement self.child1OfView1 Vers une zone située en dehors de la limite de self.child1OfView Vers un autre emplacement sur self.view, Le calcul de la nouvelle position pour self.child1OfView1 Dans le self.view:

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

J'ai lu la réponse et compris les mécanismes, mais je pense que le dernier exemple n'est pas correct. Selon la documentation API, la propriété center d’une vue contient le point central connu de la vue dans le système de coordonnées de superview.

Si tel est le cas, j'estime qu'il serait illogique d'essayer de demander à Superview de convertir le centre d'une sous-vue FROM à partir du système de coordonnées de sous-vue, car la valeur ne se trouve pas dans le système de coordonnées. Ce qui serait logique, c’est de faire le contraire, c’est-à-dire de convertir le système de coordonnées superview en système de 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?

1
Moonwalker

Un autre point important sur l'utilisation de ces API. Assurez-vous que la chaîne de vues parent est complète entre le rect 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 ait été ajouté en tant que sous-vue de cView, nous obtiendrons une réponse bunk. Malheureusement, aucune méthode n'est intégrée dans les méthodes pour ce cas. Cela peut sembler évident, mais il faut en être conscient dans les cas où la conversion passe par une longue chaîne de parents.

1
D-avid