iOS 導航欄 - UINavigationBar

iOS開發中,UINavigationController是一種常用的視圖控制器,主要用來控制UI界面流程跳轉。
在UINavigationController使用過程中,經常會遇到導航欄(UINavigationBar)顯示/隱藏、透明/不透明,以及透明漸變的問題。

本文主要介紹導航欄(UINavigationBar)顯示/隱藏、透明/不透明,以及透明漸變的問題。

以下是我整理的與導航欄相關的控件組織圖:


UINavigationController

通過上圖可以看出:
1、UINavigationController繼承于UIViewController;
2、UINavigationController包含viewControllers(UIViewController數組)、UINavigationBar、UIToolbar;
3、UINavigationBar管理items(UINavigationItem數組);
4、UIViewController包含UINavigationItem。

下圖能更好的理解UINavigationController組織結構


UINavigationController

隱藏與顯示

方法一:
NS_CLASS_AVAILABLE_IOS(2_0) @interface UINavigationController : UIViewController

@property(nonatomic,getter=isNavigationBarHidden) BOOL navigationBarHidden;
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated; 
@property(nonatomic,readonly) UINavigationBar *navigationBar;

@end

使用UINavigationController的navigationBarHidden屬性獲取隱藏導航方法。常用于以下方法中

- (void)viewWillAppear:(BOOL)animated; 
- (void)viewDidAppear:(BOOL)animated; 
- (void)viewWillDisappear:(BOOL)animated; 
- (void)viewDidDisappear:(BOOL)animated;

當然還有實現UINavigationControllerDelegate,在代理方法中通過判斷showViewController類型,來控制顯示。

// Called when the navigation controller shows a new top view controller via a push, pop or setting of the view controller stack.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

這種隱藏方式在滑動過程中,有些iOS版本會出現過渡不自然現象。(現不建議使用)
效果如下:


導航欄隱藏與顯示.gif
方法二:

使用UINavigationController+FDFullscreenPopGesture
該類重寫了UINavigationController的+ (void)load;方法。
具體可參照以下代碼:

+ (void)load {
    // Inject "-pushViewController:animated:"
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(pushViewController:animated:);
        SEL swizzledSelector = @selector(fd_pushViewController:animated:);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

運用了runtime技術,在執行-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated時,執行- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated;,以此來解決滑動過程中,過渡不自然問題。

該方法有一個不算缺陷的缺陷,就是一個工程只能只能置換一次。
如果你在一個SDK中用到了該技術(修改類別名),并且另一個工程引入了該SDK,并且也使用了該技術。會造成滑動不自然。當然,你可以把該技術單獨提出來,供SDK和工程共同引用,這樣就可以解決問題了。

透明與不透明,以及透明漸變

方法一:
NS_CLASS_AVAILABLE_IOS(2_0) @interface UINavigationBar : UIView <NSCoding, UIBarPositioning> 

/*
 New behavior on iOS 7.
 Default is YES.
 You may force an opaque background by setting the property to NO.
 If the navigation bar has a custom background image, the default is inferred 
 from the alpha values of the image—YES if it has any pixel with alpha < 1.0
 If you send setTranslucent:YES to a bar with an opaque custom background image
 it will apply a system opacity less than 1.0 to the image.
 If you send setTranslucent:NO to a bar with a translucent custom background image
 it will provide an opaque background for the image using the bar's barTintColor if defined, or black
 for UIBarStyleBlack or white for UIBarStyleDefault if barTintColor is nil.
 */
// Default is NO on iOS 6 and earlier. Always YES if barStyle is set to UIBarStyleBlackTranslucent
@property(nonatomic,assign,getter=isTranslucent) BOOL translucent NS_AVAILABLE_IOS(3_0) UI_APPEARANCE_SELECTOR; 

@end
方法二:

通過文章剛開始介紹的UINavigationController組織圖,可發現UINavigationController中只包含一個UINavigationBar。那么我們可以從UINavigationBar直接入手。

self.edgesForExtendedLayout = UIRectEdgeTop;
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
if ([self.navigationController.navigationBar respondsToSelector:@selector(shadowImage)]) {
   [self.navigationController.navigationBar setShadowImage:[UIImage new]];
}
self.navigationController.navigationBar.backgroundColor = [UIColor clearColor];
self.navigationController.navigationBar.alpha = 0.0;
self.navigationController.navigationBar.backItem.hidesBackButton = YES;
self.navigationController.navigationItem.hidesBackButton = YES;
self.navigationItem.hidesBackButton = YES;

該方法是直接控制UINavigationBar的背景圖片、陰影圖片、背景色、navigationItem等,也能達到類似- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated;效果。
常用于以下方法中

- (void)viewWillAppear:(BOOL)animated; 
- (void)viewDidAppear:(BOOL)animated; 
- (void)viewWillDisappear:(BOOL)animated; 
- (void)viewDidDisappear:(BOOL)animated;

當然還有實現UINavigationControllerDelegate,在代理方法中通過判斷showViewController類型,來控制顯示。

// Called when the navigation controller shows a new top view controller via a push, pop or setting of the view controller stack.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

這種方法好處是直接控制UINavigationBar,不好的地方是調用的方法過多,還要考慮viewControllers中的navigationItem。

透明漸變

效果圖


透明漸變

主要實現思路代碼

#pragma mark - UIScrollViewDelegate

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGFloat offset = scrollView.contentOffset.y;
    [self setLifeNav:offset];
}
#pragma mark - Nav Bar

