عناصر UITabBar تقفز في التنقل الخلفي على iOS 12.1




swift (8)

لدي تطبيق iOS مع UITabBarController على شاشة رئيسية ، UITabBarController إلى شاشة التفاصيل التي تخفي UITabBarController مع إعداد hidesBottomBarWhenPushed = true .

عند الرجوع إلى الشاشة الرئيسية ، يقوم UITabBarController بإجراء "قفزة" غريبة كما هو موضح في GIF:

يحدث هذا فقط على نظام التشغيل iOS 12.1 ، وليس على الإصدار 12.0 أو الإصدار 11.x.

يبدو وكأنه خطأ iOS 12.1 ، لأنني لاحظت تطبيقات أخرى مثل FB Messenger مع هذا السلوك ، لكنني كنت أتساءل ، هل هناك نوع من الحل لذلك؟


أعتقد أن هذا خطأ في Apple ، ولكن يمكنك تجربة ذلك كحل سريع: ما عليك سوى إنشاء فصل لبرنامج TabBar الخاص بك باستخدام التعليمة البرمجية التالية:

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

إذا كنت لا تزال ترغب في الحفاظ على شريط علامات التبويب شفافًا ، فأنت بحاجة إلى فئة فرعية من UITabBar وتجاوز خاصية 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
    }
} 

}

تكمن الفكرة في عدم السماح للنظام بتعيين zero داخلي ، لذلك لن ينتقل شريط علامات التبويب.


شكرًا لفكرة @ElonChan ، لقد غيرت الدالة c المضمنة إلى الأسلوب الثابت OC ، حيث أنني لن أستخدم هذا @ElonChan كثيرًا. وكذلك ، تم تعديل هذا المقتطف إلى iPhoneX الآن.

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

في UITabBarController ، قم بتعيين isTranslucent = false


كنت أواجه نفس المشكلة بالضبط ، حيث تم تصميم التطبيق بوحدة تحكم تنقل واحدة لكل علامة تبويب. الطريقة الأسهل غير الاختراقية التي وجدت لإصلاحها هي وضع UITabBarController داخل UINavigationController وإزالة UINavigationController الفردية.

قبل:

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

بعد:

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

باستخدام UINavigationController الخارجي ، لا تحتاج إلى إخفاء UITabBar عند دفع وحدة تحكم العرض إلى مكدس التنقل.

مذكرة قانونية:

المشكلة الوحيدة التي وجدتها حتى الآن ، هي أن تعيين عناصر زر العنوان أو شريط اليمين / اليسار على كل UIViewController ليس له نفس التأثير. للتغلب على هذه المشكلة ، قمت بتطبيق التغييرات عبر UITabBarControllerDelegate عند UITabBarControllerDelegate المرئية.

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
}

لاحظ أنني قمت بإضافة preconditionFailure للقبض على أي حالة عند تعديل بنية التنقل


لقد حددت Apple ذلك الآن في نظام iOS 12.1.1


هناك طريقتان لإصلاح هذه المشكلة ، أولاً ، في UITabBarController ، قم بتعيين isTranslucent = false مثل:

[[UITabBar appearance] setTranslucent:NO];

sencondly ، إذا كان الحل الأول لا يصلح لإصدار المصدر الخاص بك ، حاول بهذه الطريقة:

هنا هو رمز الهدف جيم

// .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

مزيد من المعلومات: https://github.com/ChenYilong/CYLTabBarController/commit/2c741c8bffd47763ad2fca198202946a2a63c4fc


يمكنك تجاوز - (UIEdgeInsets)safeAreaInsets طريقة - (UIEdgeInsets)safeAreaInsets لعدد قليل من - (UIEdgeInsets)safeAreaInsets iOS 12 مع هذا:

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




uitabbar