Skip to content

Commit b660a51

Browse files
committed
Get rid of LogStore.shared singleton
1 parent e48f334 commit b660a51

File tree

2 files changed

+71
-58
lines changed

2 files changed

+71
-58
lines changed

Sources/LayoutInspector/DebugLayout.swift

Lines changed: 2 additions & 8 deletions
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-
self.modifier(InspectLayout())
7+
modifier(InspectLayout())
88
}
99

1010
/// Monitor the layout proposals and responses for this view and add them
@@ -14,12 +14,6 @@ extension View {
1414
file: StaticString = #fileID,
1515
line: UInt = #line
1616
) -> some View {
17-
DebugLayout(label: label) {
18-
self
19-
}
20-
.onAppear {
21-
LogStore.shared.registerViewLabelAndWarnIfNotUnique(label, file: file, line: line)
22-
}
23-
.modifier(DebugLayoutSelectionHighlight(viewID: label))
17+
modifier(DebugLayoutModifier(label: label, file: file, line: line))
2418
}
2519
}

Sources/LayoutInspector/DebugLayoutImpl.swift

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ 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()
56
@State private var selectedView: String? = nil
67
@State private var generation: Int = 0
78
@State private var frame: CGRect = CGRect(x: 0, y: 0, width: 300, height: 300)
8-
@ObservedObject private var logStore = LogStore.shared
99

1010
private static let coordSpaceName = "InspectLayout"
1111

1212
func body(content: Content) -> some View {
13-
ClearDebugLayoutLog {
13+
ClearDebugLayoutLog(logStore: logStore) {
1414
content
1515
.id(generation)
1616
.environment(\.debugLayoutSelectedViewID, selectedView)
@@ -39,6 +39,7 @@ struct InspectLayout: ViewModifier {
3939
.offset(x: frame.minX, y: frame.minY)
4040
.coordinateSpace(name: Self.coordSpaceName)
4141
}
42+
.environmentObject(logStore)
4243
}
4344
}
4445

@@ -160,21 +161,40 @@ struct Draggable: ViewModifier {
160161
}
161162
}
162163

