Skip to content

Commit

Permalink
Use variadic view (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahsmartin authored Oct 16, 2023
1 parent 8ff68eb commit 3b71edd
Show file tree
Hide file tree
Showing 8 changed files with 27 additions and 199 deletions.
4 changes: 2 additions & 2 deletions Sources/PreviewGallery/ModuleScreens.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ struct ModuleScreens: View {
let featurePreviews = provider.previews.filter { $0.requiresFullScreen }
NavigationLink {
if featurePreviews.count == 1 {
try! featurePreviews[0].view()
featurePreviews[0].view()
} else {
List {
ForEach(featurePreviews) { preview in
NavigationLink(preview.displayName ?? provider.displayName) {
try! preview.view()
preview.view()
}
}
}.navigationTitle(provider.displayName)
Expand Down
2 changes: 1 addition & 1 deletion Sources/PreviewGallery/PreviewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct PreviewCell: View {
var body: some View {
PreferredColorSchemeWrapper {
VStack {
try! preview.view()
preview.view()
.padding(.vertical, 8)
.border(Color.dynamicBackground)
.background {
Expand Down
8 changes: 2 additions & 6 deletions Sources/SnapshotPreviewsCore/ExpandingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,12 @@ public final class ExpandingViewController<Content: View>: UIHostingController<A

private var didCall = false
private var previousHeight: CGFloat?
private let supportsExpansion: Bool

private var heightAnchor: NSLayoutConstraint?

var expansionSettled: ((EmergeRenderingMode?, Float?) -> Void)?

init(rootView: Content, layout: PreviewLayout, supportsExpansion: Bool) {
self.supportsExpansion = supportsExpansion
init(rootView: Content, layout: PreviewLayout) {
let newView = finder?(rootView)
super.init(rootView: newView != nil ? AnyView(newView!) : AnyView(rootView))

Expand Down Expand Up @@ -94,9 +92,7 @@ public final class ExpandingViewController<Content: View>: UIHostingController<A
return
}

let expansionPreference = stateMirror?.descendant("expansionPreference") as? Bool

let supportsExpansion = expansionPreference ?? self.supportsExpansion
let supportsExpansion = stateMirror?.descendant("expansionPreference") as? Bool ?? true
let scrollView = view.firstScrollView
if let scrollView, supportsExpansion {
let diff = Int(scrollView.contentSize.height - scrollView.visibleContentHeight)
Expand Down
21 changes: 0 additions & 21 deletions Sources/SnapshotPreviewsCore/PreviewProviderExtraction.swift

This file was deleted.

22 changes: 7 additions & 15 deletions Sources/SnapshotPreviewsCore/SnapshotPreviewsCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,14 @@ extension View {
}

public struct Preview: Identifiable {
init<P>(preview: SwiftUI._Preview, extraction: PreviewProviderExtraction<P>) {
init<P: SwiftUI.PreviewProvider>(preview: _Preview, type: P.Type) {
previewId = "\(preview.id)"
orientation = preview.interfaceOrientation
displayName = preview.displayName
device = preview.device
layout = preview.layout
_view = {
let children = try extraction.previews.get()

let (v, modifiers) = children[preview.id]
var result = v
for mod in modifiers {
result = result.applyModifier(mod)
}
return AnyView(result)
AnyView(P.previews.selectSubview(preview.id))
}
}

Expand All @@ -43,7 +36,7 @@ public struct Preview: Identifiable {
displayName = preview.descendant("displayName") as? String
let source = Mirror(reflecting: preview.descendant("source")!)
let sourceType = source.subjectType
let _view: @MainActor () throws -> AnyView
let _view: @MainActor () -> AnyView
if (String(describing: sourceType) == "ViewPreviewSource") {
_view = {
let makeView = source.descendant("makeView") as! @MainActor () -> any SwiftUI.View
Expand All @@ -63,9 +56,9 @@ public struct Preview: Identifiable {
public let displayName: String?
public let device: PreviewDevice?
public let layout: PreviewLayout
private let _view: @MainActor () throws -> AnyView
@MainActor public func view() throws -> AnyView {
try _view()
private let _view: @MainActor () -> AnyView
@MainActor public func view() -> AnyView {
_view()
}
}

Expand All @@ -74,8 +67,7 @@ public struct PreviewType: Hashable, Identifiable {
init<A: PreviewProvider>(typeName: String, preivewProvider: A.Type) {
self.typeName = typeName
self.fileID = nil
let extraction = PreviewProviderExtraction<A>()
self.previews = A._allPreviews.map { Preview(preview: $0, extraction: extraction) }
self.previews = A._allPreviews.map { Preview(preview: $0, type: A.self) }
self.platform = A.platform
}

Expand Down
8 changes: 3 additions & 5 deletions Sources/SnapshotPreviewsCore/View+Snapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@ extension View {
public func snapshot(
layout: PreviewLayout,
window: UIWindow,
legacySupportsExpansion: Bool,
legacyRenderingMode: EmergeRenderingMode?,
async: Bool,
completion: @escaping (Result<UIImage, Error>, Float?) -> Void)
{
UIView.setAnimationsEnabled(false)
let animationDisabledView = self.transaction { transaction in
transaction.disablesAnimations = true
}
let controller = ExpandingViewController(rootView: animationDisabledView, layout: layout, supportsExpansion: legacySupportsExpansion)
let controller = ExpandingViewController(rootView: animationDisabledView, layout: layout)
if #available(iOS 16, *) {
controller.sizingOptions = .intrinsicContentSize
}
Expand All @@ -40,11 +38,11 @@ extension View {
controller.expansionSettled = { renderingMode, precision in
if async {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
completion(Self.takeSnapshot(layout: layout, renderingMode: legacyRenderingMode ?? renderingMode, rootVC: containerVC, controller: controller), precision)
completion(Self.takeSnapshot(layout: layout, renderingMode: renderingMode, rootVC: containerVC, controller: controller), precision)
}
} else {
DispatchQueue.main.async {
completion(Self.takeSnapshot(layout: layout, renderingMode: legacyRenderingMode ?? renderingMode, rootVC: containerVC, controller: controller), precision)
completion(Self.takeSnapshot(layout: layout, renderingMode: renderingMode, rootVC: containerVC, controller: controller), precision)
}
}
}
Expand Down
155 changes: 11 additions & 144 deletions Sources/SnapshotPreviewsCore/ViewInspection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,154 +7,21 @@

import SwiftUI

public enum ViewInspection {

static func attribute(label: String, value: Any) -> Any? {
let mirror = (value as? Mirror) ?? Mirror(reflecting: value)
return mirror.descendant(label)
}

static func tupleChildren(_ content: Any) -> [any View] {
let tupleViews = Self.attribute(label: "value", value: content)!
let childrenCount = Mirror(reflecting: tupleViews).children.count
return (0..<childrenCount).map { Self.attribute(label: ".\($0)", value: tupleViews) as! any View }
}

public static func childrenIfMultiple(of view: some View) -> [(any View, [any ViewModifier])] {
let children = children(of: view)
if children.count == 1 {
return [(view, [])]
}
return children
}

private static func children(of view: some View) -> [(any View, [any ViewModifier])] {
let viewType = type(of: view)
let typeName = String(reflecting: viewType)
if typeName.starts(with: "Swift.Optional<") {
if let child = Mirror(reflecting: view).children.first?.value as? any View {
return children(of: child)
}
} else if typeName.starts(with: "SwiftUI._ConditionalContent") {
let storage = Self.attribute(label: "storage", value: view)!
if let trueContent = Self.attribute(label: "trueContent", value: storage) as? any View {
return childrenIfMultiple(of: trueContent)
} else {
let content = Self.attribute(label: "falseContent", value: storage) as! any View
return childrenIfMultiple(of: content)
}
} else if typeName.starts(with: "SwiftUI.EmptyView") {
return []
} else if typeName.starts(with: "SwiftUI.Tuple") {
return tupleChildren(view).flatMap { childrenIfMultiple(of: $0) }
} else if typeName.starts(with: "SwiftUI.Group") {
let content = Self.attribute(label: "content", value: view) as! any View
return childrenIfMultiple(of: content)
} else if typeName.starts(with: "SwiftUI.ForEach"), let provider = view as? ViewsProvider {
return provider.views().flatMap { childrenIfMultiple(of: $0) }
} else if typeName.starts(with: "SwiftUI.AnyView") {
let storage = Self.attribute(label: "storage", value: view)!
let storageView = Self.attribute(label: "view", value: storage) as! any View
return childrenIfMultiple(of: storageView)
} else if typeName.starts(with: "SwiftUI.ModifiedContent") {
let content = Self.attribute(label: "content", value: view) as! any View
let modifier = Self.attribute(label: "modifier", value: view) as! any ViewModifier
return childrenIfMultiple(of: content).map { (view, modifiers) in
var modifiers = modifiers
modifiers.append(modifier)
return (view, modifiers)
}
}
if viewType.Body != Never.self && !typeName.starts(with: "SwiftUI.") {
return childrenIfMultiple(of: view.body)
}
return [(view, [])]
}

private static func getModifier<T>(of view: some View, parseModifier: (Any) -> T?) -> T? {
let viewType = type(of: view)
let typeName = String(reflecting: viewType)
if typeName.starts(with: "SwiftUI.ModifiedContent") {
let modifier = Self.attribute(label: "modifier", value: view)!
if let result = parseModifier(modifier) {
return result
}
let content = Self.attribute(label: "content", value: view) as! any View
return getModifier(of: content, parseModifier: parseModifier)
} else if typeName.starts(with: "SwiftUI.AnyView") {
let storage = Self.attribute(label: "storage", value: view)!
let storageView = Self.attribute(label: "view", value: storage) as! any View
return getModifier(of: storageView, parseModifier: parseModifier)
} else if typeName.starts(with: "SwiftUI.Tuple") {
return getModifier(of: tupleChildren(view)[0], parseModifier: parseModifier)
} else if typeName.starts(with: "SwiftUI.Group") {
let content = Self.attribute(label: "content", value: view) as! any View
return getModifier(of: content, parseModifier: parseModifier)
} else if typeName.starts(with: "SwiftUI.ForEach"), let provider = view as? ViewsProvider {
return getModifier(of: provider.views()[0], parseModifier: parseModifier)
}
if viewType.Body != Never.self && !typeName.starts(with: "SwiftUI.") {
return getModifier(of: view.body, parseModifier: parseModifier)
}
return nil
}

public static func precision(of view: some View) -> Float? {
return getModifier(of: view) { modifier in
let typeName = String(reflecting: modifier)
if typeName.contains(".EmergePrecisionModifier") {
if let precision = Self.attribute(label: "precision", value: modifier) as? Float {
return precision
fileprivate struct ViewSelector: _VariadicView_MultiViewRoot {
let position: Int
func body(children: _VariadicView.Children) -> some View {
ForEach(Array(children.enumerated()), id: \.0) { count, item in
if count == position {
item
}
}
return nil
}
}

public static func shouldExpand(_ view: some View) -> Bool {
return getModifier(of: view) { modifier in
let typeName = String(reflecting: modifier)
if typeName.contains(".EmergeExpansionModifier") {
if let enabled = Self.attribute(label: "enabled", value: modifier) as? Bool {
return enabled
}
}
return true
} ?? true
}
}

public static func renderingMode(of view: some View) -> EmergeRenderingMode? {
return getModifier(of: view) { modifier in
let typeName = String(reflecting: modifier)
if typeName.contains(".RenderingModeModifier") {
if let mode = Self.attribute(label: "renderingMode", value: modifier) {
let caseName = String(reflecting: mode)
if caseName.contains("EmergeRenderingMode.coreAnimation") {
return .coreAnimation
} else if caseName.contains("EmergeRenderingMode.uiView") {
return .uiView
}
}
}
return nil
extension View {
func selectSubview(_ position: Int) -> some View {
_VariadicView.Tree(ViewSelector(position: position)) {
self
}
}
}

protocol ViewsProvider {
func views() -> [any View]
}

extension ForEach: ViewsProvider where Content: View {

func views() -> [any View] {

typealias Builder = (Data.Element) -> Content
let data = ViewInspection.attribute(label: "data", value: self) as! Data
let builder = ViewInspection.attribute(label: "content", value: self) as! Builder
let indecies = (0..<data.count).map { i in
return data.index(data.startIndex, offsetBy: i)
}
return indecies.map { builder(data[$0]) }
}
}
6 changes: 1 addition & 5 deletions Sources/SnapshottingSwift/Snapshots.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,11 @@ class Snapshots {
}

@MainActor func display(preview: SnapshotPreviewsCore.Preview, completion: @escaping (Result<UIImage, Error>, Float?) -> Void) throws {
var view = try preview.view()
let supportsExpansion = ViewInspection.shouldExpand(view)
let renderingMode = ViewInspection.renderingMode(of: view)
var view = preview.view()
view = AnyView(PreferredColorSchemeWrapper { view })
view.snapshot(
layout: preview.layout,
window: window,
legacySupportsExpansion: supportsExpansion,
legacyRenderingMode: renderingMode,
async: false,
completion: completion)
}
Expand Down

0 comments on commit 3b71edd

Please sign in to comment.