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