Comprenda convertRect: toView :, convertRect: FromView :, convertPoint: toView: y convertPoint: fromView: métodos

128

Estoy tratando de entender las funcionalidades de estos métodos. ¿Podría proporcionarme un caso de uso simple para comprender su semántica?

De la documentación, por ejemplo, convertPoint: fromView: el método se describe de la siguiente manera:

Convierte un punto del sistema de coordenadas de una vista dada a la del receptor.

¿Qué significa el sistema de coordenadas ? ¿Qué hay del receptor ?

Por ejemplo, ¿tiene sentido usar convertPoint: fromView: como el siguiente?

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

Usando la utilidad NSLog, he verificado que el valor p coincide con el centro de view1.

Gracias de antemano.

EDITAR: para aquellos interesados, he creado un fragmento de código simple para comprender estos métodos.

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

En ambos casos, self.window es el receptor. Pero hay una diferencia. En el primer caso, el parámetro convertPoint se expresa en coordenadas view1. El resultado es el siguiente:

convertPoint: fromView: {100, 100}

En el segundo, en cambio, el convertPoint se expresa en coordenadas de supervista (self.window). El resultado es el siguiente:

convertPoint: toView: {0, 0}

Lorenzo B
fuente

Respuestas:

184

Cada vista tiene su propio sistema de coordenadas, con un origen de 0,0 y un ancho y alto. Esto se describe en el boundsrectángulo de la vista. La framevista, sin embargo, tendrá su origen en el punto dentro de los límites del rectángulo de su supervista.

La vista más externa de su jerarquía de vistas tiene su origen en 0,0, que corresponde a la esquina superior izquierda de la pantalla en iOS.

Si agrega una subvista a las 20,30 a esta vista, entonces un punto a 0,0 en la subvista corresponde a un punto a las 20,30 en la supervista. Esta conversión es lo que están haciendo esos métodos.

Su ejemplo anterior no tiene sentido (sin juego de palabras) ya que convierte un punto de una vista a sí mismo, por lo que no sucederá nada. Con mayor frecuencia, descubriría dónde se encontraba algún punto de una vista en relación con su supervista, para probar si una vista se movía fuera de la pantalla, por ejemplo:

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

El "receptor" es un término estándar de objetivo-c para el objeto que recibe el mensaje (los métodos también se conocen como mensajes), así que en mi ejemplo aquí está el receptor superview.

jrturton
fuente
3
Gracias por tu respuesta, jrturton. Muy útil explicación. convertPointy convertRectdifieren en el tipo de devolución. CGPointo CGRect. Pero ¿qué pasa fromy to? ¿Hay alguna regla general que pueda usar? Gracias.
Lorenzo B
¿desde cuándo quieres convertir, a cuándo quieres convertir?
jrturton
3
¿Qué sucede si la supervista no es la vista principal directa de la subvista, seguirá funcionando?
Van Du Tran
3
@VanDuTran sí, siempre que estén en la misma ventana (que la mayoría de las vistas en una aplicación de iOS lo están)
jrturton
Gran respuesta. Me ayudó a aclarar mucho. ¿Alguna lectura adicional?
JaeGeeTee
39

Siempre encuentro esto confuso, así que hice un parque infantil donde puedes explorar visualmente lo que hace la convertfunción. Esto se hace en Swift 3 y 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

Recuerde mostrar el Editor Asistente ( ) para ver las vistas, debería verse así:

ingrese la descripción de la imagen aquí

Siéntase libre de contribuir con más ejemplos aquí o en este resumen .

fi
fuente
Gracias, esto fue realmente útil para comprender las conversiones.
Anand
Gran resumen Sin embargo, creo que self.viewson redundantes, de uso simple viewsi selfno es necesario.
Jakub Truhlář
Gracias @ JakubTruhlář, acabo de eliminarself
phi
¡Muchas gracias! Su patio de recreo me ayudó mucho a descubrir cómo funcionan estas conversiones.
Anvar Azizov
30

Aquí hay una explicación en inglés simple.

Cuando desee convertir el rect de una subvista ( aViewes una subvista de [aView superview]) al espacio de coordenadas de otra 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];
herradura7
fuente
3
Esto no es verdad No mueve la vista, solo le da las coordenadas de la vista en términos de otra. En su caso, le dará las coordenadas de aView en términos de dónde estarían en uno mismo.
Carl
Correcto. No mueve la vista. El método devuelve un CGRect. Lo que eliges hacer con ese CGRect es tu negocio. :-) En el caso anterior, podría usarse para mover una vista a la jerarquía de otra mientras se mantiene su posición visual en la pantalla.
herradura7
14

Cada vista en iOS tiene un sistema de coordenadas. Un sistema de coordenadas es como un gráfico, que tiene eje x (línea horizontal) y eje y (línea vertical). El punto en el que las líneas interesect se llama origen. Un punto está representado por (x, y). Por ejemplo, (2, 1) significa que el punto queda 2 píxeles hacia la izquierda y 1 píxel hacia abajo.

