OS X Cocoa Auto Layout verborgen elementen

Ik probeer de nieuwe Automatische lay-out te gebruiken in Lion omdat het best aardig lijkt. Maar ik kan geen goede informatie vinden over hoe dingen te doen. Bijvoorbeeld:

Ik heb twee labels:

+----------------+
| +------------+ |
| + label 1    | |
| +------------+ |
|                |
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

maar het eerste label wordt niet altijd gevuld met inhoud, soms is er gewoon geen inhoud voor. Wat ik zou willen doen is automatisch het label 2 laten zien waar label 1 zou zijn als het inhoud zou hebben.

+----------------+
| +------------+ |
| + label 2    | |
| +------------+ |
|                |
|                |
|                |
|                |
+----------------+

Welke beperkingen zou ik moeten toevoegen, zodat het automatisch werkt met autolayout? Ik begrijp dat ik alles gewoon kan coderen, maar ik heb ongeveer 30 van dergelijke labels en afbeeldingen en knoppen van verschillende stijlen en vormen die allemaal optioneel zijn en ik wil geen reeksen coderegels toevoegen als het automatisch ook best leuk zou kunnen werken .

Als het niet werkt, gebruik ik een WebView en doe ik het met HTML en CSS.

29
Een tafel zou helpen als het van boven naar beneden zou zijn, maar sommige dingen zijn te van links naar rechts en moeten de plaats innemen van anderen enzo. Maar het idee is niet slecht, geef ik toe.
toegevoegd de auteur Jeena, de bron
Ik weet niet zeker of dat mogelijk is met automatische lay-out, maar het lijkt erop dat wat je echt wilt een tabel is.
toegevoegd de auteur user557219, de bron

8 antwoord

Dit is mogelijk met automatische lay-out, maar schaalt niet precies.

Dus, als we uw voorbeeld nemen, laten we zeggen dat u label A en label B (of knop of iets anders eigenlijk) heeft. Begin eerst door een topbeperking toe te voegen aan de superview voor A. Dan een verticale afstandsbeperking tussen A en B. Dit is tot nu toe normaal. Als u op dit punt A zou verwijderen, zou B een ambigue lay-out hebben. Als je het zou verbergen, zou het nog steeds zijn ruimte innemen, inclusief de ruimte tussen de labels.

Vervolgens moet u een nieuwe beperking van B toevoegen aan de bovenkant van de superview. Wijzig de prioriteit hiervan lager dan de andere (zeg 900) en stel vervolgens in dat deze constant is om standaard te zijn (of een andere kleinere waarde). Nu, wanneer A wordt verwijderd uit zijn superview, zal de lagere prioriteitsbeperking ingaan en B naar de bovenkant trekken. De beperkingen zien er ongeveer zo uit:

Interface Builder screenshot

Het probleem komt wanneer u dit probeert te doen met een lange lijst met labels.

24
toegevoegd
Dit is een schone oplossing voor de gepresenteerde casus. Voor meer labels is het waarschijnlijk het gemakkelijkst om dit in code te repliceren. IB suggereert een oplossing in de pop-up "Add new constraint" -editor, wanneer deze verwijst naar "dichtstbijzijnde buur". Als het precies dat zou doen, zou het uit de doos werken. Ik denk dat het probleem is dat ze te vroeg bindend zijn ("neigbhor") en je kunt dit zien in de storyboard-bestandsindeling.
toegevoegd de auteur Chris Conover, de bron
hoe werkt het tweede deel van dit werk? Als u een andere beperking van B aan de superview toevoegt, wordt deze dan niet altijd geactiveerd, ongeacht de prioriteit?
toegevoegd de auteur kevinl, de bron
nevermind, ik probeerde deze specifieke logica te gebruiken in mijn scenario dat niet werkte: ik heb knop A, B, C. Ik verwijder B en ik wil dat C de plaats van B. inneemt
toegevoegd de auteur kevinl, de bron

Samenvouwen UILabel subklasse

Een eenvoudige oplossing is om UILabel onder te verdelen en de intrinsieke inhoudsgrootte te wijzigen.

@implementation WBSCollapsingLabel

- (CGSize)intrinsicContentSize
{
    if (self.isHidden) {
        return CGSizeMake(UIViewNoIntrinsicMetric, 0.0f);
    } else {
        return [super intrinsicContentSize];
    }
}

- (void)setHidden:(BOOL)hidden
{
    [super setHidden:hidden];

    [self updateConstraintsIfNeeded];
    [self layoutIfNeeded];
}

