web-dev-qa-db-fra.com

Eléments UITabBar sautant dans la navigation arrière sur iOS 12.1

J'ai une application iOS avec UITabBarController sur un écran principal, en naviguant vers un écran de détail masquant la UITabBarController avec le paramètre hidesBottomBarWhenPushed = true.

Lorsque vous revenez à l'écran principal, la UITabBarController fait un "saut" étrange, comme indiqué sur ce GIF:

 enter image description here

Cela se produit uniquement sur iOS 12.1, pas sur 12.0 ou 11.x.

Cela ressemble à un bogue iOS 12.1, car j'ai remarqué que d'autres applications, telles que FB Messenger, présentaient ce comportement, mais je me demandais s'il existait une solution de contournement.

44
Igor Kulman

Dans votre UITabBarController, définissez isTranslucent = false

47
binhnguyen14

Apple a maintenant corrigé cela dans iOS 12.1.1

7
Valerio

Je suppose que c'est le bogue d'Apple Mais vous pouvez essayer ceci comme un correctif: créez simplement une classe pour votre tabBar avec le code suivant:

import UIKit

class FixedTabBar: UITabBar {

    var itemFrames = [CGRect]()
    var tabBarItems = [UIView]()


    override func layoutSubviews() {
        super.layoutSubviews()

        if itemFrames.isEmpty, let UITabBarButtonClass = NSClassFromString("UITabBarButton") as? NSObject.Type {
            tabBarItems = subviews.filter({$0.isKind(of: UITabBarButtonClass)})
            tabBarItems.forEach({itemFrames.append($0.frame)})
        }

        if !itemFrames.isEmpty, !tabBarItems.isEmpty, itemFrames.count == items?.count {
            tabBarItems.enumerated().forEach({$0.element.frame = itemFrames[$0.offset]})
        }
    }
}
5
Legis

Voici une solution qui peut gérer les éléments de rotation et de barre d’onglet ajoutés ou supprimés:

class FixedTabBar: UITabBar {

    var buttonFrames: [CGRect] = []
    var size: CGSize = .zero

    override func layoutSubviews() {
        super.layoutSubviews()

        if UIDevice.current.systemVersion >= "12.1" {
            let buttons = subviews.filter {
                String(describing: type(of: $0)).hasSuffix("Button")
            }
            if buttonFrames.count == buttons.count, size == bounds.size {
                Zip(buttons, buttonFrames).forEach { $0.0.frame = $0.1 }
            } else {
                buttonFrames = buttons.map { $0.frame }
                size = bounds.size
            }
        }
    }
}
3
Nick Dowell
import UIKit

extension UITabBar{

open override func layoutSubviews() {
    super.layoutSubviews()
    if let UITabBarButtonClass = NSClassFromString("UITabBarButton") as? NSObject.Type{
        let subItems = self.subviews.filter({return $0.isKind(of: UITabBarButtonClass)})
        if subItems.count > 0{
            let tmpWidth = UIScreen.main.bounds.width / CGFloat(subItems.count)
            for (index,item) in subItems.enumerated(){
                item.frame = CGRect(x: CGFloat(index) * tmpWidth, y: 0, width: tmpWidth, height: item.bounds.height)
                }
            }
        }
    }

open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if let view:UITabBar = super.hitTest(point, with: event) as? UITabBar{
        for item in view.subviews{
            if point.x >= item.frame.Origin.x  && point.x <= item.frame.Origin.x + item.frame.size.width{
                return item
                }
            }
        }
        return super.hitTest(point, with: event)
    }
}
2
user10589608

il existe deux manières de résoudre ce problème, Premièrement, dans votre UITabBarController, définissez isTranslucent = false comme suit:

[[UITabBar appearance] setTranslucent:NO];

enfin, si la première solution ne résout pas votre problème, essayez cette procédure:

voici le code Objective-C

// .h
@interface CYLTabBar : UITabBar
@end 

// .m
#import "CYLTabBar.h"

CG_INLINE BOOL
OverrideImplementation(Class targetClass, SEL targetSelector, id (^implementationBlock)(Class originClass, SEL originCMD, IMP originIMP)) {
   Method originMethod = class_getInstanceMethod(targetClass, targetSelector);
   if (!originMethod) {
       return NO;
   }
   IMP originIMP = method_getImplementation(originMethod);
   method_setImplementation(originMethod, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originIMP)));
   return YES;
}
@implementation CYLTabBar

+ (void)load {

   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       if (@available(iOS 12.1, *)) {
           OverrideImplementation(NSClassFromString(@"UITabBarButton"), @selector(setFrame:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) {
               return ^(UIView *selfObject, CGRect firstArgv) {

                   if ([selfObject isKindOfClass:originClass]) {

                       if (!CGRectIsEmpty(selfObject.frame) && CGRectIsEmpty(firstArgv)) {
                           return;
                       }
                   }

                   // call super
                   void (*originSelectorIMP)(id, SEL, CGRect);
                   originSelectorIMP = (void (*)(id, SEL, CGRect))originIMP;
                   originSelectorIMP(selfObject, originCMD, firstArgv);
               };
           });
       }
   });
}
@end

Plus d'informations: https://github.com/ChenYilong/CYLTabBarController/commit/2c741c8bffd47763ad2fca198202946a2a63c4fc

1
ElonChan

Si vous souhaitez toujours garder la barre de tabulation transparente, vous devez sous-classer à partir de UITabBar et remplacer la propriété safeAreaInsets

