tab bar ios swift uitabbar

ios - uitabbarcontroller swift 4



Los elementos de UITabBar saltan en la navegaciĆ³n trasera en iOS 12.1 (12)

Tengo una aplicación para iOS con UITabBarController en una pantalla maestra, navegando a una pantalla detallada que oculta el UITabBarController con la configuración hidesBottomBarWhenPushed = true .

Al volver a la pantalla maestra, el UITabBarController hace un "salto" extraño como se muestra en este GIF:

Esto sucede solo en iOS 12.1 , no en 12.0 o 11.x.

Parece un error de iOS 12.1, porque me di cuenta de otras aplicaciones como FB Messenger con este comportamiento, pero me estaba preguntando, ¿hay algún tipo de solución para esto?


Apple ya ha arreglado eso en iOS 12.1.1


Aquí está el código rápido

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

}

clase SwizzlingHelper {

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

} más información https://github.com/tonySwiftDev/UITabbar-fixIOS12.1Bug


Aquí hay una solución que puede manejar la rotación y los elementos de la barra de pestañas que se agregan o eliminan:

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 } } } }


En mi caso (iOS 12.1.4), encontré que este extraño comportamiento glitchy fue provocado por los modales que se presentan con el .modalPresentationStyle = .fullScreen

Después de actualizar su estilo de presentación a .overFullScreen , el problema desapareció.


En su UITabBarController , establezca isTranslucent = false


Gracias por la idea de @ElonChan , acabo de cambiar la función c en línea al método estático OC, ya que no overrideImplementation demasiado esta overrideImplementation . Y también, este fragmento se ajustó a iPhoneX ahora.

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


Me enfrentaba exactamente al mismo problema, en el que la aplicación estaba diseñada con un controlador de navegación por pestaña. La forma más fácil de no hackear que encontré para solucionar este problema fue colocar el UITabBarController dentro de un UINavigationController y eliminar los UINavigationController individuales.

Antes de:

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

Después:

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

Al utilizar el UINavigationController externo de UINavigationController , no es necesario que oculte la UITabBar cuando se empuja un controlador de vista en la pila de navegación.

Advertencia:

El único problema que encontré hasta ahora, es que la configuración del título o los elementos del botón de la barra derecha / izquierda en cada UIViewController no tiene el mismo efecto. Para superar este problema, apliqué los cambios a través de UITabBarControllerDelegate cuando el UIViewController visible ha cambiado.

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 }

Tenga en cuenta que he agregado un preconditionFailure de preconditionFailure para detectar cualquier caso cuando se haya modificado la arquitectura de navegación


Puede anular el método - (UIEdgeInsets)safeAreaInsets para algunas subversiones de iOS 12 con esto:

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


Si aún desea mantener su barra de pestañas translúcida, debe UITabBar subclase de UITabBar y anular la propiedad 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 } }

}

La idea es no permitir que el sistema establezca inserciones de zero , por lo que la barra de pestañas no saltará.


Supongo que es el error de Apple. Pero puedes probar esto como una solución: crea una clase para tu barra de pestañas con el siguiente código:

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]}) } } }


hay dos formas de solucionar este problema: En primer lugar, en su UITabBarController, establezca isTranslucent = false como:

[[UITabBar appearance] setTranslucent:NO];

Sencillamente, si la primera solución no soluciona su problema, intente de esta manera:

Aquí está el código 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

Más información: https://github.com/ChenYilong/CYLTabBarController/commit/2c741c8bffd47763ad2fca198202946a2a63c4fc


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) } }