@end
10
toegevoegd
Nou ... geweldige vraag. Over het algemeen zijn onderwerpen over automatische lay-out niet goed behandeld op SO. Ik denk ook dat veel mensen gewoon een stopcontact aansluiten bij de beperking en de hoogte misten. Subclassering is ook een beetje vervelend.
toegevoegd de auteur Cameron Lowell Palmer, de bron
De marges als in H: | - (8) - [view1] - (12) - [hidden1] - (8) - |? Ja ... Je hebt gelijk, maar dat is hetzelfde probleem als bij frames. Je moet er nog steeds voor zorgen dat de ruimte tussen de weergave wordt afgehandeld, maar dat is een aparte vraag!
toegevoegd de auteur Cameron Lowell Palmer, de bron
Het probleem met deze aanpak is dat er geen rekening wordt gehouden met de marges die u normaal gesproken rond uw inzichten hebt.
toegevoegd de auteur tcurdt, de bron
Dit is zo'n leuke oplossing dat het ook in Cocoa werkt, schakel UIViewNoIntrinsicMetric naar NSViewNoInstrinsicMetric , updateConstraintsIfNeeded naar updateConstraintsForSubtreeIfNeeded en layoutIfNeeded naar layoutSubtreeIfNeeded en het werkt als een charme. Dank je!
toegevoegd de auteur bithavoc, de bron
Dit lijkt een heel eenvoudige, elegante manier om te gaan. Ik ben benieuwd waarom het niet hoger is gestemd als het echt werkt?
toegevoegd de auteur software evolved, de bron

Deze categorie maakt het samenvouwen van de beperkingen van de Auto-indeling beperkt:

https://github.com/depth42/AutolayoutExtensions

Ik heb het net aan een project toegevoegd en het werkt geweldig.

4
toegevoegd
Ik probeer dit in mijn project te gebruiken, maar nieuwe public outlet PWHidingMasterView verschijnt niet in Xcode voor de views op mijn project. Wanneer ik het voorbeeldproject open, is de uitlaat daar. Ik kan niet achterhalen wat er anders is dan mijn project en het voorbeeld. Enige tips?
toegevoegd de auteur Ricardo Sanchez-Saez, de bron

Ik denk niet dat je het op die manier zou kunnen doen. Als u de lay-out voor label 2 hebt gebaseerd op een afstandsbeperking van label 1, zelfs als u label 1 automatisch samenvouwt tot nul hoogte als deze geen inhoud bevat, zal label 2 nog steeds die afstand naar beneden zijn, dat wil zeggen in:

+----------------+
| +------------+ |
| + label 1    | |
| +------------+ |
|        ^       |
|        ^       !
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

Waar ^ de autolayout-afstandsbeperking is - Als Label 1 weet hoe hij nul hoogte kan worden als zijn string leeg is, krijg je nog steeds:

+----------------+
| +------------+ |
|        ^       |
|        ^       !
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

Maybe it is possible by creating your NSLayoutConstraint manually. You could make the second attribute be the height of label 1, make the constant zero, and then carefully work out what the multiplier would be to make the distance be what you want based on a multiple of the non-zero label height.

Maar nadat u dit allemaal hebt gedaan, hebt u nu een NSLabel-subklasse gecodeerd die automatisch wordt aangepast, een beperkingsobject handmatig maakt in plaats van via de visuele taal en gebogen NSLayoutConstraint buiten zijn wil.

Ik denk dat je beter af bent als je alleen het kader van label 2 verandert als het snoer van label 1 leeg is!

2
toegevoegd
zonder constant op 0 te zetten. Is dat een directe optie beschikbaar in interface builder?
toegevoegd de auteur user2223516, de bron

Hier is een voorbeeld van hoe ik dit programmatisch behandeld in plaats van het gebruik van Interface Builder. Samengevat; Ik voeg alleen de weergave toe als deze is ingeschakeld en herhaal de subviews door verticale beperkingen toe te voegen.

Merk op dat de betreffende opvattingen vooraf zijn geïnitialiseerd.

/*
  Begin Auto Layout
*/
NSMutableArray *constraints = [NSMutableArray array];
NSMutableDictionary *views = [[NSMutableDictionary alloc] init];


/*
  Label One
*/
if (enableLabelOne) {
    [contentView addSubview:self.labelOne];

    self.labelOne.translatesAutoresizingMaskIntoConstraints = NO;

    [views setObject:self.labelOne
              forKey:@"_labelOne"];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_labelOne(44)]"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_labelOne]-|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];
}

/*
    Label Two
*/
if (enableLabelTwo) {
    [contentView addSubview:self.labelTwo];

    self.labelTwo.translatesAutoresizingMaskIntoConstraints = NO;

    [views setObject:self.labelTwo
              forKey:@"_labelTwo"];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_labelTwo(44)]"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];
}

[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_labelTwo]-|"
                                                                         options:0
                                                                         metrics:nil
                                                                           views:views]];

/*
  Dynamically add vertical spacing constraints to subviews
*/
NSArray *subviews = [contentView subviews];

if ([subviews count] > 0) {
    UIView *firstView = [subviews objectAtIndex:0];
    UIView *secondView = nil;
    UIView *lastView = [subviews lastObject];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[firstView]"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:NSDictionaryOfVariableBindings(firstView)]];

    for (int i = 1; i < [subviews count]; i++) {
        secondView = [subviews objectAtIndex:i];
        [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[firstView]-10-[secondView]"
                                                                                 options:0
                                                                                 metrics:nil
                                                                                   views:NSDictionaryOfVariableBindings(firstView, secondView)]];
        firstView = secondView;
    }

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[lastView]-|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:NSDictionaryOfVariableBindings(lastView)]];
}


