Зрозуміти convertRect: toView :, convertRect: FromView :, convertPoint: toView: і convertPoint: fromView: методи


128

Я намагаюся зрозуміти функціональність цих методів. Не могли б ви надати мені простий футляр для розуміння їх семантики?

З документації, наприклад, метод convertPoint: fromView: описується так:

Перетворює точку з системи координат заданого виду в огляд приймача.

Що означає система координат ? А що з приймачем ?

Наприклад, чи має сенс використовувати convertPoint: fromView: як-от наступне?

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

Використовуючи утиліту NSLog, я перевірив, що значення p збігається з центром view1.

Спасибі заздалегідь.

EDIT: для тих, хто цікавиться, я створив простий фрагмент коду, щоб зрозуміти ці методи.

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

В обох випадках self.window - це приймач. Але є різниця. У першому випадку параметр convertPoint виражається у координатах view1. Вихід такий:

convertPoint: fromView: {100, 100}

У другому, натомість, ConverPoint виражається в координатах супервигляду (self.window). Вихід такий:

convertPoint: toView: {0, 0}

Відповіді:


184

Кожен вид має свою систему координат - з початком у 0,0 та шириною та висотою. Це описано у boundsпрямокутнику подання. Однак frameпогляд має мати своє початок у точці в межах прямокутника його нагляду.

Зовнішній вигляд ієрархії перегляду має свій початок у 0,0, що відповідає лівій верхній частині екрана в iOS.

Якщо до цього виду додати підпогляд о 20,30, то точка у 0,0 в підпогляді відповідає точці в 20,30 у суперпрегляді. Це перетворення - це те, що роблять ці методи.

Ваш приклад вище безглуздий (каламбур не призначений), оскільки він перетворює точку з погляду на себе, тому нічого не відбудеться. Ви частіше дізнаєтесь, де знаходиться деяка точка зору по відношенню до його нагляду - щоб перевірити, чи подання рухається з екрана, наприклад:

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

"Одержувач" є стандартним терміном-c для об'єкта, який отримує повідомлення (методи також відомі як повідомлення), тому в моєму прикладі тут є приймач superview.


3
Дякую за вашу відповідь, jrturton. Дуже корисне пояснення. convertPointі convertRectвідрізняються типом повернення. CGPointабо CGRect. Але про що fromі to? Чи є правило, яке я можу використовувати? Дякую.
Лоренцо Б

від того, коли ви хочете конвертувати, до того, коли ви хочете конвертувати?
jrturton

3
Що робити, якщо нагляд не є прямим батьківським поданням підпідгляду, чи все ще він буде працювати?
Van Du Tran

3
@VanDuTran так, якщо вони знаходяться в одному вікні (яке найбільше переглядів у додатку для iOS)
jrturton

Чудова відповідь. Допоміг багато зрозуміти для мене. Будь-яке додаткове читання?
JaeGeeTee

39

Я завжди вважаю це заплутаним, тому я зробив ігровий майданчик, де можна візуально вивчити, що convertробить ця функція. Це робиться в Swift 3 та 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

Не забудьте показати Помічник редактора ( ), щоб побачити види, він повинен виглядати так:

введіть тут опис зображення

Не соромтеся надати більше прикладів тут або в цій суті .


Дякую, це було дуже корисно для розуміння конверсій.
Ананд

Чудовий огляд. Однак я вважаю, що self.viewце зайве, просте використання, viewякщо selfвони не потрібні.
Jakub Truhlář

Дякую @ JakubTruhlář, я щойно видаливself
phi

Дуже дякую! Ваш ігровий майданчик мені дуже допоміг зрозуміти, як працює ця конверсія.
Анвар Азізов

30

Ось пояснення простою англійською мовою.

Коли ви хочете перетворити пряму субпрограму ( aViewце підвід [aView superview]) в координатний простір іншого виду ( 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
Це неправда. Він не переміщує погляд, він просто дає вам координати подання з точки зору іншого. У вашому випадку він дасть вам координати aView з точки зору того, де вони опиняться.
Карл

Правильно. Це не переміщує погляд. Метод повертає CGRect. Що ви вирішите зробити з цим CGRect - це ваш бізнес. :-) У випадку, описаному вище, його можна використовувати для переміщення одного перегляду до ієрархії іншого, зберігаючи його візуальне положення на екрані.
підкова7

14

Кожен погляд в iOS має систему координат. Система координат подібно до графіка, який має вісь x (горизонтальна лінія) та вісь y (вертикальна лінія). Точка, в якій лінії перетинаються, називається початком. Точка представлена ​​(x, y). Наприклад, (2, 1) означає, що точка залишається на 2 пікселі, а на 1 піксель вниз.

