Skip to content

Commit c02dfff

Browse files
authored
Merge pull request ole#6 from ole/duplicate-label-warnings
Runtime warnings for duplicate view labels
2 parents 96af6cc + 1cecace commit c02dfff

File tree

3 files changed

+71
-1
lines changed

3 files changed

+71
-1
lines changed

DebugLayout/Sources/DebugLayout/DebugLayout.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@ extension View {
1515
}
1616

1717
/// Monitor the layout proposals and responses for this view and add them to the log.
18-
public func debugLayout(_ label: String) -> some View {
18+
public func debugLayout(
19+
_ label: String,
20+
file: StaticString = #fileID,
21+
line: UInt = #line
22+
) -> some View {
1923
DebugLayout(label: label) {
2024
self
2125
}
26+
.onAppear {
27+
LogStore.shared.registerViewLabelAndWarnIfNotUnique(label, file: file, line: line)
28+
}
2229
.modifier(DebugLayoutSelectionHighlight(viewID: label))
2330
}
2431
}

DebugLayout/Sources/DebugLayout/DebugLayoutLog.swift

+12
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct ClearDebugLayoutLog: Layout {
4545
assert(subviews.count == 1)
4646
DispatchQueue.main.async {
4747
LogStore.shared.log.removeAll()
48+
LogStore.shared.viewLabels.removeAll()
4849
}
4950
return subviews[0].sizeThatFits(proposal)
5051
}
@@ -59,6 +60,17 @@ public final class LogStore: ObservableObject {
5960
public static let shared: LogStore = .init()
6061

6162
@Published var log: [LogEntry] = []
63+
var viewLabels: Set<String> = []
64+
65+
func registerViewLabelAndWarnIfNotUnique(_ label: String, file: StaticString, line: UInt) {
66+
DispatchQueue.main.async {
67+
if self.viewLabels.contains(label) {
68+
let message: StaticString = "Duplicate view label '%s' detected. Use unique labels in debugLayout() calls"
69+
runtimeWarning(message, [label], file: file, line: line)
70+
}
71+
self.viewLabels.insert(label)
72+
}
73+
}
6274
}
6375

6476
struct LogEntry: Identifiable {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Source: Point-Free, Swift Composable Architecture, RuntimeWarnings.swift
2+
// https://github.com/pointfreeco/swift-composable-architecture/blob/399bc83dcfc7bdcee99f7f6cc0a687ca29e8494b/Sources/ComposableArchitecture/Internal/RuntimeWarnings.swift
3+
//
4+
// Based on: Point-Free, Unobtrusive runtime warnings for libraries (2022-01-03)
5+
// https://www.pointfree.co/blog/posts/70-unobtrusive-runtime-warnings-for-libraries
6+
//
7+
// Slightly modified by Ole Begemann
8+
9+
#if DEBUG
10+
import os
11+
12+
// NB: Xcode runtime warnings offer a much better experience than traditional assertions and
13+
// breakpoints, but Apple provides no means of creating custom runtime warnings ourselves.
14+
// To work around this, we hook into SwiftUI's runtime issue delivery mechanism, instead.
15+
//
16+
// Feedback filed: https://gist.github.com/stephencelis/a8d06383ed6ccde3e5ef5d1b3ad52bbc
17+
private let rw = (
18+
dso: { () -> UnsafeMutableRawPointer in
19+
let count = _dyld_image_count()
20+
for i in 0..<count {
21+
if let name = _dyld_get_image_name(i) {
22+
let swiftString = String(cString: name)
23+
if swiftString.hasSuffix("/SwiftUI") {
24+
if let header = _dyld_get_image_header(i) {
25+
return UnsafeMutableRawPointer(mutating: UnsafeRawPointer(header))
26+
}
27+
}
28+
}
29+
}
30+
return UnsafeMutableRawPointer(mutating: #dsohandle)
31+
}(),
32+
log: OSLog(subsystem: "com.apple.runtime-issues", category: "DebugLayout")
33+
)
34+
#endif
35+
36+
@_transparent
37+
@inline(__always)
38+
func runtimeWarning(
39+
_ message: @autoclosure () -> StaticString,
40+
_ args: @autoclosure () -> [CVarArg] = [],
41+
file: StaticString? = nil,
42+
line: UInt? = nil
43+
) {
44+
#if DEBUG
45+
let message = message()
46+
unsafeBitCast(
47+
os_log as (OSLogType, UnsafeRawPointer, OSLog, StaticString, CVarArg...) -> Void,
48+
to: ((OSLogType, UnsafeRawPointer, OSLog, StaticString, [CVarArg]) -> Void).self
49+
)(.fault, rw.dso, rw.log, message, args())
50+
#endif
51+
}

0 commit comments

Comments
 (0)