Jak programowo używać bezpiecznego układu obszaru?

100

Ponieważ nie używam scenorysów do tworzenia widoków, zastanawiałem się, czy istnieje programowa opcja „Użyj prowadnic obszaru bezpiecznego”, czy coś w tym rodzaju.

Próbowałem zakotwiczyć swoje poglądy

view.safeAreaLayoutGuide

ale w symulatorze iPhone'a X wciąż zachodzą na siebie.

Phillip
źródło
1
Brak udokumentowanej właściwości bool na ten temat według: developer.apple.com/documentation/uikit/uiview/…
dvp.petrov
A co powiesz view.safeAreaInsets? Próbowałeś tego?
Karthikeyan Bose
@KarthikeyanBose tak, niestety bez powodzenia.
Phillip,
pracuje dla mnie. Jak wygląda kod
Daniel Springer

Odpowiedzi:

157

Oto przykładowy kod (Ref from: Safe Area Layout Guide ):
Jeśli tworzysz ograniczenia w kodzie, użyj właściwości safeAreaLayoutGuide UIView, aby uzyskać odpowiednie kotwice układu. Odtwórzmy powyższy przykład Interface Builder w kodzie, aby zobaczyć, jak to wygląda:

Zakładając, że mamy zielony widok jako właściwość w naszym kontrolerze widoku:

private let greenView = UIView()

Moglibyśmy mieć funkcję do ustawiania widoków i ograniczeń wywoływanych z viewDidLoad:

private func setupView() {
  greenView.translatesAutoresizingMaskIntoConstraints = false
  greenView.backgroundColor = .green
  view.addSubview(greenView)
}

Utwórz wiodące i końcowe ograniczenia marginesu, jak zawsze, używając layoutMarginsGuide widoku głównego:

 let margins = view.layoutMarginsGuide
 NSLayoutConstraint.activate([
    greenView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
    greenView.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
 ])

Teraz, chyba że celujesz w system iOS 11 lub nowszy, będziesz musiał zawinąć ograniczenia prowadnicy układu bezpiecznego obszaru za pomocą #available i wrócić do górnych i dolnych prowadnic układu dla wcześniejszych wersji iOS:

if #available(iOS 11, *) {
  let guide = view.safeAreaLayoutGuide
  NSLayoutConstraint.activate([
   greenView.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0),
   guide.bottomAnchor.constraintEqualToSystemSpacingBelow(greenView.bottomAnchor, multiplier: 1.0)
   ])
} else {
   let standardSpacing: CGFloat = 8.0
   NSLayoutConstraint.activate([
   greenView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: standardSpacing),
   bottomLayoutGuide.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: standardSpacing)
   ])
}

Wynik:

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj


Oto oficjalna dokumentacja programisty Apple dotycząca przewodnika po układzie bezpiecznego obszaru


Obszar bezpieczny jest wymagany do obsługi projektowania interfejsu użytkownika dla iPhone-X. Oto podstawowe wytyczne dotyczące projektowania interfejsu użytkownika dla telefonu iPhone-X przy użyciu układu bezpiecznego obszaru

Krunal
źródło
2
Czy można by to również podać w celu-C? Wygląda na to, czego potrzebuję
Tom Hammond
6
@TomHammond Here is in Objective-C for you stackoverflow.com/a/47076040/5638630
Krunal
2
@ZonilyJame Teraz o twoim zapytaniu - SaFeAreaLayout to struktura specyficzna dla iOS (nie specyficzna dla urządzenia iPhoneX), Zastępuje przewodnik po układzie góra-dół w iOS 11, dlatego musimy użyć / ustawić warunek dla iOS, a nie dla urządzenia. SafeAreaLayout zajmuje się projektowaniem dla wszystkich typów urządzeń (iPhone-X i inne). Możesz zapytać mnie o więcej szczegółów, jeśli nadal masz jakieś pytania / niejasności.
Krunal
3
Niesamowite. Dziękuję :)
Rajamohan S
1
dlaczego jest akceptowana jako poprawna odpowiedź? Próbowałem ustawić konkretną pozycję - wynik jest w pełni losowy - kontrola jest umieszczona w nieprzewidywalnej pozycji!
Vyachaslav Gerchicov
83

Właściwie używam do tego rozszerzenia i kontroluję, czy jest to iOS 11, czy nie.

extension UIView {

