Informacje o metodach convertRect: toView :, convertRect: FromView :, convertPoint: toView: i convertPoint: fromView:


130

Próbuję zrozumieć funkcjonalności tych metod. Czy mógłbyś podać mi prosty przypadek użycia, aby zrozumieć ich semantykę?

Z dokumentacji, na przykład, metoda convertPoint: fromView: jest opisana w następujący sposób:

Konwertuje punkt z układu współrzędnych danego widoku na punkt w odbiorniku.

Co oznacza układ współrzędnych ? A co z odbiornikiem ?

Na przykład, czy ma sens używanie convertPoint: fromView: jak poniżej?

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

Używając narzędzia NSLog, zweryfikowałem, że wartość p pokrywa się ze środkiem view1.

Z góry dziękuję.

EDYCJA: dla zainteresowanych stworzyłem prosty fragment kodu, aby zrozumieć te metody.

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

W obu przypadkach self.window jest odbiornikiem. Ale jest różnica. W pierwszym przypadku parametr convertPoint jest wyrażony we współrzędnych view1. Wynik jest następujący:

convertPoint: fromView: {100, 100}

W drugim natomiast convertPoint jest wyrażony we współrzędnych superview (self.window). Wynik jest następujący:

convertPoint: toView: {0, 0}

Odpowiedzi:


186

Każdy widok ma swój własny układ współrzędnych - z początkiem w punkcie 0,0 oraz szerokością i wysokością. Jest to opisane w boundsprostokącie widoku. frameUważa jednak, będzie miał swój początek w punkcie w granicach prostokąta jej SuperView.

Najbardziej zewnętrzny widok hierarchii widoków ma swój początek w 0,0, co odpowiada lewym górnym rogu ekranu w iOS.

Jeśli dodasz podwidok o 20,30 do tego widoku, wtedy punkt o 0,0 w podglądzie odpowiada punktowi o 20,30 w superviewie. Ta konwersja jest tym, co robią te metody.

Twój przykład powyżej jest bezcelowy (gra słów nie jest zamierzona), ponieważ konwertuje punkt z punktu widzenia na siebie, więc nic się nie stanie. Częściej dowiedziałbyś się, gdzie był jakiś punkt widzenia w stosunku do jego superwizji - aby sprawdzić, czy widok opuszcza ekran, na przykład:

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

„Odbiornik” jest standardowym terminem obiektywnym c dla obiektu, który odbiera wiadomość (metody są również znane jako wiadomości), więc w moim przykładzie tutaj odbiorcą jest odbiorca superview.


3
Dziękuję za odpowiedź, jrturton. Bardzo przydatne wyjaśnienie. convertPointi convertRectróżnią się typem zwracania. CGPointlub CGRect. Ale co z fromi to? Czy jest jakaś praktyczna zasada, której mógłbym użyć? Dziękuję Ci.
Lorenzo B

od kiedy chcesz konwertować z, do kiedy chcesz konwertować na?
jrturton,

3
Co, jeśli nadzór nie jest bezpośrednim widokiem podrzędnym dla rodzica, czy nadal będzie działać?
Van Du Tran

3
@VanDuTran tak, o ile są w tym samym oknie (którym jest większość wyświetleń w aplikacji na iOS)
jrturton

Świetna odpowiedź. Pomogło mi wiele wyjaśnić. Jakieś dodatkowe lektury?
JaeGeeTee

40

Zawsze uważam to za zagmatwane, więc stworzyłem plac zabaw, na którym możesz wizualnie zbadać, co convertrobi ta funkcja. Odbywa się to w Swift 3 i 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

Pamiętaj, aby pokazać Asystenta Edytora ( ), aby zobaczyć widoki, powinien on wyglądać tak:

wprowadź opis obrazu tutaj

Zapraszam do dodania więcej przykładów tutaj lub w tym streszczeniu .


Dziękuję, to było naprawdę przydatne w zrozumieniu konwersji.
Anand

Świetny przegląd. Myślę jednak, że self.viewsą one zbędne, proste w użyciu, viewjeśli selfnie są potrzebne.
Jakub Truhlář

Dzięki @ JakubTruhlář, właśnie usunąłemself
phi

Wielkie dzięki! Twój plac zabaw bardzo mi pomógł w zrozumieniu, jak działają te konwersje.
Anvar Azizov

30

Oto wyjaśnienie w prostym języku angielskim.

Gdy chcesz przekonwertować prostokąt widoku podrzędnego ( aViewjest to widok podrzędny [aView superview]) na przestrzeń współrzędnych innego widoku ( 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
To nieprawda. Nie zmienia widoku, po prostu podaje współrzędne innego widoku. W twoim przypadku da ci to współrzędne aView pod względem tego, gdzie będą w sobie.
Carl

Poprawny. Nie porusza widoku. Metoda zwraca CGRect. To, co zdecydujesz się zrobić z tym CGRect, to Twoja sprawa. :-) W powyższym przypadku można go użyć do przeniesienia jednego widoku do hierarchii drugiego, zachowując jego wizualną pozycję na ekranie.
podkowa7

14

Każdy widok w iOS ma układ współrzędnych. Układ współrzędnych jest podobny do wykresu, który ma oś X (linia pozioma) i oś Y (linia pionowa). Punkt, w którym przecinają się linie, nazywany jest początkiem. Punkt jest reprezentowany przez (x, y). Na przykład (2, 1) oznacza, że ​​punkt ma 2 piksele na lewo i 1 piksel w dół.

