listenzz / HBDNavigationBar

A custom UINavigationBar for smooth switching between various states, including bar style, bar tint color, background image, background alpha, bar hidden, title text attributes, tint color, shadow hidden...
MIT License
1.65k stars 215 forks source link

隐藏导航栏后,顶部使用 Safe Area 进行布局会有问题 #99

Closed rakuyoMo closed 4 years ago

rakuyoMo commented 4 years ago

需求:

隐藏 NavigationBar 后,顶部 View 距离 view.safeAreaLayoutGuide.top 10px 的距离。

前提:

  1. 设置 barHiddenYES
  2. 参照 demo 设置 isExtendedLayoutIncludesTopBar = YES,即 self.edgesForExtendedLayout = UIRectEdgeNone;

问题:

我发现隐藏了导航栏后,无法使用 Safe Area 进行布局,会有以下问题:

顶部 view 的布局是从 NavigationBar 下面开始的,仿佛导航栏还在,即使它成功隐藏了。

当我放弃使用 Safe Area ,直接使用 view.mas_top 进行布局的时候,view 又延伸到了 Status Bar 下面,所以我还要额外加上 UIApplication.shared.statusBarFrame.size.height 的高度。

请问这个有办法避免吗?即继续使用 safeAreaLayoutGuide 进行布局。

ps:

我是在 Swift 环境下进行操作的,我将本库整个翻译成了 Swift,后续可能会考虑提 pr。

修改后 viewDidLayoutSubviews 中的布局代码大致如下(使用 SnapKit,Swift 下的 Masonry):

headerView.snp.remakeConstraints {
    $0.top.equalTo(view.snp.top).offset(10 + UIApplication.shared.statusBarFrame.size.height)

    if #available(iOS 11.0, *) {
        // $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(10)
        $0.left.equalTo(view.safeAreaLayoutGuide.snp.left).offset(10)
        $0.right.equalTo(view.safeAreaLayoutGuide.snp.right).offset(-10)
    } else {
        $0.left.equalTo(view.snp.left).offset(10)
        $0.right.equalTo(view.snp.right).offset(-10)
    }

    $0.height.equalTo(85)
}
listenzz commented 4 years ago

当希望使用 Safe Area 时, 将 self.edgesForExtendedLayout 恢复成默认值,即 UIRectEdgeAll。它们是冲突的。

rakuyoMo commented 4 years ago

当希望使用 Safe Area 时, 将 self.edgesForExtendedLayout 恢复成默认值,即 UIRectEdgeAll。它们是冲突的。

这样的话相当于不设置 isExtendedLayoutIncludesTopBar 吧,但是这样的话顶部导航栏的位置会有个黑条。

listenzz commented 4 years ago

当希望使用 Safe Area 时, 将 self.edgesForExtendedLayout 恢复成默认值,即 UIRectEdgeAll。它们是冲突的。

这样的话相当于不设置 isExtendedLayoutIncludesTopBar 吧,但是这样的话顶部导航栏的位置会有个黑条。

我去看下

rakuyoMo commented 4 years ago

可能是我翻译的问题... 我发现你的 demo 里,DemoViewController push DemoViewController 的时候,开启 hbd_barHidden,没有任何 edgesForExtendedLayout 的设置,导航栏的位置没有黑条....

但是为什么布局没有从顶部开始?没看到有基类呀。

listenzz commented 4 years ago

ScrollView 的缘故

rakuyoMo commented 4 years ago

ScrollView 的缘故

soga,那看来黑边还是我的问题。我再研究一下。

这个翻译还是有点困难的...

listenzz commented 4 years ago

经过我测试,当设置 self.hbd_barHidden = YES; 以及 self.edgesForExtendedLayout = UIRectEdgeAll; 时,SafeArea 能正常工作。

我提交了测试的代码

打开 Main.storyboard,将初始指针指向最上面的 Navigation Controller.

用于测试的两个 ViewController 分别是 EViewController 和 FViewController。

截屏2019-11-27下午5 24 15
rakuyoMo commented 4 years ago

找到问题了,是我基类中关于 edgesForExtendedLayout 的判断出了问题。与本库无关。谢谢作者的耐心回复~

rakuyoMo commented 4 years ago

demo 中的这段代码是不是有些问题?

