Auto Layout in programmacode: zo werkt het

  • een paar jaar geleden /
  • Door Roelf

In verreweg de meeste gevallen zul je met het Storyboard werken om Auto Layout constraints aan te brengen. Het kan echter ook in programmacode: Auto Layout constraints zijn objecten van de class NSLayoutConstraint met hun eigen methodes en properties. In dit artikel zie je hoe dit werkt.

Auto Layout in programmacode

Het eBook: UIKit - Views en Auto Layout

Apps bouwen met Swift - UIKit: Views en Auto Layout eBookDit artikel komt uit het eBook UIKit – Views en Auto Layout. Dit eBook van ruim 300 pagina’s bevat uitgebreide informatie en tientallen voorbeelden om alles te leren over twee van de belangrijkste onderdelen van UIKit: Views en Auto Layout.

Je leert hoe je met de bestaande views uit de Xcode Object Library kunt werken, maar ook hoe je je eigen views kunt maken. Met de uitgebreide hoofdstukken over Auto Layout en Size Classes leer je hoe je apps er op alle devices fraai kunnen uitzien. Ook leer je hoe je animatie kunt gebruiken om je apps aantrekkelijker te maken.

Meer informatie

Er bestaan twee mogelijkheden om Auto Layout-constraints in programmacode te gebruiken:

  • De ‘klassieke’ manier, die we in dit artikel gebruiken: je maakt voor elke benodigde constraint een NSLayoutConstraint-object en wijzigt de properties ervan. Hiervoor is een speciale initializer beschikbaar.
  • De alternatieve manier: met behulp van Visual Format Langage. Apple heeft een speciale ‘taal’ gemaakt waarmee je met behulp van één string een constraint kunt maken, zonder dat je de afzonderlijke properties van de NSLayoutConstraint dus hoeft op te geven.

Om het voorbeeld in dit artikel te gebruiken, maak je in Xcode een Single View App voor iPhone of iPad.

Auto Layout zonder Storyboard? In dit artikel zie je hoe dat werkt #iosdev Klik om te Tweeten

Auto Layout in code: de klassieke manier

Een voorbeeld van de klassieke manier zie je in de volgende app. De .viewDidLoad()-methode is gewijzigd: er wordt een grijze UIView gemaakt, die met behulp van vier constraints een hoogte van 100 punten en een breedte van 200 punten krijgt en die horizontaal en verticaal binnen de superview wordt gecentreerd.

Hieronder zie je het resultaat van deze regels: de grijze view heeft de juiste grootte gekregen en wordt zowel in portretmodus als in landschapsmodus gecentreerd.

Auto Layout zonder het Storyboard, in programmacode

De programmacode

Hoewel de code uit het vorige voorbeeld er wellicht wat complex lijkt uit te zien, valt het in de praktijk mee:

Stap 1

Er wordt een nieuwe subview gemaakt: een UIView. Deze krijgt een grijze achtergrondkleur. Omdat het frame van onze subview straks door Auto Layout zal worden berekend, gebruiken we de CGRect-property .zero, die een frame met nulwaarden oplevert.

Stap 2

Dit is een belangrijke stap! De subview wordt meteen aan de view-hiërarchie toegevoegd. Voor sommige constraints is dit noodzakelijk. De twee constraints voor de breedte en hoogte zouden geen probleem opleveren (dit zijn constante-constraints waarbij geen andere view dan onze subview is betrokken), maar voor de positie (gecentreerd in de superview) hebben we de superview nodig. Onthoud dus: als je in programmacode constraints toevoegt, moeten beide views al in de view-hiërarchie staan. Zo niet: crash!

Auto Layout crash

Stap 3

Je weet inmiddels dat het niet verplicht is om Auto Layout te gebruiken. iOS werkt intern echter altijd met Auto Layout: als een view geen Auto Layout constraints heeft, zal iOS die constraints bij het laden van de viewcontroller zelf maken. Het gebruikt daarvoor alle informatie die het kan vinden, bijvoorbeeld de positie en grootte op het Storyboard.

Zodra je op het Storyboard constraints aan een view toevoegt, zal iOS zelf geen constraints voor die view meer maken. Als je echter een view in programmacode maakt, zal iOS echter wel constraints voor die view gaan bedenken – zelfs als je je eigen constraints toevoegt! Dit levert natuurlijk allerlei conflicterende constraints op. Vandaar dat je, als je een view in programmacode toevoegt, iOS moet vertellen dat jij verder voor de Auto Layout constraints zorgt. Dat doe je met de property .translatesAutoresizingMaskIntoConstraints. Door hier de waarde false aan te geven, vertel je iOS dat er geen constraints voor die view moeten bedacht.

Belangrijk: Voor elke view die je in programmacode maakt, moet je dus de property .translatesAutoresizingMaskIntoConstraints instellen en hem de waarde false geven. Doe je dat niet dan krijg je gegarandeerd conflicterende constraints: de door iOS bedachte constraints liggen overhoop met je eigen constraints.

Auto Layout zonder het Storyboard, in programmacode

Stap 4

Dit is onze eerste constraint, waarmee we de breedte van de view opgeven. De makkelijkste manier om dit te doen, is met de initializer NSLayoutConstraint(). Deze verwacht de volgende argumenten:

  • item: de eerste view die bij de constraint is betrokken. Bij constraints waarbij een constante waarde wordt toegewezen en waarbij dus geen andere constraint betrokken is, is dit dus de constraint ‘waarom het gaat’.
  • attribute: een enum met, voor de eerste view, het view-attribuut dat we willen instellen. De mogelijke waarden zijn .Left, .Right, .Top, .Bottom, .Leading, .Trailing, .Width, .Height, .CenterX, .CenterY, .Baseline, .FirstBaseline, .LeftMargin, .RightMargin, .TopMargin, .BottomMargin, .LeadingMargin, .TrailingMargin, .CenterXWithinMargins, .CenterYWithinMargins en .NotAnAttribute.
  • relatedBy: het soort relatie tussen de beide attributen, of (bij constante-constraints) tussen het attribuut en de constante: .Equal, .GreaterThanOrEqual en .LessThanOrEqual.
  • toItem: de tweede view die bij de constraint is betrokken. Deze view moet in dezelfde view-hiërarchie staan als de eerste. Om een constante-constraint te maken (waarbij dus maar één view is betrokken, kun je hier nil opgeven.
  • attribute: een enum met het attribuut van de tweede view die bij de constraint is betrokken. De waarde van dit attribuut wordt vermenigvuldigd (zie bij multiplier, hieronder) en er wordt een constante bij opgeteld (zie bij constant, ook hieronder). Daarna wordt hij gebruikt om het attribuut van de eerste view in te stellen.
    Als er geen tweede view bij de constraint is betrokken, geef je hier .NotAnAttribute op. De constante (zie bij constant, hieronder) zal dan worden vermenigvuldigd (zie bij multiplier, hieronder) en aan het attribuut van de eerste view worden toegewezen.
  • multiplier: het getal waarmee de constante moet worden vermenigvuldigd alvorens deze aan het attribuut van de eerste view toe te wijzen.
  • constant: het getal waarbij het (eventuele) attribuut van de tweede view moet worden opgeteld; daarna wordt het aan het attribuut van de eerste view toegewezen.

We kijken nog even naar de coderegel:

Hier staat dus: Het .Width-attribuut van eenView krijgt een waarde die (.Equal) gelijk is aan de constant-waarde 200, vermenigvuldigd met de multiplier 1.

Stap 5

Lettend op de argumenten die in stap 4 zijn besproken, staat hier dus:

Het .Height-attribuut van eenView krijgt een waarde die (.Equal) gelijk is aan de constant-waarde 100, vermenigvuldigd met de multiplier 1

Stap 6

Als je de argumenten die in stap 4 zijn besproken er nog even bij pakt, zie je al snel wat hier staat:

Het .CenterX-attribuut van eenView krijgt een waarde die (.Equal) gelijk is aan het .CenterX-attribuut van (view) de hoofd-view, vermenigvuldigd met de multiplier 1.

Ofte wel: het horizontale middelpunt van eenView wordt gelijk aan het horizontale middelpunt van de hoofd-view. Hierdoor wordt eenView horizontaal gecentreerd binnen de hoofd-view.

Stap 7

Deze regel kun je op soortgelijke wijze als bij stap 6 lezen:

Het .CenterY-attribuut van eenView krijgt een waarde die (.Equal) gelijk is aan het .CenterY-attribuut van (view) de hoofd-view, vermenigvuldigd met de multiplier 1.

Ofte wel: het verticale middelpunt van eenView wordt gelijk aan het verticale middelpunt van de hoofd-view. Hierdoor wordt eenView verticaal gecentreerd binnen de hoofd-view.

Stap 8

Met de .addConstraints-methode kun je een array met constraints aan een view toevoegen. We voegen de beide constante-constraints voor de breedte en hoogte hiermee dus toe aan de view waar ze bijhoren: eenView.

Er bestaat ook een methode om één constraint toe te voegen: .addConstraint(). De volgende code was dus ook correct geweest:

Stap 9

We hoeven nu nog maar één ding te doen: de twee constraints toevoegen waardoor eenView aan de hoofd-view wordt gepind. Bij het toevoegen van constraints waarbij twee views zijn betrokken, geldt altijd de volgende regel:

“Constraints waarbij twee views zijn betrokken, moeten worden toegevoegd aan een view waar beide views van afstammen, of aan de erbij betrokken view die het hoogste in de view-hiërarchie staat.”

We kunnen de twee constraints voor de X- en Y-positie dus niet aan eenView toevoegen: die staat namelijk lager in de view-hiërarchie dan de hoofd-view. Vandaar dat we ze daaraan toevoegen:

Een tip uit de praktijk: Je kunt er ook voor kiezen om altijd de .addConstraints()-methode van de hoofd-view te gebruiken om constraints toe te voegen, ongeacht welke view(s) bij die constraints zijn betrokken. De volgende code werkt dus ook, zodat je Stap 8 kunt besparen:

Constraints wijzigen in programmacode

Constraints die (in programmacode of via het Storyboard) aan een view zijn toegevoegd, kun je achteraf aanpassen. Ook kun je de constraints die bij een view horen, verwijderen of vervangen door nieuwe constraints. De belangrijkste properties en methodes die je daarbij gebruikt, zie je hieronder.

  • .constraints: Deze property bevat een array met daarin alle constraints die aan een view zijn toegevoegd. Dit hoeven dus niet noodzakelijkerwijs constraints te zijn die iets met de view zelf doen! Het kan immers om constraints van één of meer subviews gaan.
  • .constraintsAffectingLayout(for:): met deze methode kun je opvragen welke constraints iets doen met de view, in horizontale of verticale richting. Deze methode verwacht als argument één van de volgende twee enums:
    • UILayoutConstraintAxis.horizontal
    • UILayoutConstraintAxis.vertical
  • .removeConstraint(_:) en .removeConstraints(_:): deze methodes gebruik je om één of meer constraints van een view te verwijderen.

In de volgende app (opnieuw, een Single View App) zie je hoe je een constraint in programmacode kunt veranderen. Op het scherm verschijnen een button en een view (die keurig roteren als je het scherm kantelt, dankzij de Auto Layout constraints). Telkens als je op de button drukt, worden de breedte en hoogte van de view aangepast.

Auto Layout zonder het Storyboard, in programmacode

De button staat onderin, dankzij de constraint die hem aan de Bottom Layout Guide pint. Zodra de breedte van de grijze view verandert, verandert de hoogte ook: deze is namelijk via een constraint aan de breedte gekoppeld.

Auto Layout zonder het Storyboard, in programmacode