Szerokość i wysokość równa superView przy użyciu programowego automatycznego układu?

83

Szukałem wielu fragmentów w sieci i nadal nie mogę znaleźć odpowiedzi na mój problem. Moje pytanie brzmi: mam scrollView (SV) i chcę programowo dodać przycisk wewnątrz scrollView (SV) z tą samą szerokością i wysokością jego superview, którym jest scrollView (SV), aby po obróceniu użytkownika przycisk urządzenia miał taką samą ramkę scrollView (SV). jak wykonać NSLayout / NSLayoutConstraint? dzięki

Bordz
źródło

Odpowiedzi:

125

Jeśli ktoś szuka rozwiązania Swift - stworzyłbym rozszerzenieUIView Swift, które pomoże ci za każdym razem, gdy chcesz powiązać ramkę subviews z jej granicami superwizji:

Swift 2:

extension UIView {

    /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
    /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this.
    func bindFrameToSuperviewBounds() {
        guard let superview = self.superview else {
            print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
            return
        }

        self.translatesAutoresizingMaskIntoConstraints = false
        superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
        superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
    }

}

Swift 3:

extension UIView {

    /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
    /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this.
    func bindFrameToSuperviewBounds() {
        guard let superview = self.superview else {
            print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
            return
        }

        self.translatesAutoresizingMaskIntoConstraints = false
        superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
        superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
    }
}

Swift 4.2:

extension UIView {

    /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
    /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this.
    func bindFrameToSuperviewBounds() {
        guard let superview = self.superview else {
            print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
            return
        }

        self.translatesAutoresizingMaskIntoConstraints = false
        self.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
        self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
        self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0).isActive = true
        self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: 0).isActive = true

    }
}

Następnie nazwij to tak :

// after adding as a subview, e.g. `view.addSubview(subview)`
subview.bindFrameToSuperviewBounds()
MadNik
źródło
podczas tworzenia niestandardowego UIView przy użyciu .xib należy wywołać bindFrameToSuperviewBounds w ramach „wymaganego init? (koder aDecoder)” zaraz po self.addSubview (self.view)
user1603721
Warto zauważyć, że rozwiązania wykorzystujące format wizualny nie są bezpieczne dla obszaru. Jeśli na przykład wywołujesz to w widoku znajdującym się wewnątrz kontrolera nawigacyjnego, pokazującym paski nawigacyjne i paski narzędzi, widok zostanie umieszczony pod paskiem nawigacyjnym i pod paskiem narzędzi, jeśli zajdzie tak daleko w dół.
Andy Ibanez
Działa to również jako rozwiązanie dla Swift 5. Nie mogę dostosować mojego niestandardowego widoku podrzędnego do rozmiaru nadrzędnego widoku przy użyciu układu AutoLayout. Używanie tego po dodaniu widoku podrzędnego działało jak urok.
toni_piu
Rozwiązanie Swift 4.2 działa dobrze. Możesz nawet trochę skrócić, usuwając constant: 0część.
Zyphrax,
69

Nie jestem pewien, czy jest to najefektywniejszy sposób, ale działa.

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.translatesAutoresizingMaskIntoConstraints = NO;
// initialize


[coverForScrolView addSubview:button];

NSLayoutConstraint *width =[NSLayoutConstraint
                                    constraintWithItem:button
                                    attribute:NSLayoutAttributeWidth
                                    relatedBy:0
                                    toItem:coverForScrolView
                                    attribute:NSLayoutAttributeWidth
                                    multiplier:1.0
                                    constant:0];
NSLayoutConstraint *height =[NSLayoutConstraint
                                     constraintWithItem:button
                                     attribute:NSLayoutAttributeHeight
                                     relatedBy:0
                                     toItem:coverForScrolView
                                     attribute:NSLayoutAttributeHeight
                                     multiplier:1.0
                                     constant:0];
NSLayoutConstraint *top = [NSLayoutConstraint
                                   constraintWithItem:button
                                   attribute:NSLayoutAttributeTop
                                   relatedBy:NSLayoutRelationEqual
                                   toItem:coverForScrolView
                                   attribute:NSLayoutAttributeTop
                                   multiplier:1.0f
                                   constant:0.f];