class MyTabBar: UITabBar {

private var safeInsets = UIEdgeInsets.zero

@available(iOS 11.0, *)
override var safeAreaInsets: UIEdgeInsets {
    set {
        if newValue != UIEdgeInsets.zero {
            safeInsets = newValue
        }
    }
    get {
        return safeInsets
    }
} 

}

L'idée est de ne pas autoriser le système à définir des incrustations zero, de sorte que la barre de tabulation ne saute pas. 

0
Artem

Je faisais face au même problème, où l'application était conçue avec un contrôleur de navigation par onglet. Pour résoudre ce problème, la méthode la plus simple que j'ai trouvée pour résoudre ce problème était de placer la variable UITabBarController dans une variable UINavigationController et de supprimer la variable UINavigationControllers.

Avant:

                   -> UINavigationController -> UIViewController
                   -> UINavigationController -> UIViewController
UITabBarController -> UINavigationController -> UIViewController
                   -> UINavigationController -> UIViewController
                   -> UINavigationController -> UIViewController

Après:

                                             -> UIViewController
                                             -> UIViewController
UINavigationController -> UITabBarController -> UIViewController
                                             -> UIViewController
                                             -> UIViewController

En utilisant la variable UINavigationController extérieure, il n'est pas nécessaire de masquer la UITabBar lorsque vous placez un contrôleur de vue sur la pile de navigation.

Caveat:

Le seul problème que j’ai trouvé jusqu’à présent est que le réglage du titre ou des boutons de la barre de droite/gauche sur chaque UIViewController n’a pas le même effet. Pour résoudre ce problème, j'ai appliqué les modifications via la variable UITabBarControllerDelegate lorsque la variable visible UIViewController a été modifiée.

func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
    guard let topItem = self.navigationController?.navigationBar.topItem else { return }
    precondition(self.navigationController == viewController.navigationController, "Navigation controllers do not match. The following changes might result in unexpected behaviour.")
    topItem.title = viewController.title
    topItem.titleView = viewController.navigationItem.titleView
    topItem.leftBarButtonItem = viewController.navigationItem.leftBarButtonItem
    topItem.rightBarButtonItem = viewController.navigationItem.rightBarButtonItem
}

Notez que j'ai ajouté une preconditionFailure pour intercepter les cas où l'architecture de navigation a été modifiée.

0
vfn

Merci pour l’idée de @ElonChan , Je viens de changer la fonction inline c en méthode statique OC, car je n’utiliserai pas trop cette overrideImplementation. Et aussi, cet extrait a été ajusté pour iPhoneX maintenant.

static CGFloat const kIPhoneXTabbarButtonErrorHeight = 33;
static CGFloat const kIPhoneXTabbarButtonHeight = 48;


@implementation FixedTabBar


typedef void(^NewTabBarButtonFrameSetter)(UIView *, CGRect);
typedef NewTabBarButtonFrameSetter (^ImpBlock)(Class originClass, SEL originCMD, IMP originIMP);


+ (BOOL)overrideImplementationWithTargetClass:(Class)targetClass targetSelector:(SEL)targetSelector implementBlock:(ImpBlock)implementationBlock {
    Method originMethod = class_getInstanceMethod(targetClass, targetSelector);
    if (!originMethod) {
        return NO;
    }
    IMP originIMP = method_getImplementation(originMethod);
    method_setImplementation(originMethod, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originIMP)));
    return YES;
}


+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (@available(iOS 12.1, *)) {
            [self overrideImplementationWithTargetClass:NSClassFromString(@"UITabBarButton")
                                         targetSelector:@selector(setFrame:)
                                         implementBlock:^NewTabBarButtonFrameSetter(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) {
                return ^(UIView *selfObject, CGRect firstArgv) {
                    if ([selfObject isKindOfClass:originClass]) {
                        if (!CGRectIsEmpty(selfObject.frame) && CGRectIsEmpty(firstArgv)) {
                            return;
                        }
                        if (firstArgv.size.height == kIPhoneXTabbarButtonErrorHeight) {
                            firstArgv.size.height = kIPhoneXTabbarButtonHeight;
                        }
                    }
                    void (*originSelectorIMP)(id, SEL, CGRect);
                    originSelectorIMP = (void (*)(id, SEL, CGRect))originIMP;
                    originSelectorIMP(selfObject, originCMD, firstArgv);
                };
            }];
        }
    });
}

@end
0
boog

voici le code Swift

extension UIApplication {
open override var next: UIResponder? {
    // Called before applicationDidFinishLaunching
    SwizzlingHelper.enableInjection()
    return super.next
}

}

classe SwizzlingHelper {

static func enableInjection() {
    DispatchQueue.once(token: "com.SwizzlingInjection") {
        //what to need inject
        UITabbarButtonInjection.inject()
    }

} plus d'informations https://github.com/tonySwiftDev/UITabbar-fixIOS12.1Bug

0
tonySwiftDev

Dans mon cas (iOS 12.1.4), j’ai constaté que ce comportement pervers étrange était déclenché par le fait que les modaux étaient présentés avec le .modalPresentationStyle = .fullScreen

Après avoir mis à jour leur presentationStyle en.overFullScreen, le problème est parti. 

0
Edouard Barbier

Vous pouvez remplacer la méthode - (UIEdgeInsets)safeAreaInsets pour quelques sous-versions iOS 12 avec ceci:

- (UIEdgeInsets)safeAreaInsets {
    UIEdgeInsets insets = [super safeAreaInsets];
    CGFloat h = CGRectGetHeight(self.frame);
    if (insets.bottom >= h) {
        insets.bottom = [self.window safeAreaInsets].bottom;
    }
    return insets;
}
0
Sound Blaster