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:
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.
Dans votre UITabBarController, définissez isTranslucent = false
Apple a maintenant corrigé cela dans iOS 12.1.1
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]})
}
}
}
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
}
}
}
}
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)
}
}
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
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.
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 UINavigationController
s.
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.
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
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
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.
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;
}