Verstehen Sie die Methoden convertRect: toView:, convertRect: FromView:, convertPoint: toView: und convertPoint: fromView:

128

Ich versuche die Funktionen dieser Methoden zu verstehen. Könnten Sie mir einen einfachen Anwendungsfall geben, um ihre Semantik zu verstehen?

In der Dokumentation wird beispielsweise die Methode convertPoint: fromView: wie folgt beschrieben:

Konvertiert einen Punkt aus dem Koordinatensystem einer bestimmten Ansicht in den des Empfängers.

Was bedeutet das Koordinatensystem ? Was ist mit dem Empfänger ?

Ist es beispielsweise sinnvoll, convertPoint: fromView: wie folgt zu verwenden?

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

Mit dem Dienstprogramm NSLog habe ich überprüft, ob der p-Wert mit der Mitte von view1 übereinstimmt.

Vielen Dank im Voraus.

BEARBEITEN: Für Interessenten habe ich ein einfaches Code-Snippet erstellt, um diese Methoden zu verstehen.

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 beiden Fällen ist self.window der Empfänger. Aber es gibt einen Unterschied. Im ersten Fall wird der Parameter convertPoint in view1-Koordinaten ausgedrückt. Die Ausgabe ist die folgende:

convertPoint: fromView: {100, 100}

Im zweiten Fall wird der convertPoint stattdessen in Koordinaten der Übersicht (self.window) ausgedrückt. Die Ausgabe ist die folgende:

convertPoint: toView: {0, 0}

Lorenzo B.
quelle

Antworten:

184

Jede Ansicht hat ein eigenes Koordinatensystem - mit einem Ursprung bei 0,0 und einer Breite und Höhe. Dies wird im boundsRechteck der Ansicht beschrieben. Die frameAnsicht hat jedoch ihren Ursprung an dem Punkt innerhalb des Begrenzungsrechtecks ​​ihrer Übersicht.

Die äußerste Ansicht Ihrer Ansichtshierarchie hat ihren Ursprung bei 0,0, was in iOS oben links auf dem Bildschirm entspricht.

Wenn Sie dieser Ansicht eine Unteransicht bei 20,30 hinzufügen, entspricht ein Punkt bei 0,0 in der Unteransicht einem Punkt bei 20,30 in der Übersicht. Diese Konvertierung ist das, was diese Methoden tun.

Ihr Beispiel oben ist sinnlos (kein Wortspiel beabsichtigt), da es einen Punkt von einer Ansicht in sich selbst konvertiert, sodass nichts passiert. Sie würden häufiger herausfinden, wo sich ein Punkt einer Ansicht in Bezug auf ihre Übersicht befindet - um zu testen, ob sich eine Ansicht vom Bildschirm entfernt, zum Beispiel:

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

Der "Empfänger" ist ein Standard-Ziel-c-Begriff für das Objekt, das die Nachricht empfängt (Methoden werden auch als Nachrichten bezeichnet), also ist in meinem Beispiel hier der Empfänger superview.

jrturton
quelle
3
Vielen Dank für Ihre Antwort, jrturton. Sehr nützliche Erklärung. convertPointund convertRectunterscheiden sich in der Rückgabeart. CGPointoder CGRect. Aber was ist mit fromund to? Gibt es eine Faustregel, die ich verwenden könnte? Danke dir.
Lorenzo B
von wann möchten Sie konvertieren von, bis wann möchten Sie konvertieren?
Jrturton
3
Was ist, wenn die Übersicht nicht die direkte übergeordnete Ansicht der Unteransicht ist? Funktioniert sie trotzdem?
Van Du Tran
3
@ VanDuTran ja, solange sie sich im selben Fenster befinden (was die meisten Ansichten in einer iOS-App sind)
jrturton
Gute Antwort. Hat mir sehr geholfen aufzuklären. Irgendwelche zusätzlichen Lesungen?
JaeGeeTee
39

Ich finde das immer verwirrend, deshalb habe ich einen Spielplatz geschaffen, auf dem Sie visuell erkunden können, was die convertFunktion tut. Dies geschieht in Swift 3 und 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

