UITableViewAlertForLayoutOutsideViewHierarchy错误:仅警告一次(iOS 13 GM)

时间:2019-09-11 21:55:08

标签: ios swift tableview ios13

在执行Segue时,iOS13出现一个奇怪的错误,我无法弄清这是什么意思,也找不到针对此错误的任何文档。问题在于,这似乎会导致很多延迟(几秒钟),直到执行segue。

  

2019-09-11 22:45:38.861982 + 0100 Thrive [2324:414597] [TableView]仅警告一次:通知UITableView布局其可见单元格   和其他内容,而不必属于视图层次结构(表视图   或其父视图之一尚未添加到窗口中)。这可能   通过强制表视图内的视图加载和执行而导致错误   没有准确信息的布局(例如表格视图范围,特征   收集,布局边距,安全区域插图等),并且还将   由于额外的布局传递而导致不必要的性能开销。   在以下位置建立符号断点   UITableViewAlertForLayoutOutsideViewHierarchy可以在   调试器,看看是什么原因导致这种情况的发生,因此可以避免这种情况   如果可能,请完全采取行动,或将其推迟到表格视图中   已添加到窗口。表格视图:层=; contentOffset:{0,0}; contentSize:{315,118};   AdjustedContentInset:{0,0,0,0}; dataSource:>

我正在使用Hero,但是我尝试禁用它并使用常规的Segue,但这并没有阻止这种滞后。

启动segue的代码是didSelectRowAt

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.section == 0 {
            selectedCell = realIndexFor(activeGoalAt: indexPath)
            performSegue(withIdentifier: "toGoalDetails", sender: nil)
        } else if indexPath.section == 1 {
            selectedCell = indexPath.row
            performSegue(withIdentifier: "toIdeaDetails", sender: nil)
        } else {
            selectedDecision = indexPath.row
            hero(destination: "DecisionDetails", type: .zoom)
        }
    }

然后来自目标VC的viewDidLoad或viewWillAppear中的任何代码都不会以任何方式影响这一点(我尝试将它们全部注释掉,没有任何区别。

知道这是什么原因吗?我可以分享其他任何需要的细节。

谢谢。

15 个答案:

答案 0 :(得分:8)

这件事发生在我身上,因为我在viewWillAppear( :)方法中注册了用于更改方向通知的设备。 我将注册移到了viewDidAppear( :)中,并且Xcode不再在断点处停止。

我可以说的是,当视图已经可见时,可以运行布局更改...

答案 1 :(得分:7)

就像@ joe-h一样,我遇到了这个错误,也感到惊讶,因为他上面展示的放松方法是许多开发人员使用的一种方法,并且在某些重要的Apple iOS示例代码中使用。

我的代码中的触发行(@ joe-h,我想也可能在您的代码中)是selectedIndexPath处的tableView.reloadRows(这是未包装的tableView.indexPathForSelectedRow):

tableView.reloadRows(at: [selectedIndexPath], with: .automatic)

不幸的是,如果在更新现有tableView行中的值后要平仓,则无法注释掉该行(这是上述Apple FoodTracker教程以及Apple的Everyone Can Code系列中使用的一种方法) )。如果您不重新加载行,则您的更改将不会显示在tableView中。在取消对重新加载的注释之后,我添加了一个带有以下代码的viewDidAppear,这似乎可以解决问题:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if let selectedIndexPath = tableView.indexPathForSelectedRow {
        tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
    }
}

我欢迎评论这是否是一种合理的方法,但是目前看来,这是可行的。

答案 2 :(得分:4)

此警告可能发生在不可见的更新表视图或集合视图时,例如在父视图控制器上时。为了解决这个问题,首先,我在视图控制器中创建了一个属性,其中包含表视图,以检查视图控制器是否可见,如下所示:

var isVisible: Bool = false

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.isVisible = true
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.isVisible = false
}

然后在对更改做出反应之前,首先在数据源委托中检查视图控制器是否可见。如果不是,请不要进行任何更新。例如

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    guard isVisible else { return }
    tableView.beginUpdates()
}

