UITableViewAlertForLayoutOutsideViewHierarchy error: Warning once only (iOS 13 GM)
UITableViewAlertForLayoutOutsideViewHierarchy error: Warning once only (iOS 13 GM)
我在执行 Segue 时收到 iOS13 的奇怪错误,我无法弄清楚它的含义,也找不到有关此错误的任何文档。问题是这似乎会导致很多滞后(几秒钟),直到执行 segue。
2019-09-11 22:45:38.861982+0100 Thrive[2324:414597] [TableView] Warning once only: UITableView was told to layout its visible cells
and other contents without being in the view hierarchy (the table view
or one of its superviews has not been added to a window). This may
cause bugs by forcing views inside the table view to load and perform
layout without accurate information (e.g. table view bounds, trait
collection, layout margins, safe area insets, etc), and will also
cause unnecessary performance overhead due to extra layout passes.
Make a symbolic breakpoint at
UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the
debugger and see what caused this to occur, so you can avoid this
action altogether if possible, or defer it until the table view has
been added to a window. Table view: ; layer = ; 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)
}
}
然后 none 来自目标 VC 的 viewDidLoad 或 viewWillAppear 中的代码会以任何方式影响这一点(我尝试将其全部注释掉,没有任何区别。
知道是什么原因造成的吗?我可以分享任何其他需要的细节。
谢谢。
它发生在我身上,因为我在 viewWillAppear(:) 方法中注册了更改方向通知的设备。
我在 viewDidAppear(:) 中移动了注册,Xcode 它不再在断点处停止了。
我能说的是,当视图已经可见时,布局更改可能 运行...
我是 Xcode/Swift 的新手,所以这可能对任何人都没有帮助。从详细视图返回列表时,我在应用程序中更新到 iOS 13 和 Xcode 11 后开始收到此错误。
我发现我在放松时做了 tableView.reloadRows
和 tableView.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()
并没有给我错误。
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
为真时执行更新。
iPadOS 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 之前,此应用程序一直运行完美。
与@joe-h 一样,我遇到了这个错误,同时也感到惊讶,因为他展示的展开方法被许多开发人员使用 + 在一些重要的 Apple iOS 示例代码中。
我的代码中的触发行(@joe-h,我猜你的代码中也可能)是 selectedIndexPath 中的 tableView.reloadRows(这是一个展开的 tableView.indexPathForSelectedRow):
tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
不幸的是,如果您在更新现有 tableView 行中的值后展开(这是上面提到的 Apple FoodTracker 教程中的一种方法,以及 Apple 的 Everyone Can 中使用的方法),则不能注释掉该行代码系列)。如果您不重新加载行,那么您的更改将不会显示在 tableView 中。在放松中注释掉重新加载后,我添加了一个带有以下代码的 viewDidAppear,这似乎可以解决问题:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let selectedIndexPath = tableView.indexPathForSelectedRow {
tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
}
}
我欢迎就这是否是一种合理的方法发表评论,但就目前而言,这似乎有效。
我在使用 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[[=10=]]).tag([=10=])
}
}
}
}
.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] 警告
once only: UITableView was told to layout its visible cells and other
contents without being in the view hierarchy (the table view or one of
its superviews has not been added to a window). This may cause bugs by
forcing views inside the table view to load and perform layout without
accurate information (e.g. table view bounds, trait collection, layout
margins, safe area insets, etc), and will also cause unnecessary
performance overhead due to extra layout passes. Make a symbolic
breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch
this in the debugger and see what caused this to occur, so you can
avoid this action altogether if possible, or defer it until the table
view has been added to a window.*
** SwiftUI 中的 UITableViewAlertForLayoutOutsideViewHierarchy 在哪里? **
此警告可能发生在更新 table 视图或集合视图时不可见时,例如当它在父视图控制器上时。为了解决这个问题,首先,我在视图控制器中创建了一个 属性,包含 table 视图以检查视图控制器是否可见,如下所示:
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()
}
在 table 视图中进行任何更改之前,您应该检查该可见性。例如,在 NSFetchedResultsController 的情况下,它必须在我们已经实现的所有委托回调中完成。
更新
我最近发现,如果将 table 视图更新为动画 false,即使它不可见,也不会出现任何警告。
我的项目出现了同样的错误;具有可区分数据源的 tableView。几个小时以来一直在窃听它。问题在于更新快照,更具体地说是在后台线程(默认)上。在主线程上强制更新数据源解决了这个问题!希望这对外面的人有帮助!
func updateData(on annotations: [Annotation]) {
var snapshot = NSDiffableDataSourceSnapshot<AnnotationType, Annotation>()
//Append available sections
AnnotationType.allCases.forEach { snapshot.appendSections([[=10=]]) }
//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)
}
}
在 viewDidDisappear 方法中,我声明了 tableView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
函数。你们中有些人说这不重要,但它影响了 tableView 委托方法。例如,当我收到此警告时,不会调用 viewForHeader 函数。
有同样的问题,删除 tableView.reloadSections
修复了它。这是导致警告的代码行:
iOS 13:
tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
在 iOS 14 中,删除 tableView.reloadSections
没有修复警告。
对于使用 DiffableDataSource
的人,将 animatingDifferences
设置为 false
警告将消失。
dataSource.apply(snapshot, animatingDifferences: false)
请检查以下函数
override func viewWillLayoutSubviews()
或者您的代码(例如我的代码)没有任何问题,只是随机开始弹出此消息。在这种情况下,请清理您的项目,重新启动 Xcode 并观察消息神奇地消失!
我发现最稳健和安全的方法是等待 table 视图/集合视图的 didMoveToWindow
因为即使在 viewWillAppear
中,视图也可能 NOT 附加到 window 并且将您的代码放在 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()
}
}
对于详细视图控制器中的 UISplitViewController 和 UITableView 存在此问题的任何人,您可以尝试子类化并像这样重写 layoutSubviews (From this thread):
class CustomTableView: UITableView {
override func layoutSubviews() {
if (self.window == nil) {
return
}
super.layoutSubviews()
}
}
而不是重新加载 viewDidAppear
中的行,这对我有用:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.tableView.reloadRows(at: [indexPath], with: .none)
}
此外,如果您正在使用 DiffableDataSource
并且您正在手动选择 indexPath,则需要在 apply snapshot
方法的 completion
块上执行此操作:
dataSource.apply(snapshot, to: section, animatingDifferences: false, completion: {
// select the indexPath programmatically or do UITableView UI stuff here.
})
我在执行 Segue 时收到 iOS13 的奇怪错误,我无法弄清楚它的含义,也找不到有关此错误的任何文档。问题是这似乎会导致很多滞后(几秒钟),直到执行 segue。
2019-09-11 22:45:38.861982+0100 Thrive[2324:414597] [TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. Table view: ; layer = ; 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)
}
}
然后 none 来自目标 VC 的 viewDidLoad 或 viewWillAppear 中的代码会以任何方式影响这一点(我尝试将其全部注释掉,没有任何区别。
知道是什么原因造成的吗?我可以分享任何其他需要的细节。
谢谢。
它发生在我身上,因为我在 viewWillAppear(:) 方法中注册了更改方向通知的设备。 我在 viewDidAppear(:) 中移动了注册,Xcode 它不再在断点处停止了。
我能说的是,当视图已经可见时,布局更改可能 运行...
我是 Xcode/Swift 的新手,所以这可能对任何人都没有帮助。从详细视图返回列表时,我在应用程序中更新到 iOS 13 和 Xcode 11 后开始收到此错误。
我发现我在放松时做了 tableView.reloadRows
和 tableView.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()
并没有给我错误。
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
为真时执行更新。
iPadOS 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 之前,此应用程序一直运行完美。
与@joe-h 一样,我遇到了这个错误,同时也感到惊讶,因为他展示的展开方法被许多开发人员使用 + 在一些重要的 Apple iOS 示例代码中。
我的代码中的触发行(@joe-h,我猜你的代码中也可能)是 selectedIndexPath 中的 tableView.reloadRows(这是一个展开的 tableView.indexPathForSelectedRow):
tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
不幸的是,如果您在更新现有 tableView 行中的值后展开(这是上面提到的 Apple FoodTracker 教程中的一种方法,以及 Apple 的 Everyone Can 中使用的方法),则不能注释掉该行代码系列)。如果您不重新加载行,那么您的更改将不会显示在 tableView 中。在放松中注释掉重新加载后,我添加了一个带有以下代码的 viewDidAppear,这似乎可以解决问题:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let selectedIndexPath = tableView.indexPathForSelectedRow {
tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
}
}
我欢迎就这是否是一种合理的方法发表评论,但就目前而言,这似乎有效。
我在使用 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[[=10=]]).tag([=10=])
}
}
}
}
.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] 警告
once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window.*
** SwiftUI 中的 UITableViewAlertForLayoutOutsideViewHierarchy 在哪里? **
此警告可能发生在更新 table 视图或集合视图时不可见时,例如当它在父视图控制器上时。为了解决这个问题,首先,我在视图控制器中创建了一个 属性,包含 table 视图以检查视图控制器是否可见,如下所示:
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()
}
在 table 视图中进行任何更改之前,您应该检查该可见性。例如,在 NSFetchedResultsController 的情况下,它必须在我们已经实现的所有委托回调中完成。
更新
我最近发现,如果将 table 视图更新为动画 false,即使它不可见,也不会出现任何警告。
我的项目出现了同样的错误;具有可区分数据源的 tableView。几个小时以来一直在窃听它。问题在于更新快照,更具体地说是在后台线程(默认)上。在主线程上强制更新数据源解决了这个问题!希望这对外面的人有帮助!
func updateData(on annotations: [Annotation]) {
var snapshot = NSDiffableDataSourceSnapshot<AnnotationType, Annotation>()
//Append available sections
AnnotationType.allCases.forEach { snapshot.appendSections([[=10=]]) }
//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)
}
}
在 viewDidDisappear 方法中,我声明了 tableView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
函数。你们中有些人说这不重要,但它影响了 tableView 委托方法。例如,当我收到此警告时,不会调用 viewForHeader 函数。
有同样的问题,删除 tableView.reloadSections
修复了它。这是导致警告的代码行:
iOS 13:
tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
在 iOS 14 中,删除 tableView.reloadSections
没有修复警告。
对于使用 DiffableDataSource
的人,将 animatingDifferences
设置为 false
警告将消失。
dataSource.apply(snapshot, animatingDifferences: false)
请检查以下函数
override func viewWillLayoutSubviews()
或者您的代码(例如我的代码)没有任何问题,只是随机开始弹出此消息。在这种情况下,请清理您的项目,重新启动 Xcode 并观察消息神奇地消失!
我发现最稳健和安全的方法是等待 table 视图/集合视图的 didMoveToWindow
因为即使在 viewWillAppear
中,视图也可能 NOT 附加到 window 并且将您的代码放在 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()
}
}
对于详细视图控制器中的 UISplitViewController 和 UITableView 存在此问题的任何人,您可以尝试子类化并像这样重写 layoutSubviews (From this thread):
class CustomTableView: UITableView {
override func layoutSubviews() {
if (self.window == nil) {
return
}
super.layoutSubviews()
}
}
而不是重新加载 viewDidAppear
中的行,这对我有用:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.tableView.reloadRows(at: [indexPath], with: .none)
}
此外,如果您正在使用 DiffableDataSource
并且您正在手动选择 indexPath,则需要在 apply snapshot
方法的 completion
块上执行此操作:
dataSource.apply(snapshot, to: section, animatingDifferences: false, completion: {
// select the indexPath programmatically or do UITableView UI stuff here.
})