#ios #swift #autolayout
#iOS #swift #автозаполнение
Вопрос:
Я использую NSLayoutConstraint для ограничения представления. Я хочу, чтобы его высота по умолчанию занимала 50% экрана, но если для других компонентов недостаточно места (например, iphone в альбомной ориентации), вид может уменьшиться до 10% высоты.
Я пытаюсь:
let y1 = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
let y2 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .lessThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)
let y3 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)
К сожалению, это отображает только 10% высоты экрана.
Меня смущают две вещи:
-
Когда я устанавливаю неоднозначные ограничения, подобные этому, которые в основном говорят «от 10% до 50%», как он определяет, какую высоту ему дать? По умолчанию используется минимальный объем пространства?
-
Я думал, что ограничения должны иметь только одно решение. Почему я не получаю ошибку неоднозначности, поскольку любые высоты от 10% до 50% были бы допустимыми решениями здесь?
Наконец, как мне получить то, что я хочу, представление на 50%, которое при необходимости может уменьшаться?
Большое спасибо!
Комментарии:
1. «Почему я не получаю ошибку неоднозначности» Такой вещи, как ошибка неоднозначности, не существует.
2. @matt Ах, я прочитал это на сайте разработчика Apple «При использовании автоматической компоновки цель состоит в том, чтобы предоставить ряд уравнений, которые имеют одно и только одно возможное решение. Неоднозначные ограничения имеют более одного возможного решения. У неудовлетворительных ограничений нет допустимых решений «. и предполагаемая двусмысленность означала ошибку, но я предполагаю, что они просто означают, что это нежелательно в качестве макета, потому что оно неоднозначно.
3. Ну, подумайте об этом так. Если вы добавляете ограничения по одному, они неоднозначны после каждого оператора. Вы бы не хотели получать сообщение об ошибке только потому, что вы еще не закончили. Один ботинок — это не ошибка; вы просто не полностью одеты. Это плохо, но это может быть временным. Обе туфли на неправильных ногах — ошибка. 🙂
Ответ №1:
Вы можете сделать это, изменив Priority
ограничение высоты 50%.
Мы сообщим автоматической компоновке, что кнопка должна составлять не менее 10% от высоты представления.
И мы сообщим auto-layout, что мы хотим, чтобы кнопка составляла 50% от высоты представления, но:
.priority = .defaultHigh
в котором говорится: «вы можете нарушить это ограничение, если это необходимо».
Итак…
// constrain button Top to view Top
let btnTop = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .top, multiplier: 1, constant: 0)
// button Height Greater Than Or Equal To 10%
let percent10 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual,
toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)
// button Height Equal To 50%
let percent50 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal,
toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)
// let auto-layout break the 50% height constraint if necessary
percent50.priority = .defaultHigh
[btnTop, percent10, percent50].forEach {
$0.isActive = true
}
Или с более современным синтаксисом…
let btnTop = button.topAnchor.constraint(equalTo: view.topAnchor)
let percent10 = button.heightAnchor.constraint(greaterThanOrEqualTo: view.heightAnchor, multiplier: 0.10)
let percent50 = button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.50)
percent50.priority = .defaultHigh
NSLayoutConstraint.activate([btnTop, percent10, percent50])
Теперь, какие бы другие элементы пользовательского интерфейса у вас ни были, которые уменьшат доступное пространство, авто-макет установит высоту кнопки на «как можно ближе к 50%, но всегда не менее 10%»
Вот полный пример для демонстрации. Я использую две метки (синяя сверху как «кнопка» и красная снизу). Нажатие увеличит высоту красной метки, пока она не начнет «поднимать нижнюю часть» или «сжимать» синюю метку:
class ExampleViewController: UIViewController {
let blueLabel = UILabel()
let redLabel = UILabel()
var viewSafeAreaHeight: CGFloat = 0
var adjustableLabelHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
[blueLabel, redLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
v.textAlignment = .center
v.textColor = .white
v.numberOfLines = 0
}
blueLabel.backgroundColor = .blue
redLabel.backgroundColor = .red
view.addSubview(blueLabel)
view.addSubview(redLabel)
// blueLabel should be 50% of the height if possible
// otherwise, let it shrink to minimum of 10%
// so, we'll constrain redLabel to the bottom of the view
// and give it a Height constraint that we can change
// so it can "compress" blueLabel
// we'll constrain the bottom of blueLabel to stay above the top of redLabel
// let's respect the safe-area
let safeArea = view.safeAreaLayoutGuide
// start by horizontally centering both elements,
// and 75% of the width of the view
blueLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
redLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
blueLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
redLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
// now, let's constrain redLabel to the bottom
redLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
// tell the Bottom of blueLabel to stay Above the top of redLabel
blueLabel.bottomAnchor.constraint(lessThanOrEqualTo: redLabel.topAnchor, constant: 0.0).isActive = true
// next, constrain the top of blueLabel to the top
let blueLabelTop = blueLabel.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 0.0)
// blueLabel height must be At Least 10% of the view
let blue10 = blueLabel.heightAnchor.constraint(greaterThanOrEqualTo: safeArea.heightAnchor, multiplier: 0.10)
// blueLabel should be 50% if possible -- so we'll set the
// Priority on that constraint to less than Required
let blue50 = blueLabel.heightAnchor.constraint(equalTo: safeArea.heightAnchor, multiplier: 0.50)
blue50.priority = .defaultHigh
// start redLabel Height at 100-pts
adjustableLabelHeightConstraint = redLabel.heightAnchor.constraint(equalToConstant: 100.0)
// we'll be increasing the Height constant past the available area,
// so we also need to change its Priority so we don't get
// auto-layout conflict errors
// and, we need to set it GREATER THAN blueLabel's height priority
adjustableLabelHeightConstraint.priority = UILayoutPriority(rawValue: blue50.priority.rawValue 1)
// activate those constraints
NSLayoutConstraint.activate([blueLabelTop, blue10, blue50, adjustableLabelHeightConstraint])
// add a tap gesture recognizer so we can increas the height of the label
let t = UITapGestureRecognizer(target: self, action: #selector(self.gotTap(_:)))
view.addGestureRecognizer(t)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
viewSafeAreaHeight = view.frame.height - (view.safeAreaInsets.top view.safeAreaInsets.bottom)
updateLabelText()
}
@objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
adjustableLabelHeightConstraint.constant = 50
updateLabelText()
}
func updateLabelText() -> Void {
let blueHeight = blueLabel.frame.height
let redHeight = redLabel.frame.height
let redConstant = adjustableLabelHeightConstraint.constant
let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent
percentFormatter.minimumFractionDigits = 2
percentFormatter.maximumFractionDigits = 2
guard let bluePct = percentFormatter.string(for: blueHeight / viewSafeAreaHeight) else { return }
var s = "SafeArea Height: (viewSafeAreaHeight)"
s = "n"
s = "Blue Height: (blueHeight)"
s = "n"
s = "(blueHeight) / (viewSafeAreaHeight) = (bluePct)"
blueLabel.text = s
s = "Tap to increase..."
s = "n"
s = "Red Height Constant: (redConstant)"
s = "n"
s = "Red Actual Height: (redHeight)"
redLabel.text = s
}
}