[self addConstraints:constraints];

Ik stel alleen de lastView -beperking in omdat deze code is aangepast van iets binnen een UIScrollView.

Ik heb dit oorspronkelijk geïmplementeerd op basis van dit Stack Overflow-antwoord en dingen gewijzigd om aan mijn eigen behoeften te voldoen.

1
toegevoegd

Ik vond een schijnbaar fatsoenlijke manier om dit ook te doen. Het lijkt op David's . Hier is hoe het werkt in code. Ik heb de superview en al zijn subweergaven gemaakt, zelfs degene die mogelijk niet altijd worden weergegeven. Ik heb veel van de beperkingen zoals V: | - [_ btn] aan de superview toegevoegd. Zoals je kunt zien aan het einde van deze beperkingen is er geen link naar de onderkant van de superview. Ik heb vervolgens twee arrays van beperkingen gecreëerd voor beide standaarden van het zicht, voor mij is het verschil een 'More Options' disclosure triangle. Wanneer ik vervolgens op de driehoek klik, afhankelijk van de staat, voeg ik dienovereenkomstig beperkingen en subweergaven toe en verwijder deze. Bijvoorbeeld om toe te voegen dat doe ik:

[self.backgroundView removeConstraints:self.lessOptionsConstraints];
[self.backgroundView addSubview:self.nameField];
[self.backgroundView addConstraints:self.moreOptionsConstraints];

De beperkingen die ik verwijderde, bonden de knop aan de onderkant van de superview, zoals V: [_ btn] - | . De beperkingen die ik heb toegevoegd zien er uit als V: [_ btn] - [_ nameField] - | omdat je deze beperking ziet plaatst de nieuwe weergave tussen de originele weergave erboven en de onderkant van de superview die de superview's hoogte.

0
toegevoegd

Dit probleem is programmatisch opgelost. Ik heb een paar knoppen op een rij en ik kan besluiten om er op elk moment een te verbergen.

enter image description here

Gebruikt Cartography om ze telkens te vervangen wanneer hidden in een van deze wijzigingen wordt gewijzigd.

let buttons = self.buttons!.filter { button in
    return !button.hidden
}

constrain(buttons, replace: self.constraintGroup) { buttons in
    let superview = buttons.first!.superview!

    buttons.first!.left == superview.left

    for var i = 1; i < buttons.count; i++ {
        buttons[i].left == buttons[i-1].right + 10
    }

    buttons.last!.right == superview.right
}
0
toegevoegd

Ik heb een ENere manier gevonden om dit te doen. Deze methodologie kan overal worden toegepast, heeft geen schaalproblemen; en verwerkt ook de marges. En je hebt geen dingen van derden nodig.

Gebruik eerst deze lay-out niet:

V:|-?-[Label1]-10-[Label2]-10-|
H:|-?-[Label1]-?-|
H:|-20-[Label2]-20-|

Gebruik deze in plaats daarvan:

("|" is the real (outer) container)
V:|-?-[Label1]-0-[Label2HideableMarginContainer]-0-|
H:|-?-[Label1]-?-|
H:|-0-[Label2HideableMarginContainer]-0-|

("|" is Label2HideableMarginContainer)
V:|-10-[Label2]-10-|
H:|-20-[Label2]-20-|

Wat hebben we nu gedaan? Label2 wordt niet rechtstreeks in de lay-out gebruikt; het is in een marge-container geplaatst. Die container wordt gebruikt als een proxy van Label2 , met 0 marges in de lay-out. De echte marges worden in van de margecontainer geplaatst.

Nu kunnen we Label2 verbergen met:

  • instelling verborgen in JA erop

EN

  • Disabling the Top, Bottom, Leading EN Trailing constraints. So seek them out, than set Active to NO on them. This will cause the Margin-Container to have a Frame Size of (0,0); because it does have subview(s); but there aren't any (active) layout constraints which anchors those subviews to it.

Maybe a bit complex, but you only have to develop it once. All the logic can be put into a separate place, EN be reused every time you need to hide smg.

Hier is de code C# Xamarin om te zoeken naar die beperkingen die de subview (s) verankeren aan de binnenranden van de weergave Margin-container :

public List SubConstraints { get; private set; }

private void ReadSubContraints()
{
    var constraints = View.Constraints;//View: the Margin-Container NSView
    if(constraints?.Any() ?? false)
    {
        SubConstraints = constraints.Where((NSLayoutConstraint c) => {
            var predicate = 
                c.FirstAttribute == NSLayoutAttribute.Top ||
                c.FirstAttribute == NSLayoutAttribute.Bottom ||
                c.FirstAttribute == NSLayoutAttribute.Leading ||
                c.FirstAttribute == NSLayoutAttribute.Trailing;
            predicate &= ViewENSubviews.Contains(c.FirstItem);//ViewENSubviews: The View EN View.Subviews
            predicate &= ViewENSubviews.Contains(c.SecondItem);
            return predicate;
        }).ToList();
    }
}
0
toegevoegd