J'ai commencé à adapter mon application pour iPhone X et constaté un problème dans Interface Builder . Les guides de mise en page des zones sûres sont censés être rétro-compatibles, selon les vidéos officielles d'Apple. J'ai trouvé que ça fonctionne très bien dans les storyboards.
Mais dans mes fichiers XIB, les guides de disposition de la zone sécurisée ne sont pas respectés dans iOS 10.
Ils fonctionnent bien pour la nouvelle version du système d'exploitation, mais les appareils iOS 10 semblent simplement supposer que la distance de la zone de sécurité est égale à zéro (en ignorant la taille de la barre d'état).
Est-ce que je manque une configuration requise? S'agit-il d'un bogue Xcode et, le cas échéant, de solutions de contournement connues?
Voici une capture d'écran du problème dans un projet test (iOS 10 gauche, iOS 11 droite):
La configuration de la zone de sécurité et la compatibilité avec les versions antérieures posent quelques problèmes. Voir mon commentaire sur ici .
Vous pourrez peut-être contourner les problèmes avec des contraintes supplémentaires, telles qu'une priorité 1000> = 20.0 vers superview.top et une priorité 750 == safearea.top. Si vous affichez toujours une barre d'état, cela devrait régler le problème.
Une meilleure approche peut être d’avoir des storyboards/xibs distincts pour les versions antérieures à iOS 11 et iOS-11, et plus, en particulier si vous rencontrez plus de problèmes que cela. Cela est préférable parce qu'avant iOS 11, vous devez disposer de contraintes de mise en forme dans les guides de présentation supérieur/inférieur, mais pour iOS 11, vous devez les répartir dans des zones sûres. Les guides de mise en page sont partis. La présentation dans les guides de mise en forme pour la version antérieure à iOS 11 est stylistiquement meilleure qu’un simple décalage de 20 pixels au minimum, même si le résultat sera le même. Si vous affichez toujours une barre d’état.
Si vous adoptez cette approche, vous devrez définir chaque fichier sur la cible de déploiement correcte sur laquelle il sera utilisé (iOS 11 ou une version antérieure) afin que Xcode ne vous avertisse pas et vous permette d'utiliser des guides de mise en forme. zones de sécurité, en fonction. Dans votre code, recherchez iOS 11 au moment de l'exécution, puis chargez le storyboard/xibs approprié.
L'inconvénient de cette approche est la maintenance (vous aurez deux ensembles de contrôleurs de vue à maintenir et à synchroniser), mais une fois que votre application prend uniquement en charge iOS 11+ ou Apple corrige la génération de contraintes du guide de mise en forme avec compatibilité, débarrasser des versions antérieures à iOS 11.
Au fait, comment affichez-vous le contrôleur avec lequel vous voyez cela? Est-ce juste le contrôleur de vue racine ou l'avez-vous présenté, ou ..? Le problème que j'ai remarqué concerne le fait de pousser des contrôleurs de vue, de sorte que vous risquez de vous retrouver dans un cas différent.
Actuellement, la compatibilité en amont ne fonctionne pas bien.
Ma solution consiste à créer 2 contraintes dans le générateur d'interface et à en supprimer une en fonction de la version d'ios que vous utilisez:
view.top == safe area.top
view.top == superview.top + 20
Ajoutez-les en tant que sorties en tant que myConstraintSAFEAREA
et myConstraintSUPERVIEW
respectivement. Ensuite:
override func viewDidLoad() {
if #available(iOS 11.0, *) {
view.removeConstraint(myConstraintSUPERVIEW)
} else {
view.removeConstraint(myConstraintSAFEAREA)
}
}
Pour moi, un correctif simple pour le faire fonctionner sur les deux versions était
if #available(iOS 11, *) {}
else {
self.edgesForExtendedLayout = []
}
Dans la documentation: "Sous iOS 10 et versions antérieures, utilisez cette propriété pour indiquer les bords de votre contrôleur de vue qui s'étendent sous les barres de navigation ou d'autres vues fournies par le système." . Leur configuration dans un tableau vide garantit donc que le contrôleur de vue Est-ce que not ne s'étend pas sous les barres de navigation.
Docu est disponible ici
J'ai combiné certaines des réponses de cette page dans ce document, qui fonctionne à merveille (uniquement pour le guide de disposition supérieur, comme demandé dans la question):
view
ayant une constraint
attachée à SafeArea.top view
constraint
À l'intérieur du ViewController sur viewDidLoad:
if (@available(iOS 11.0, *)) {}
else {
// For each view and constraint do:
[self.view.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
self.constraint.active = NO;
}
Modifier:
Voici la version améliorée que j'ai finalement utilisée dans notre base de code. Copiez/collez simplement le code ci-dessous et connectez chaque vue et chaque contrainte à leur IBOutletCollection.
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *constraintsAttachedToSafeAreaTop;
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *viewsAttachedToSafeAreaTop;
if (@available(iOS 11.0, *)) {}
else {
for (UIView *viewAttachedToSafeAreaTop in self.viewsAttachedToSafeAreaTop) {
[viewAttachedToSafeAreaTop.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
}
for (NSLayoutConstraint *constraintAttachedToSafeAreaTop in self.constraintsAttachedToSafeAreaTop) {
constraintAttachedToSafeAreaTop.active = NO;
}
}
Le nombre de chaque IBOutletCollection doit être égal. par exemple. pour chaque vue il devrait y avoir sa contrainte associée
J'ai utilisé celui-ci, ajouter la disposition de la zone supérieure sécurisée et me connecter
@IBOutlet weak var topConstraint : NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
if !DeviceType.IS_IPHONE_X {
if #available(iOS 11, *) {
}
else{
topConstraint.constant = 20
}
}
}
J'ai ajouté une sous-classe NSLayoutConstraint pour résoudre ce problème (IBAdjustableConstraint
), avec une variable @IBInspectable
, qui ressemble à ceci.
class IBAdjustableConstraint: NSLayoutConstraint {
@IBInspectable var safeAreaAdjustedConstant: CGFloat = 0 {
didSet {
if OS.TenOrBelow {
constant += safeAreaAdjustedConstantLegacy
}
}
}
}
Et OS.TenOrBelow
struct OS {
static let TenOrBelow = UIDevice.current.systemVersion.compare("10.9", options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
}
Définissez-la simplement comme sous-classe de votre contrainte dans IB et vous pourrez apporter des modifications spécifiques à iOS11. J'espère que ça aide quelqu'un.
Cela fonctionne aussi:
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 11.0, *) {}
else {
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height - 80).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20).isActive = true
}
}
J'ai fini par supprimer la contrainte sur la zone sécurisée que j'avais dans mon fichier xib . À la place, j'ai créé un point de vente pour UIView en question, et du code je l'ai connecté comme ceci, dans viewDidLayoutSubviews.
let constraint = alert.viewContents.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 0)
constraint.priority = 998
constraint.isActive = true
Cela lie une petite "alerte" au haut de l'écran mais garantit que le contenu affiché dans l'alerte est toujours situé sous la zone de sécurité supérieure (iOS11ish)/topLayoutGuide (iOS10ish).
Une solution simple et unique. Si quelque chose se brise, je serai de retour ????.