  var safeTopAnchor: NSLayoutYAxisAnchor {
    if #available(iOS 11.0, *) {
      return self.safeAreaLayoutGuide.topAnchor
    }
    return self.topAnchor
  }

  var safeLeftAnchor: NSLayoutXAxisAnchor {
    if #available(iOS 11.0, *){
      return self.safeAreaLayoutGuide.leftAnchor
    }
    return self.leftAnchor
  }

  var safeRightAnchor: NSLayoutXAxisAnchor {
    if #available(iOS 11.0, *){
      return self.safeAreaLayoutGuide.rightAnchor
    }
    return self.rightAnchor
  }

  var safeBottomAnchor: NSLayoutYAxisAnchor {
    if #available(iOS 11.0, *) {
      return self.safeAreaLayoutGuide.bottomAnchor
    }
    return self.bottomAnchor
  }
}
Alper
źródło
To taki prosty sposób i załatwia sprawę. Dzięki za pomysł.
alper_k
To taki prosty, ale przyjemny sposób na zrobienie tego! Zwróć uwagę na użycie self.safeAreaLayoutGuidezamiast self.layoutMarginsGuide. Bezpieczny użyty w tej odpowiedzi działał poprawnie, aby pozostać w bezpiecznym obszarze! Jedną rzeczą, którą sugerowałbym zmianę, byłoby użycie leadingAnchori trailingAnchorzamiast leftAnchori rightAnchor. Brawo!
Pranoy C
22

SafeAreaLayoutGuidejest UIViewwłasnością,

Górna część safeAreaLayoutGuide wskazuje niezasłoniętą górną krawędź widoku (np. Nie za paskiem stanu lub paskiem nawigacji, jeśli jest obecny). Podobnie dla pozostałych krawędzi.

Użyj, safeAreaLayoutGuideaby uniknąć przycinania / nakładania się naszych obiektów z zaokrąglonych narożników, pasków nawigacji, pasków kart, pasków narzędzi i innych widoków przodków.

Możemy odpowiednio utworzyć safeAreaLayoutGuideobiekt i ustawić ograniczenia obiektu.

Ograniczenia dla pozycji pionowej i poziomej to -

Obraz portretowy

Obraz poziomy

        self.edgesForExtendedLayout = []//Optional our as per your view ladder

        let newView = UIView()
        newView.backgroundColor = .red
        self.view.addSubview(newView)
        newView.translatesAutoresizingMaskIntoConstraints = false
        if #available(iOS 11.0, *) {
            let guide = self.view.safeAreaLayoutGuide
            newView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
            newView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
            newView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
            newView.heightAnchor.constraint(equalToConstant: 100).isActive = true

        }
        else {
            NSLayoutConstraint(item: newView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
            NSLayoutConstraint(item: newView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
            NSLayoutConstraint(item: newView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0).isActive = true

            newView.heightAnchor.constraint(equalToConstant: 100).isActive = true
        }

UILayoutGuide

safeAreaLayoutGuide

Jacek
źródło
2
Nigdy, przenigdy nie ustawiaj ograniczeń w programie viewDidAppear, chyba że absolutnie wiesz, co robisz. viewDidAppearjest wywoływana wiele razy, więc Twoje ograniczenia będą powielane za każdym razem, gdy zostanie wywołane.
Yevhen Dubinin
Tak! , Edytowana odpowiedź, której możesz użyć jako swojego przypadku użycia.
Jack
14

Dla tych z Was, którzy używają SnapKit , tak jak ja, rozwiązaniem jest zakotwiczenie twoich ograniczeń, aby view.safeAreaLayoutGuidepolubić:

yourView.snp.makeConstraints { (make) in
    if #available(iOS 11.0, *) {
        //Bottom guide
        make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottomMargin)
        //Top guide
        make.top.equalTo(view.safeAreaLayoutGuide.snp.topMargin)
        //Leading guide
        make.leading.equalTo(view.safeAreaLayoutGuide.snp.leadingMargin)
        //Trailing guide
        make.trailing.equalTo(view.safeAreaLayoutGuide.snp.trailingMargin)

     } else {
        make.edges.equalToSuperview()
     }
}
Phillip
źródło
1
Świetna odpowiedź. Aby uczynić go nieco bardziej kompaktowym, możesz powiedzieć: if #available (iOS 11.0, *) {make.edges.equalTo (view.safeAreaLayoutGuide.snp.margins)}
Don Miguel
7

Używam tego zamiast dodawać wiodące i końcowe ograniczenia marginesów do układu:

UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
yourView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
                                           [safe.trailingAnchor constraintEqualToAnchor:yourView.trailingAnchor],
                                           [yourView.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
                                           [yourView.topAnchor constraintEqualToAnchor:safe.topAnchor],
                                           [safe.bottomAnchor constraintEqualToAnchor:yourView.bottomAnchor]
                                          ]];

Proszę również zaznaczyć opcję dla niższej wersji ios 11 z odpowiedzi Krunala.

Tony TRAN
źródło
Upewnij się, że dodałeś już yourView do superView. Jest to self.view W moim kodzie jako prosty przykład.
Tony TRAN
6

Użycie UIWindowlub UIView„ssafeAreaInsets .bottom .top .left .right

// #available(iOS 11.0, *)
// height - UIApplication.shared.keyWindow!.safeAreaInsets.bottom

// On iPhoneX
// UIApplication.shared.keyWindow!.safeAreaInsets.top =  44
// UIApplication.shared.keyWindow!.safeAreaInsets.bottom = 34

// Other devices
// UIApplication.shared.keyWindow!.safeAreaInsets.top =  0
// UIApplication.shared.keyWindow!.safeAreaInsets.bottom = 0

