Общие сведения о методах convertRect: toView :, convertRect: FromView :, convertPoint: toView: и convertPoint: fromView:


128

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

Из документации, например, метод convertPoint: fromView: описывается следующим образом:

Преобразует точку из системы координат данного вида в систему координат приемника.

Что означает система координат ? А как насчет ресивера ?

Например, имеет ли смысл использовать convertPoint: fromView: как показано ниже?

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

Используя утилиту NSLog, я убедился, что значение p совпадает с центром view1.

Заранее спасибо.

РЕДАКТИРОВАТЬ: для тех, кто заинтересован, я создал простой фрагмент кода, чтобы понять эти методы.

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}

Во втором случае convertPoint выражается в координатах superview (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];

«Получатель» - это стандартный термин objective-c для объекта, получающего сообщение (методы также известны как сообщения), поэтому в моем примере это получатель superview.


3
Спасибо за ответ, jrturton. Очень полезное объяснение. convertPointи convertRectразличаются типом возвращаемого значения. CGPointили CGRect. А как насчет fromи to? Есть ли какое-то практическое правило, которое я мог бы использовать? Спасибо.
Lorenzo B

из, когда вы хотите преобразовать, в, когда вы хотите преобразовать в?
jrturton,

3
Что, если суперпредставление не является прямым родительским представлением подпредставления, будет ли оно работать?
Van Du Tran

3
@VanDuTran да, если они находятся в одном окне (а это большинство просмотров в приложении для iOS)
jrturton

Отличный ответ. Мне очень помогло прояснить ситуацию. Любое дополнительное чтение?
JaeGeeTee 08

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не нужны.
Якуб Трухларж

Спасибо @ JakubTruhlář, я только что удалилself
фи

Большое спасибо! Ваша игровая площадка очень помогла мне понять, как работают эти преобразования.
Анвар Азизов

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 с точки зрения того, где они будут в себе.
Carl

Верный. Это не меняет вид. Метод возвращает CGRect. Что вы решите делать с этим CGRect, - это ваше дело. :-) В приведенном выше случае его можно использовать для перемещения одного представления в иерархию другого, сохраняя при этом его визуальное положение на экране.
Horsehoe7

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. Теперь возьмем точку ВНУТРИ системы координат 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];
}

Существенная часть, которая исходит из объяснения, данного в первых двух ответах выше, заключалась в том, что прямоугольник, из которого мне нужно было преобразовать, был границей кнопки добавления, а не ее фреймом.

Я не пробовал это с более сложной иерархией представлений, но подозреваю, что, используя представление, предоставленное в вызове метода (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, свойство center представления содержит известную центральную точку представления в системе координат супервизора.

Если это так, то я думаю, что не имеет смысла пытаться попросить супервизор преобразовать центр субпредставления ИЗ системы координат субпредставления, потому что значение не находится в системе координат субпредставления. Что имело бы смысл, так это сделать противоположное, то есть преобразовать систему координат супервизора в систему субпредставления ...

Вы можете сделать это двумя способами (оба должны давать одно и то же значение):

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

или

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

Я не понимаю, как это должно работать?


Мой пример был неверным, я обновил ответ. Как вы говорите, свойство center уже находится в координатном пространстве супервизора.
jrturton

1

Еще один важный момент об использовании этих API. Убедитесь, что родительская цепочка представлений завершена между прямоугольником, который вы конвертируете, и представлением в / из. Например - aView, bView и cView -

  • aView - это часть bView
  • мы хотим преобразовать aView.frame в cView

Если мы попытаемся выполнить метод до того, как bView будет добавлен в качестве подпредставления cView, мы получим ответ спама. К сожалению, для этого случая в методы не встроена защита. Это может показаться очевидным, но об этом следует помнить в тех случаях, когда преобразование проходит через длинную цепочку родителей.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.