Странная проблема при настройке ограничений автоматического макета в коде

Я хочу иметь вид нижнего колонтитула UITableView со статической ячейкой, который имеет три метки, которые расположены на одинаковом расстоянии друг от друга, например (из симулятора): введите здесь описание изображения

Я могу предоставить представление нижнего колонтитула из моего контроллера табличного представления, используя этот вызов делегата:

override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
    // construct view here
    return view
}

И я могу построить представление двумя способами:

  • Создайте метки и разделители в коде и добавьте соответствующие ограничения.
  • Сделайте все это в файле XIB, затем загрузите представление из файла.

Моя проблема в том, что первый подход не работает, а второй работает.

Это мой код для первого подхода:

override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {

    if section == 0 {

        // Create footer view
        let view = UIView()
        view.backgroundColor = UIColor.yellowColor()
        view.clipsToBounds = false
        view.layer.borderColor = UIColor.greenColor().CGColor
        view.layer.borderWidth = 2
        view.setTranslatesAutoresizingMaskIntoConstraints(false)

        // Create labels
        var labels: [UIView] = []
        for name in ["Label 1", "AAAAAABBB", "Last label"] {
            let v = UILabel()

            v.font = UIFont.preferredFontForTextStyle(UIFontTextStyleFootnote)
            v.textColor = UIColor.darkTextColor()
            v.textAlignment = .Center
            v.text = name
            v.setTranslatesAutoresizingMaskIntoConstraints(false)

            view.addSubview(v)
            labels += [v]
        }

        // Create spacers
        var spacers: [UIView] = []
        for i in 1...4 {
            let v = UIView()

            v.backgroundColor = UIColor.blueColor() // Background color is just so we can see where the view is and what size it has
            v.setTranslatesAutoresizingMaskIntoConstraints(false)

            view.addSubview(v)
            spacers += [v]
        }

        // Constrain all views to top and bottom of superview
        for i in labels + spacers {
            view.addConstraint(NSLayoutConstraint(item: i, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0))
            view.addConstraint(NSLayoutConstraint(item: i, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0))
        }

        // Equal width for labels
        labels.pairs {
            view.addConstraint(NSLayoutConstraint(item: $0, attribute: .Width, relatedBy: .Equal, toItem: $1, attribute: .Width, multiplier: 1, constant: 0))
        }

        // Equal width for spacers
        spacers.pairs {
            view.addConstraint(NSLayoutConstraint(item: $0, attribute: .Width, relatedBy: .Equal, toItem: $1, attribute: .Width, multiplier: 1, constant: 0))
        }

        view.addConstraint(NSLayoutConstraint(item: view, attribute: .Left, relatedBy: .Equal, toItem: spacers[0], attribute: .Left, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: spacers[0], attribute: .Right, relatedBy: .Equal, toItem: labels[0], attribute: .Left, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: labels[0], attribute: .Right, relatedBy: .Equal, toItem: spacers[1], attribute: .Left, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: spacers[1], attribute: .Right, relatedBy: .Equal, toItem: labels[1], attribute: .Left, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: labels[1], attribute: .Right, relatedBy: .Equal, toItem: spacers[2], attribute: .Left, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: spacers[2], attribute: .Right, relatedBy: .Equal, toItem: labels[2], attribute: .Left, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: labels[2], attribute: .Right, relatedBy: .Equal, toItem: spacers[3], attribute: .Left, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: spacers[3], attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: 0))

        return view
    }
    else {
        return nil
    }
}

extension Array {
    func pairs(block: (Element, Element?)->()) {
        if count == 0 { return }
        if count == 1 { block(self.first!, nil) }

        var last = self[0]
        for i in self[1..<count] {
            block(last, i)
            last = i
        }
    }
}

Вот результат:

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

Совсем не то, что я ожидал, правда?

Теперь о втором методе. Вместо того, чтобы публиковать кучу скриншотов из Interface Builder, я создал пример проекта, доступный здесь специально для проверки этой проблемы. Если вы откроете его, файл FooterView.xib содержит представление нижнего колонтитула, созданное в IB, которое, насколько мне известно, имеет точно такую ​​же структуру представления и ограничения автоматического макета.

Используя это представление, например так:

return (NSBundle.mainBundle().loadNibNamed("FooterView", owner: self, options: nil).first as UIView)

дает результат, который вы видели на первом снимке экрана, и это именно то, что я хочу.

Таким образом, в Interface Builder ограничения работают так, как ожидалось. Почему это не работает, когда представления и ограничения создаются в коде? Что я упускаю?


person Alex    schedule 08.11.2014    source источник


Ответы (1)


Размер представления неизвестен во время создания, поэтому установка его setTranslatesAutoresizingMaskIntoConstraintsto true помогает:

view.setTranslatesAutoresizingMaskIntoConstraints(true)

Результат:

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

person zisoft    schedule 08.11.2014
comment
Оно работает! Но почему? Какое отношение setTranslatesAutoresizingMaskIntoConstraints имеет к тому, что размер неизвестен во время создания? - person Alex; 08.11.2014
comment
Я заметил, что представление не имеет ширины tableView.frame.width, и попытался установить это вручную. К сожалению, это не сработало, поэтому я поэкспериментировал с setTranslatesAutoresizingMaskIntoConstraints. Я думаю, что представление теперь получает conttaints, которое автоматически изменяет его размер до ширины tableView. - person zisoft; 08.11.2014
comment
Рад помочь! Кстати: действительно классный способ установить ограничения с помощью вашего расширения Array. - person zisoft; 08.11.2014