if (!(self.hbd_extendedLayoutIncludesTopBar || hasAlpha(self.hbd_barTintColor))) {
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

当我通过下面这种方法设置 barTintColor

UINavigationBar.appearance().barTintColor = UIColor(hex: 0x2a2a2a)

hbd_barTintColor 属性实际上是 nil 的,也就是控制器的 edgesForExtendedLayout 会被设置为 UIRectEdgeNone。但是实际上我是有状态栏,并且不希望被这么设置的。

在下面的方法中,没有用 UINavigationBar.appearance().barTintColor 作为默认返回值,而且也不能用它作为默认返回值...

- (UIColor *)hbd_barTintColor {
    return objc_getAssociatedObject(self, _cmd);
}
listenzz commented 4 years ago

hbd_barTintColor 换成 hbd_computedBarTintColor

rakuyoMo commented 4 years ago

hbd_barTintColor 换成 hbd_computedBarTintColor

换成 hbd_computedBarTintColor 也不行

- (UIColor *)hbd_computedBarTintColor {
    if (self.hbd_barImage) {
        return nil;
    }
    UIColor *color = self.hbd_barTintColor;
    if (!color) {
        if ([[UINavigationBar appearance] backgroundImageForBarMetrics:UIBarMetricsDefault]) {
            return nil;
        }
        if ([UINavigationBar appearance].barTintColor) {
            color = [UINavigationBar appearance].barTintColor;
        } else {
            color = [UINavigationBar appearance].barStyle == UIBarStyleDefault ? [UIColor colorWithRed:247/255.0 green:247/255.0 blue:247/255.0 alpha:0.8]: [UIColor colorWithRed:28/255.0 green:28/255.0 blue:28/255.0 alpha:0.729];
        }
    }
    return color;
}

上面这是 hbd_computedBarTintColor 的代码,其中也会把 [UINavigationBar appearance].barTintColor 作为默认色。

当我的场景是隐藏导航栏的时候,下面这个判断的后半段,结果是 NO,再取反,edgesForExtendedLayout 依然会被设置为 UIRectEdgeNone。从而导致顶部导航栏的位置出现黑条

if (!(self.hbd_extendedLayoutIncludesTopBar || hasAlpha(self.hbd_barTintColor))) {
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

我感觉基类的这个逻辑判断是不是有点问题?感觉除了动态的,仅使用 self.hbd_extendedLayoutIncludesTopBar 来判断之外,好像没有别的办法?

rakuyoMo commented 4 years ago

或者像这样:

if (!(hbd_barHidden || hbd_barAlpha < 1 || hasAlpha(self. hbd_computedBarTintColor))) {
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

但是这样子类在修改 NavigationBar 的一些配置的时候,都要放在 [super viewDidLoad] 之前了...

listenzz commented 4 years ago

我打算内部处理掉 edgesForExtendedLayout

rakuyoMo commented 4 years ago

edgesForExtendedLayout

好的!期待。那我先用 self.hbd_extendedLayoutIncludesTopBar 暂时凑合一下。

listenzz commented 4 years ago

我把代码推上去了

你测试下有没有问题

https://github.com/listenzz/HBDNavigationBar/blob/master/HBDNavigationBar/Classes/HBDNavigationController.m#L196

rakuyoMo commented 4 years ago

我把代码推上去了

你测试下有没有问题

https://github.com/listenzz/HBDNavigationBar/blob/master/HBDNavigationBar/Classes/HBDNavigationController.m#L196

好快啊。目前试了下来没有发现问题,感觉这个可以。

rakuyoMo commented 4 years ago

@listenzz 发现你这么改了之后,Safe Area 约束有问题了... demo 就可以直接复现,按上面提到的 这个 步骤

具体表现为,隐藏导航栏前后,“Test Safe Area” 文本框的位置不变。按理说隐藏导航栏之后,文本框的位置应该会上移的。

listenzz commented 4 years ago

这样啊,哈哈。因为 NavigationBar 还在,并没有真正隐藏。

rakuyoMo commented 4 years ago

这样啊,哈哈。因为 NavigationBar 还在,并没有真正隐藏。

那这样的话 top 是不是就不能用 Safe Area 布局了😂

listenzz commented 4 years ago

像下面那样修改 adjustLayout

void adjustLayout(UIViewController *vc) {
    BOOL isTranslucent = vc.hbd_barHidden || vc.hbd_barAlpha < 1.0;
    if (!isTranslucent) {
        UIImage *image = vc.hbd_computedBarImage;
        if (image) {
            isTranslucent = imageHasAlphaChannel(image);
        } else {
            UIColor *color = vc.hbd_computedBarTintColor;
            isTranslucent = colorHasAlphaComponent(color);
        }
    }

    if (isTranslucent || vc.extendedLayoutIncludesOpaqueBars) {
        vc.edgesForExtendedLayout |= UIRectEdgeTop;
    } else {
        vc.edgesForExtendedLayout &= ~UIRectEdgeTop;
    }

    if (vc.hbd_barHidden) {
        if (@available(iOS 11.0, *)) {
            vc.additionalSafeAreaInsets = UIEdgeInsetsMake(-44, 0, 0, 0);
        } else {
            // Fallback on earlier versions
        }
    }
}
rakuyoMo commented 4 years ago

像下面那样修改 adjustLayout

这样修改是有效果的。建议把 -44 改为获取导航栏的高度吧。写死好像不太好。

不过看你更新了 1.7.1 版本但没有包含这个修改?

listenzz commented 4 years ago

1.7.1 确保只调用一次 adjustLayout

listenzz commented 4 years ago

动态获取导航栏高度有什么建议没,考虑屏幕旋转的情况

rakuyoMo commented 4 years ago

动态获取导航栏高度有什么建议没,考虑屏幕旋转的情况

测了一下屏幕旋转的情况,发现导航栏的高度没必要动态获取。我是这么写的:

if viewController.mbc.isBarHidden {
    if #available(iOS 11.0, *) {
        if let height = viewController.navigationController?.navigationBar.frame.size.height {

            let insets = viewController.additionalSafeAreaInsets

            viewController.additionalSafeAreaInsets = UIEdgeInsets(
                top: -height + insets.top,
                left: insets.left,
                bottom: insets.bottom,
                right: insets.right
            )
        }
    }
}

布局还是按照最上面提到的写的,打开了 Safe Area 的注释。实测 UI 方面没有什么问题。