Puede leer más sobre los sistemas de coordenadas aquí: http://en.wikipedia.org/wiki/Coordinate_system

Pero lo que necesita saber es que, en iOS, cada vista tiene su PROPIO sistema de coordenadas, donde la esquina superior izquierda es el origen. El eje X continúa aumentando hacia la derecha, y el eje Y continúa aumentando hacia abajo.

Para la pregunta de puntos de conversión, tome este ejemplo.

Hay una vista, llamada V1, que tiene 100 píxeles de ancho y 100 píxeles de alto. Ahora dentro de eso, hay otra vista, llamada V2, en (10, 10, 50, 50) lo que significa que (10, 10) es el punto en el sistema de coordenadas de V1 donde debe ubicarse la esquina superior izquierda de V2, y ( 50, 50) es el ancho y la altura de V2. Ahora, tome un punto DENTRO del sistema de coordenadas de V2, digamos (20, 20). Ahora, ¿cuál sería ese punto dentro del sistema de coordenadas de V1? Para eso están los métodos (por supuesto, puedes calcularte a ti mismo, pero te ahorran trabajo extra). Para el registro, el punto en V1 sería (30, 30).

Espero que esto ayude.

Saurav Sachidanand
fuente
9

Gracias a todos por publicar la pregunta y sus respuestas: me ayudó a resolver esto.

Mi controlador de vista tiene su vista normal.

Dentro de esa vista hay varias vistas de agrupación que hacen poco más que dar a sus vistas secundarias una interacción limpia con restricciones de diseño automático.

Dentro de una de esas vistas de agrupación tengo un botón Agregar que presenta un controlador de vista emergente donde el usuario ingresa cierta información.

view
--groupingView
----addButton

Durante la rotación del dispositivo, se alerta al controlador de vista mediante la llamada 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 parte esencial que proviene de la explicación dada por las dos primeras respuestas anteriores fue que el rect desde el que necesitaba convertir era los límites del botón Agregar, no su marco.

No he intentado esto con una jerarquía de vista más compleja, pero sospecho que al usar la vista proporcionada en la llamada al método (inView :) obtenemos las complicaciones de los tipos de fealdad de vista de hoja de varios niveles.

tobinjim
fuente
3
"Necesitaba convertir los límites del botón Agregar, no su marco". - esencialmente me salvó de un montón de código de marco feo para que funcione. Gracias por tomarse el tiempo para señalar esto
latenitecoder
3

Usé esta publicación para aplicar en mi caso. Espero que esto ayude a otro lector en el futuro.

Una vista solo puede ver sus vistas secundarias e primarias inmediatas. No puede ver las opiniones de sus abuelos o nietos.

Por lo tanto, en mi caso, tengo una gran vista padre llamado self.view, en este self.viewhe añadido subvistas llamados self.child1OfView, self.child2OfView. En self.child1OfView, he añadido subvistas llamados self.child1OfView1, self.child2OfView1.
Ahora, si me muevo físicamente self.child1OfView1a un área fuera del límite de otro self.child1OfViewlugar self.view, entonces para calcular la nueva posición para self.child1OfView1elself.view:

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

Puede ver el código a continuación para que pueda entender cómo funciona realmente.

    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
fuente
1

Leí la respuesta y entiendo la mecánica, pero creo que el ejemplo final no es correcto. Según el documento API, la propiedad central de una vista contiene el punto central conocido de la vista en el sistema de coordenadas de la supervista.

Si este es el caso, creo que no tendría sentido intentar pedirle a la supervista que convierta el centro de una subvista DESDE el sistema de coordenadas de la subvista porque el valor no está en el sistema de coordenadas de la subvista. Lo que tendría sentido es hacer lo contrario, es decir, convertir del sistema de coordenadas de la supervista a la de una subvista ...

Puede hacerlo de dos maneras (ambas deben producir el mismo valor):

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

o

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

¿Estoy lejos de entender cómo debería funcionar esto?

Caminante lunar
fuente
Mi ejemplo fue incorrecto, he actualizado la respuesta. Como usted dice, la propiedad del centro ya está en el espacio de coordenadas de la supervista.
jrturton
1

Un punto más importante sobre el uso de estas API. Asegúrese de que la cadena de vista principal esté completa entre el rect que está convirtiendo y la vista hacia / desde. Por ejemplo - aView, bView y cView -

  • aView es una subvista de bView
  • queremos convertir aView.frame a cView

Si intentamos ejecutar el método antes de que se haya agregado bView como una subvista de cView, obtendremos una respuesta de litera. Lamentablemente, no hay protección integrada en los métodos para este caso. Esto puede parecer obvio, pero es algo a tener en cuenta en los casos en que la conversión pasa por una larga cadena de padres.

D-ávido
fuente