Докладніше про системи координат можна прочитати тут - http://en.wikipedia.org/wiki/Coordinate_system

Але що вам потрібно знати, це те, що в iOS кожен огляд має власну систему координат, де лівий верхній кут є джерелом. Вісь X продовжує збільшуватися праворуч, а вісь y продовжує збільшуватися вниз.

Для питання перетворення точок візьміть цей приклад.

Існує вид, який називається V1, шириною 100 пікселів і висотою 100 пікселів. Тепер всередині цього є ще один вигляд, званий V2, на (10, 10, 50, 50), що означає, що (10, 10) - точка в системі координат V1, де повинен бути розташований верхній лівий кут V2, і ( 50, 50) - ширина і висота V2. Тепер візьміть точку ВІДКРИТТЯ координатної системи, скажімо, (20, 20). Тепер, яка ця точка була б у системі координат V1? Саме для цього є методи (звичайно, ви можете самі порахувати, але вони заощадять додаткову роботу). Для запису точка в V1 була б (30, 30).

Сподіваюся, це допомагає.


9

Дякую всім за те, що ви написали запитання та ваші відповіді: Це допомогло мені розібратися в цьому.

Мій контролер перегляду має нормальний вигляд.

Всередині цього перегляду є ряд представлень групувань, які роблять трохи більше, ніж дають своїм поданням дітей чітку взаємодію з обмеженнями автоматичного розміщення.

Всередині одного з цих переглядів угруповання у мене є кнопка Додати, яка представляє контролер подання перекидання, де користувач вводить певну інформацію.

view
--groupingView
----addButton

Під час обертання пристрою контролер виду оповіщається через виклик 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];
}

Основна частина, що випливає з пояснення, яке дано двома першими відповідями вище, полягало в тому, що прямістю, з якої мені потрібно було перетворити, є межі кнопки "add", а не її кадр.

Я не пробував цього з більш складною ієрархією перегляду, але підозрюю, що, використовуючи представлення, подане у виклику методу (inView :), ми долаємо ускладнення різноярусного виду листя.


3
"Мені потрібно було перетворити межі кнопки" Додати ", а не кадр." - По суті, врятував мене від безлічі потворних кадрів, щоб він працював. Дякуємо, що
знайшли

3

Я використовував цю посаду, щоб застосувати у своєму випадку. Сподіваюся, це допоможе іншому читачеві в майбутньому.

Перегляд може бачити лише його найближчих дітей та батьківських поглядів. Він не бачить своїх онуків чи батьків, онуків.

Так, в моєму випадку, у мене є грандіозний вид батьківський під назвою self.view, в цьому self.viewя додав підвиди званих self.child1OfView, self.child2OfView. В self.child1OfView, я додав підвидів звані self.child1OfView1, self.child2OfView1.
Тепер , якщо я фізично перемістити self.child1OfView1в область за межами кордону self.child1OfViewв пильовиках місця на self.view, потім в калькуляторі нової позиції для self.child1OfView1всерединіself.view:

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

3

Ви можете побачити код нижче, щоб зрозуміти, як він насправді працює.

    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

Я читаю відповідь і розумію механіку, але думаю, що остаточний приклад не є правильним. Згідно з документом API, властивість центру перегляду містить відому центральну точку зору в системі координат нагляду.

У такому випадку я вважаю, що не було б сенсу намагатися попросити супервізор перетворити центр підвідного ПО з системи координат підгляду, оскільки значення не знаходиться в системі координат підгляду. Що було б сенсом - це зробити навпаки, тобто перетворити з системи координат нагляду в систему підпрогляду ...

Це можна зробити двома способами (обидва повинні отримувати однакове значення):

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

або

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

Невже я не розумію, як це має працювати?


Мій приклад був невірним, я оновив відповідь. Як ви кажете, властивість центру вже знаходиться в координатному просторі нагляду.
jrturton

1

Ще один важливий момент щодо використання цих API. Переконайтеся, що ланцюжок батьківського виду є повною між прямою перетворенню та переходом до / з перегляду. Наприклад - aView, bView і cView -

  • aView - це підвід bView
  • ми хочемо перетворити aView.frame в cView

Якщо ми спробуємо виконати метод до того, як bView буде доданий як підвід cView, ми отримаємо зворотну відповідь. На жаль, не існує вбудованого захисту в методи для цього випадку. Це може здатися очевидним, але це слід пам’ятати у випадках, коли конверсія проходить через довгий ланцюжок батьків.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.