Skip to content

Commit 6e66be3

Browse files
committed
Implement PreviewGroup
1 parent c984957 commit 6e66be3

File tree

5 files changed

+198
-14
lines changed

5 files changed

+198
-14
lines changed

Classes/Structs/LivePreview.swift

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@ public class Preview {
153153
semanticContentAttribute = v ? .forceRightToLeft : .forceLeftToRight
154154
return self
155155
}
156+
157+
fileprivate var liveView: some SwiftUI.View {
158+
LiveView(view)
159+
.preferredColorScheme(colorScheme.val)
160+
.previewLayout(layout)
161+
.previewDisplayName(title)
162+
.edgesIgnoringSafeArea(.all)
163+
.previewDevice(device.name == "none" ? nil : PreviewDevice(rawValue: device.name))
164+
}
156165
}
157166

158167
public protocol DeclarativePreview: SwiftUI.PreviewProvider {
@@ -165,12 +174,52 @@ extension DeclarativePreview {
165174
public static var previews: some SwiftUI.View {
166175
Localization.current = preview.language
167176
UIView.appearance().semanticContentAttribute = preview.semanticContentAttribute
168-
return LiveView(preview.view)
169-
.preferredColorScheme(preview.colorScheme.val)
170-
.previewLayout(preview.layout)
171-
.previewDisplayName(preview.title)
172-
.edgesIgnoringSafeArea(.all)
173-
.previewDevice(preview.device.name == "none" ? nil : PreviewDevice(rawValue: preview.device.name))
177+
return preview.liveView
178+
}
179+
}
180+
181+
public protocol DeclarativePreviewGroup: SwiftUI.PreviewProvider {
182+
@available(iOS 13.0, *)
183+
static var previewGroup: PreviewGroup { get }
184+
}
185+
186+
extension DeclarativePreviewGroup {
187+
@available(iOS 13.0, *)
188+
public static var previews: some SwiftUI.View {
189+
Localization.current = previewGroup.language
190+
UIView.appearance().semanticContentAttribute = previewGroup.semanticContentAttribute
191+
return SwiftUI.Group {
192+
if previewGroup.previews.count > 0 {
193+
previewGroup.previews[0].liveView
194+
}
195+
if previewGroup.previews.count > 1 {
196+
previewGroup.previews[1].liveView
197+
}
198+
if previewGroup.previews.count > 2 {
199+
previewGroup.previews[2].liveView
200+
}
201+
if previewGroup.previews.count > 3 {
202+
previewGroup.previews[3].liveView
203+
}
204+
if previewGroup.previews.count > 4 {
205+
previewGroup.previews[4].liveView
206+
}
207+
if previewGroup.previews.count > 5 {
208+
previewGroup.previews[5].liveView
209+
}
210+
if previewGroup.previews.count > 6 {
211+
previewGroup.previews[6].liveView
212+
}
213+
if previewGroup.previews.count > 7 {
214+
previewGroup.previews[7].liveView
215+
}
216+
if previewGroup.previews.count > 8 {
217+
previewGroup.previews[8].liveView
218+
}
219+
if previewGroup.previews.count > 9 {
220+
previewGroup.previews[9].liveView
221+
}
222+
}
174223
}
175224
}
176225
#endif
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import UIKit
2+
3+
@available(iOS 13.0, *)
4+
public class PreviewGroup {
5+
let previews: [Preview]
6+
var language: Language = Localization().detectCurrentLanguage()
7+
var semanticContentAttribute: UISemanticContentAttribute = .unspecified
8+
9+
public init (@PreviewBuilder block: PreviewBuilder.Block) {
10+
previews = block().previewBuilderItems
11+
}
12+
13+
@discardableResult
14+
public func language(_ v: Language) -> Self {
15+
language = v
16+
return self
17+
}
18+
19+
@discardableResult
20+
public func rtl(_ v: Bool) -> Self {
21+
semanticContentAttribute = v ? .forceRightToLeft : .forceLeftToRight
22+
return self
23+
}
24+
}
25+
26+
@available(iOS 13.0, *)
27+
@_functionBuilder public struct PreviewBuilder {
28+
public typealias Block = () -> PreviewBuilderItem
29+
30+
/// Builds an empty preview from an block containing no statements, `{ }`.
31+
public static func buildBlock() -> PreviewBuilderItem { [] }
32+
33+
/// Passes a single preview written as a child view (e..g, `{ Text("Hello") }`) through unmodified.
34+
public static func buildBlock(_ attrs: PreviewBuilderItem...) -> PreviewBuilderItem {
35+
buildBlock(attrs)
36+
}
37+
38+
/// Passes a single preview written as a child view (e..g, `{ Text("Hello") }`) through unmodified.
39+
public static func buildBlock(_ attrs: [PreviewBuilderItem]) -> PreviewBuilderItem {
40+
PreviewBuilderItems(items: attrs.flatMap { $0.previewBuilderItems })
41+
}
42+
43+
/// Provides support for "if" statements in multi-statement closures, producing an `Optional` preview
44+
/// that is visible only when the `if` condition evaluates `true`.
45+
public static func buildIf(_ content: PreviewBuilderItem?) -> PreviewBuilderItem {
46+
guard let content = content else { return [] }
47+
return content
48+
}
49+
50+
/// Provides support for "if" statements in multi-statement closures, producing
51+
/// ConditionalContent for the "then" branch.
52+
public static func buildEither(first: PreviewBuilderItem) -> PreviewBuilderItem {
53+
first
54+
}
55+
56+
/// Provides support for "if-else" statements in multi-statement closures, producing
57+
/// ConditionalContent for the "else" branch.
58+
public static func buildEither(second: PreviewBuilderItem) -> PreviewBuilderItem {
59+
second
60+
}
61+
}
62+
63+
@available(iOS 13.0, *)
64+
extension Array: PreviewBuilderItem where Element: Preview {
65+
public var previewBuilderItems: [Preview] { self }
66+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import UIKit
2+
3+
@available(iOS 13.0, *)
4+
public protocol PreviewBuilderItem {
5+
var previewBuilderItems: [Preview] { get }
6+
}
7+
8+
@available(iOS 13.0, *)
9+
public struct PreviewBuilderItems: PreviewBuilderItem {
10+
public let items: [Preview]
11+
12+
public init (items: [Preview]) {
13+
self.items = items
14+
}
15+
16+
public var previewBuilderItems: [Preview] { items }
17+
}
18+
19+
@available(iOS 13.0, *)
20+
extension Preview: PreviewBuilderItem {
21+
public var previewBuilderItems: [Preview] { [self] }
22+
}

README.md

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Good mood
4040

4141
Add the following line to your Podfile:
4242
```ruby
43-
pod 'UIKit-Plus', '~> 1.19.0'
43+
pod 'UIKit-Plus', '~> 1.20.0'
4444
```
4545

4646
#### With [Swift Package Manager](https://swift.org/package-manager/)
@@ -165,21 +165,32 @@ Live preview provided by SwiftUI (available only since macOS Catalina).
165165

166166
> The only problem we have is that since names of views are the same in `UIKitPlus` and `SwiftUI` we should use aliases like `UButton` for `Button` or `UView` for `View`, so everything with `U` prefix. It is only necessary if you want to use live previews, otherwise there is no need to import `SwiftUI`, so no name conflicts.
167167
168+
#### Preview single item
169+
170+
> 💡 You can create as many preview structs as you need
171+
172+
`ViewController` example
173+
168174
```swift
169175
#if canImport(SwiftUI)
170176
import SwiftUI
171177
@available(iOS 13.0, *)
172178
struct MyViewController_Preview: PreviewProvider, DeclarativePreview {
173-
Preview {
174-
MainViewController()
179+
static var preview: Preview {
180+
Preview {
181+
MainViewController()
182+
}
183+
.colorScheme(.dark)
184+
.device(.iPhoneX)
185+
.language(.fr)
186+
.rtl(true)
175187
}
176-
.colorScheme(.dark)
177-
.device(.iPhoneX)
178-
.language(.fr)
179-
.rtl(true)
180188
}
181189
#endif
182190
```
191+
192+
`View` example
193+
183194
```swift
184195
#if canImport(SwiftUI)
185196
import SwiftUI
@@ -204,6 +215,42 @@ struct MyButton_Preview: PreviewProvider, DeclarativePreview {
204215
#endif
205216
```
206217

218+
#### Preview group 🔥
219+
220+
It is just convenience way to create multiple previews inside one struct, but it has limitations related to `rtl` and `language` properties which should be set to whole group
221+
222+
```swift
223+
#if canImport(SwiftUI)
224+
import SwiftUI
225+
@available(iOS 13.0, *)
226+
struct MyViewController_Preview: PreviewProvider, DeclarativePreviewGroup {
227+
static var previewGroup: PreviewGroup {
228+
PreviewGroup { // 1 to 10 previews inside
229+
Preview {
230+
MainViewController()
231+
}
232+
.colorScheme(.dark)
233+
.device(.iPhoneX)
234+
Preview {
235+
MainViewController()
236+
}
237+
.colorScheme(.light)
238+
.device(.iPhoneX)
239+
UButton(String(.en("Hello"), .fr("Bonjour"), .ru("Привет"))) // in this group title will be shown in `fr`
240+
.circle()
241+
.background(.blackHole / .white)
242+
.color(.white / .black)
243+
.height(54)
244+
.edgesToSuperview(h: 8)
245+
.centerYInSuperview()
246+
}
247+
.language(.fr) // limited to group
248+
.rtl(true) // limited to group
249+
}
250+
}
251+
#endif
252+
```
253+
207254
## Usage
208255

209256
```swift

UIKit-Plus.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Pod::Spec.new do |s|
1010
s.name = 'UIKit-Plus'
1111
s.module_name = 'UIKitPlus'
12-
s.version = '1.19.0'
12+
s.version = '1.20.0'
1313
s.summary = '🏰 Declarative UIKit wrapper inspired by SwiftUI'
1414

1515
s.swift_version = '5.2'

0 commit comments

Comments
 (0)