// example
let window = UIApplication.shared.keyWindow!
let viewWidth = window.frame.size.width
let viewHeight = window.frame.size.height - window.safeAreaInsets.bottom
let viewFrame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
let aView = UIView(frame: viewFrame)
aView.backgroundColor = .red
view.addSubview(aView)
aView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Warif Akhand Rishi
źródło
3
To jest droga, jeśli Twój widok nie korzysta z Autoukładu.
Jonathan Cabrera
4

Użyj ograniczeń z formatem wizualnym, a zyskasz szacunek dla bezpiecznego obszaru za darmo.

class ViewController: UIViewController {

    var greenView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        greenView.backgroundColor = .green
        view.addSubview(greenView)
    }
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        greenView.translatesAutoresizingMaskIntoConstraints = false
        let views : [String:Any] = ["greenView":greenView]
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[greenView]-|", options: [], metrics: nil, views: views))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[greenView]-|", options: [], metrics: nil, views: views))
    }
}

wynik

AtomicBoolean
źródło
2
Prosimy o komentarz podczas głosowania przeciwnego, abyśmy wszyscy mogli dowiedzieć się, czy jest przypadek, w którym ta technika nie ma zastosowania. Dzięki!
AtomicBoolean
To działa, podoba mi się również wizualne rozwiązania formatowe! Dzięki! Ale czy to działa dla wszystkich wersji iOS?
PaFi
4

Rozszerzenie bezpiecznego obszaru dla Objective-C

@implementation UIView (SafeArea)

- (NSLayoutAnchor *)safeTopAnchor{

    if (@available(iOS 11.0, *)){
        return self.safeAreaLayoutGuide.topAnchor;
    } else {
        return self.topAnchor;
    }

}


- (NSLayoutAnchor *)safeBottomAnchor{

    if (@available(iOS 11.0, *)) {
        return self.safeAreaLayoutGuide.bottomAnchor;
    } else {
        return self.bottomAnchor;
    }

}

@end
czarna Perła
źródło
to nie działa (Swift 4.2, iOS 12). Wynik ignoruje pasek stanu
Vyachaslav Gerchicov
2

Swift 4.2 i 5.0. Załóżmy, że chcesz dodać wiodące, końcowe, górne i dolne ograniczenia na viewBg. Możesz więc użyć poniższego kodu.

let guide = self.view.safeAreaLayoutGuide
viewBg.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
viewBg.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
viewBg.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
viewBg.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
Gurjinder Singh
źródło
1

To rozszerzenie pomaga ograniczyć UIVIew do jego superview i superview + safeArea:

extension UIView {

    ///Constraints a view to its superview
    func constraintToSuperView() {
        guard let superview = superview else { return }
        translatesAutoresizingMaskIntoConstraints = false

        topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
        leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true
        bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
        rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true
    }

    ///Constraints a view to its superview safe area
    func constraintToSafeArea() {
        guard let superview = superview else { return }
        translatesAutoresizingMaskIntoConstraints = false

        topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor).isActive = true
        leftAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leftAnchor).isActive = true
        bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor).isActive = true
        rightAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.rightAnchor).isActive = true
    }

}
Reimond Hill
źródło
0

Możesz użyć view.safeAreaInsets, jak wyjaśniono tutaj https://www.raywenderlich.com/174078/auto-layout-visual-format-language-tutorial-2

przykładowy kod (pobrany z raywenderlich.com):

override func viewSafeAreaInsetsDidChange() {
  super.viewSafeAreaInsetsDidChange()

  if !allConstraints.isEmpty {
    NSLayoutConstraint.deactivate(allConstraints)
    allConstraints.removeAll()
  }

  let newInsets = view.safeAreaInsets
  let leftMargin = newInsets.left > 0 ? newInsets.left : Metrics.padding
  let rightMargin = newInsets.right > 0 ? newInsets.right : Metrics.padding
  let topMargin = newInsets.top > 0 ? newInsets.top : Metrics.padding
  let bottomMargin = newInsets.bottom > 0 ? newInsets.bottom : Metrics.padding

  let metrics = [
    "horizontalPadding": Metrics.padding,
    "iconImageViewWidth": Metrics.iconImageViewWidth,
    "topMargin": topMargin,
    "bottomMargin": bottomMargin,
    "leftMargin": leftMargin,
    "rightMargin": rightMargin]
}


let views: [String: Any] = [
  "iconImageView": iconImageView,
  "appNameLabel": appNameLabel,
  "skipButton": skipButton,
  "appImageView": appImageView,
  "welcomeLabel": welcomeLabel,
  "summaryLabel": summaryLabel,
  "pageControl": pageControl]

let iconVerticalConstraints = NSLayoutConstraint.constraints(
  withVisualFormat: "V:|-topMargin-[iconImageView(30)]",
  metrics: metrics,
  views: views)
allConstraints += iconVerticalConstraints

let topRowHorizontalFormat = """
  H:|-leftMargin-[iconImageView(iconImageViewWidth)]-[appNameLabel]-[skipButton]-rightMargin-|
  """
...
Serg
źródło