Denken Sie daran, den Assistenten-Editor ( ) anzuzeigen, damit die Ansichten angezeigt werden. Er sollte folgendermaßen aussehen:

Geben Sie hier die Bildbeschreibung ein

Fühlen Sie sich frei, hier oder in diesem Kern weitere Beispiele beizutragen .

Phi
quelle
Vielen Dank, dies war sehr hilfreich, um die Conversions zu verstehen.
Anand
Toller Überblick. Ich denke jedoch, dass diese self.viewredundant sind und einfach verwendet werden können, viewwenn sie selfnicht benötigt werden.
Jakub Truhlář
Danke @ JakubTruhlář, ich habe gerade entferntself
Phi
Vielen Dank! Ihr Spielplatz hat mir sehr geholfen herauszufinden, wie diese Konvertierungen funktionieren.
Anvar Azizov
30

Hier ist eine Erklärung in einfachem Englisch.

Wenn Sie das Rechteck einer Unteransicht ( aViewist eine Unteransicht von [aView superview]) in den Koordinatenraum einer anderen Ansicht ( self) konvertieren möchten .

// 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];
Hufeisen7
quelle
3
Das ist nicht wahr. Es verschiebt die Ansicht nicht, es gibt Ihnen nur die Koordinaten der Ansicht in Bezug auf eine andere. In Ihrem Fall erhalten Sie die Koordinaten von aView in Bezug darauf, wo sie sich selbst befinden würden.
Carl
Richtig. Die Ansicht wird nicht verschoben. Die Methode gibt ein CGRect zurück. Was Sie mit diesem CGRect tun, ist Ihr Geschäft. :-) Im obigen Fall kann eine Ansicht in die Hierarchie einer anderen verschoben werden, während die visuelle Position auf dem Bildschirm beibehalten wird.
Hufeisen7
14

Jede Ansicht in iOS hat ein Koordinatensystem. Ein Koordinatensystem ähnelt einem Diagramm mit einer x-Achse (horizontale Linie) und einer y-Achse (vertikale Linie). Der Punkt, an dem sich die Linien schneiden, wird als Ursprung bezeichnet. Ein Punkt wird durch (x, y) dargestellt. Zum Beispiel bedeutet (2, 1), dass der Punkt noch 2 Pixel und 1 Pixel nach unten ist.

Weitere Informationen zu Koordinatensystemen finden Sie hier - http://en.wikipedia.org/wiki/Coordinate_system

Was Sie jedoch wissen müssen, ist, dass in iOS jede Ansicht ein EIGENES Koordinatensystem hat, wobei die obere linke Ecke der Ursprung ist. Die X-Achse steigt weiter nach rechts und die Y-Achse weiter nach unten.

Nehmen Sie für die Frage der Konvertierungspunkte dieses Beispiel.

Es gibt eine Ansicht namens V1, die 100 Pixel breit und 100 Pixel hoch ist. Darin befindet sich eine weitere Ansicht namens V2 bei (10, 10, 50, 50), was bedeutet, dass (10, 10) der Punkt im Koordinatensystem von V1 ist, an dem sich die obere linke Ecke von V2 befinden sollte, und ( 50, 50) ist die Breite und Höhe von V2. Nehmen Sie nun einen Punkt in das Koordinatensystem von VIDE V2, z. B. (20, 20). Was wäre dieser Punkt innerhalb des Koordinatensystems von V1? Dafür sind die Methoden gedacht (natürlich können Sie sich selbst berechnen, aber sie sparen Ihnen zusätzliche Arbeit). Für die Aufzeichnung wäre der Punkt in V1 (30, 30).

Hoffe das hilft.

Saurav Sachidanand
quelle
9

Vielen Dank, dass Sie die Frage und Ihre Antworten gepostet haben: Es hat mir geholfen, dies zu klären.

Mein View Controller hat seine normale Ansicht.

In dieser Ansicht gibt es eine Reihe von Gruppierungsansichten, die kaum mehr bewirken, als ihren untergeordneten Ansichten eine saubere Interaktion mit Einschränkungen für das automatische Layout zu ermöglichen.

In einer dieser Gruppierungsansichten befindet sich eine Schaltfläche Hinzufügen, die einen Popover-Ansichts-Controller anzeigt, in den der Benutzer einige Informationen eingibt.

