previewDependencies api - #463
pschuette22 wants to merge 12 commits into
Conversation
|
I also considered making the api take a preview content closure at the to display either the error or the preview. Open to either or adding both. Curious what y'all think. We've found this to be a nice little helper for our previews. Once thing that's interesting is the dependencies bleed into the next preview unless they are updated. Not the end of the world, just a callout |
Why not preview traits?Preview traits are applied eagerly across all preview instances as they inflate the preview macro. They are not re-evaluated in a deterministic way which can lead to inconsistencies in dependency preparation. #Preview(
"with modifier trait",
traits: .dependencies { print("PREPARE") }
) {
MyView()
}expands to: @available(iOS 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0, watchOS 10.0, *)
struct $s1T…PreviewRegistryfMu_: DeveloperToolsSupport.PreviewRegistry {
static var fileID: String { "T/T.swift" }
static var line: Int { 19 }
static var column: Int { 1 }
static func makePreview() throws -> DeveloperToolsSupport.Preview {
DeveloperToolsSupport.Preview("with modifier trait", traits: .dependencies {
print("PREPARE")
}) {
func __b_buildView(@SwiftUI.ViewBuilder body: () -> any SwiftUI.View) -> any SwiftUI.View {
body()
}
return __b_buildView {
MyView()
}
}
}
}which is then handed off to xcode for rendering. Trait invocation is not tied to layout so switching between previews without changing the underlying view file may cause dependencies to pass the preview boundary via static dependency containers. Why not PrepareDependencies view?Similar to above, prepareDependencies should be called when buildView is invoked. It should be decoupled from view diffing logic and idempotent. By keeping as a plain @ViewBuilder function, prepareDependencies is kept at the top of the call stack and ordering is deterministic relative to Previewed views initalizer. If moved to a Xcode invalidates the entire preview when the previewed file changes and the new This does introduce a minor inefficiency where changes to the View file will trigger a redraw across all preview instances (when multiple are defined in a file) that will largely be thrown away, but selecting a preview from within the Xcode canvas will ensure view dependencies are up to date. |
| let site = PreparationSite(fileID: "\(fileID)", line: line, column: column) | ||
| guard | ||
| lastPreparationSite.withValue({ lastSite -> Bool in | ||
| guard lastSite != site else { return false } | ||
| lastSite = site | ||
| return true | ||
| }) | ||
| else { | ||
| // Short circuit to avoid resetting previews across state change | ||
| return nil | ||
| } | ||
|
|
||
| DependencyValues._current.cachedValues.resetPreviewCache() |
There was a problem hiding this comment.
Critical bit for intelligently resetting dependency caches across preview instances
|
Preview traits re-rendering concern turns out to be non-true and debunked in #465 Closing this PR in favor of that |
Adds a
previewDependenciesconvenience api. It accomplishes two main thingsforce_tryandredundant_discardable_let)Error display can be customized, if needed, with the errorView ViewBuilder argument
By default, the PreviewErrorView just displays a scrollable view with the errors localized description. Open to suggestions on ways to make this more useful out of box.