Почему на iOS, использующей SnapKit, исходная точка подпредставления не является исходной точкой его супервизора?

Обычно, когда вы думаете об отношениях суперпредставления и подпредставления, можно ожидать, что исходная точка подпредставления будет содержаться в супервизии (обычно являющейся исходной точкой супервизора). Однако в UIKit на iOS это не так. Почему исходная точка подпредставления (0,0) UIWindow?

Чтобы продемонстрировать, следующий код создает два UIViews, одно из которых является подпредставлением другого. По какой-то причине исходная точка innerView оказывается (0,0) всего UIWindow. Я ожидал бы, что исходная точка innerView будет исходной точкой outerView (чья исходная точка продиктована центральной точкой его супервизора).

Что я получаю:

self.edgesForExtendedLayout = []

let superView = self.view!
superView.backgroundColor = .white

let outerView = UIView()
outerView.backgroundColor = .cyan
superView.addSubview(outerView)

outerView.snp.makeConstraints { make in
    make.width.equalToSuperview().inset(20)
    make.height.equalTo(superView.snp.height).dividedBy(3)
    make.center.equalToSuperview()
 }

let innerView = UIView()
innerView.backgroundColor = .purple
outerView.addSubview(innerView)
innerView.snp.makeConstraints { (make) in
    make.height.width.equalToSuperview().dividedBy(2)
}

Фактический

Чего я хочу:

Добавление дополнительной SnapKit линии ограничения устанавливает innerView исходную точку в исходную точку outerView, но я считаю этот шаг лишним.

self.edgesForExtendedLayout = []

let superView = self.view!
superView.backgroundColor = .white

let outerView = UIView()
outerView.backgroundColor = .cyan
superView.addSubview(outerView)

outerView.snp.makeConstraints { make in
    make.width.equalToSuperview().inset(20)
    make.height.equalTo(superView.snp.height).dividedBy(3)
    make.center.equalToSuperview()
 }

let innerView = UIView()
innerView.backgroundColor = .purple
outerView.addSubview(innerView)
innerView.snp.makeConstraints { (make) in
    make.top.left.equalToSuperview() // Set origins equal
    make.height.width.equalToSuperview().dividedBy(2)
}

Ожидается

Вероятно, это фундаментальное непонимание иерархии представлений в iOS, поскольку до сих пор я полагался на раскадровки. Это моя первая попытка освоить программный интерфейс на iOS, поэтому я все еще пытаюсь осмыслить все нюансы. Прямо сейчас доказательства заставляют меня поверить, что каждое отдельное представление - независимо от того, где оно существует в иерархии представлений - начинается с позиции (0,0) ключа UIWindow.


person Nick Alexander    schedule 30.07.2017    source источник
comment
Не обращайте внимания на тот факт, что я не использую equalToSuperview() в некоторых случаях, когда это имело бы смысл. Это было для моих целей тестирования, и я забыл вернуться.   -  person Nick Alexander    schedule 30.07.2017


Ответы (1)


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

import UIKit

class MeasureViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //view will start at 0,0 of superview which is the main view and be the same size

        let green = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
        green.backgroundColor = .green
        self.view.addSubview(green)

        //first inside view  half of outers width and height
        let blue = UIView(frame: CGRect(x: 0, y: 0, width: green.bounds.midX, height: green.bounds.midY))
        blue.backgroundColor = .blue
        // in this case it is self.view.center and outer.center but won't always be just works because coordinate space matches
        blue.center = green.center
        green.addSubview(blue)

        let white = UIView(frame: CGRect(x: 0, y: 0, width: blue.bounds.midX, height: blue.bounds.midY))
        //will not be in the center of firstInside because it is using the coordinate derived from outer
        white.center = blue.center
        white.backgroundColor = .white
        blue.addSubview(white)

        let redDot = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
        redDot.layer.cornerRadius = 5
        redDot.backgroundColor = .red
        //will be the center of blue center because it is half the width and height and be correct
        redDot.center = CGPoint(x: blue.bounds.midX, y: blue.bounds.midY)
        blue.addSubview(redDot)

        //same size as white view above but no origin.  this is bad
        let noOriginView = UIView()
        noOriginView.backgroundColor = .cyan
        noOriginView.bounds.size = CGSize(width: blue.bounds.midX, height: blue.bounds.midY)

        blue.addSubview(noOriginView)
        print("Test withoutFrame \(noOriginView.frame.origin)")
    }

}

Описание и снимок экрана  screenshot

Эти основы верны для ручного макета, автоматического макета и Snapkit. С помощью autolayout и Snapkit вы не будете напрямую устанавливать точки и границы, но должны быть ограничения, которые делают то же самое.

С Autolayout: после долгого тестирования с Autolayout я действительно думаю, что если в представлении отсутствуют ограничения положения, он преобразуется в 0,0 окна, что означает, что фактическое начало координат будет отрицательными значениями для x, y с приведенными выше примерами настройки для преобразования начало координат в пространстве координат окна.