view
--groupingView
----addButton

Während der Gerätedrehung wird der View Controller über den UIPopoverViewControllerDelegate-Aufruf popoverController: willRepositionPopoverToRect: inView: alarmiert.

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

Der wesentliche Teil, der sich aus der Erklärung der ersten beiden Antworten oben ergibt, war, dass das Rechteck, aus dem ich konvertieren musste, die Grenzen der Schaltfläche zum Hinzufügen und nicht deren Rahmen waren.

Ich habe dies nicht mit einer komplexeren Ansichtshierarchie versucht, aber ich vermute, dass wir durch die Verwendung der im Methodenaufruf (inView :) bereitgestellten Ansicht die Komplikationen der Hässlichkeit von mehrstufigen Blattansichten umgehen.

Tobinjim
quelle
3
"Ich musste aus den Grenzen der Schaltfläche" Hinzufügen "konvertieren, nicht aus dem Rahmen." - hat mich im Wesentlichen vor viel hässlichem Frame-Code bewahrt, damit es funktioniert. Vielen Dank, dass Sie sich die Zeit genommen haben, um darauf hinzuweisen
Latenitecoder
3

Ich habe diesen Beitrag verwendet, um mich in meinem Fall zu bewerben. Ich hoffe, dies wird einem anderen Leser in Zukunft helfen.

Eine Ansicht kann nur ihre unmittelbaren untergeordneten und übergeordneten Ansichten sehen. Es kann die Ansichten seiner Großeltern oder Enkelkinder nicht sehen.

In meinem Fall habe ich also eine übergeordnete Ansicht namens self.view, in der self.viewich Unteransichten mit dem Namen self.child1OfView, self.child2OfView. In habe self.child1OfViewich Unteransichten mit dem Namen self.child1OfView1, self.child2OfView1.
Wenn ich mich nun physisch self.child1OfView1zu einem Bereich außerhalb der Grenze self.child1OfViewzu einem anderen Punkt bewege self.view, dann um die neue Position für die self.child1OfView1innerhalb des zu berechnenself.view:

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

Sie können den folgenden Code sehen, damit Sie verstehen, wie er tatsächlich funktioniert.

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

Ich lese die Antwort und verstehe die Mechanik, aber ich denke, das letzte Beispiel ist nicht korrekt. Gemäß dem API-Dokument enthält die Eigenschaft center einer Ansicht den bekannten Mittelpunkt der Ansicht im Koordinatensystem der Übersicht.

Wenn dies der Fall ist, wäre es meines Erachtens nicht sinnvoll, die Übersicht zu bitten, die Mitte einer Unteransicht aus dem Unteransichtskoordinatensystem zu konvertieren, da sich der Wert nicht im Unteransichtskoordinatensystem befindet. Sinnvoll wäre es, das Gegenteil zu tun, dh vom Superview-Koordinatensystem in das einer Unteransicht zu konvertieren ...

Sie können dies auf zwei Arten tun (beide sollten den gleichen Wert ergeben):

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

oder

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

Bin ich weit davon entfernt zu verstehen, wie das funktionieren soll?

Mondwanderer
quelle
Mein Beispiel war falsch, ich habe die Antwort aktualisiert. Wie Sie sagen, befindet sich die Eigenschaft center bereits im Koordinatenraum der Übersicht.
Jrturton
1

Ein weiterer wichtiger Punkt bei der Verwendung dieser APIs. Stellen Sie sicher, dass die übergeordnete Ansichtskette zwischen dem zu konvertierenden Rechteck und der Ansicht von / nach vollständig ist. Zum Beispiel - aView, bView und cView -

  • aView ist eine Unteransicht von bView
  • Wir möchten aView.frame in cView konvertieren

Wenn wir versuchen, die Methode auszuführen, bevor bView als Unteransicht von cView hinzugefügt wurde, erhalten wir eine Bunk-Antwort zurück. Leider ist in den Methoden für diesen Fall kein Schutz eingebaut. Dies mag offensichtlich erscheinen, ist jedoch in Fällen zu beachten, in denen die Bekehrung eine lange Kette von Eltern durchläuft.

David
quelle