L'activation des guides de mise en forme de zones sûres est-elle compatible avec iOS au-dessous de 11?
J'ai réussi à travailler avec les nouveaux guides d'agencement de la zone sécurisée et à maintenir une compatibilité ascendante avec iOS 9 et iOS 10: (EDIT: comme indiqué dans les commentaires de @NickEntin, cette implémentation présumera qu'il existe une barre d'état présente, ce qui ne sera pas vrai en mode paysage sur l'iPhone X. Il en résultera trop d'espace au sommet (20 points). Cela fonctionnera toutefois parfaitement.
Par exemple. si vous souhaitez qu'une vue soit 10 points sous la barre d'état (et 10 points sous le boîtier du capteur sur l'iPhone X):
File Inspector
et activez le coffre-fort en vérifiant Use Safe Area Layout Guides
.>=
contrainte (supérieure ou égale à), constante 30
(30 car nous voulons un espacement de 10 points de la barre d'état qui est haut de 20 points ) et priorité High
(750).=
(égale), la constante 10
et la priorité Low
(250).La même chose peut être faite pour une vue en bas (et pour le début/fin ou la gauche/droite vers la zone de sécurité):
File Inspector
et activez le coffre-fort en vérifiant Use Safe Area Layout Guides
.>=
(supérieure ou égale), la constante 10
et la priorité High
(750).=
(égale), la constante 10
et la priorité Low
(250).La compatibilité ascendante de Zones sécurisées pour iOS 9 et iOS 10 ne fonctionne que si vous utilisez des storyboards. Si vous utilisez xibs, il n’ya pas de guide de mise en page auquel vous pouvez vous référer. https://forums.developer.Apple.com/thread/87329
Les solutions de contournement semblent être soit
(a) migrez vos xibs dans des storyboards, ou
(b) ajouter des contraintes supplémentaires par programme.
Si (a) n'est pas vraiment une option, l'approche manuelle ressemblera à ceci:
En supposant que vous ayez dans votre xib une vue que vous souhaitez conserver dans la zone de sécurité (c'est-à-dire en dessous de la barre d'état ou de la barre de navigation).
Ajoutez des contraintes dans votre xib entre votre vue et la zone sécurisée pour iOS 11. Attribuez à la contrainte supérieure une priorité de 750.
Dans votre contrôleur de vue, ajoutez une propriété:
@property (nonatomic, strong) NSLayoutConstraint *topLayoutConstraint;
Et ensuite dans viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (@available(iOS 11, *)) {
// safe area constraints already set
}
else {
if (!self.topLayoutConstraint) {
self.topLayoutConstraint = [self.<yourview>.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor];
[self.topLayoutConstraint setActive:YES];
}
}
}
La nouvelle contrainte ne sera créée que pour iOS 9 et iOS 10, a une priorité par défaut de 1000 et remplace celle de xib.
Répétez l'opération pour une contrainte de fond si vous devez éviter l'indicateur de maison.
Version de Swift 4:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11, *) {
// safe area constraints already set
} else {
if topLayoutConstraint == nil {
topLayoutConstraint = <yourview>.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor)
topLayoutConstraint?.isActive = true
}
}
}
Il existe au moins un problème de compatibilité ascendante avec les contraintes de zone de sécurité d'iOS 11 que j'ai observées dans Xcode 9 GM: les contrôleurs de vue poussés avec des contraintes de zone de sécurité.
Si votre barre de navigation est masquée et que vous affichez une vue limitée en haut d'une zone sécurisée, la vue poussée chevauchera la barre d'état sur iOS 9 & 10.
Si la barre de navigation est visible et que l'option "sous les barres du haut" est désactivée, la vue poussée continuera à glisser vers le haut sous la barre de navigation pour atteindre le haut de l'écran. La barre de navigation est correctement placée.
Sur iOS 11, la mise en page sera correcte dans les deux cas.
Voici un exemple simple: http://www.filedropper.com/foobar
Et voici une vidéo de cette barre de navigation masquée (iOS 10.3 à gauche, iOS 11 à droite): https://vimeo.com/234174841/1e27a96a87
Voici une version où la barre de navigation est visible (activée dans la nib): https://vimeo.com/234316256/f022132d57
J'ai déposé cela comme radar # 34477706.
Merci à @ Sander pour avoir signalé le cas visible de la barre de navigation.
Si vous utilisez xibs sans storyboard, ils ne disposent pas de guides de mise en page sur ios 10. Par conséquent, déplacez xib vers le storyboard pour obtenir une compatibilité ascendante.
Oui, votre projet/application fonctionnera dans les versions iOS antérieures à iOS 11 sans aucun problème. Dans les versions iOS antérieures à la version 11, il remplace/considère la mise en forme de la zone sécurisée dans la mise en forme automatique normale et respecte le guide de présentation des règles de haut et de bas.
J'ai testé mon projet existant avec et sans 'SafeAreaLayout' sur les deux plates-formes (iOS 11 et iOS 10 arrière). Ça fonctionne bien.
Assurez-vous juste:
Si vous avez conçu votre projet/interface utilisateur dans AutoLayout; les contraintes de votre UIElement suivent/par rapport au guide de disposition Haut et Bas (ne pas afficher de vue d'ensemble). Ainsi, par un simple clic (activation) sur l'option SafeAreaLayout, la mise en page SafeArea sera automatiquement mise en œuvre correctement pour tous les fichiers Interface Builders de votre storyboard.
Si vous avez conçu votre projet/interface utilisateur dans SafeAreaLayout; alors il suivra automatiquement le guide de disposition supérieur et inférieur dans iOS arrière.
Voici un exemple d'instantané avec résultat. Activer ou désactiver la disposition de la zone sécurisée n'a aucune incidence sur la conception existante.
Disposition de la zone sécurisée:
AutoLayout
En bref, la réponse à votre question est la suivante: "Activer les guides de présentation de la zone sécurisée compatibles avec iOS avant 11"
Vous pouvez implémenter la disposition de la zone sécurisée dans votre projet/application et cela fonctionnera correctement avec les versions précédentes d’IOS en convertissant la disposition de la zone sécurisée en dispositions de haut et de bas.
J'utilisais cela dans Objective-C avec un bon résultat pour iOS 10. Si vous utilisez SafeArea dans votre xib, vous pouvez ajouter dans votre viewDidLoad
:
if (@available(iOS 11.0, *)) {}
else {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
J'ai trouvé un moyen plus pratique où il suffit de sous-classer la NSLayoutConstraint
qui est épinglée à votre safeArea
.
C'est un peu hacky puisque vous devez obtenir le ViewController d'un UIView, mais à mon avis, c'est une alternative simple et efficace jusqu'à ce que Apple corrige enfin la compatibilité avec les versions antérieures de safeArea dans Xibs.
Sous-classe:
class SafeAreaBackwardsCompatabilityConstraint: NSLayoutConstraint {
private weak var newConstraint: NSLayoutConstraint?
override var secondItem: AnyObject? {
get {
if #available(iOS 11.0, *) {}
else {
if let vc = (super.secondItem as? UIView)?.parentViewController, newConstraint == nil {
newConstraint = (self.firstItem as? UIView)?.topAnchor.constraint(equalTo: vc.topLayoutGuide.bottomAnchor)
newConstraint?.isActive = true
newConstraint?.constant = self.constant
}
}
return super.secondItem
}
}
override var priority: UILayoutPriority {
get {
if #available(iOS 11.0, *) { return super.priority }
else { return 750 }
}
set { super.priority = newValue }
}
}
private extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
Xib:
Le "guide de disposition des zones sûres" est compatible avec les versions antérieures. Eh bien, sauf si vous l'utilisez dans xib. Avec le storyboard, ça semble bien.
J'ai résolu mon problème en accédant à la "contrainte de disposition maximale" du premier objet situé en haut de ma vue.
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topLayoutConstraint;
ensuite, j'ai changé la valeur de Constant pour cette contrainte et actualiser la vue. Par exemple, si vous utilisez une barre de navigation (hauteur 44) plus la barre d'état (hauteur 20):
if (SYSTEM_VERSION_LESS_THAN(@"11.0")) {
_topLayoutConstraint.constant = 64;
[self.view layoutIfNeeded];
}
Avec SYSTEM_VERSION_LESS_THAN qui est défini comme ça:
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
J'ai des problèmes de compatibilité ascendante avec WKWebView et la zone sécurisée sous iOS 9. Pour une raison quelconque, WKWebView ignore simplement les paramètres de disposition de la zone sécurisée.
En Objective-C pour les marges supérieure et inférieure sur iPhone-X
if (@available(iOS 11, *)) {
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.parentView.safeAreaLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.parentView.safeAreaLayoutGuide
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
} else {
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.parentView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.parentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
}
Lorsque vous avez un ViewController générique que tous vos ViewControllers étendent, une autre solution consisterait à placer les éléments devant être ajustés dans un IBOutletCollection et à les ajuster par programme dans ce GenericViewController. Voici mon code:
@IBOutlet var adjustTopSpaceViews: [UIView]?
override func viewDidLoad() {
super.viewDidLoad()
adjustViews()
....
}
func adjustViews() {
guard let views = adjustTopSpaceViews,
ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11 else {
return
}
let statusBarHeight = UIApplication.shared.statusBarFrame.height
for subview in views {
subview.superview?.constraints.filter({ (constraint) -> Bool in
return constraint.firstAttribute == .top
&& constraint.secondAttribute == .top
&& (constraint.firstItem as? UIView == subview || constraint.secondItem as? UIView == subview)
}).forEach({ (constraint) in
constraint.constant += (constraint.firstItem as? UIView == subview) ? statusBarHeight : -statusBarHeight
})
}
}
Voici ce que j'ai fait avec mes projets
Dans mon cas, mes topConstraint
et bottomConstraint
s sont @IBOutlet
s. Ceci est également compatible pour iOS 8
.
Ma configuration initiale pour les contraintes du haut et du bas concerne les iPhones normaux. C’est pourquoi je n’édite que les contraintes pour iPhone X
// iOS 11 Layout Fix. (For iPhone X)
if #available(iOS 11, *) {
self.topConstraint.constant = self.topConstraint.constant + self.view.safeAreaInsets.top
self.bottomConstraint.constant = self.bottomConstraint.constant + self.view.safeAreaInsets.bottom
}
.
NOTE:self.view
est votre superView, c'est pourquoi je l'utilise pour safeAreaInsets
Voici mon wrapper de solution iOS 9 à iOS 11+ dans Swift 4 +
let safeAreaTopAnchor:NSLayoutYAxisAnchor?
if #available(iOS 11.0, *) {
safeAreaTopAnchor = contentView.safeAreaLayoutGuide.topAnchor
} else {
// Fallback on earlier versions
var parentViewController: UIViewController? {
var parentVCResponder: UIResponder? = self
while parentVCResponder != nil {
parentVCResponder = parentVCResponder!.next
if let viewController = parentVCResponder as? UIViewController {
return viewController
}
}
return nil
}
safeAreaTopAnchor = parentViewController?.topLayoutGuide.bottomAnchor
}