Skip to content

Commit e8b8775

Browse files
authored
Merge pull request #17 from shapehq/feature/custom-views
Adds support for inlining and presenting custom views
2 parents e927455 + d1ada1a commit e8b8775

File tree

8 files changed

+320
-3
lines changed

8 files changed

+320
-3
lines changed

Examples/SwiftUIExample/AppSpiceStore.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import Spices
3+
import SwiftUI
34

45
enum ServiceEnvironment: String, CaseIterable {
56
case production
@@ -13,6 +14,14 @@ final class AppSpiceStore: SpiceStore {
1314
@Spice(presentation: .inline) var debugging = DebuggingSpiceStore()
1415

1516
@Spice var featureFlags = FeatureFlagsSpiceStore()
17+
@Spice(presentation: .push) var helloWorld = VStack {
18+
Image(systemName: "globe")
19+
.imageScale(.large)
20+
.foregroundStyle(.tint)
21+
Text("Hello, world!")
22+
}
23+
.padding()
24+
@Spice var version = LabeledContent("Version", value: "1.0 (1)")
1625
}
1726

1827
final class DebuggingSpiceStore: SpiceStore {

Examples/UIKitExample/AppSpiceStore.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import Spices
3+
import SwiftUI
34

45
enum ServiceEnvironment: String, CaseIterable {
56
case production
@@ -16,6 +17,14 @@ final class AppSpiceStore: SpiceStore {
1617
@Spice(presentation: .inline) var debugging = DebuggingSpiceStore()
1718

1819
@Spice var featureFlags = FeatureFlagsSpiceStore()
20+
@Spice(presentation: .push) var helloWorld = VStack {
21+
Image(systemName: "globe")
22+
.imageScale(.large)
23+
.foregroundStyle(.tint)
24+
Text("Hello, world!")
25+
}
26+
.padding()
27+
@Spice var version = LabeledContent("Version", value: "1.0 (1)")
1928

2029
private init() {}
2130
}

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
- [Buttons](#buttons)
2929
- [Text Fields](#text-fields)
3030
- [Group Settings Using Nested Spice Stores](#group-settings-using-nested-spice-stores)
31+
- [Inject Your Own Views](#inject-your-own-views)
3132
- [Require Restart](#require-restart)
3233
- [Display Custom Name](#display-custom-name)
3334
- [Specify Editor Title](#specify-editor-title)
@@ -349,6 +350,53 @@ When inlining a nested spice store, a header and footer can be provided for bett
349350
var featureFlags = FeatureFlagsSpiceStore()
350351
```
351352

353+
### Inject Your Own Views
354+
355+
You can embed your own views into Spices, for example, to display static information.
356+
357+
The `@Spice` property wrapper allows you to define custom views within Spices settings. These views can be inlined by default or presented using different styles.
358+
359+
By default, views are inlined within the settings list:
360+
361+
```swift
362+
@Spice var version = LabeledContent("Version", value: "1.0 (1)")
363+
```
364+
365+
You can change the presentation style using the presentation argument.
366+
367+
The `.push` presentation pushes the view onto the navigation stack.
368+
369+
```swift
370+
@Spice(presentation: .push) var helloWorld = VStack {
371+
Image(systemName: "globe")
372+
.imageScale(.large)
373+
.foregroundStyle(.tint)
374+
Text("Hello, world!")
375+
}
376+
.padding()
377+
```
378+
379+
The `.modal` presentation presents the view modally on top of Spices.
380+
381+
```swift
382+
@Spice(presentation: .modal) var helloWorld = // ...
383+
```
384+
385+
### Nest Spice Stores
386+
387+
Spice stores can be nested to create a hierarchical user interface.
388+
389+
```swift
390+
class AppSpiceStore: SpiceStore {
391+
@Spice var featureFlags = FeatureFlagsSpiceStore()
392+
}
393+
394+
class FeatureFlagsSpiceStore: SpiceStore {
395+
@Spice var notifications = false
396+
@Spice var fastRefreshWidgets = false
397+
}
398+
```
399+
352400
### Require Restart
353401

354402
Setting `requiresRestart` to true will cause the app to be shut down after changing the value. Use this only when necessary, as users do not expect a restart.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
final class ViewMenuItem: MenuItem {
5+
enum PresentationStyle {
6+
case modal
7+
case push
8+
case inline
9+
}
10+
11+
let id = UUID().uuidString
12+
let name: Name
13+
let presentationStyle: PresentationStyle
14+
let content: AnyView
15+
16+
init(name: Name, presentationStyle: PresentationStyle, content: AnyView) {
17+
self.name = name
18+
self.presentationStyle = presentationStyle
19+
self.content = content
20+
}
21+
}

Sources/Spices/Internal/Views/MenuItemView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ struct MenuItemView: View {
1717
AsyncButtonMenuItemView(menuItem: menuItem)
1818
} else if let menuItem = menuItem as? ChildSpiceStoreMenuItem {
1919
ChildSpiceStoreMenuItemView(menuItem: menuItem, dismiss: dismiss)
20+
} else if let menuItem = menuItem as? ViewMenuItem {
21+
ViewMenuItemView(menuItem: menuItem)
2022
} else {
2123
fatalError("Unknown menu item of type \(type(of: menuItem))")
2224
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import SwiftUI
2+
3+
struct ViewMenuItemView: View {
4+
let menuItem: ViewMenuItem
5+
6+
var body: some View {
7+
switch menuItem.presentationStyle {
8+
case .modal:
9+
ModalPresentationView(menuItem.name.rawValue, content: menuItem.content)
10+
case .push:
11+
NavigationLink(menuItem.name.rawValue, destination: menuItem.content)
12+
case .inline:
13+
menuItem.content
14+
}
15+
}
16+
}
17+
18+
private extension ViewMenuItemView {
19+
struct ModalPresentationView<Content: View>: View {
20+
private let title: String
21+
private let content: Content
22+
@State private var isModalPresented = false
23+
24+
init(_ title: String, content: Content) {
25+
self.title = title
26+
self.content = content
27+
}
28+
29+
var body: some View {
30+
Button {
31+
isModalPresented = true
32+
} label: {
33+
Text(title)
34+
}
35+
.sheet(isPresented: $isModalPresented) {
36+
content
37+
}
38+
}
39+
}
40+
}

Sources/Spices/PresentationStyle.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// A type that represents different styles for presenting a view within Spices settings.
1+
/// A type that represents different styles for presenting a view within the in-app debug menu.
22
public protocol PresentationStyle {}
33

44
/// A presentation style that displays the view modally on top of the Spices settings.
@@ -25,6 +25,17 @@ public extension PresentationStyle {
2525
///
2626
/// ```swift
2727
/// @Spice(presentation: .modal) var featureFlags = FeatureFlagsSpiceStore()
28+
///
29+
/// The presentation style can also be used to present a view modally.
30+
///
31+
/// ```swift
32+
/// @Spice(presentation: .modal) var helloWorld = VStack {
33+
/// Image(systemName: "globe")
34+
/// .imageScale(.large)
35+
/// .foregroundStyle(.tint)
36+
/// Text("Hello, world!")
37+
/// }
38+
/// .padding()
2839
/// ```
2940
static var modal: ModalPresentationStyle {
3041
ModalPresentationStyle()
@@ -38,6 +49,17 @@ public extension PresentationStyle {
3849
///
3950
/// ```swift
4051
/// @Spice(presentation: .push) var featureFlags = FeatureFlagsSpiceStore()
52+
///
53+
/// The presentation style can also be used to push a view onto the navigation stack.
54+
///
55+
/// ```swift
56+
/// @Spice(presentation: .push) var helloWorld = VStack {
57+
/// Image(systemName: "globe")
58+
/// .imageScale(.large)
59+
/// .foregroundStyle(.tint)
60+
/// Text("Hello, world!")
61+
/// }
62+
/// .padding()
4163
/// ```
4264
static var push: PushPresentationStyle {
4365
PushPresentationStyle()
@@ -51,6 +73,11 @@ public extension PresentationStyle {
5173
///
5274
/// ```swift
5375
/// @Spice(presentation: .inline) var featureFlags = FeatureFlagsSpiceStore()
76+
///
77+
/// The presentation style can also be used to inline a view.
78+
///
79+
/// ```swift
80+
/// @Spice var version = LabeledContent("Version", value: "1.0 (1)")
5481
/// ```
5582
static var inline: InlinePresentationStyle {
5683
InlinePresentationStyle()

0 commit comments

Comments
 (0)