在tableView中进行任何更改之前,应检查可见性。例如,对于NSFetchedResultsController,必须在我们实现的所有委托回调中完成。

更新

我最近发现,如果使用动画false更新表视图,即使它不可见,也不会发出任何警告。

答案 3 :(得分:3)

我的专案有相同的错误;一个具有可扩散数据源的tableView。一直困扰着它几个小时。问题在于更新快照,更具体地说是在后台线程上更新(默认)。强制在主线程上更新数据源摆脱了这个问题!希望这对外面的人有帮助!

    func updateData(on annotations: [Annotation]) {
        var snapshot = NSDiffableDataSourceSnapshot<AnnotationType, Annotation>()
        //Append available sections
        AnnotationType.allCases.forEach { snapshot.appendSections([$0]) }

        //Append annotations to their corresponding sections
        annotations.forEach { (annotation) in
            snapshot.appendItems([annotation], toSection: annotation.type as AnnotationType)
        }

        //Force the update on the main thread to silence a warning about tableview not being in the hierarchy!
        DispatchQueue.main.async {
            self.dataSource.apply(snapshot, animatingDifferences: true)
        }
    }

答案 4 :(得分:3)

我发现最健壮和安全的方法是等待表视图/集合视图的didMoveToWindow

因为即使在 viewWillAppear 中,视图也可能附加到窗口并且将您的代码放在 viewDidAppear 中可能会导致不必要的图形故障

class MyTableViewOrCollectionView: UITableView {

    var didMoveToWindowCallback: (()->())? = nil
    
    override func didMoveToWindow() {
        
        super.didMoveToWindow()
        
        didMoveToWindowCallback?()
        didMoveToWindowCallback = nil
        
    }

}

你可以

override func viewDidLoad() {
    
    super.viewDidLoad()
    
    tableView.didMoveToWindowCallback = { [weak self] in
        self?.setupInitialContent()
    }
    
}

答案 5 :(得分:2)

我是Xcode / Swift的新手,所以这可能会或可能不会帮助任何人。从详细信息视图返回列表时,我在更新至应用程序中的iOS 13和Xcode 11后开始出现此错误。

我发现自己正在放松tableView.reloadRowstableView.insertRows(如Apple在one of their tutorials中所建议的那样)

@IBAction func unwindToMealList(sender: UIStoryboardSegue) {
    if let sourceViewController = sender.source as? MealViewController, let meal = sourceViewController.meal {

        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            // Update an existing meal.
            meals[selectedIndexPath.row] = meal
            tableView.reloadRows(at: [selectedIndexPath], with: .none)
        }
        else {
            // Add a new meal.
            let newIndexPath = IndexPath(row: meals.count, section: 0)

            meals.append(meal)
            tableView.insertRows(at: [newIndexPath], with: .automatic)
        }
    }
}

)

我注释掉了这段代码,然后它消失了。

奇怪的是,离开排序和self.tableView.reloadData()并没有给我错误。

答案 6 :(得分:2)

我在SwiftUI上遇到了类似的断点,甚至没有处理viewDidLoad或viewDidappear

    //
//  ContentView.swift
//  DD
//
//  Created by Roman Emperor on 3/29/20.
//  Copyright © 2020 Emperors. All rights reserved.
//
import Combine
import SwiftUI

// Defining a class Booking of type Bindable Object [changed to ObservableObject]
class Booking: ObservableObject {
    var didChange = PassthroughSubject<Void, Never>()

    // Array of types to work with
    static let types = ["Consultation", "Tooth Pain", "Cleaning", "Brases", "Dental Implant" ]
    // Setting instance varibale type
    var type = 0 { didSet { update() } }

    func update () {
        didChange.send(())
    }
}


struct ContentView: View {
    @ObservedObject var booking = Booking() //bindableObject in old swift version

