Je rencontre un problème ennuyeux lorsque je teste le dernier iOS 11 sur le simulateur iPhone X.
J'ai une UITabBarController
et à l'intérieur de chaque onglet il y a une UINavigationController
, chaque UINavigationBar
a également défini une barre d'outils inférieure (setToolbarHidden:
) et, par défaut, elles apparaissent en bas, juste au-dessus de la barre de tabulation.
Cela a bien fonctionné jusqu'à présent et semble fonctionner également dans les modèles à venir pour iPhone 8 et 8 Plus, mais sur l'iPhone X, il y a un écart entre la barre d'outils et la barre de tabulation. Mon hypothèse est que la barre d’outils ne réalise pas qu’elle s’affiche à l’intérieur d’un tabBar et qu’elle laisse ensuite l’espace de rangement en bas.
Je suppose que le seul moyen de résoudre ce problème consiste à utiliser une barre d’outils personnalisée et à l’afficher/l’animer moi-même au lieu d’utiliser les valeurs par défaut UINavigationBar
, mais j’aimerais entendre d’autres options :)
J'ai classé cela sous le nom radr: // problem/34421298, qui était fermé en tant que duplicata de radr: // problem/34462371. Cependant, dans la dernière version bêta de Xcode 9.2 (9C32c) avec iOS 11.2, cela semble être résolu. Voici un exemple de mon application en cours d'exécution dans le simulateur de chaque appareil, sans aucun changement entre les deux.
Ce n'est pas vraiment une solution à votre problème, mis à part le fait qu'un peu de patience peut le résoudre sans avoir besoin de recourir à la ruse de l'interface utilisateur. Mon hypothèse est qu'iOS 11.2 sera disponible avant la fin de l'année, car il est nécessaire pour prendre en charge HomePod.
Si vous ne tenez pas compte des rotations, vous pouvez essayer de manipuler le calque de la barre d’outils comme une solution de contournement très simple mais rapide.
class FixNavigationController: UINavigationController
{
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateTollbarPosition()
}
func updateTollbarPosition() {
guard let tabbarFrame = tabBarController?.tabBar.frame else {
return
}
let gapHeight = tabbarFrame.Origin.y-toolbar.frame.Origin.y-toolbar.frame.size.height
var
frame = toolbar.layer.frame
frame.Origin.y += gapHeight
toolbar.layer.frame = frame
}
}
Malheureusement, l’animation par rotation n’a pas l’air bon pour cette approche. Dans ce cas, l’ajout de la barre d’outils personnalisée à la place de la barre standard sera une meilleure solution.
iOS 11.1 et iPhone X sont publiés et ce bug/fonctionnalité n'est pas encore corrigé. J'ai donc implémenté cette solution de contournement. Ce code fonctionne dans iOS 9.0+.
Définissez simplement cette classe dans votre storyboard en tant que classe du contrôleur de navigation. Il utilisera la barre d’outils personnalisée de l’iPhone X avec des contraintes de mise en page correctes et retombera sur celle d’origine dans d’autres appareils. La barre d'outils personnalisée est ajoutée à la vue du contrôleur de navigation au lieu de votre contrôleur de vue, afin de rendre les transitions plus douces.
updateItems(animated:)
manuellement après avoir défini toolbarItems
de votre contrôleur de vue pour mettre à jour l'interface. Si vous définissez la propriété toolbarItems
du contrôleur de navigation, vous pouvez ignorer cette étape.Il simule tous les comportements natifs de la barre d'outils (y compris la modification de la hauteur de la barre d'outils en mode portrait/paysage), à l'exception des animations Push/pop.
import UIKit
class FixNavigationController: UINavigationController {
private weak var alterToolbarHeightConstraint: NSLayoutConstraint?
private var _alterToolbar: UIToolbar?
private func initAlretToolbar() {
_alterToolbar = UIToolbar()
_alterToolbar!.isTranslucent = true
_alterToolbar!.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(_alterToolbar!)
if view.traitCollection.verticalSizeClass == .compact {
alterToolbarHeightConstraint = _alterToolbar!.heightAnchor.constraint(equalToConstant: 32.0)
} else {
alterToolbarHeightConstraint = _alterToolbar!.heightAnchor.constraint(equalToConstant: 44.0)
}
let bottomAnchor: NSLayoutConstraint
if #available(iOS 11.0, *) {
bottomAnchor = _alterToolbar!.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
} else {
bottomAnchor = _alterToolbar!.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor)
}
NSLayoutConstraint.activate([
_alterToolbar!.leadingAnchor.constraint(equalTo: view.leadingAnchor),
_alterToolbar!.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bottomAnchor,
alterToolbarHeightConstraint!
])
self.view.updateFocusIfNeeded()
self.view.layoutIfNeeded()
}
private var alterToolbarInSuper: UIToolbar? {
var superNavigationController = self.navigationController as? FixNavigationController
while superNavigationController != nil {
if superNavigationController?._alterToolbar != nil {
return superNavigationController?._alterToolbar
}
superNavigationController = superNavigationController?.navigationController as? FixNavigationController
}
return nil
}
private var alterToolbar: UIToolbar! {
get {
if let t = alterToolbarInSuper {
return t
}
if _alterToolbar == nil {
initAlretToolbar()
}
return _alterToolbar
}
}
// This is the logic to determine should use custom toolbar or fallback to native one
private var shouldUseAlterToolbar: Bool {
// return true if height is iPhone X's one
return UIScreen.main.nativeBounds.height == 2436
}
/// Manually call it after setting toolbar items in child view controllers
func updateItems(animated: Bool = false) {
if shouldUseAlterToolbar {
(_alterToolbar ?? alterToolbarInSuper)?.setItems(viewControllers.last?.toolbarItems ?? toolbarItems, animated: animated)
}
}
override var isToolbarHidden: Bool {
get {
if shouldUseAlterToolbar {
return _alterToolbar == nil && alterToolbarInSuper == nil
} else {
return super.isToolbarHidden
}
}
set {
if shouldUseAlterToolbar {
if newValue {
super.isToolbarHidden = newValue
_alterToolbar?.removeFromSuperview()
_alterToolbar = nil
self.view.updateFocusIfNeeded()
self.view.layoutIfNeeded()
// TODO: Animation when Push/pop
alterToolbarHeightConstraint = nil
var superNavigationController = self.navigationController as? FixNavigationController
while let superNC = superNavigationController {
if superNC._alterToolbar != nil {
superNC._alterToolbar?.removeFromSuperview()
superNC._alterToolbar = nil
superNC.view.updateFocusIfNeeded()
superNC.view.layoutIfNeeded()
}
superNavigationController = superNC.navigationController as? FixNavigationController
}
} else {
alterToolbar.setItems(viewControllers.last?.toolbarItems ?? toolbarItems, animated: false)
}
} else {
super.isToolbarHidden = newValue
}
}
}
override func setToolbarItems(_ toolbarItems: [UIBarButtonItem]?, animated: Bool) {
super.setToolbarItems(toolbarItems, animated: animated)
updateItems(animated: animated)
}
override var toolbarItems: [UIBarButtonItem]? {
get {
return super.toolbarItems
}
set {
super.toolbarItems = newValue
updateItems()
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
guard let _alterToolbar = _alterToolbar else {
return
}
self.alterToolbarHeightConstraint?.isActive = false
let height: CGFloat = (view.traitCollection.verticalSizeClass == .compact) ? 32.0 : 44.0
let alterToolbarHeightConstraint = _alterToolbar.heightAnchor.constraint(equalToConstant: height)
alterToolbarHeightConstraint.isActive = true
self.alterToolbarHeightConstraint = alterToolbarHeightConstraint
}
}
Apple n'a toujours pas corrigé ce bogue dans iOS 11.2. Dérivée de la solution de Mousavian, voici une approche plus simple que j'ai adoptée.
J'ai choisi cette approche car je n'ai qu'un seul UITableViewController où ce bogue se produit. Donc dans mon cas, je viens d'ajouter le code suivant répertorié ci-dessous à mon ViewController (qui est UITableViewController) où ce bogue se produit.
Les avantages sont:
Et voici le code:
1.Ajouter startFixIPhoneXToolbarBug à votre viewWillAppear comme ceci:
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
startFixIPhoneXToolbarBug()
}
2.Ajouter endFixIPhoneXToolbarBug à votre viewWillDisappear comme ceci:
override func viewWillDisappear(_ animated: Bool)
{
super.viewWillDisappear(animated)
endFixIPhoneXToolbarBug()
}
3.Implement start/endFixIPhoneXToolbarBug dans votre viewController comme ceci:
private var alterToolbarHeightConstraint: NSLayoutConstraint? = nil
private var alterToolbar: UIToolbar? = nil
func startFixIPhoneXToolbarBug()
{
// Check if we are running on an iPhone X
if UIScreen.main.nativeBounds.height != 2436
{
return // No
}
// See if we have a Toolbar
if let tb:UIToolbar = self.navigationController?.toolbar
{
// See if we already added our own
if alterToolbar == nil
{
// Should always be the case
if let tbView = tb.superview
{
// Create a new Toolbar and apply correct constraints
alterToolbar = UIToolbar()
alterToolbar!.isTranslucent = true
alterToolbar!.translatesAutoresizingMaskIntoConstraints = false
tb.isHidden = true
tbView.addSubview(alterToolbar!)
if tbView.traitCollection.verticalSizeClass == .compact
{
alterToolbarHeightConstraint = alterToolbar!.heightAnchor.constraint(equalToConstant: 32.0)
}
else
{
alterToolbarHeightConstraint = alterToolbar!.heightAnchor.constraint(equalToConstant: 44.0)
}
let bottomAnchor: NSLayoutConstraint
if #available(iOS 11.0, *)
{
bottomAnchor = alterToolbar!.bottomAnchor.constraint(equalTo: tbView.safeAreaLayoutGuide.bottomAnchor)
}
else
{
bottomAnchor = alterToolbar!.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor)
}
NSLayoutConstraint.activate([
alterToolbar!.leadingAnchor.constraint(equalTo: tbView.leadingAnchor),
alterToolbar!.trailingAnchor.constraint(equalTo: tbView.trailingAnchor),
bottomAnchor,
alterToolbarHeightConstraint!
])
tbView.updateFocusIfNeeded()
tbView.layoutIfNeeded()
}
}
// Add the original items to the new toolbox
alterToolbar!.setItems(tb.items, animated: false)
}
}
func endFixIPhoneXToolbarBug()
{
if alterToolbar != nil
{
alterToolbar!.removeFromSuperview()
alterToolbar = nil
alterToolbarHeightConstraint = nil
if let tb:UIToolbar = self.navigationController?.toolbar
{
tb.isHidden = false
}
}
}