Centrowanie widoku w jego nadzorze przy użyciu języka Visual Format Language

160

Właśnie zacząłem uczyć się AutoLayout dla iOS i przyjrzałem się Visual Format Language.

Wszystko działa dobrze, z wyjątkiem jednej rzeczy: po prostu nie mogę wyśrodkować widoku w ramach superwizji.
Czy jest to możliwe w przypadku VFL, czy też muszę samodzielnie utworzyć ograniczenie?

Christian Schnorr
źródło

Odpowiedzi:

232

Obecnie nie, nie wygląda na to, aby można było wyśrodkować widok w superviewie używając tylko VFL. Nie jest to jednak trudne do zrobienia przy użyciu pojedynczego ciągu VFL i jednego dodatkowego ograniczenia (na oś):

VFL: "|-(>=20)-[view]-(>=20)-|"

[NSLayoutConstraint constraintWithItem:view
                             attribute:NSLayoutAttributeCenterX
                             relatedBy:NSLayoutRelationEqual
                                toItem:view.superview
                             attribute:NSLayoutAttributeCenterX
                            multiplier:1.f constant:0.f];

Można by pomyśleć, że po prostu byłbyś w stanie to zrobić (o czym początkowo myślałem i próbowałem, gdy zobaczyłem to pytanie):

[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=20)-[view(==200)]-(>=20)-|"
                                 options: NSLayoutFormatAlignAllCenterX | NSLayoutFormatAlignAllCenterY
                                 metrics:nil
                                   views:@{@"view" : view}];

Próbowałem wielu różnych wariantów powyższego, próbując nagiąć to do mojej woli, ale wydaje się, że nie ma to zastosowania do superwizji, nawet jeśli jawnie mam dwa oddzielne ciągi VFL dla obu osi ( H:|V:). I wtedy zaczął próbować izolować kiedy dokładnie opcje mają się stosować do VFL. Wydaje się, że nie mają one zastosowania do superviewu w VFL i będą miały zastosowanie tylko do wszelkich wyraźnych poglądów, które są wymienione w ciągu VFL (co jest rozczarowujące w niektórych przypadkach).

Mam nadzieję, że w przyszłości Apple doda jakąś nową opcję, aby opcje VFL uwzględniały superview, nawet jeśli robią to tylko wtedy, gdy istnieje tylko jeden wyraźny widok oprócz superview w VFL. Innym rozwiązaniem mogłoby być innej opcji przeszedł do VFL, który mówi coś takiego: NSLayoutFormatOptionIncludeSuperview.

Nie trzeba dodawać, że wiele się nauczyłem o VFL, próbując odpowiedzieć na to pytanie.

larsacus
źródło
2
Dziękuję za szczegółową odpowiedź. Jednak jedynym powodem, dla którego używam VFL, jest pozbycie się tych brzydkich kawałków ograniczeń. Szkoda, że ​​Apple jeszcze tego nie obsługuje ...
Christian Schnorr
@larsacus Czy możesz skomentować, dlaczego odpowiedź poniżej działa? Zaskoczyło mnie to.
Matt G
8
Odpowiedź Evgeny'ego poniżej działa, ponieważ mechanizm autoukładu traktuje |i [superview]jako oddzielne jednostki wewnętrzne, mimo że reprezentują ten sam obiekt semantyczny. Używając [superview]autolayout, włącza obiekt superview do swoich metryk wyrównania (w przypadku, gdy w odpowiedzi na link github, jest to NSLayoutFormatAlignAllCenterY/ Xmetryka).
larsacus
@larsacus Awesome, Thanks!
Matt G
Czy wiemy, czy ograniczenie nadal obowiązuje w wersji VFL, która jest dostarczana z obecnym systemem iOS 7.x?
Drux
138

Tak, można wyśrodkować widok w jego nadzorze za pomocą języka formatu wizualnego. Zarówno w pionie, jak iw poziomie. Oto demo:

https://github.com/evgenyneu/center-vfl

