iOS開發中,UINavigationController是一種常用的視圖控制器,主要用來控制UI界面流程跳轉。
在UINavigationController使用過程中,經常會遇到導航欄(UINavigationBar)顯示/隱藏、透明/不透明,以及透明漸變的問題。
本文主要介紹導航欄(UINavigationBar)顯示/隱藏、透明/不透明,以及透明漸變的問題。
以下是我整理的與導航欄相關的控件組織圖:
通過上圖可以看出:
1、UINavigationController繼承于UIViewController;
2、UINavigationController包含viewControllers(UIViewController數組)、UINavigationBar、UIToolbar;
3、UINavigationBar管理items(UINavigationItem數組);
4、UIViewController包含UINavigationItem。
下圖能更好的理解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版本會出現過渡不自然現象。(現不建議使用)
效果如下:
方法二:
使用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完全透明時,也可達到隱藏導航欄效果。