From 68ff8bc6f9648278bca0c9fb28948d2b9a0ff6e1 Mon Sep 17 00:00:00 2001 From: Itay Date: Wed, 25 Sep 2024 17:44:01 -0300 Subject: [PATCH 1/2] Add support for previewModifiers --- .../scripts/update_framework_interface.rb | 25 +++++++ .../PreviewModifierCache.swift | 14 ++++ .../SnapshotPreviewsCore.swift | 68 ++++++++++++++++++- 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 Sources/SnapshotPreviewsCore/PreviewModifierCache.swift diff --git a/PreviewsSupport/scripts/update_framework_interface.rb b/PreviewsSupport/scripts/update_framework_interface.rb index d0eae2b3..572b66af 100644 --- a/PreviewsSupport/scripts/update_framework_interface.rb +++ b/PreviewsSupport/scripts/update_framework_interface.rb @@ -31,6 +31,31 @@ ") 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 { + + /// Gets the current body of the caller. + /// + /// `content` is a proxy for the view that will have the modifier + /// represented by `Self` applied to it. + public func body(content: _ViewModifier_Content>) -> some View + + + public init(modifier: A, context: A.Context) + + public static func metadata() -> any Any.Type + + public static var nominalTypeDescriptor: String { get } +} + +extension PreviewModifierViewModifier : ViewModifier { +}") + 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) From f80d203d4409ea4f277b1937f15aaf7c3d98ec92 Mon Sep 17 00:00:00 2001 From: Itay Date: Thu, 26 Sep 2024 13:18:08 -0300 Subject: [PATCH 2/2] Update interfaces script --- .../scripts/update_framework_interface.rb | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/PreviewsSupport/scripts/update_framework_interface.rb b/PreviewsSupport/scripts/update_framework_interface.rb index 572b66af..71a03cbc 100644 --- a/PreviewsSupport/scripts/update_framework_interface.rb +++ b/PreviewsSupport/scripts/update_framework_interface.rb @@ -36,22 +36,10 @@ 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 { - - /// Gets the current body of the caller. - /// - /// `content` is a proxy for the view that will have the modifier - /// represented by `Self` applied to it. - public func body(content: _ViewModifier_Content>) -> some View - - + public func body(content: SwiftUI._ViewModifier_Content>) -> some View public init(modifier: A, context: A.Context) - - public static func metadata() -> any Any.Type - - public static var nominalTypeDescriptor: String { get } } - -extension PreviewModifierViewModifier : ViewModifier { +extension PreviewModifierViewModifier : SwiftUI.ViewModifier where A : SwiftUI.PreviewModifier { }") end end