Evgenii
źródło
15
To jest niesamowite, myślisz, że mógłbyś to rozwinąć i wyjaśnić, dlaczego to działa?
tapi
3
Skończyło się na tym, że użyłem czegoś bliższego zaznaczonej odpowiedzi, ale +1 dla twojego załącznika na githubie
Rembrandt Q. Einstein
4
@Dan Jeśli to nie zadziała, może być konieczne zastosowanie ograniczeń dotyczących szerokości i wysokości. VFL wyglądałoby tak dla szerokości: @ "[etykieta (== 200)]" i tak samo dla wysokości: @ "V: [etykieta (== 200)]"
Jonathan Zhan
1
Dlaczego są wyniki z | a [superview] inny? Czy jest to coś, co powinno być radarowe, czy dzieje się coś, czego po prostu nie śledzę?
Matt G,
3
Nie można przeanalizować formatu ograniczenia: opcje maskują wymagane widoki do wyrównania do poziomej krawędzi, co nie jest dozwolone w przypadku układu, który jest również poziomy. H: [superview] - (<= 1) - [label1]
grabner
82

Myślę, że lepiej jest ręcznie tworzyć ograniczenia.

[superview addConstraint:
 [NSLayoutConstraint constraintWithItem:view
                              attribute:NSLayoutAttributeCenterX
                              relatedBy:NSLayoutRelationEqual
                                 toItem:superview
                              attribute:NSLayoutAttributeCenterX
                             multiplier:1
                               constant:0]];
[superview addConstraint:
 [NSLayoutConstraint constraintWithItem:view
                              attribute:NSLayoutAttributeCenterY
                              relatedBy:NSLayoutRelationEqual
                                 toItem:superview
                              attribute:NSLayoutAttributeCenterY
                             multiplier:1
                               constant:0]];

Teraz możemy użyć NSLayoutAnchor do programowego zdefiniowania AutoLayout:

(Dostępne w iOS 9.0 i nowszych wersjach)

view.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true

Polecam używanie SnapKit , który jest DSL, aby ułatwić Auto Layout zarówno w systemie iOS, jak i macOS.

view.snp.makeConstraints({ $0.center.equalToSuperview() })
jqgsninimo
źródło
1
Najlepiej działało w moim kontekście - jednak bez VFL.
mózgray
to daje mi ostrzeżenie:Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
Tapas Pal
10

Absolutnie możliwe udało się to zrobić w ten sposób:

[NSLayoutConstraint constraintsWithVisualFormat:@"H:[view]-(<=1)-[subview]"
                                        options:NSLayoutFormatAlignAllCenterY
                                        metrics:nil
                                          views:NSDictionaryOfVariableBindings(view, subview)];

Nauczyłem się tego stąd. Kod powyżej centrów jest oczywiście widoczny w widoku Y.

Swift3:

        NSLayoutConstraint.constraints(withVisualFormat: "H:[view]-(<=1)-[subview]",
                                       options: .alignAllCenterY,
                                                       metrics: nil,
                                                       views: ["view":self, "subview":_spinnerView])
Igor Muzyka
źródło
7

Dodaj poniższy kod i umieść przycisk na środku ekranu. Absolutnie możliwe.

[self.view addConstraints:[NSLayoutConstraint
                           constraintsWithVisualFormat:@"H:|-[button]-|"
                           options:NSLayoutFormatAlignAllCenterY
                           metrics:0
                           views:views]];

[self.view addConstraints:[NSLayoutConstraint
                           constraintsWithVisualFormat:@"V:|-[button]-|"
                           options:NSLayoutFormatAlignAllCenterX
                           metrics:0
                           views:views]];
Pankaj Gaikar
źródło
1

Wiem, że nie chcesz, ale możesz oczywiście obliczyć marginesy i użyć ich do stworzenia wizualnego ciągu formatu;)

W każdym razie nie. Niestety nie jest możliwe zrobienie tego „automatycznie” z VFL - przynajmniej na razie.

Tobi
źródło
Cóż, możesz użyć NSString stringWithFormat: do dynamicznego zbudowania łańcucha VFL w czasie wykonywania.
TRVD1707
1

Dzięki odpowiedzi od @Evgenii tworzę pełny przykład w skrócie:

wyśrodkuj czerwony kwadrat w pionie z niestandardową wysokością

https://gist.github.com/yallenh/9fc2104b719742ae51384ed95dcaf626

Możesz wyśrodkować widok w pionie, używając VFL (co nie jest całkiem intuicyjne) lub ręcznie, używając ograniczeń. Nawiasem mówiąc, nie mogę łączyć razem środka Y i wysokości w VFL, jak:

"H:[subView]-(<=1)-[followIcon(==customHeight)]"

W obecnym rozwiązaniu ograniczenia VFL są dodawane oddzielnie.

Allen Huang
źródło
0

To, co trzeba zrobić, to zadeklarować superwiew w słowniku. Zamiast używać |, używasz @{@"super": view.superview};.

I NSLayoutFormatAlignAllCenterXdla pionowego i NSLayoutFormatAlignAllCenterYpoziomego jako opcje.

Lagos
źródło
0

Możesz użyć dodatkowych widoków.

NSDictionary *formats =
@{
  @"H:|[centerXView]|": @0,
  @"V:|[centerYView]|": @0,
  @"H:[centerYView(0)]-(>=0)-[yourView]": @(NSLayoutFormatAlignAllCenterY),
  @"V:[centerXView(0)]-(>=0)-[yourView]": @(NSLayoutFormatAlignAllCenterX),
};
NSDictionary *views = @{
  @"centerXView": centerXView, 
  @"centerYView": centerYView, 
  @"yourView": yourView,
};
 ^(NSString *format, NSNumber *options, BOOL *stop) {
     [superview addConstraints:
      [NSLayoutConstraint constraintsWithVisualFormat:format
                                              options:options.integerValue
                                              metrics:nil
                                                views:views]];
 }];

Jeśli chcesz, żeby to było schludne, to centerXView.hidden = centerYView.hidden = YES;.

PS: Nie jestem pewien, czy mogę nazwać dodatkowe widoki „symbolami zastępczymi”, ponieważ angielski jest moim drugim językiem. Będziemy wdzięczni, jeśli ktoś mi to powie.

CopperCash
źródło
Czegoś tu brakuje. Jak wywołać blok pod deklaracją widoków ? To nie jest kodeks prawny, tak jak to
ujęłeś
0

Dla tych, którzy przyszli tutaj po Interface Builderrozwiązanie bazowe (Google prowadzi mnie tutaj), po prostu dodaj stałe ograniczenia szerokości / wysokości i wybierz widok podrzędny -> przeciągnij kontrolkę do jego superview -> wybierz „wyśrodkuj pionowo / poziomo w superviewie”.

superarts.org
źródło
0

To jest moja metoda

//create a 100*100 rect in the center of superView
var consts = NSLayoutConstraint.constraints(withVisualFormat: "H:|-space-[myView]-space-|", options: [], metrics:["space":view.bounds.width/2-50], views:["myView":myView])

consts += NSLayoutConstraint.constraints(withVisualFormat: "V:|-space-[myView]-space-|", options: [], metrics: ["space":view.bounds.height/2-50], views: ["myView":myView])
Calcifer
źródło
-1

Wystarczy powiedzieć, że możesz użyć tego zamiast ograniczeń:

child.center = [parent convertPoint:parent.center fromView:parent.superview];
Nik Kov
źródło
4
Muszę tylko powiedzieć, że nie o to prosi ani nie zadziała, jeśli
obrócisz
-1

@ igor-muzyka answer w Swift 2 :

NSLayoutConstraint.constraintsWithVisualFormat("H:[view]-(<=1)-[subview]", 
                                               options: .AlignAllCenterY,
                                               metrics: nil,
                                               views: ["view":view, "subview":subview])
Federico Zanetello
źródło
-3

Zamiast używać VFL, możesz to łatwo zrobić w kreatorze interfejsów. W głównej serii ujęć naciśnij klawisz Ctrl i kliknij widok, który chcesz wyśrodkować. Przeciągnij do Superview (trzymając ctrl) i wybierz „Center X” lub „Center Y”. Nie potrzeba żadnego kodu.

leenyburger
źródło