Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion WorkflowSwiftUI/Sources/PerceptionCheckSuppression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,28 @@ import Perception
/// meant to be set once at app startup, and a preview has no equivalent entry point — the canvas
/// instantiates a view directly, with no app delegate and no runtime to configure — so a preview
/// would otherwise have no way to reach the configuration at all.
func withPerceptionCheckSuppressed<T>(_ operation: () -> T) -> T {
///
/// Two kinds of read need this. A ``Store`` read from a view body is the obvious one. The other is
/// a read a workflow makes of its own state during `render`, which never passes through a `Store`:
/// a preview host drives that render pass synchronously from a `UIViewControllerRepresentable`
/// callback, and *SwiftUI* is what calls that callback. Perception decides whether it is looking at
/// a SwiftUI view body by walking the call stack for AttributeGraph frames, so those frames are
/// present and the whole render pass is misreported. The same workflow in an app renders off a
/// runtime update instead, leaving no AttributeGraph frame on the stack, which is why these
/// warnings appear only in the canvas.
///
/// Known limitation below iOS 17 and its siblings: the availability check leaves suppression off
/// there, because a view body on those versions genuinely does need `WithPerceptionTracking` to
/// observe state at all — a warning about one is actionable, and hiding it would turn a preview
/// that silently stops updating into a preview that silently stops updating for no visible reason.
/// The cost is that a render-pass read still warns on those versions, where nothing can act on it,
/// since `WithPerceptionTracking` is a view modifier and a workflow's `render` cannot be wrapped in
/// one.
///
/// SPI rather than public API. It exists for preview hosts, of which there are few and all of them
/// library code, and it is meaningless to an app.
@_spi(PreviewHosting)
public func withPerceptionCheckSuppressed<T>(_ operation: () -> T) -> T {
#if DEBUG && canImport(Observation)
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *),
Runtime.configuration.suppressPerceptionCheckingWhenUsingObservation
Expand Down
13 changes: 3 additions & 10 deletions WorkflowSwiftUI/Sources/Workflow+Preview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,9 @@ private struct PreviewView<WorkflowType: Workflow>: UIViewControllerRepresentabl
let customizeEnvironment: (inout ViewEnvironment) -> Void
let onOutput: (WorkflowType.Output) -> Void

// Both representable callbacks drive a workflow render pass synchronously, and the check
// Perception performs there decides whether it is looking at a SwiftUI view body by walking the
// call stack for AttributeGraph frames. SwiftUI is what calls these methods, so those frames are
// present and every observable read the render pass makes is misreported — including reads a
// workflow makes of its own state, which never pass through a `Store` and so cannot be covered
// by the suppression there.
//
// These warnings are unique to previews. The same workflow running in an app renders off a
// runtime update rather than a SwiftUI one, so no AttributeGraph frame is on the stack and the
// check correctly stays quiet.
// Both callbacks drive a workflow render pass synchronously, and SwiftUI is what calls them, so
// every observable read the pass makes is misreported. See `withPerceptionCheckSuppressed` for
// why, and for why it only happens in previews.

func makeUIViewController(context: Context) -> UIViewControllerType {
withPerceptionCheckSuppressed {
Expand Down
Loading