person agibson007    schedule 30.07.2017
comment
Ах, спасибо! Я знал, что это должно быть что-то простое, чего мне не хватало. Я заметил странности в свойствах рамки и привязки, но не понял, какое это значение. Итак, следующий вопрос: почему addSubview по своей сути не устанавливает фрейм дочернего элемента в родительский? Это могло бы показаться мне логичным, но должна быть причина, по которой это не так? Спасибо за подробный ответ. - person Nick Alexander; 30.07.2017
comment
Итак, еще одно продолжение. Я ожидал, что, если я использую let innerView = UIView(frame: outerView.frame, эта innerView исходная точка будет равна outerView исходной точке - как то, что я хочу, чтобы произошло. Однако этого все же не произошло. Я все еще что-то упускаю? В вашем примере вы имели дело в основном с центрами. Я не хочу сосредотачивать свои взгляды; Я хочу, чтобы мой innerView начинался в крайнем левом верхнем положении (т. Е. В исходной точке) моего outerView - person Nick Alexander; 30.07.2017
comment
Если вы использовали let innerView = UIView (frame: outerView.bounds), вы получите ту же исходную точку. Используя фрейм, вы получаете координату начала координат outerView, которая может быть отличной от 0,0. Предположим, что внешнее представление имеет начало 50,50, тогда у innerView будет исходное значение 50,50 из outerView, которое не будет 0,0 - person agibson007; 30.07.2017
comment
Последний пример - это результат, который я хочу. Я хочу, чтобы innerView был надлежащим потомком outerView - существовал только в области outerView. Если outerView происхождение (50, 50), я хочу, чтобы innerView также происходил от (50, 50). К сожалению, я до сих пор не наблюдаю такого поведения, когда использую UIView(frame: outerView.frame. Я также не вижу ожидаемого поведения при использовании UIView(frame: outerView.bounds. Возможно, в вопросе было неясно, но второе изображение - это результат, который я хочу, но я не хочу, чтобы вручную устанавливали ограничения Top и Left вручную. - person Nick Alexander; 30.07.2017
comment
Проведя несколько экспериментов с простым UIView без использования AutoLayout и SnapKit, представления ведут себя именно так, как я ожидал. Моя проблема существует где-то в моих ограничениях AutoLayout. - person Nick Alexander; 30.07.2017
comment
@NickAlexander, вы добавляете их все в основной вид или добавляете в качестве подсистемы, потому что система координат различается в зависимости от того, где вы добавляете. Он сбрасывается каждый раз в дочернем представлении. - person agibson007; 30.07.2017
comment
Да, способ сброса координат имеет смысл, и это именно то, что я ожидал. Если outerView происходит в (50,50) суперпредставления, а innerView является подпредставлением outerView, тогда innerView origin равно (0,0), который будет верхним левым углом outerView. Однако по какой-то причине такое поведение не наблюдалось после применения моих ограничений AutoLayout. Я добавляю outerView как подпредставление self.view и добавляю innerView как подвид outerView, и то и другое путем вызова addSubview. - person Nick Alexander; 30.07.2017
comment
Я хочу, чтобы innerView начинался в верхнем левом углу outerView или (0,0). Если innerView является подвидом outerView, разве не должно быть так по умолчанию? - person Nick Alexander; 30.07.2017
comment
Это результат: imgur.com/B75yJEK Это происходит потому, что AutoLayout не имеет ограничений для положения вид? Я все еще не ожидал, что представление будет расположено вне его супервизора. - person Nick Alexander; 30.07.2017
comment
Возможно, Snapkit переводит окно 0,0 и автоматически его назначает. Дай мне проверить. Никогда не использовал это - person agibson007; 30.07.2017
comment
Я наблюдаю точно такое же поведение, если использую ручные ограничения AutoLayout вместо SnapKit. Я предполагаю, что AutoLayout просто паникует, потому что он не имеет ограничения позиции и автоматически помещает его в (0,0) корневого представления ViewController. Я полагаю, мне просто нужно установить верхнее и левое поля, равные супервизору для каждого вложенного подпредставления, как в примере с источником. - person Nick Alexander; 30.07.2017
comment
@NickAlexander, возможно, autolayout переводит вид в координаты окна. Мне нужен лучший интернет, чем телефон, чтобы проверить. - person agibson007; 30.07.2017
comment
Похоже, мой комментарий чуть выше верен, и вы что-то знаете по своему вопросу. После долгого тестирования с Autolayout я почти уверен, что если в представлении отсутствуют ограничения положения, оно преобразуется в 0,0 окна, что означает, что в нашем примере фактическое начало координат будет отрицательным x, y, чтобы приспособиться к этому преобразованию в окно координатное пространство. Я обновил нижнюю часть своего ответа, чтобы заявить об этом. Я никогда не отказывался от ограничений по позициям, поэтому я не наткнулся на это. Удачи - person agibson007; 31.07.2017