Skip to content

Commit aed9803

Browse files
committed
Do not observe LogStore on the top level (InspectLayout)
Avoids an infinite view update loop.
1 parent 95e6aa1 commit aed9803

File tree

3 files changed

+27
-28
lines changed

3 files changed

+27
-28
lines changed

Sources/LayoutInspector/DebugLayout.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import SwiftUI
44
extension View {
55
/// Inspect the layout for this subtree.
66
public func inspectLayout() -> some View {
7-
modifier(InspectLayout())
7+
modifier(InspectLayout(logStore: LogStore()))
88
}
99

1010
/// Monitor the layout proposals and responses for this view and add them

Sources/LayoutInspector/DebugLayoutImpl.swift

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import SwiftUI
22

33
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
44
struct InspectLayout: ViewModifier {
5-
@StateObject private var logStore: LogStore = .init()
5+
// Don't observe LogStore. Avoids an infinite update loop.
6+
var logStore: LogStore
67
@State private var selectedView: String? = nil
78
@State private var generation: Int = 0
89
@State private var inspectorFrame: CGRect = CGRect(x: 0, y: 0, width: 300, height: 300)
@@ -31,13 +32,12 @@ struct InspectLayout: ViewModifier {
3132
.offset(x: inspectorFrame.minX, y: inspectorFrame.minY)
3233
.coordinateSpace(name: Self.coordSpaceName)
3334
}
34-
.environment(\.didCallInspectLayout, true)
35-
.environmentObject(logStore)
35+
.environment(\.logStore, logStore)
3636
}
3737

3838
@ViewBuilder private var inspectorUI: some View {
3939
ScrollView([.vertical, .horizontal]) {
40-
LogEntriesGrid(logEntries: logStore.log, highlight: $selectedView)
40+
LogEntriesGrid(logStore: logStore, highlight: $selectedView)
4141
.measureSize { size in
4242
tableSize = size
4343
}
@@ -91,18 +91,16 @@ struct InspectLayout: ViewModifier {
9191
}
9292
}
9393

94-
enum DidCallInspectLayout: EnvironmentKey {
95-
static var defaultValue: Bool = false
94+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
95+
enum LogStoreKey: EnvironmentKey {
96+
static var defaultValue: LogStore? = nil
9697
}
9798

99+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
98100
extension EnvironmentValues {
99-
/// Marker to signal that a valid LogStore environment object has been injected.
100-
/// Clients that use `@EnvironmentObject var logStore: LogStore` must verify that
101-
/// this value is true before accessing the environment object because it may be
102-
/// missing.
103-
var didCallInspectLayout: Bool {
104-
get { self[DidCallInspectLayout.self] }
105-
set { self[DidCallInspectLayout.self] = newValue }
101+
var logStore: LogStore? {
102+
get { self[LogStoreKey.self] }
103+
set { self[LogStoreKey.self] = newValue }
106104
}
107105
}
108106

@@ -111,16 +109,11 @@ struct DebugLayoutModifier: ViewModifier {
111109
var label: String
112110
var file: StaticString
113111
var line: UInt
114-
@Environment(\.didCallInspectLayout) private var didCallInspectLayout
115-
/// The log store for the current inspectLayout() subtree.
116-
///
117-
/// - Important: You must verify that `didCallInspectLayout == true` before accessing
118-
/// this property. Failure to do so will result in a crash as the object won't be
119-
/// in the environment.
120-
@EnvironmentObject private var logStore: LogStore
112+
// Using @Environment rather than @EnvironmentObject because we don't want to observe this.
113+
@Environment(\.logStore) var logStore: LogStore?
121114

122115
func body(content: Content) -> some View {
123-
if didCallInspectLayout {
116+
if let logStore {
124117
DebugLayout(label: label, logStore: logStore) {
125118
content
126119
}
@@ -196,9 +189,14 @@ struct ClearDebugLayoutLog: Layout {
196189

197190
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
198191
public final class LogStore: ObservableObject {
199-
@Published public var log: [LogEntry] = []
192+
@Published public var log: [LogEntry]
200193
var viewLabels: Set<String> = []
201194

195+
init(log: [LogEntry] = []) {
196+
self.log = log
197+
self.viewLabels = Set(log.map(\.label))
198+
}
199+
202200
func registerViewLabelAndWarnIfNotUnique(_ label: String, file: StaticString, line: UInt) {
203201
DispatchQueue.main.async { [self] in
204202
if viewLabels.contains(label) {

Sources/LayoutInspector/LogEntriesGrid.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import SwiftUI
22

33
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
44
public struct LogEntriesGrid: View {
5-
var logEntries: [LogEntry]
5+
@ObservedObject var logStore: LogStore
66
@Binding var highlight: String?
77

88
private static let tableRowHorizontalPadding: CGFloat = 8
99
private static let tableRowVerticalPadding: CGFloat = 4
1010

11-
public init(logEntries: [LogEntry], highlight: Binding<String?>? = nil) {
12-
self.logEntries = logEntries
11+
public init(logStore: LogStore, highlight: Binding<String?>? = nil) {
12+
self._logStore = ObservedObject(initialValue: logStore)
1313
if let binding = highlight {
1414
self._highlight = binding
1515
} else {
@@ -42,7 +42,7 @@ public struct LogEntriesGrid: View {
4242
.padding(.horizontal, Self.tableRowHorizontalPadding)
4343

4444
// Table rows
45-
ForEach(logEntries) { item in
45+
ForEach(logStore.log) { item in
4646
let isSelected = highlight == item.label
4747
GridRow {
4848
HStack(spacing: 0) {
@@ -92,6 +92,7 @@ public struct LogEntriesGrid: View {
9292
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
9393
struct LogEntriesGrid_Previews: PreviewProvider {
9494
static var previews: some View {
95-
LogEntriesGrid(logEntries: sampleLogEntries)
95+
let logStore = LogStore(log: sampleLogEntries)
96+
LogEntriesGrid(logStore: logStore)
9697
}
9798
}

0 commit comments

Comments
 (0)