Skip to content

Commit a58b31d

Browse files
Add new DSL for more powerful and declarative previews
1 parent 46b34b3 commit a58b31d

7 files changed

+303
-171
lines changed

README.md

+80-42
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,157 @@
11
# PreviewView
22

3-
Make use of SwiftUI previews for rapidly protyping your UIViewControllers and UIViews!
3+
Make use of SwiftUI previews for rapidly protyping your `UIViewControllers` and `UIViews`!
44

5-
### Previewing a `UIView`
5+
## Important Note
6+
7+
The SwiftUI preview canvas is tied to a specific version of Xcode, not the the target OS version. This means you can make use of this utility even if you're not targetting iOS 13 or higher, as long as you're using Xcode 10 or higher.
8+
9+
If your application targets earlier than iOS 13 you will need to do is mark your `PreviewProvider` structs with an `@available(iOS 13, *)` attribute to ensure the app can still compile.
10+
11+
## Installation
12+
13+
You can manually drop the files into your project, or take a look at Apple's documentation for [adding Swift Packages in Xcode](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).
14+
15+
Adding this as a dependency on a Swift Package is **not** recommended as it will then force the dependency on anyone that consumes your library.
16+
17+
## Previewing a view
618

719
```swift
820
struct YourViewController_Previews: PreviewProvider {
921
static var previews: some View {
10-
Preview(for: YourView())
22+
ViewPreview(YourView())
1123
.previewLayout(.fixed(width: 375, height: 86))
1224
}
1325
}
1426
```
1527

16-
Update the `previewLayout` values to be the typical size of your view.
28+
**Important:** Update the `previewLayout` values to be the typical size of your view.
29+
30+
## Previewing a view controller
1731

18-
### Previewing a `UIViewController`
32+
### Standalone
1933

2034
```swift
2135
struct YourViewController_Previews: PreviewProvider {
2236
static var previews: some View {
23-
Preview(for: YourViewController())
37+
ViewControllerPreview(YourViewController())
38+
.edgesIgnoringSafeArea(.all)
2439
}
2540
}
2641
```
2742

28-
### Previewing a `UIViewController` embedded in a `UINavigationController`
29-
30-
#### With a standard navigation bar
43+
If you wish to test a custom `UINavigationController` you can do so with `ViewControllerPreview`.
3144

3245
```swift
33-
struct YourViewController_Previews: PreviewProvider {
46+
struct YourNavigationController_Previews: PreviewProvider {
3447
static var previews: some View {
35-
Preview(navigationControllerFor: YourViewController())
48+
ViewControllerPreview(YourNavigationController())
49+
.edgesIgnoringSafeArea(.all)
3650
}
3751
}
3852
```
3953

40-
#### With a large title navigation bar
54+
### Embedded in a `UINavigationController`
4155

4256
```swift
4357
struct YourViewController_Previews: PreviewProvider {
4458
static var previews: some View {
45-
Preview(navigationControllerFor: YourViewController(), withNavigationBarStyle: .largeTitle)
59+
NavigationControllerPreview {
60+
YourViewController()
61+
}
62+
.edgesIgnoringSafeArea(.all)
4663
}
4764
}
4865
```
4966

50-
#### Without a navigation bar
51-
52-
Helpful when you may be using Live Preview and any other view controllers that are pushed from your view controller require a navigation bar.
67+
The body content of the `NavigationControllerPreview` accepts an entire navigation stack, allowing your previews to show back bar button items, and even be navigatable in Live Preview.
5368

5469
```swift
55-
struct YourViewController_Previews: PreviewProvider {
70+
struct DetailViewController_Previews: PreviewProvider {
5671
static var previews: some View {
57-
Preview(navigationControllerFor: YourViewController(), withNavigationBarStyle: .none)
72+
NavigationControllerPreview {
73+
ListViewController()
74+
DetailViewController()
75+
}
76+
.edgesIgnoringSafeArea(.all)
5877
}
5978
}
6079
```
6180

62-
### Previewing a `UIViewController` embedded in a `UITabBarController`
81+
You can customise the navigation bar settings and toolbar settings of the navigation controller via the initializer parameters.
6382

6483
```swift
65-
struct YourViewController_Previews: PreviewProvider {
84+
struct DetailViewController_Previews: PreviewProvider {
6685
static var previews: some View {
67-
Preview(tabBarControllerFor: YourViewController())
86+
NavigationControllerPreview(barStyle: .largeTitle, showsToolbar: true) {
87+
ListViewController()
88+
DetailViewController()
89+
}
90+
.edgesIgnoringSafeArea(.all)
6891
}
6992
}
7093
```
7194

72-
You can also have the preview show the tab bar with other tab items <sup>1</sup>. By default your supplied view controller will be shown in the first position of the tab bar.
95+
### Embedded in a `UITabBarController`
7396

7497
```swift
7598
struct YourViewController_Previews: PreviewProvider {
76-
static let secondTabBarItem = UITabBarItem(title: "First",
77-
image: UIImage(systemName: "capsule"),
78-
selectedImage: UIImage(systemName: "capsule.fill"))
79-
80-
static let thirdTabBarItem = UITabBarItem(title: "Third",
81-
image: UIImage(systemName: "diamond"),
82-
selectedImage: UIImage(systemName: "diamond.fill"))
83-
8499
static var previews: some View {
85-
Preview(tabBarControllerFor: YourViewController(), withOtherTabs: secondTabBarItem, thirdTabBarItem)
100+
TabBarControllerPreview {
101+
ViewControllerPreview(YourViewController())
102+
}
103+
.edgesIgnoringSafeArea(.all)
86104
}
87105
}
88106
```
89107

