Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for previewModifiers #200

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
13 changes: 13 additions & 0 deletions PreviewsSupport/scripts/update_framework_interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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<A> where A : PreviewModifier {
public func body(content: SwiftUI._ViewModifier_Content<PreviewModifierViewModifier<A>>) -> 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
Expand Down
14 changes: 14 additions & 0 deletions Sources/SnapshotPreviewsCore/PreviewModifierCache.swift
Original file line number Diff line number Diff line change
@@ -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] = [:]
}
68 changes: 67 additions & 1 deletion Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift
Original file line number Diff line number Diff line change
@@ -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<M: PreviewModifier>(_ 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<M: PreviewModifier>(_ modifier: M) {
self.modifier = modifier
}

func body(content: Content) -> some View {
content
.modifier(PreviewModifierViewModifier(modifier: AnyPreviewModifier(modifier), context: ()))
}
}

public struct Preview: Identifiable {
init<P: SwiftUI.PreviewProvider>(preview: _Preview, type: P.Type) {
previewId = "\(preview.id)"
Expand Down Expand Up @@ -39,14 +84,35 @@ 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
let source = preview.descendant("source")!
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)
Expand Down
Loading