diff --git a/PreviewsSupport/scripts/update_framework_interface.rb b/PreviewsSupport/scripts/update_framework_interface.rb index d0eae2b3..71a03cbc 100644 --- a/PreviewsSupport/scripts/update_framework_interface.rb +++ b/PreviewsSupport/scripts/update_framework_interface.rb @@ -31,6 +31,19 @@ ") end end + + if !File.read(file_path).include?("PreviewModifierViewModifier") + File.open(file_path, 'a') do |file| + file.puts("@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *) +public struct PreviewModifierViewModifier where A : PreviewModifier { + public func body(content: SwiftUI._ViewModifier_Content>) -> some View + public init(modifier: A, context: A.Context) +} +extension PreviewModifierViewModifier : SwiftUI.ViewModifier where A : SwiftUI.PreviewModifier { +}") + end + end + end for file_path in uikit_interface diff --git a/Sources/SnapshotPreviewsCore/PreviewModifierCache.swift b/Sources/SnapshotPreviewsCore/PreviewModifierCache.swift new file mode 100644 index 00000000..cf9fc38b --- /dev/null +++ b/Sources/SnapshotPreviewsCore/PreviewModifierCache.swift @@ -0,0 +1,14 @@ +// +// PreviewModifierCache.swift +// SnapshotPreviews +// +// Created by Itay Brenner on 25/9/24. +// + +import SwiftUI + +struct PreviewModifierContextCache { + static let shared = PreviewModifierContextCache() + + static var contextCache: [String: Any] = [:] +} diff --git a/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift b/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift index dba656ef..55594f1e 100644 --- a/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift +++ b/Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift @@ -1,6 +1,51 @@ import SwiftUI import PreviewsSupport +@available(iOS 18.0, *) +struct AnyPreviewModifier: PreviewModifier { + private let _body: (PreviewModifier.Content) -> AnyView + + static var contextCache: [String: Any] = [:] + + // Initialize with a concrete PreviewModifier + init(_ modifier: M) { + let type = type(of: modifier) + let hash = String(describing: type) + + _body = { content in + var cachedContext = PreviewModifierContextCache.contextCache[hash] ?? () + // TODO: Load all makeSharedContext() + guard let typedContext = cachedContext as? M.Context else { + fatalError("Context type mismatch, expected: \(String(describing: M.Context.self)), got: \(String(describing: cachedContext.self))") + } + return AnyView(modifier.body(content: content, context: typedContext)) + } + } + + static func makeSharedContext() async throws -> Any { + // Not necessary since we load it from the PreviewModifier + return () + } + + func body(content: PreviewModifier.Content, context: Any) -> AnyView { + return _body(content) + } +} + +@available(iOS 18.0, *) +struct AnyModifier: ViewModifier { + private var modifier: any PreviewModifier + + init(_ modifier: M) { + self.modifier = modifier + } + + func body(content: Content) -> some View { + content + .modifier(PreviewModifierViewModifier(modifier: AnyPreviewModifier(modifier), context: ())) + } +} + public struct Preview: Identifiable { init(preview: _Preview, type: P.Type) { previewId = "\(preview.id)" @@ -39,6 +84,18 @@ public struct Preview: Identifiable { } } } + + let previewModifiers = traits.compactMap({ trait in + if let value = Mirror(reflecting: trait).descendant("value") { + if #available(iOS 18.0, *) { + if let value = value as? [(any PreviewModifier)] { + return value + } + } + } + return nil + }).flatMap(\.self) + self.orientation = orientation self.layout = layout displayName = preview.descendant("displayName") as? String @@ -46,7 +103,16 @@ public struct Preview: Identifiable { let _view: @MainActor () -> any View if let source = source as? MakeViewProvider { _view = { - return source.makeView() + // TODO: use external function + if #available(iOS 18.0, *) { + var currentView: AnyView = AnyView(source.makeView()) + for modifier in previewModifiers { + currentView = AnyView(currentView.modifier(AnyModifier(AnyPreviewModifier(modifier)))) + } + return currentView + } else { + return AnyView(source.makeView()) + } } } else { #if canImport(UIKit) && !os(watchOS)