90-
You can have it shown at any other position by providing a position index parameter. The following code the view controller will be shown in the second position.
108+
Displaying a single tab would be weird, so to allow your previews to closely match your real app you can provide the other tabs within the body.
91109

92110
```swift
93111
struct YourViewController_Previews: PreviewProvider {
94-
static let firstTabBarItem = UITabBarItem(title: "First",
95-
image: UIImage(systemName: "capsule"),
96-
selectedImage: UIImage(systemName: "capsule.fill"))
112+
static var previews: some View {
113+
TabBarControllerPreview {
114+
PreviewBlankTabItem(title: "First", symbolNamed: "capsule")
115+
116+
ViewControllerPreview(YourViewController())
117+
118+
ViewControllerPreview(YourOtherViewController())
119+
}
120+
.edgesIgnoringSafeArea(.all)
121+
}
122+
}
123+
```
97124

98-
static let thirdTabBarItem = UITabBarItem(title: "Third",
99-
image: UIImage(systemName: "diamond"),
100-
selectedImage: UIImage(systemName: "diamond.fill"))
125+
You can even embed your view controllers in a navigation controller to get the full in-app experience.
101126

127+
```swift
128+
struct YourViewController_Previews: PreviewProvider {
102129
static var previews: some View {
103-
Preview(tabBarControllerFor: YourViewController(), atPosition: 1, withOtherTabs: firstTabBarItem, thirdTabBarItem)
130+
TabBarControllerPreview {
131+
PreviewBlankTabItem(title: "First", symbolNamed: "capsule")
132+
133+
NavigationControllerPreview {
134+
YourViewController()
135+
}
136+
137+
PreviewBlankTabItem(title: "Third", symbolNamed: "diamond")
138+
}
139+
.edgesIgnoringSafeArea(.all)
104140
}
105141
}
106142
```
107143

108-
### Pro Tip
144+
## Pro Tip
109145

110146
Did you know that you can easily create multiple previews with a simple `ForEach` loop?
111147

112148
```swift
113149
struct YourViewController_Previews: PreviewProvider {
150+
static let supportedDevices = ["iPhone 11", "iPhone 8", "iPhone SE (1st generation)"]
151+
114152
static var previews: some View {
115-
ForEach(["iPhone 11", "iPhone 8", "iPhone SE (1st generation)"], id: \.self) { deviceName in
116-
Preview(for: YourViewController())
153+
ForEach(Self.supportedDevices, id: \.self) { deviceName in
154+
ViewControllerPreview(YourViewController())
117155
.previewDevice(PreviewDevice(rawValue: deviceName))
118156
.previewDisplayName(deviceName)
119157
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// NavigationControllerPreview.swift
3+
// PreviewView
4+
//
5+
// Created by Josh Asbury on 7/8/21.
6+
//
7+
8+
import SwiftUI
9+
10+
/// A custom parameter attribute that constructs a navigation stack from a closure.
11+
///
12+
/// This is used by ``NavigationControllerPreview`` to provide a navigation stack. The last element in the navigation stack will be shown in the preview.
13+
@resultBuilder public enum PreviewNavigationStackBuilder {
14+
public static func buildBlock(_ viewControllers: UIViewController...) -> [UIViewController] { viewControllers }
15+
}
16+
17+
/// A type that can be used to preview in Xcode a `UIViewController` embedded within a `UINavigationController`.
18+
///
19+
/// If you wish to preview a custom `UINavigationController` you can use ``ViewControllerPreview``.
20+
///
21+
/// - SeeAlso: ``ViewControllerPreview``
22+
/// - SeeAlso: ``TabBarControllerPreview``
23+
public struct NavigationControllerPreview: UIViewControllerRepresentable {
24+
/// A style for displaying the navigation bar of this navigation controller.
25+
public enum NavigationBarStyle {
26+
/// Hides the navigation bar.
27+
case none
28+
29+
/// Defers to the active view controller's navigation item for how the navigation bar should behave.
30+
case `default`
31+
32+
/// Display a large title within an expanded navigation bar.
33+
case largeTitle
34+
}
35+
36+
/// The navigation controller that is being previewed.
37+
public let navigationController: UINavigationController
38+
39+
/// Creates a navigation controller preview that displays a view controller with the given bar styles.
40+
///
41+
/// - Parameters:
42+
/// - barStyle: The style to be applied to this navigation controller's `UINavigationBar`. The default value is ``NavigationBarStyle/default``.
43+
/// - showsToolbar: Whether the navigation controller should show its `UIToolbar`. The default value is `false`.
44+
/// - content: The view controllers of the navigation controller.
45+
/// - Returns: The initialized preview object.
46+
public init(barStyle: NavigationBarStyle = .default, showsToolbar: Bool = false, @PreviewNavigationStackBuilder _ content: () -> [UIViewController]) {
47+
let navigationController = UINavigationController()
48+
navigationController.viewControllers = content()
49+
switch barStyle {
50+
case .default: break
51+
case .none: navigationController.isNavigationBarHidden = true
52+
case .largeTitle: navigationController.navigationBar.prefersLargeTitles = true
53+
}
54+
navigationController.isToolbarHidden = !showsToolbar
55+
self.navigationController = navigationController
56+
}
57+
58+
public func makeUIViewController(context: Context) -> some UIViewController { navigationController }
59+
public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
60+
}

Sources/PreviewView/Preview.swift

-129
This file was deleted.

0 commit comments

Comments
 (0)