NSLayoutConstraint *leading = [NSLayoutConstraint
                                       constraintWithItem:button
                                       attribute:NSLayoutAttributeLeading
                                       relatedBy:NSLayoutRelationEqual
                                       toItem:coverForScrolView
                                       attribute:NSLayoutAttributeLeading
                                       multiplier:1.0f
                                       constant:0.f];
[coverForScrolView addConstraint:width];
[coverForScrolView addConstraint:height];
[coverForScrolView addConstraint:top];
[coverForScrolView addConstraint:leading];
Bordz
źródło
4
Byłoby znacznie bardziej wydajne w użyciuNSLayoutConstraint.activateConstraints([width, height, top, leading])
Berik
Możesz użyć[coverForScrolView addConstraints:@[width, height, top, leading]];
Islam Q.
1
warto zauważyć (po latach) ten kod jest bardzo nieaktualny . teraz znacznie łatwiej jest dodawać ograniczenia - zobacz odpowiedź z 2017 roku poniżej
Fattie
49

Ten link może ci pomóc, postępuj zgodnie z instrukcjami: http://www.raywenderlich.com/20881/beginning-auto-layout-part-1-of-2

EDYTOWAĆ :

użyj następującego fragmentu kodu, gdzie podwidok jest Twoim podziałem.

[subview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addConstraints:[NSLayoutConstraint
                           constraintsWithVisualFormat:@"H:|-0-[subview]-0-|"
                           options:NSLayoutFormatDirectionLeadingToTrailing
                           metrics:nil
                           views:NSDictionaryOfVariableBindings(subview)]];
[self.view addConstraints:[NSLayoutConstraint
                           constraintsWithVisualFormat:@"V:|-0-[subview]-0-|"
                           options:NSLayoutFormatDirectionLeadingToTrailing
                           metrics:nil
                           views:NSDictionaryOfVariableBindings(subview)]];
uniruddh
źródło
4
W tym przypadku format wizualny mógłby być również V:|[subview]|iH:|[subview]|
Gustavo Barbosa
4
warto zauważyć (po latach) ten kod jest bardzo nieaktualny . teraz znacznie łatwiej jest dodawać ograniczenia - zobacz odpowiedź z 2017 roku poniżej
Fattie
19

addConstrainti removeConstraintmetody dla UIView będą przestarzałe, dlatego warto skorzystać z „udogodnień do tworzenia ograniczeń”:

view.topAnchor.constraint(equalTo: superView.topAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: superView.bottomAnchor, constant: 0).isActive = true
view.leadingAnchor.constraint(equalTo: superView.leadingAnchor, constant: 0).isActive = true
view.trailingAnchor.constraint(equalTo: superView.trailingAnchor, constant: 0).isActive = true
beryl
źródło
To działa dobrze. Możesz nawet trochę skrócić, usuwając constant: 0część.
Zyphrax,
8

Podejście nr 1: rozszerzenie przez UIView

Oto bardziej funkcjonalne podejście w Swift 3+ z warunkiem wstępnym zamiast print(który może łatwo zginąć w konsoli). Ten zgłosi błędy programisty jako nieudane kompilacje.

Dodaj to rozszerzenie do swojego projektu:

extension UIView {
    /// Adds constraints to the superview so that this view has same size and position.
    /// Note: This fails the build if the `superview` is `nil` – add it as a subview before calling this.
    func bindEdgesToSuperview() {
        guard let superview = superview else {
            preconditionFailure("`superview` was nil – call `addSubview(view: UIView)` before calling `bindEdgesToSuperview()` to fix this.")
        }
        translatesAutoresizingMaskIntoConstraints = false
        ["H:|-0-[subview]-0-|", "V:|-0-[subview]-0-|"].forEach { visualFormat in
            superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: visualFormat, options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
        }
    }
}

Teraz po prostu nazwij to tak:

// after adding as a subview, e.g. `view.addSubview(subview)`
subview.bindEdgesToSuperview()

Zauważ, że powyższa metoda jest już zintegrowana z moim frameworkiem HandyUIKit , który dodaje również kilka bardziej przydatnych pomocników UI do twojego projektu.


Podejście nr 2: Korzystanie z frameworka

Jeśli dużo pracujesz z ograniczeniami programistycznymi w swoim projekcie, polecam zakup SnapKita . Dzięki temu praca z ograniczeniami jest dużo łatwiejsza i mniej podatna na błędy .

Postępuj zgodnie z instrukcjami instalacji w dokumentacji, aby dołączyć SnapKit do swojego projektu. Następnie zaimportuj go u góry pliku Swift:

import SnapKit

Teraz możesz osiągnąć to samo dzięki temu:

subview.snp.makeConstraints { make in
    make.edges.equalToSuperview()
}
Jeehut
źródło
6

Swift 3:

import UIKit

extension UIView {

    func bindFrameToSuperviewBounds() {
        guard let superview = self.superview else {
            print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
            return
        }

        self.translatesAutoresizingMaskIntoConstraints = false
        superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
        superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
    }

}
PLJNS
źródło
Jeśli zmieniłeś kod tylko w celu dostosowania go do Swift 3, powinieneś raczej edytować oryginalną odpowiedź plakatu zamiast publikować nową odpowiedź (ponieważ nie jest to zmiana pierwotnego zamiaru plakatów). Jeśli nie masz wystarczającej liczby punktów na zmianę, skomentuj oryginalny post, podając wskazówkę dotyczącą zmian niezbędnych do dostosowania się do języka Swift 3. Oryginalny plakat (lub ktoś inny, kto zobaczy Twój komentarz), prawdopodobnie zaktualizuje odpowiedź. W ten sposób chronimy wątek przed zduplikowanymi odpowiedziami i przestarzałym kodem.
Jeehut
Hej @Dschee - całkowicie się z tobą zgadzam, ale mylimy się. „konsensus” na stronie jest przeciwieństwem tego, co tutaj wyrażasz. meta.stackoverflow.com/questions/339024/… ( Ciągle ignoruję decyzję konsensusu, robię to, co rozsądne, a potem
wpadam
2

Swift 4 przy użyciu NSLayoutConstraint:

footerBoardImageView.translatesAutoresizingMaskIntoConstraints = false
let widthConstraint  = NSLayoutConstraint(item: yourview, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: superview, attribute: NSLayoutAttribute.width, multiplier: 1, constant: 0)
let heightConstraint = NSLayoutConstraint(item: yourview, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: superview, attribute: NSLayoutAttribute.height, multiplier: 1, constant: 0)
superview.addConstraints([widthConstraint, heightConstraint])
Masih
źródło
1

Jako odpowiedź uzupełniająca i dla tych, którzy nie sprzeciwiają się włączaniu bibliotek innych firm, biblioteka PureLayout zapewnia metodę, która to robi. Po zainstalowaniu biblioteki jest to tak proste, jak

myView.autoPinEdgesToSuperviewEdges()

Istnieją inne biblioteki, które również mogą zapewnić podobną funkcjonalność w zależności od gustu, np. Murarstwo , kartografia .

Matt Pinkston
źródło
1

Z pozostałych odpowiedzi wybrałem najlepsze elementy:

extension UIView {
  /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
  /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this.
  func bindFrameToSuperviewBounds() {
    guard let superview = self.superview else {
      print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
      return
    }

    self.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
      self.topAnchor.constraint(equalTo: superview.topAnchor),
      self.bottomAnchor.constraint(equalTo: superview.bottomAnchor),
      self.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
      self.trailingAnchor.constraint(equalTo: superview.trailingAnchor)
    ])
  }
}

Możesz go używać w ten sposób, na przykład w swoim niestandardowym UIView:

let myView = UIView()
myView.backgroundColor = UIColor.red

self.addSubview(myView)
myView.bindFrameToSuperviewBounds()
Zyphrax
źródło
0