164+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
165+
struct DebugLayoutModifier: ViewModifier {
166+
var label: String
167+
var file: StaticString
168+
var line: UInt
169+
@EnvironmentObject private var logStore: LogStore
170+
171+
func body(content: Content) -> some View {
172+
DebugLayout(label: label, logStore: logStore) {
173+
content
174+
}
175+
.onAppear {
176+
logStore.registerViewLabelAndWarnIfNotUnique(label, file: file, line: line)
177+
}
178+
.modifier(DebugLayoutSelectionHighlight(viewID: label))
179+
}
180+
}
181+
163182
/// A custom layout that saves the layout proposals and responses for a view
164183
/// to a log.
165184
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
166185
struct DebugLayout: Layout {
167186
var label: String
187+
var logStore: LogStore
168188

169189
func sizeThatFits(
170190
proposal: ProposedViewSize,
171191
subviews: Subviews,
172192
cache: inout ()
173193
) -> CGSize {
174194
assert(subviews.count == 1)
175-
logLayoutStep(label, step: .proposal(proposal))
195+
logStore.logLayoutStep(label, step: .proposal(proposal))
176196
let response = subviews[0].sizeThatFits(proposal)
177-
logLayoutStep(label, step: .response(response))
197+
logStore.logLayoutStep(label, step: .response(response))
178198
return response
179199
}
180200

@@ -192,15 +212,17 @@ struct DebugLayout: Layout {
192212
/// placed in the view tree.
193213
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
194214
struct ClearDebugLayoutLog: Layout {
215+
var logStore: LogStore
216+
195217
func sizeThatFits(
196218
proposal: ProposedViewSize,
197219
subviews: Subviews,
198220
cache: inout ()
199221
) -> CGSize {
200222
assert(subviews.count == 1)
201223
DispatchQueue.main.async {
202-
LogStore.shared.log.removeAll()
203-
LogStore.shared.viewLabels.removeAll()
224+
logStore.log.removeAll()
225+
logStore.viewLabels.removeAll()
204226
}
205227
return subviews[0].sizeThatFits(proposal)
206228
}
@@ -216,59 +238,56 @@ struct ClearDebugLayoutLog: Layout {
216238
}
217239
}
218240

219-
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
220-
func logLayoutStep(_ label: String, step: LogEntry.Step) {
221-
DispatchQueue.main.async {
222-
guard let prevEntry = LogStore.shared.log.last else {
223-
// First log entry → start at indent 0.
224-
LogStore.shared.log.append(LogEntry(label: label, step: step, indent: 0))
225-
return
226-
}
227-
228-
var newEntry = LogEntry(label: label, step: step, indent: prevEntry.indent)
229-
let isSameView = prevEntry.label == label
230-
switch (isSameView, prevEntry.step, step) {
231-
case (true, .proposal(let prop), .response(let resp)):
232-
// Response follows immediately after proposal for the same view.
233-
// → We want to display them in a single row.
234-
// → Coalesce both layout steps.
235-
LogStore.shared.log.removeLast()
236-
newEntry = prevEntry
237-
newEntry.step = .proposalAndResponse(proposal: prop, response: resp)
238-
LogStore.shared.log.append(newEntry)
239-
240-
case (_, .proposal, .proposal):
241-
// A proposal follows a proposal → nested view → increment indent.
242-
newEntry.indent += 1
243-
LogStore.shared.log.append(newEntry)
244-
245-
case (_, .response, .response),
246-
(_, .proposalAndResponse, .response):
247-
// A response follows a response → last child returns to parent → decrement indent.
248-
newEntry.indent -= 1
249-
LogStore.shared.log.append(newEntry)
250-
251-
default:
252-
// Keep current indentation.
253-
LogStore.shared.log.append(newEntry)
254-
}
255-
}
256-
}
257-
258241
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
259242
public final class LogStore: ObservableObject {
260-
public static let shared: LogStore = .init()
261-
262243
@Published public var log: [LogEntry] = []
263244
var viewLabels: Set<String> = []
264245

265246
func registerViewLabelAndWarnIfNotUnique(_ label: String, file: StaticString, line: UInt) {
266-
DispatchQueue.main.async {
267-
if self.viewLabels.contains(label) {
247+
DispatchQueue.main.async { [self] in
248+
if viewLabels.contains(label) {
268249
let message: StaticString = "Duplicate view label '%s' detected. Use unique labels in layoutStep() calls"
269250
runtimeWarning(message, [label], file: file, line: line)
270251
}
271-
self.viewLabels.insert(label)
252+
viewLabels.insert(label)
253+
}
254+
}
255+
256+
func logLayoutStep(_ label: String, step: LogEntry.Step) {
257+
DispatchQueue.main.async { [self] in
258+
guard let prevEntry = log.last else {
259+
// First log entry → start at indent 0.
260+
log.append(LogEntry(label: label, step: step, indent: 0))
261+
return
262+
}
263+
264+
var newEntry = LogEntry(label: label, step: step, indent: prevEntry.indent)
265+
let isSameView = prevEntry.label == label
266+
switch (isSameView, prevEntry.step, step) {
267+
case (true, .proposal(let prop), .response(let resp)):
268+
// Response follows immediately after proposal for the same view.
269+
// → We want to display them in a single row.
270+
// → Coalesce both layout steps.
271+
log.removeLast()
272+
newEntry = prevEntry
273+
newEntry.step = .proposalAndResponse(proposal: prop, response: resp)
274+
log.append(newEntry)
275+
276+
case (_, .proposal, .proposal):
277+
// A proposal follows a proposal → nested view → increment indent.
278+
newEntry.indent += 1
279+
log.append(newEntry)
280+
281+
case (_, .response, .response),
282+
(_, .proposalAndResponse, .response):
283+
// A response follows a response → last child returns to parent → decrement indent.
284+
newEntry.indent -= 1
285+
log.append(newEntry)
286+
287+
default:
288+
// Keep current indentation.
289+
log.append(newEntry)
290+
}
272291
}
273292
}
274293
}

0 commit comments

Comments
 (0)