    var body: some View {
        NavigationView {
            Form {
                Section {
                    Picker(selection: $booking.type, label: Text("Select a Booking Type")) {
                        ForEach(0 ..< Booking.types.count){
                            Text(Booking.types[$0]).tag($0)
                        }
                    }
                }
            }
        .navigationBarTitle(Text("Darpan Dental Home"))
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

完整的输出日志在这里:

*> 2020-03-29 09:22:09.626082 + 0545 DD [1840:76404] [TableView]警告

仅一次:通知UITableView布置其可见单元格和其他 内容,而不会出现在视图层次结构中(表视图或以下之一) 其超级视图尚未添加到窗口)。这可能会导致错误 强制表格视图内的视图加载和执行布局而无需 准确的信息(例如表格视图范围,特征收集,布局 边距,安全区域插图等),并且也将导致不必要的 由于额外的布局次数而导致的性能开销成为象征 UITableViewAlertForLayoutOutsideViewHierarchy的断点以捕获 并在调试器中查看此情况, 如果可能的话,请完全避免执行此操作,或者将其推迟到表格中 视图已添加到窗口。*

**此UITableViewAlertForLayoutOutsideViewHierarchy在SwiftUI中在哪里? **

答案 7 :(得分:2)

在viewDidDisappear方法中,我声明了tableView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)函数。有人说这并不重要,但它影响了tableView委托方法。例如,当我收到此警告时,不会调用viewForHeader函数。

答案 8 :(得分:1)

extension UIView {

  func rootView() -> UIView {
     var view = self
     while view.superview.isNotNil {
         view = view.superview!
     }
     return view
  }

  var isOnWindow: Bool {
     return self.rootView() is UIWindow
    }
  }

然后,您只需要检查tableView isOnWindow是否像...

if self.tableView.isOnWindow {
/// do stuff
}

免责声明:如文档所述,您可能需要推迟调用,这意味着没有保证将再次调用您的方法,因此当isOnWindow为真时,您有责任执行更新。

答案 9 :(得分:1)

iPad OS 13.2.3 swift 5.2 Xcode 11.2.1

仅在设备为横向时启动应用程序时遇到此问题。 我在主控制器的viewDidLoad函数中调用了详细信息序列,以确保正确设置了详细信息视图。

     override func viewDidLoad() {
       super.viewDidLoad()
            ...
       self.performSegue(withIdentifier: "showDetail", sender: self)
     }

当我移除performSeque时,警告不再出现,但是, 细节控制器上的左栏按钮不再正常工作,仅在设备处于横向状态时启动应用程序时才再次正常工作。最左侧的按钮将激活右侧的下一个按钮,而不是第一个按钮应该执行的操作。

修复条形按钮的方法是将其添加到viewDidLoad

     override func viewDidLoad() {
       super.viewDidLoad()
            ...
       self.splitViewController?.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
     }

然后执行

     override func viewWillAppear(_ animated: Bool) {
       self.splitViewController?.preferredDisplayMode = UISplitViewController.DisplayMode.automatic
       super.viewWillAppear(animated)
     }

我无法解释为什么这样做!

在加载iPados 13之前,此应用程序可以正常工作。

答案 10 :(得分:1)

发生同样的问题,删除了tableView.reloadSections,已修复。这是引起警告的代码行:

iOS 13:

tableView.reloadSections(IndexSet(integer: 0), with: .automatic)

在iOS 14中,删除tableView.reloadSections不能解决该警告。

答案 11 :(得分:1)

对于使用 DiffableDataSource 的人,将 animatingDifferences 设置为 false 并且警告将消失。

dataSource.apply(snapshot, animatingDifferences: false)

答案 12 :(得分:1)

或者也许您的代码(如我的)没有任何问题,并且此消息只是随机开始弹出。在这种情况下,请清理您的项目,重新启动 Xcode 并观察消息神奇地消失!

答案 13 :(得分:0)

请检查以下功能

override func viewWillLayoutSubviews()

答案 14 :(得分:0)

对于在详细视图控制器中使用 UISplitViewController 和 UITableView 遇到此问题的任何人,您可以尝试子类化并覆盖 layoutSubviews,如下所示 (From this thread):

class CustomTableView: UITableView {
    override func layoutSubviews() {
        if (self.window == nil) {
            return
        }
        super.layoutSubviews()
    }
 }