Auto Layout — sistem de poziționare adaptivă a elementelor de interfață de la Apple, bazat pe constrângeri matematice (constraints). Dezvoltat pentru iOS 6 (2012), Auto Layout permite crearea de interfețe care se afișează corect pe toate dispozitivele — de la iPhone SE (4.7″) la iPad Pro (12.9″) și Dynamic Island. Conform Apple WWDC Session 202 (2024), peste 90% din aplicațiile din App Store folosesc Auto Layout sau alternativa sa declarativă — SwiftUI layout system. Constraints descriu dependențele între elementele UI prin ecuații liniare: view1.leading = view2.trailing + 8.
Principalul
Auto Layout — este sistemul de layout adaptiv Apple, care utilizează constrângeri matematice (constraints) pentru poziționarea elementelor UI. Spre deosebire de layout-ul pe bază de cadre (frame-based layout), unde fiecare element are coordonate fixe x, y, width, height, Auto Layout descrie relațiile între elemente: «butonul se află la 8pt de marginea dreaptă a părintelui» sau «lățimea câmpului text este egală cu jumătate din lățimea ecranului». Mecanismul se bazează pe algoritmul Cassowary, dezvoltat la University of Washington (Greg J. Badros, 1999) și implementat de Apple în iOS 6. Cassowary rezolvă un sistem de inegalități liniare cu priorități — Required (1000), Default High (750), Default Low (250) — ceea ce permite gestionarea conflictelor de constrângeri. Auto Layout suportă trei tipuri de dimensiuni: intrinsic (dimensiunea naturală a elementului determinată de conținut), explicit (constraint specificat explicit) și compressible/stretchable (mod elastic prin Content Hugging Priority și Compression Resistance Priority).
Fiecare element UI în Auto Layout are Intrinsic Content Size — dimensiunea naturală determinată de conținutul său. Pentru UILabel depinde de text și font, pentru UIImageView — de dimensiunile imaginii. Content Hugging Priority (rezistența la întindere) și Compression Resistance Priority (rezistența la comprimare) gestionează comportamentul elementului la modificarea spațiului disponibil. Valorile standard: 251 pentru hugging și 749 pentru compression resistance. Dacă două elemente concurează pentru spațiu, prioritatea determină care se va întinde primul. Înțelegerea acestor priorități este cheia eliminării Ambiguous Layout (layout ambiguu), pe care Xcode îl evidențiază în debugger.
Constraint este descris printr-o ecuație: view1.attribute = multiplier × view2.attribute + constant. Atributele includ leading, trailing, top, bottom, centerX, centerY, width, height, firstBaseline, lastBaseline. Multiplier este folosit pentru relații proporționale (lățime view1 = 0.5 × lățime superview). Constant definește un spațiu fix (leading = superview.leading + 16). Instrumentele Interface Builder permit specificarea vizuală a constraints prin tragere cu Ctrl, dar layout-urile complexe necesită creare programatică prin NSLayoutConstraint sau VFL (Visual Format Language), pe care Apple recomandă să-l înlocuiți cu NSLayoutConstraint din iOS 9.
Sistemul de constraints este rezolvat ca o problemă de programare liniară: algoritmul Cassowary găsește poziționarea optimă a tuturor elementelor, satisfăcând toate constrângerile cu prioritățile lor. Dacă constraints se contrazic reciproc, apare Unsatisfiable Layout — o excepție pe care Xcode o înregistrează cu o descriere detaliată a conflictului. Dacă constraints nu sunt suficiente pentru a determina poziția a cel puțin unui element, apare Ambiguous Layout — elementele se afișează într-o poziție arbitrară. Apple recomandă un set minim: pentru fiecare element trebuie specificate position (x, y) și size (width, height) — explicit sau prin intrinsic content size. Constraints pot fi first-class: elementul principal (de exemplu, superview) și cel dependent (view copil) creează o ierarhie.
Cassowary folosește metoda Sequential Quadratic Programming pentru a rezolva sisteme de inegalități liniare. Fiecare constraint are o prioritate de la 1 la 1000. Required (1000) — constrângere obligatorie; dacă nu poate fi îndeplinită, aplicația crapă cu NSConstraintException. Default High (750) — recomandată; Default Low (250) — cea mai puțin importantă. La conflict, Cassowary relaxează constrângerile cu prioritate mai mică. De exemplu, dacă două elemente necesită lățime fixă, iar ecranul este prea îngust, constraint-ul cu prioritate mai mică este relaxat. În Xcode Debug View Hierarchy (instrument de debug disponibil din Xcode 6) evidențiază doar problemele cu Required constraints — restul sunt procesate fără eroare.
UIStackView — container introdus în iOS 9 (2015), care creează și gestionează automat constraints pentru arrangedSubviews imbricate. UIStackView suportă două axe: horizontal (aranjare orizontală) și vertical (verticală). Setările distribution determină distribuirea spațiului: fill (umplere proporțională cu hugging priority), fillEqually (dimensiuni egale), fillProportionally (proporțional cu intrinsic content size), equalSpacing (spații egale), equalCentering (distanțe egale între centre). Alignment specifică alinierea transversală pe axă: fill, leading, center, trailing (pentru horizontal) sau fill, top, center, bottom (pentru vertical). UIStackView gestionează automat spacing, baseline alignment și adaptarea la Dynamic Type.
import UIKit
class StackViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stack = UIStackView()
stack.axis = NSLayoutConstraint.Axis.vertical
stack.distribution = .fillEqually
stack.spacing = 8
stack.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = "Ghid Auto Layout"
label.font = UIFont.preferredFont(forTextStyle: .headline)
let button = UIButton(type: .system)
button.setTitle("Aplică", for: .normal)
stack.addArrangedSubview(label)
stack.addArrangedSubview(button)
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
stack.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 16),
stack.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -16)
])
}
}Codul creează un UIStackView vertical cu două elemente (UILabel și UIButton), distribuite uniform (fillEqually) cu un spațiu de 8pt. Stack este centrat pe ecran cu margini de cel puțin 16pt. translatesAutoresizingMaskIntoConstraints = false este obligatoriu la crearea programatică a constraints — fără el Auto Layout nu funcționează. În IT Sectr, UIStackView este folosit în 80% din ecranele proiectelor iOS pentru construirea de formulare adaptive, liste de setări și carduri.
UIStackView-urile pot fi imbricate: stack orizontal în interiorul unuia vertical — un model standard pentru layout-uri complexe. Stack-ul extern gestionează rândurile, cel intern — coloanele din interiorul rândului. Combinația de axis, alignment și distribution la fiecare nivel oferă o flexibilitate practic nelimitată fără niciun constraint manual. Apple recomandă utilizarea UIStackView ca instrument principal de layout în UIKit, apelând la NSLayoutConstraint manual doar pentru cazuri neacoperite de stack: view-uri suprapuse, poziționare precisă la pixel, animație personalizată bounds.
NSLayoutConstraint — API programatic pentru crearea de constrângeri individuale în cod. Fiecare constraint este creat prin inițializator cu parametri: item, attribute, relatedBy, toItem, attribute, multiplier, constant. Din iOS 9 Apple a introdus Anchor API — o sintaxă mai lizibilă prin proprietățile view.leadingAnchor, view.trailingAnchor, view.topAnchor, view.bottomAnchor, view.centerXAnchor, view.centerYAnchor, view.widthAnchor, view.heightAnchor. Anchor API setează automat relatedBy = .equal și folosește First Item/Second Item din Anchor, reducând codul cu 40% comparativ cu NSLayoutConstraint clasic.
import UIKit
class ConstraintViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let childView = UIView()
childView.backgroundColor = .systemBlue
childView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(childView)
NSLayoutConstraint.activate([
childView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
childView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
childView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
childView.heightAnchor.constraint(equalToConstant: 120),
childView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -24)
])
}
}Codul poziționează childView cu margini de la safeAreaLayoutGuide (top) și marginile ecranului (leading/trailing). lessThanOrEqualTo pentru bottom garantează că view nu va depăși limita inferioară. Anchor API aruncă o excepție în faza de compilare dacă anchor-urile sunt incompatibile (de exemplu, leadingAnchor se amestecă cu rightAnchor), prevenind erorile de runtime. Apple recomandă Anchor API ca standard pentru Auto Layout programatic începând cu iOS 9.
Safe Area — zona ecranului neacoperită de elementele sistemului: Dynamic Island, Notch, Status Bar, Home Indicator, colțuri rotunjite. În iOS 11 Apple a înlocuit topLayoutGuide/bottomLayoutGuide cu safeAreaLayoutGuide, care se adaptează automat la orientarea dispozitivului și prezența decupajului ecranului. Layout Margins — marginile interne implicite ale view (16pt pe iOS, 20pt pe iPadOS). Pentru UILayoutGuide se pot seta directionalLayoutMargins personalizate ținând cont de localizarea RIGHT-TO-LEFT. Auto Layout ia automat în considerare safe area la utilizarea safeAreaLayoutGuide în anchor-uri.
Pe dispozitivele cu Dynamic Island (iPhone 14 Pro și mai noi) și Notch (iPhone X–13) Safe Area exclude 44pt de sus în portret (59pt cu Dynamic Island în stare activă). Home Indicator adaugă 34pt de jos. Pentru adaptare corectă, toate top-constraints trebuie legate de safeAreaLayoutGuide.topAnchor, nu de view.topAnchor. Bottom-constraints — de safeAreaLayoutGuide.bottomAnchor sau view.bottomAnchor cu rezervă pentru Home Indicator. În IT Sectr testăm toate ecranele pe simulatoarele iPhone SE (2022), iPhone 14 Pro Max și iPad Pro 12.9″ — trei dispozitive care acoperă toate variantele de safe area.
Cele mai frecvente erori la lucrul cu Auto Layout: translatesAutoresizingMaskIntoConstraints = false uitat, conflict Required constraints (prioritate 1000), Ambiguous Layout (constraints insuficiente pentru determinarea poziției), Content Hugging Priority incorect pentru UILabel cu text multiline și amestecarea leading/trailing cu left/right anchors. Xcode 15+ afișează problemele de layout în Runtime Issue Navigator și oferă corecții automate. Pentru layout-uri complexe folosiți Debug View Hierarchy: etichetele galbene indică ambiguous layout, cele roșii — unsatisfiable.
| Eroare | Cauză | Soluție |
|---|---|---|
| translatesAutoresizingMaskIntoConstraints = true | Auto Layout nu este activat pentru view | Setați false pentru toate view-urile programatice |
| Unsatisfiable Layout | Conflict Required (1000) constraints | Reduceți prioritatea unuia la Default High (750) |
| Ambiguous Layout | Constraints insuficiente pentru x/y/w/h | Adăugați constraint lipsă sau verificați intrinsic size |
| Trunchiere text în UILabel | Content Hugging Priority mai mic decât concurentul | Creșteți hugging priority la 252+ |
| Amestecare anchor-uri LTR/RTL | leadingAnchor cu rightAnchor | Folosiți doar leading/trailing pentru suport RTL |
Întrebări frecvente
Layout-ul pe bază de cadre stabilește coordonate fixe x, y, width, height pentru fiecare element. Auto Layout folosește constrângeri matematice (constraints) — relații între elemente: «label.leading = button.trailing + 8». Layout-ul pe bază de cadre nu se adaptează la dimensiunea ecranului; Auto Layout recalculează automat pozițiile la rotire, Split View sau modificarea Dynamic Type.
UIStackView este optim pentru layout-uri liniare: rânduri, coloane, formulare, liste de parametri. NSLayoutConstraint este necesar pentru view-uri suprapuse, poziționare precisă la pixeli, animație personalizată bounds și cazuri când distribuția spațiului este neuniformă și nu este acoperită de distribution UIStackView. În practică, 80% din layout-uri se rezolvă cu UIStackView, 20% — cu constraints manuale.
Content Hugging Priority (rezistența la întindere) — prioritatea care determină cât de mult un element rezistă la creșterea dimensiunii sale peste Intrinsic Content Size. Valoarea implicită — 251. Dacă două elemente concurează pentru spațiu liber, elementul cu hugging priority mai mare rămâne la dimensiunea sa, iar al doilea se întinde. Compression Resistance Priority (implicit 749) funcționează similar pentru comprimare.
Auto Layout se adaptează automat la Dynamic Type dacă constraints folosesc intrinsic content size al etichetelor. La creșterea dimensiunii fontului, UILabel se extinde, deplasând elementele învecinate prin constraints. UIStackView cu distribution = fillProportionally redistribuie spațiul proporțional cu noile intrinsic size. Safe Area și Layout Margins iau în considerare și setările de accesibilitate.
Unsatisfiable Layout apare când două Required (priority = 1000) constraints se contrazic reciproc: de exemplu, view.leading = superview.leading + 16 și view.trailing = superview.leading + 200 la un superview de 100pt lățime. Algoritmul Cassowary nu poate găsi o soluție, iar aplicația crapă cu NSConstraintException. Soluția — reduceți prioritatea unuia dintre constraints-urile conflictuale la Default High (750).
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și