- (CGFloat)navBarColorAlpha:(CGFloat)offsetY {
    CGFloat alpha;
    CGFloat height = 200.0;
    if (offsetY <= 0) {
        alpha = 0.0;
    } else if (offsetY > 0 && offsetY < height) {
        alpha = offsetY / height;
    } else {
        alpha = 1.0;
    }
    return alpha;
}

/// 使用顏色填充圖片
- (UIImage *)imageWithColor:(UIColor *)color
{
    // 描述矩形
    CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    // 開啟位圖上下文
    UIGraphicsBeginImageContext(rect.size);
    // 獲取位圖上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 使用color演示填充上下文
    CGContextSetFillColorWithColor(context, [color CGColor]);
    // 渲染上下文
    CGContextFillRect(context, rect);
    // 從上下文中獲取圖片
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    // 結束上下文
    UIGraphicsEndImageContext();
    return theImage;
}


- (void)setLifeNav:(CGFloat)offset {
    CGFloat maxHeight;
    if (@available(iOS 11.0, *)) {
        UIEdgeInsets insets = [[UIApplication sharedApplication] keyWindow].safeAreaInsets;
        maxHeight = 200.0 - MAX(insets.top, 20.0) - 44.0;
    }else {
        maxHeight = 200.0 - 64.0;
    }
    if (offset < maxHeight) {
        if ([self.window.rootViewController isKindOfClass:[UITabBarController class]]) {
            UITabBarController *tabC = (UITabBarController *)self.window.rootViewController;
            for (UINavigationController *navC in tabC.viewControllers) {
                if ([navC.topViewController isKindOfClass:[LifeViewController class]]) {
                    CGFloat alpha = [self navBarColorAlpha:offset];
                    UIImage *image = [self imageWithColor:[UIColor colorWithRed:60/255.0 green:131/255.0 blue:255/255.0 alpha:alpha]];
                    [navC.navigationBar setBackgroundImage:image forBarMetrics:UIBarMetricsDefault];
                    break;
                }
            }
        }
    }else {
        if ([self.window.rootViewController isKindOfClass:[UITabBarController class]]) {
            UITabBarController *tabC = (UITabBarController *)self.window.rootViewController;
            for (UINavigationController *navC in tabC.viewControllers) {
                if ([navC.topViewController isKindOfClass:[LifeViewController class]]) {
                    UIImage *image = [self imageWithColor:[UIColor colorWithRed:60/255.0 green:131/255.0 blue:255/255.0 alpha:1.0]];
                    [navC.navigationBar setBackgroundImage:image forBarMetrics:UIBarMetricsDefault];
                    break;
                }
            }
        }
    }
}

結束語

當UINavigationBar完全透明時,也可達到隱藏導航欄效果。

結尾附上Demo地址(GitHub)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容