IOS11中奇怪的 uitableview 行为。单元格通过导航推动动画向上滚动

我最近将一些代码迁移到了新的 iOS11Beta5SDK。

现在,我从 UITableView 得到了一个非常令人困惑的行为。餐桌景观本身并没有那么华丽。我有自定义细胞,但在大多数情况下,这只是为了他们的高度。

当我使用 tableview 推动我的视图控制器时,我得到了一个额外的动画,其中单元格“向上滚动”(或者可能整个 tableview 框架都改变了)并沿着推/弹出导航动画向下滚动。请参阅 gif:

wavy tableview

我在 loadView方法中手动创建 tableview,并设置自动布局约束等于 tableview 的超视图的前导、尾随、顶部和底部。Superview 是视图控制器的根视图。

视图控制器推送代码是非常标准的: self.navigationController?.pushViewController(notifVC, animated: true)

同样的代码在 iOS10上提供了正常的行为。

你能告诉我哪里出了问题吗?

编辑: 我做了一个非常简单的桌面视图控制器,我可以在那里重现同样的行为。代码:

class VerySimpleTableViewController : UITableViewController {


override func viewDidLoad() {
super.viewDidLoad()


self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}




override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}


override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}




override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)


cell.textLabel?.text = String(indexPath.row)
cell.accessoryType = .disclosureIndicator


return cell
}




override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)


let vc = VerySimpleTableViewController.init(style: .grouped)


self.navigationController?.pushViewController(vc, animated: true)
}
}

编辑2: 我可以把问题缩小到我定制的 UINavigationBar。我有一个这样的定制:

rootNavController.navigationBar.setBackgroundImage(createFilledImage(withColor: .white, size: 1), for: .default)

其中 createFilledImage创建具有给定大小和颜色的正方形图像。

如果我注释掉这一行,我会得到正常的行为。

如果你对这件事有任何想法,我将不胜感激。

43933 次浏览

这是由于 UIScrollView's (UITableView 是 UIScrollview 的子类)新的 contentInsetAdjustmentBehavior属性,默认情况下设置为 .automatic

您可以在任何受影响控制器的 viewDidLoad 中使用以下代码片段覆盖此行为:

    tableView.contentInsetAdjustmentBehavior = .never

https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior

除了麦琪的回答

目的 C

if (@available(iOS 11.0, *)) {
scrollViewForView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

这个问题是由 iOS11中的一个 bug 引起的 在导航期间,视图控制器的视图设置不正确 转换,这应该在 iOS 11.2中修复 从 contentInsetAdjustmentBehavior.never不是一个很好的变通方案 因为它可能会有其他不良的副作用。如果你这样做 使用变通方法你应该确保删除它的 iOS 版本 > = 11.2

微笑堡(苹果公司的软件工程师)提到

please make sure along with above code, add additional code as follows. It solved the problem

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) // print(thisUISBTV.adjustedContentInset)
}

这看起来更像是一个错误,而不是预期的行为。当导航条不是半透明或者背景图像设置时,会出现这种情况。

如果您只是将 contentInset调整行为设置为。从来没有,内容插入不会在 iPhone X 上正确设置,例如内容会进入底部区域,滚动条下面。

It is necessary to do two things:
1. 防止 scrollView 在推送/弹出时动画化
2.保留。自动行为,因为它是 iPhone X 所需要的。如果没有这一点,例如在肖像,内容将低于底部滚动条。

New simple solution: in XIB: Just add new UIView on top of your main view with top, leading and trailing to superview and height set to 0. You don't have to connect it to other subviews or anything.

老办法:

注意: 如果在横向模式下使用 UIScrollView,它仍然没有正确设置横向插入(另一个 bug?),所以您必须将 scrollView 的前导/尾随固定到 IB 中的 safeAreaInsets。

注意2: 下面的解决方案也有问题,如果 tableView 滚动到底部,你推控制器并弹回,它将不再在底部。

override func viewDidLoad()
{
super.viewDidLoad()


// This parts gets rid of animation when pushing
if #available(iOS 11, *)
{
self.tableView.contentInsetAdjustmentBehavior = .never
}
}


override func viewDidDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
// This parts gets rid of animation when popping
if #available(iOS 11, *)
{
self.tableView.contentInsetAdjustmentBehavior = .never
}
}


override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
// This parts sets correct behaviour(insets are correct on iPhone X)
if #available(iOS 11, *)
{
self.tableView.contentInsetAdjustmentBehavior = .automatic
}
}

另外,如果使用选项卡栏,则集合视图的底部内容插入将为零。为此,将下面的代码放入 viewDidAppear:

if #available(iOS 11, *) {
tableView.contentInset = self.collectionView.safeAreaInsets
}

You can edit this behavior at once throughout the application by using NSProxy in for example didFinishLaunchingWithOptions:

if (@available(iOS 11.0, *)) {
[UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

删除这个代码,为我工作

self.edgesForExtendedLayout = UIRectEdgeNone

下面是我如何设法修复这个问题 同时仍然允许 iOS11自动设置插入。我使用的是 UITableViewController

  • 在情节串连板(或 程序化)中的视图控制器中,选择“在顶部条形图下扩展边缘”和“在不透明条形图下扩展边缘”。安全区域插入将防止您的视图进入顶部酒吧。
  • Check the "Insets to Safe Area" button on your table view in your storyboard. (or tableView.insetsContentViewsToSafeArea = true) - This might not be necessary but it's what I did.
  • Set the content inset adjustment behavior to "Scrollable Axes" (or tableView.contentInsetAdjustmentBehavior = .scrollableAxes) - .always might also work but I did not test.

如果其他方法都失败了,还有一种方法可以尝试:

重写 viewSafeAreaInsetsDidChange UIViewController方法以获得表视图,从而强制将滚动视图插入设置为安全区域插入。这与麦琪回答中的“从来没有”设置有关。

- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
self.tableView.contentInset = self.view.safeAreaInsets;
}

注意: self.tableViewself.view对于 UITableViewController应该是相同的

在我的例子中,这个方法起作用了(把它放到 viewDidLoad 中) :

self.navigationController.navigationBar.translucent = YES;

我可以为 iOS11.1重现这个 bug,但似乎这个 bug 在 iOS11.2之后就被修复了。 请参阅 http://openradar.appspot.com/34465226

移除 collectionViewtableView顶部的额外空间

    if #available(iOS 11.0, *) {
collectionView.contentInsetAdjustmentBehavior  = .never
//tableView.contentInsetAdjustmentBehavior  = .never
} else {
automaticallyAdjustsScrollViewInsets = false
}

以上代码 collectionViewtableView放在导航栏下面。
Below code prevent the collection view to go under the navigation

    self.edgesForExtendedLayout = UIRectEdge.bottom

但我喜欢使用下面的逻辑和代码为 UICollectionView

边缘插入值应用于矩形以缩小或展开 通常使用边插入 在视图布局期间修改视图的框架。正值导致 要插入(或缩小)指定数量的框架。负数 values cause the frame to be outset (or expanded) by the specified amount.

collectionView.contentInset = UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)
//tableView.contentInset = UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)

UICollectionView的最佳途径

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)
}
  if #available(iOS 11, *) {
self.edgesForExtendedLayout = UIRectEdge.bottom
}

我正在使用 UISearchController 和具有表视图的自定义 result tsController。将新的控制器推到结果控制器上导致 tableview 被搜索。

上面列出的代码完全解决了这个问题