Irbisa · Шпаргалка
Auto Layout и UIKit-вёрстка
Constraint-ы, intrinsic content size, UIStackView, frame-ы, safe area
Что такое Auto Layout и чем он отличается от вёрстки на frame?
Какое уравнение выражает constraint в Auto Layout и что такое layout anchors?
// The equation spelled out with the original initializerNSLayoutConstraint(item: avatar, attribute: .leading, relatedBy: .equal, toItem: card, attribute: .leading, multiplier: 1, constant: 16).isActive = true…Сколько constraint-ов нужно view, чтобы её layout был однозначным?
Вы задали constraint-ы для view, созданной в коде, но она не появляется там, где ожидалось, а в консоли список сломанных constraint-ов. Что здесь не так?
override func viewDidLoad() { super.viewDidLoad() let card = UIView() card.backgroundColor = .systemBlue view.addSubview(card) NSLayoutConstraint.activate([ card.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), card.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), card.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), card.heightAnchor.constraint(equalToConstant: 120), ])}Этот код настройки падает с «Unable to activate constraint ... because they have no common ancestor». Что здесь сделано не в том порядке?
let badge = UILabel()badge.text = "NEW"badge.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ badge.topAnchor.constraint(equalTo: card.topAnchor, constant: 8), badge.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -8),]) card.addSubview(badge)Что такое safe area и как в UIKit удержать контент внутри неё?
// Full-bleed background: the view's edges, not the safe areaNSLayoutConstraint.activate([ background.topAnchor.constraint(equalTo: view.topAnchor), background.bottomAnchor.constraint(equalTo: view.bottomAnchor), background.leadingAnchor.constraint(equalTo: view.leadingAnchor), background.trailingAnchor.constraint(equalTo: view.trailingAnchor),…Почему constraint-ы ставят к
leadingAnchorиtrailingAnchor, а не кleftAnchorиrightAnchor?Что такое
UIStackViewи какие у него плюсы и минусы?let metadataRow = UIStackView(arrangedSubviews: [authorLabel, dateLabel])metadataRow.axis = .horizontalmetadataRow.spacing = 4 let content = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel, metadataRow])content.axis = .vertical…В горизонтальном
UIStackViewвсе элементы получились одной ширины, а иконка растянута на всю высоту строки. Какие два свойства заданы неверно?let row = UIStackView(arrangedSubviews: [icon, title, chevron])row.axis = .horizontalrow.spacing = 8 // wrong: equal widths, and the icon stretched to the row height// row.distribution = .fillEqually…Что такое intrinsic content size и как content hugging и compression resistance решают, какая view уступит?
let title = UILabel()title.text = "A long article title that will not fit"let badge = UILabel()badge.text = "NEW" // The title stretches into free space and truncates first…Что делают
@IBDesignableи@IBInspectableи чем команды заменяют live rendering сегодня?@IBDesignablefinal class RoundedButton: UIButton { @IBInspectable var cornerRadius: CGFloat = 0 { didSet { layer.cornerRadius = cornerRadius } }…Что такое приоритеты constraint-ов и почему для constraint-а, который «обязан» выполняться, ставят 999, а не 1000?
// Preferred width: 320 if it fits, but never past the marginslet preferred = card.widthAnchor.constraint(equalToConstant: 320)preferred.priority = .defaultHigh // 750: yields to required constraintsNSLayoutConstraint.activate([ preferred, card.centerXAnchor.constraint(equalTo: view.centerXAnchor),…Этот бейдж после изменения
countрисует новое число, но сохраняет старую ширину. Чего не хватает?final class BadgeView: UIView { var count = 0 { didSet { setNeedsDisplay() } } override var intrinsicContentSize: CGSize { let text = "\(count)" as NSString let font = UIFont.preferredFont(forTextStyle: .caption1) let size = text.size(withAttributes: [.font: font]) return CGSize(width: ceil(size.width) + 16, height: ceil(size.height) + 8) } override func draw(_ rect: CGRect) { // draws the pill and the number }}Чем отличается привязка к краю view, к её
layoutMarginsGuideи к еёreadableContentGuide?// Three edges with three meaningslabel.leadingAnchor.constraint(equalTo: view.leadingAnchor) // literal boundslabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor) // plus marginslabel.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor) // readable width // Directional margins mirror in right-to-left languages…Чем отличаются
setNeedsLayout(),layoutIfNeeded()иlayoutSubviews()?final class GradientView: UIView { private let gradient = CAGradientLayer() override init(frame: CGRect) { super.init(frame: frame) layer.addSublayer(gradient)…Layout должен меняться при переключении состояния. Вы меняете
constant, переключаете активные constraint-ы или меняете приоритет?private var heightConstraint: NSLayoutConstraint! // stored once, reused func setExpanded(_ expanded: Bool, animated: Bool) { heightConstraint.constant = expanded ? 240 : 88 guard animated else { view.setNeedsLayout(); return } UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }…Найдите баг: панель должна плавно выезжать снизу, но прыгает на новое место без анимации.
final class PlayerViewController: UIViewController { private let panel = UIView() // activated in viewDidLoad with leading, trailing and height 300 private lazy var panelBottom = panel.bottomAnchor.constraint( equalTo: view.bottomAnchor, constant: 300) // starts off-screen func showPanel() { panelBottom.constant = 0 UIView.animate(withDuration: 0.3) { self.panel.layoutIfNeeded() } }}В консоли появляется «Unable to simultaneously satisfy constraints». Как найти виновника?
Что должно выполняться, чтобы ячейка таблицы сама подстраивала высоту под контент?
final class MessageCell: UITableViewCell { private let title = UILabel() private let body = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier)…Как разложить контент внутри
UIScrollViewна Auto Layout так, чтобы он действительно прокручивался?let scrollView = UIScrollView()let content = UIStackView() // one container for everythingcontent.axis = .verticalcontent.spacing = 16[scrollView, content].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }view.addSubview(scrollView)…Текстовое поле внизу формы оказывается под клавиатурой. Как его правильно поднять в современном UIKit и в чём ошибался старый код на нотификациях?
// iOS 15+: one constraint, animated together with the keyboardNSLayoutConstraint.activate([ composer.leadingAnchor.constraint(equalTo: view.leadingAnchor), composer.trailingAnchor.constraint(equalTo: view.trailingAnchor), composer.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -8), scrollView.bottomAnchor.constraint(equalTo: composer.topAnchor), // content stays visible…Что такое size classes и как переключаться между двухколоночной вёрсткой на iPad и одноколоночной на iPhone?
// wrong: a hardware question; an iPad in Split View is still .pad but gets a compact width// if UIDevice.current.userInterfaceIdiom == .pad { showTwoColumns() } // right: the trait describes the space this view actually gotprivate func applyLayout() { let regular = traitCollection.horizontalSizeClass == .regular…Как в UIKit-вёрстке обрабатывать поворот устройства?
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) let wide = size.width > size.height coordinator.animate(alongsideTransition: { _ in self.contentStack.axis = wide ? .horizontal : .vertical…Один экран загружает CPU на 100% и зависает, а
layoutSubviews()вызывается снова и снова. Что вызывает петлю обратной связи в Auto Layout?// wrong: the constant is derived from the frame this pass produced; whenever it// changes this view's own size, the next pass gets new input and the cycle never// ends. layoutIfNeeded() starts that next pass inside this one.override func layoutSubviews() { super.layoutSubviews() insetConstraint.constant = bounds.width * 0.1…Прокручиваемый список роняет кадры, а Instruments показывает время в Auto Layout. Что вы проверите и что поменяете?