Jako kontynuacja rozwiązania @ Dschee, oto składnia Swift 3.0: (Uwaga: to nie jest moje rozwiązanie , właśnie to naprawiłem dla Swift 3.0)

extension UIView {

    /// Adds constraints to this `UIView` instances `superview` object to make sure this always has the same size as the superview.
    /// Please note that this has no effect if its `superview` is `nil` – add this `UIView` instance as a subview before calling this.
    func bindFrameToSuperviewBounds() {
        guard let superview = self.superview else {
            print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
            return
        }

        self.translatesAutoresizingMaskIntoConstraints = false
        superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
    superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[subview]-0-|", options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
}
James Larcombe
źródło
1
Jeśli zmieniłeś kod tylko w celu dostosowania go do Swift 3, powinieneś raczej edytować oryginalną odpowiedź plakatu zamiast publikować nową odpowiedź (ponieważ nie jest to zmiana pierwotnego zamiaru plakatów). Jeśli nie masz wystarczającej liczby punktów na zmianę, skomentuj oryginalny post, podając wskazówkę dotyczącą zmian niezbędnych do dostosowania się do języka Swift 3. Oryginalny plakat (lub ktoś inny, kto zobaczy Twój komentarz), prawdopodobnie zaktualizuje odpowiedź. W ten sposób chronimy wątek przed zduplikowanymi odpowiedziami i przestarzałym kodem.
Jeehut
Całkowicie się z tobą zgadzam, @Dschee, ale jest (dla mnie absurd) komentarz na Meta, że ​​„nie robimy tego na SO” ... meta.stackoverflow.com/questions/339024/…
Fattie
@JoeBlow Po przeczytaniu dyskusji za twoim linkiem myślę, że to też ma sens. Zgadzam się z komentarzem Patricka Haugha (do pytania), chociaż należy udzielić nowej odpowiedzi w połączeniu z komentarzem do oryginalnej odpowiedzi. Wtedy do oryginalnego plakatu należy zaktualizowanie swojej odpowiedzi (w celu uzyskania przyszłych głosów za), czy nie. Dziękuję za link!
Jeehut,
Huh, okej, to wszystko jest dużą częścią tego, dlaczego w przeszłości pozostałem wiernym hitem i biegaczem. Pobieram odpowiedź, konwertuję ją na aktualną składnię i kontynuuję kodowanie. Głównym powodem, dla którego pisałem w ten sposób, jest to, że kiedy uczyłem swift, często pytano mnie, jak znaleźć rozwiązanie w dowolnej wersji swift, ponieważ nowi programiści mieli problemy z aktualizacją deklaracji funkcji. Było to jedno z głównych źródeł frustracji, ale także okazja do zestawienia dwóch stylów kodu. W rezultacie student był w stanie przewidzieć podobne zmiany w innych fragmentach kodu.
James Larcombe,
0

Musiałem całkowicie zakryć nadzór. Inni nie zrobiliby tego podczas zmiany orientacji. Więc napisałem nowy, który to robi - używając dowolnego mnożnika wielkości 20. Możesz dowolnie zmieniać swoje potrzeby. Zwróć również uwagę, że ten w rzeczywistości sprawia, że ​​podgląd podrzędny jest znacznie większy niż podgląd superwizji, który może różnić się od wymagań.

extension UIView {
    func coverSuperview() {
        guard let superview = self.superview else {
            assert(false, "Error! `superview` was nil – call `addSubview(_ view: UIView)` before calling `\(#function)` to fix this.")
            return
        }
        self.translatesAutoresizingMaskIntoConstraints = false
        let multiplier = CGFloat(20.0)
        NSLayoutConstraint.activate([
            self.heightAnchor.constraint(equalTo: superview.heightAnchor, multiplier: multiplier),
            self.widthAnchor.constraint(equalTo: superview.widthAnchor, multiplier: multiplier),
            self.centerXAnchor.constraint(equalTo: superview.centerXAnchor),
            self.centerYAnchor.constraint(equalTo: superview.centerYAnchor),
            ])
    }
}
Jonny
źródło