Możesz przeczytać więcej o układach współrzędnych tutaj - http://en.wikipedia.org/wiki/Coordinate_system

Ale musisz wiedzieć, że w iOS każdy widok ma swój WŁASNY układ współrzędnych, w którym lewy górny róg jest początkiem. Oś X rośnie w prawo, a oś Y rośnie w dół.

W przypadku pytania dotyczącego punktów konwersji weź ten przykład.

Jest widok zwany V1, który ma 100 pikseli szerokości i 100 pikseli wysokości. Teraz w środku jest inny widok, zwany V2, w (10, 10, 50, 50), co oznacza, że ​​(10, 10) jest punktem w układzie współrzędnych V1, w którym powinien znajdować się lewy górny róg V2, oraz ( 50, 50) to szerokość i wysokość V2. Teraz weź punkt WEWNĄTRZ układu współrzędnych V2, powiedzmy (20, 20). Jaki byłby ten punkt wewnątrz układu współrzędnych V1? Do tego służą metody (oczywiście możesz obliczyć same, ale oszczędzają one dodatkowej pracy). Dla przypomnienia, punktem w V1 byłoby (30, 30).

Mam nadzieję że to pomoże.


9

Dziękuję wszystkim za opublikowanie pytania i odpowiedzi: pomogło mi to załatwić.

Mój kontroler widoku ma normalny widok.

Wewnątrz tego widoku znajduje się wiele widoków grupujących, które robią niewiele więcej niż zapewniają ich widokom podrzędnym czystą interakcję z ograniczeniami automatycznego układu.

W jednym z tych widoków grupujących mam przycisk Dodaj, który przedstawia kontroler widoku popover, w którym użytkownik wprowadza pewne informacje.

view
--groupingView
----addButton

Podczas obracania urządzenia kontroler widoku jest ostrzegany za pośrednictwem wywołania 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];
}

Zasadniczą częścią, która pochodzi z wyjaśnienia podanego w pierwszych dwóch odpowiedziach powyżej, było to, że prostą, z której musiałem dokonać konwersji, były granice przycisku dodawania, a nie jego ramka.

Nie próbowałem tego z bardziej złożoną hierarchią widoków, ale podejrzewam, że używając widoku dostarczonego w wywołaniu metody (inView :) omijamy komplikacje wielopoziomowego widoku liści.


3
„Musiałem dokonać konwersji z granic przycisku dodawania, a nie ramki”. - w zasadzie uratował mnie przed wieloma brzydkimi kodami ramek, aby to działało. Dzięki za
poświęcenie

3

Użyłem tego posta, aby złożyć wniosek w moim przypadku. Mam nadzieję, że w przyszłości pomoże to innemu czytelnikowi.

Widok może zobaczyć tylko swoje bezpośrednie widoki podrzędne i nadrzędne. Nie widzi poglądów swoich dziadków ani wnuków.

Tak więc, w moim przypadku, mam wielki Zobacz główne nazwie self.view, w tym self.viewDodałem subviews zwane self.child1OfView, self.child2OfView. W self.child1OfViewdodałem subviews zwane self.child1OfView1, self.child2OfView1.
Teraz, jeśli fizycznie przeniosę się self.child1OfView1do obszaru znajdującego się poza granicami innego self.child1OfViewmiejsca self.view, to do obliczenia nowej pozycji self.child1OfView1wewnątrzself.view:

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

3

Możesz zobaczyć poniższy kod, aby zrozumieć, jak to naprawdę działa.

    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

Przeczytałem odpowiedź i rozumiem mechanikę, ale myślę, że ostatni przykład nie jest poprawny. Zgodnie z dokumentem API, właściwość center widoku zawiera znany punkt środkowy widoku w układzie współrzędnych superviewu.

Jeśli tak jest, to myślę, że nie ma sensu prosić superwidoku o konwersję środka widoku podrzędnego Z układu współrzędnych widoku podrzędnego, ponieważ wartość nie znajduje się w układzie współrzędnych widoku podrzędnego. Sensowne byłoby zrobienie czegoś odwrotnego, tj. Konwersja z układu współrzędnych superwizji na widok podrzędny ...

Możesz to zrobić na dwa sposoby (oba powinny dać tę samą wartość):

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

lub

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

Czy daleko mi do zrozumienia, jak to powinno działać?


Mój przykład był nieprawidłowy, zaktualizowałem odpowiedź. Jak powiedziałeś, właściwość center jest już w przestrzeni współrzędnych superwizji.
jrturton

1

Jeszcze jeden ważny punkt dotyczący korzystania z tych interfejsów API. Upewnij się, że łańcuch widoku macierzystego jest kompletny między konwertowanym prostokątem a widokiem do / z widoku. Na przykład - aView, bView i cView -

  • aView jest podziałem bView
  • chcemy przekonwertować aView.frame na cView

Jeśli spróbujemy wykonać metodę przed dodaniem bView jako podwidoku cView, otrzymamy odpowiedź bunk. Niestety w metodach w tym przypadku nie ma wbudowanej ochrony. Może się to wydawać oczywiste, ale należy o tym pamiętać w przypadkach, gdy nawrócenie przebiega przez długi łańcuch rodziców.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.