commit
def4a7aa48
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
// Running executables
|
||||||
|
// Running unit tests
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug tests on macOS",
|
||||||
|
"program": "/Applications/Xcode.app/Contents/Developer/usr/bin/xctest",
|
||||||
|
"args": [
|
||||||
|
"${workspaceFolder}/.build/debug/${workspaceFolderBasename}PackageTests.xctest"
|
||||||
|
],
|
||||||
|
"preLaunchTask": "swift-build-tests"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug tests on Linux",
|
||||||
|
"program": "${workspaceFolder}/.build/x86_64-unknown-linux/debug/${workspaceFolderBasename}PackageTests.xctest",
|
||||||
|
"preLaunchTask": "swift-build-tests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
// compile your SPM project
|
||||||
|
{
|
||||||
|
"label": "swift-build",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "swift build"
|
||||||
|
},
|
||||||
|
// compile your SPM tests
|
||||||
|
{
|
||||||
|
"label": "swift-build-tests",
|
||||||
|
"type": "process",
|
||||||
|
"command": "swift",
|
||||||
|
"group": "build",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--build-tests",
|
||||||
|
"--enable-test-discovery"
|
||||||
|
// for TensorFlow add "-Xlinker", "-ltensorflow"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ let package = Package(
|
||||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||||
.library(
|
.library(
|
||||||
name: "UIPreview",
|
name: "UIPreview",
|
||||||
targets: ["UIPreview"]),
|
targets: ["UIPreview"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
// Dependencies declare other packages that this package depends on.
|
// Dependencies declare other packages that this package depends on.
|
||||||
|
@ -26,6 +26,6 @@ let package = Package(
|
||||||
dependencies: []),
|
dependencies: []),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "UIPreviewTests",
|
name: "UIPreviewTests",
|
||||||
dependencies: ["UIPreview"]),
|
dependencies: ["UIPreview"])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,4 +9,3 @@ extension Binding {
|
||||||
return Binding(get: { value }, set: { value = $0 })
|
return Binding(get: { value }, set: { value = $0 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,14 @@ extension ColorScheme {
|
||||||
var previewName: String {
|
var previewName: String {
|
||||||
String(describing: self).capitalized
|
String(describing: self).capitalized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var systemImageName: String {
|
||||||
|
switch self {
|
||||||
|
case .dark: return "sun.max.fill"
|
||||||
|
case .light: return "sun.max"
|
||||||
|
@unknown default: return "questionmark"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13, *)
|
@available(iOS 13, *)
|
||||||
|
|
|
@ -38,4 +38,3 @@ struct ComponentPreview<Component: View>: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,41 +3,55 @@ import SwiftUI
|
||||||
#endif
|
#endif
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@available(iOS 13, *)
|
// TODO: remove UIView dependency
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
struct CatalogItem<Content: UIViewCatalogPresentable>: View {
|
struct CatalogItem<Content: UIViewCatalogPresentable>: View {
|
||||||
let configuration: UICatalog.PreviewConfiguration
|
let configuration: UICatalog.PreviewConfiguration
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ForEach(values: Content.previewModels) { model in
|
ForEach(values: Content.previewModels) { model in
|
||||||
ForEach(values: configuration.colorSchemes) { scheme in
|
ForEach(values: configuration.colorSchemes) { scheme in
|
||||||
item(model: model, scheme: scheme)
|
ForEach(values: configuration.contentSizeCategory) { category in
|
||||||
|
item(model: model, scheme: scheme, category: category)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(model: Content.PreviewModel,
|
func item(model: Content.PreviewModel,
|
||||||
scheme: ColorScheme) -> some View {
|
scheme: ColorScheme,
|
||||||
VStack(alignment: .center, spacing: 12.0) {
|
category: ContentSizeCategory) -> some View {
|
||||||
Text("\(scheme.previewName): \(String(describing: model))")
|
|
||||||
.font(.subheadline)
|
VStack(alignment: .center, spacing: 0) {
|
||||||
.fontWeight(.bold)
|
HStack {
|
||||||
|
Image(systemName: scheme.systemImageName)
|
||||||
|
Image(systemName: category.systemImageName)
|
||||||
|
Text(String(describing: model))
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
Content.preview(with: model)
|
Content.preview(with: model)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
.background(Color(.systemBackground))
|
.background(Color(.systemBackground))
|
||||||
.colorScheme(scheme)
|
.colorScheme(scheme)
|
||||||
Divider()
|
.environment(\.sizeCategory, category)
|
||||||
.background(Color.secondary)
|
|
||||||
}
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
.cornerRadius(6)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@available(iOS 13, *)
|
@available(iOS 14, *)
|
||||||
struct CatalogItem_Previews: PreviewProvider {
|
struct CatalogItem_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView(.vertical) {
|
||||||
|
PreviewLegend()
|
||||||
CatalogItem<TestView>(configuration: .init())
|
CatalogItem<TestView>(configuration: .init())
|
||||||
}.navigationBarTitle("TestView")
|
}.navigationBarTitle("TestView")
|
||||||
}
|
}
|
||||||
|
@ -47,11 +61,15 @@ struct CatalogItem_Previews: PreviewProvider {
|
||||||
private final class TestView: UILabel, UICatalogPresentable {
|
private final class TestView: UILabel, UICatalogPresentable {
|
||||||
static var previewModels = [
|
static var previewModels = [
|
||||||
"Hello world",
|
"Hello world",
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
|
"Hello2 world",
|
||||||
|
"Hello3 world",
|
||||||
|
"Hello4 world"
|
||||||
]
|
]
|
||||||
|
|
||||||
func apply(previewModel: String) {
|
func apply(previewModel: String) {
|
||||||
text = previewModel
|
text = previewModel
|
||||||
|
textColor = .systemRed
|
||||||
|
numberOfLines = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@available(iOS 13, *)
|
@available(iOS 14, *)
|
||||||
struct GroupItem: View {
|
struct GroupItem: View {
|
||||||
struct Model: Identifiable {
|
struct Model: Identifiable {
|
||||||
let id = UUID().uuidString
|
let id = UUID().uuidString // swiftlint:disable:this identifier_name
|
||||||
var isExpanded = false
|
var isExpanded = false
|
||||||
let title: String
|
let title: String
|
||||||
let content: () -> AnyView
|
let content: () -> AnyView
|
||||||
|
@ -20,8 +20,7 @@ struct GroupItem: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
@available(iOS 13, *)
|
|
||||||
struct GroupItemRow: View {
|
struct GroupItemRow: View {
|
||||||
|
|
||||||
@State var model: GroupItem.Model
|
@State var model: GroupItem.Model
|
||||||
|
@ -31,42 +30,37 @@ struct GroupItemRow: View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
model.isExpanded.toggle()
|
model.isExpanded.toggle()
|
||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
Label(model.title,
|
||||||
Text(model.title)
|
systemImage: model.isExpanded
|
||||||
.font(.headline)
|
? "chevron.up"
|
||||||
.fontWeight(.bold)
|
: "chevron.down")
|
||||||
if model.isExpanded {
|
|
||||||
Image(systemName: "chevron.up")
|
|
||||||
} else {
|
|
||||||
Image(systemName: "chevron.down")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.offset(x: 10)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding()
|
|
||||||
.background(Color.secondary)
|
|
||||||
.cornerRadius(8)
|
|
||||||
})
|
})
|
||||||
if model.isExpanded {
|
.frame(maxWidth: .infinity)
|
||||||
model.content()
|
.padding()
|
||||||
.frame(maxWidth: .infinity)
|
.background(Color(.systemGroupedBackground))
|
||||||
.padding([.top, .bottom], /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)
|
.cornerRadius(6)
|
||||||
}
|
.padding()
|
||||||
|
|
||||||
|
}
|
||||||
|
if model.isExpanded {
|
||||||
|
model.content()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding([.top, .bottom], 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@available(iOS 13, *)
|
@available(iOS 14, *)
|
||||||
struct GroupItem_Previews: PreviewProvider {
|
struct GroupItem_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
PreviewLegend()
|
||||||
GroupItem(items: [
|
GroupItem(items: [
|
||||||
.init(title: "Group 1",
|
.init(title: "Group 1",
|
||||||
content: { AnyView(Text("Preview")) } ),
|
content: { AnyView(Text("Preview")) }),
|
||||||
.init(title: "Group 2",
|
.init(title: "Group 2",
|
||||||
content: { AnyView(Image(systemName: "square.and.pencil")) } )
|
content: { AnyView(Image(systemName: "square.and.pencil")) })
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UICatalog {
|
extension UICatalog {
|
||||||
@available(iOS 13, *)
|
@available(iOS 14, *)
|
||||||
public struct Preview {
|
public struct Preview {
|
||||||
init(_ view: AnyView, title: String) {
|
init(_ view: AnyView, title: String) {
|
||||||
self.title = title
|
self.title = title
|
||||||
|
@ -13,6 +13,7 @@ extension UICatalog {
|
||||||
|
|
||||||
func preview() -> some View {
|
func preview() -> some View {
|
||||||
ScrollView(.vertical, showsIndicators: true) {
|
ScrollView(.vertical, showsIndicators: true) {
|
||||||
|
PreviewLegend()
|
||||||
view
|
view
|
||||||
}.navigationBarTitle(title)
|
}.navigationBarTitle(title)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,14 @@ import UIKit
|
||||||
|
|
||||||
extension UICatalog {
|
extension UICatalog {
|
||||||
public struct PreviewConfiguration {
|
public struct PreviewConfiguration {
|
||||||
public init(themes: [Theme] = Theme.allCases) {
|
public init(themes: [Theme] = Theme.allCases,
|
||||||
|
contentSize: [UIContentSizeCategory] = [.unspecified]) {
|
||||||
self.themes = themes
|
self.themes = themes
|
||||||
|
self.contentSize = contentSize
|
||||||
}
|
}
|
||||||
|
|
||||||
let themes: [Theme]
|
let themes: [Theme]
|
||||||
|
let contentSize: [UIContentSizeCategory]
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Theme: CaseIterable {
|
public enum Theme: CaseIterable {
|
||||||
|
@ -27,7 +30,11 @@ extension UICatalog.Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13, *)
|
@available(iOS 14, *)
|
||||||
extension UICatalog.PreviewConfiguration {
|
extension UICatalog.PreviewConfiguration {
|
||||||
var colorSchemes: [ColorScheme] { themes.map(\.scheme) }
|
var colorSchemes: [ColorScheme] { themes.map(\.scheme) }
|
||||||
|
var contentSizeCategory: [ContentSizeCategory] {
|
||||||
|
let categories = contentSize.compactMap { ContentSizeCategory($0) }
|
||||||
|
return categories.isEmpty ? [.medium] : categories
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UICatalog {
|
extension UICatalog {
|
||||||
@available(iOS 13, *)
|
@available(iOS 14, *)
|
||||||
public struct PreviewDescriptor: Identifiable, Hashable {
|
public struct PreviewDescriptor: Identifiable, Hashable {
|
||||||
let builder: () -> AnyView
|
let builder: () -> AnyView
|
||||||
public let id: String
|
public let id: String // swiftlint:disable:this identifier_name
|
||||||
public let title: String
|
public let title: String
|
||||||
public var preview: Preview { Preview(builder(), title: title) }
|
public var preview: Preview { Preview(builder(), title: title) }
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ extension UICatalog {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13, *)
|
@available(iOS 14, *)
|
||||||
public extension UICatalog.PreviewDescriptor {
|
public extension UICatalog.PreviewDescriptor {
|
||||||
init<Content>(_ content: Content.Type,
|
init<Content>(_ content: Content.Type,
|
||||||
configuration: UICatalog.PreviewConfiguration = .init(),
|
configuration: UICatalog.PreviewConfiguration = .init(),
|
||||||
|
@ -33,13 +33,11 @@ public extension UICatalog.PreviewDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ content: UICatalog.PreviewDescriptor...,
|
init(_ content: UICatalog.PreviewDescriptor...,
|
||||||
configuration: UICatalog.PreviewConfiguration = .init(),
|
|
||||||
title: String? = nil) {
|
title: String? = nil) {
|
||||||
self.init(content, configuration: configuration, title: title)
|
self.init(content, title: title)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ content: [UICatalog.PreviewDescriptor],
|
init(_ content: [UICatalog.PreviewDescriptor],
|
||||||
configuration: UICatalog.PreviewConfiguration = .init(),
|
|
||||||
title: String? = nil) {
|
title: String? = nil) {
|
||||||
id = content.map(\.id).joined()
|
id = content.map(\.id).joined()
|
||||||
self.title = title ?? content.map(\.title).joined(separator: " ")
|
self.title = title ?? content.map(\.title).joined(separator: " ")
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
#if canImport(SwiftUI)
|
||||||
|
import SwiftUI
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
|
struct PreviewLegend: View {
|
||||||
|
@State var isExpanded: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Button(action: {
|
||||||
|
isExpanded.toggle()
|
||||||
|
}, label: {
|
||||||
|
Label("Legend",
|
||||||
|
systemImage: isExpanded
|
||||||
|
? "chevron.up"
|
||||||
|
: "chevron.down")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
})
|
||||||
|
if isExpanded {
|
||||||
|
PreviewLegendBody()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
.cornerRadius(6)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
|
private struct PreviewLegendBody: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Divider()
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
Text("Theme: ")
|
||||||
|
.font(.headline)
|
||||||
|
.padding()
|
||||||
|
ForEach(values: UICatalog.Theme.allCases) { theme in
|
||||||
|
Label {
|
||||||
|
Text(theme.legend)
|
||||||
|
} icon: {
|
||||||
|
Image(systemName: theme.scheme.systemImageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text("Content size: ")
|
||||||
|
.font(.headline)
|
||||||
|
.padding()
|
||||||
|
ForEach(values: ContentSizeCategory.allCases) { category in
|
||||||
|
Label {
|
||||||
|
Text(String(describing: category))
|
||||||
|
} icon: {
|
||||||
|
Image(systemName: category.systemImageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 14, *)
|
||||||
|
struct PreviewLegend_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
PreviewLegend()
|
||||||
|
.previewLayout(.sizeThatFits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UICatalog.Theme {
|
||||||
|
var legend: String {
|
||||||
|
switch self {
|
||||||
|
case .light: return "Light theme"
|
||||||
|
case .dark: return "Dark theme"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13, *)
|
||||||
|
extension ContentSizeCategory {
|
||||||
|
var systemImageName: String {
|
||||||
|
switch self {
|
||||||
|
case .accessibilityExtraExtraExtraLarge: return "7.circle.fill"
|
||||||
|
case .accessibilityExtraExtraLarge: return "4.circle.fill"
|
||||||
|
case .extraSmall: return "1.circle"
|
||||||
|
case .small: return "2.circle"
|
||||||
|
case .medium: return "3.circle"
|
||||||
|
case .large: return "4.circle"
|
||||||
|
case .extraLarge: return "5.circle"
|
||||||
|
case .extraExtraLarge: return "6.circle"
|
||||||
|
case .extraExtraExtraLarge: return "7.circle"
|
||||||
|
case .accessibilityMedium: return "3.circle.fill"
|
||||||
|
case .accessibilityLarge: return "4.circle.fill"
|
||||||
|
case .accessibilityExtraLarge: return "5.circle.fill"
|
||||||
|
@unknown default:
|
||||||
|
return "questionmark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,6 @@ public protocol UICatalogPresentable {
|
||||||
|
|
||||||
public typealias UIViewCatalogPresentable = UIView & UICatalogPresentable
|
public typealias UIViewCatalogPresentable = UIView & UICatalogPresentable
|
||||||
|
|
||||||
|
|
||||||
@available(iOS 13, *)
|
@available(iOS 13, *)
|
||||||
extension UICatalogPresentable where Self: UIView {
|
extension UICatalogPresentable where Self: UIView {
|
||||||
public func preview(with model: PreviewModel) -> some View {
|
public func preview(with model: PreviewModel) -> some View {
|
||||||
|
@ -27,7 +26,6 @@ extension UICatalogPresentable where Self: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extension UICatalogPresentable where Self: UIView {
|
extension UICatalogPresentable where Self: UIView {
|
||||||
public static func makePreviewInstance() -> Self { self.init() }
|
public static func makePreviewInstance() -> Self { self.init() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ public struct UIViewWrapper<ContentView: UIView>: UIViewRepresentable {
|
||||||
let contextBuilder: () -> ViewCoordinator<ContentView>
|
let contextBuilder: () -> ViewCoordinator<ContentView>
|
||||||
|
|
||||||
public init(_ builder: @autoclosure @escaping () -> ContentView,
|
public init(_ builder: @autoclosure @escaping () -> ContentView,
|
||||||
update: @escaping (ContentView) -> Void = { _ in }) {
|
update: @escaping (ContentView) -> Void = { _ in }) {
|
||||||
contextBuilder = {
|
contextBuilder = {
|
||||||
ViewCoordinator(build: builder, update: update)
|
ViewCoordinator(build: builder, update: update)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue