├── .github
└── FUNDING.yml
├── Examples
└── Demo
│ ├── Media
│ ├── bill-murray.jpeg
│ └── big-buck-bunny.mp4
│ ├── Shared
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── murray.imageset
│ │ │ ├── asset-bill-murray.jpeg
│ │ │ └── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── DemoApp.swift
│ └── ContentView.swift
│ ├── Demo.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
│ ├── macOS
│ └── macOS.entitlements
│ ├── Tests iOS
│ ├── Tests_iOSLaunchTests.swift
│ └── Tests_iOS.swift
│ └── Tests macOS
│ ├── Tests_macOSLaunchTests.swift
│ └── Tests_macOS.swift
├── Sources
└── DeckUI
│ ├── DeckUI.swift
│ ├── Syntax Highlight
│ ├── Grammar
│ │ └── NoGrammar.swift
│ ├── CodeComponent.swift
│ ├── ProgrammingLanguage.swift
│ ├── CodeComponentFormat.swift
│ └── CodeTheme.swift
│ ├── DSL
│ ├── Deck.swift
│ ├── ContentItems
│ │ ├── RawView.swift
│ │ ├── Title.swift
│ │ ├── Words.swift
│ │ ├── Media.swift
│ │ ├── Bullets.swift
│ │ ├── Columns.swift
│ │ └── Code.swift
│ ├── ContentItem.swift
│ ├── Slide.swift
│ └── Theme.swift
│ ├── Views
│ ├── PresenterNotesView.swift
│ ├── SlideNavigation.swift
│ ├── CameraView.swift
│ └── Presenter.swift
│ ├── Deprecations.swift
│ ├── Compatibility.swift
│ └── PresentationState.swift
├── .gitignore
├── DeckUI.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── swiftpm
│ └── Package.resolved
├── Package.resolved
├── Tests
└── DeckUITests
│ └── DeckUITests.swift
├── LICENSE
├── Package.swift
├── CLAUDE.md
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [joshdholtz]
4 |
--------------------------------------------------------------------------------
/Examples/Demo/Media/bill-murray.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshdholtz/DeckUI/HEAD/Examples/Demo/Media/bill-murray.jpeg
--------------------------------------------------------------------------------
/Examples/Demo/Media/big-buck-bunny.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshdholtz/DeckUI/HEAD/Examples/Demo/Media/big-buck-bunny.mp4
--------------------------------------------------------------------------------
/Examples/Demo/Shared/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DeckUI.swift:
--------------------------------------------------------------------------------
1 | public struct DeckUI {
2 | public private(set) var text = "Hello, World!"
3 |
4 | public init() {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Examples/Demo/Shared/Assets.xcassets/murray.imageset/asset-bill-murray.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshdholtz/DeckUI/HEAD/Examples/Demo/Shared/Assets.xcassets/murray.imageset/asset-bill-murray.jpeg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/DeckUI.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/Demo/Shared/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/Demo/Shared/Assets.xcassets/murray.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "asset-bill-murray.jpeg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DeckUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "splash",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/johnsundell/Splash.git",
7 | "state" : {
8 | "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8",
9 | "version" : "0.16.0"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Syntax Highlight/Grammar/NoGrammar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoGrammar.swift
3 | // DeckUI
4 | //
5 | // Created by Yonatan Mittlefehldt on 2022-09-11.
6 | //
7 |
8 | import Foundation
9 | import Splash
10 |
11 | struct NoGammar: Grammar {
12 | var delimiters: CharacterSet = CharacterSet()
13 |
14 | var syntaxRules = [Splash.SyntaxRule]()
15 | }
16 |
--------------------------------------------------------------------------------
/DeckUI.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "splash",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/johnsundell/Splash.git",
7 | "state" : {
8 | "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8",
9 | "version" : "0.16.0"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Tests/DeckUITests/DeckUITests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import DeckUI
3 |
4 | final class DeckUITests: XCTestCase {
5 | func testExample() throws {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(DeckUI().text, "Hello, World!")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/Demo/Shared/DemoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoApp.swift
3 | // Shared
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import SwiftUI
9 | import DeckUI
10 | @main
11 | struct DemoApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | #if canImport(AppKit)
17 | PresenterNotes()
18 | #endif
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/Deck.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Deck.swift
3 | // DeckUI (iOS)
4 | //
5 | // Created by Josh Holtz on 8/28/22.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct Deck {
11 | let title: String
12 | let theme: Theme
13 | @SlideArrayBuilder var slides: () -> [Slide]
14 |
15 | public init(title: String, theme: Theme = .dark, @SlideArrayBuilder slides: @escaping () -> [Slide]) {
16 | self.title = title
17 | self.theme = theme
18 | self.slides = slides
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Syntax Highlight/CodeComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CodeComponent.swift
3 | // DeckUI
4 | //
5 | // Created by Yonatan Mittlefehldt on 2022-09-09.
6 | //
7 |
8 | import Splash
9 |
10 | enum CodeComponent: Equatable, Hashable {
11 | case token(String, TokenType)
12 | case plainText(String)
13 | case whitespace(String)
14 |
15 | var isWhitespace: Bool {
16 | switch self {
17 | case .whitespace(_):
18 | return true
19 | default:
20 | return false
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItems/RawView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RawView.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct RawView: ContentItem {
11 | public let id = UUID()
12 |
13 | let content: Content
14 |
15 | public init(@ViewBuilder content: () -> Content) {
16 | self.content = content()
17 | }
18 |
19 | // TODO: Use theme
20 | public func buildView(theme: Theme) -> AnyView {
21 | AnyView(
22 | self.content
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Syntax Highlight/ProgrammingLanguage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProgrammingLanguage.swift
3 | // DeckUI
4 | //
5 | // Created by Yonatan Mittlefehldt on 2022-09-09.
6 | //
7 |
8 | import Splash
9 |
10 | public enum ProgrammingLanguage: String {
11 | case none
12 | case swift
13 |
14 | var name: String {
15 | rawValue
16 | }
17 |
18 | var grammar: Grammar {
19 | switch self {
20 | case .swift:
21 | return SwiftGrammar()
22 | case .none:
23 | return NoGammar()
24 | }
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Examples/Demo/macOS/macOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.automation.apple-events
6 |
7 | com.apple.security.device.audio-input
8 |
9 | com.apple.security.device.camera
10 |
11 | com.apple.security.personal-information.calendars
12 |
13 | com.apple.security.personal-information.location
14 |
15 | com.apple.security.personal-information.photos-library
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Examples/Demo/Tests iOS/Tests_iOSLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tests_iOSLaunchTests.swift
3 | // Tests iOS
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import XCTest
9 |
10 | class Tests_iOSLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | func testLaunch() throws {
21 | let app = XCUIApplication()
22 | app.launch()
23 |
24 | // Insert steps here to perform after app launch but before taking a screenshot,
25 | // such as logging into a test account or navigating somewhere in the app
26 |
27 | let attachment = XCTAttachment(screenshot: app.screenshot())
28 | attachment.name = "Launch Screen"
29 | attachment.lifetime = .keepAlways
30 | add(attachment)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Examples/Demo/Tests macOS/Tests_macOSLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tests_macOSLaunchTests.swift
3 | // Tests macOS
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import XCTest
9 |
10 | class Tests_macOSLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | func testLaunch() throws {
21 | let app = XCUIApplication()
22 | app.launch()
23 |
24 | // Insert steps here to perform after app launch but before taking a screenshot,
25 | // such as logging into a test account or navigating somewhere in the app
26 |
27 | let attachment = XCTAttachment(screenshot: app.screenshot())
28 | attachment.name = "Launch Screen"
29 | attachment.lifetime = .keepAlways
30 | add(attachment)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Josh Holtz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItems/Title.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Title.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct Title: ContentItem {
11 | public let id = UUID()
12 | let title: String
13 | let subtitle: String?
14 |
15 | public init(_ title: String, subtitle: String? = nil) {
16 | self.title = title
17 | self.subtitle = subtitle
18 | }
19 |
20 | // TODO: Use theme
21 | public func buildView(theme: Theme) -> AnyView {
22 | return AnyView(
23 | // TODO: Fix hardcoding of alignment
24 | VStack(alignment: .leading, spacing: 0) {
25 | Text(self.title)
26 | .font(theme.title.font)
27 | .foregroundColor(theme.title.color)
28 |
29 | if let subtitle = self.subtitle {
30 | Text(subtitle)
31 | .font(theme.subtitle.font)
32 | .foregroundColor(theme.subtitle.color)
33 | }
34 | }.padding(.bottom, 20)
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.6
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "DeckUI",
8 | platforms: [.macOS(.v12), .iOS(.v15)],
9 | products: [
10 | // Products define the executables and libraries a package produces, and make them visible to other packages.
11 | .library(
12 | name: "DeckUI",
13 | targets: ["DeckUI"]),
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | .package(url: "https://github.com/johnsundell/Splash.git", from: "0.16.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
22 | .target(
23 | name: "DeckUI",
24 | dependencies: ["Splash"]),
25 | .testTarget(
26 | name: "DeckUITests",
27 | dependencies: ["DeckUI"]),
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Views/PresenterNotesView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresenterView.swift
3 | // Demo
4 | //
5 | // Created by Zachary Brass on 1/17/23.
6 | //
7 |
8 | import SwiftUI
9 | public struct PresenterNotesView: View {
10 | @ObservedObject private var presentationState = PresentationState.shared
11 |
12 | public var body: some View {
13 | Group{
14 | Text(presentationState.deck.slides()[presentationState.slideIndex].comment ?? "No notes")
15 |
16 | }.frame(maxWidth:.infinity, maxHeight: .infinity)
17 | .background(.background)
18 | #if canImport(UIKit)
19 | .slideNavigationGestures()
20 | #endif
21 |
22 | }
23 | public init() {
24 | }
25 |
26 |
27 | }
28 | #if canImport(AppKit)
29 | @available(macOS 13.0, *)
30 | public struct PresenterNotes: Scene {
31 | public var body: some Scene {
32 | Window("Presenter Notes", id: "notes") {
33 | PresenterNotesView()
34 | .toolbar {
35 | SlideNavigationToolbarButtons()
36 | }
37 | }.keyboardShortcut("1")
38 |
39 | }
40 | public init() {
41 | }
42 | }
43 | #endif
44 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Deprecations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Deprecations.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 9/11/22.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Presenter {
11 |
12 | @available(iOS, deprecated: 1, renamed: "init(deck:slideTransition:loop:defaultResolution:showCamera:cameraConfig:)")
13 | @available(tvOS, deprecated: 1, renamed: "init(deck:slideTransition:loop:defaultResolution:showCamera:cameraConfig:)")
14 | @available(watchOS, deprecated: 1, renamed: "init(deck:slideTransition:loop:defaultResolution:showCamera:cameraConfig:)")
15 | @available(macOS, deprecated: 1, renamed: "init(deck:slideTransition:loop:defaultResolution:showCamera:cameraConfig:)")
16 | @available(macCatalyst, deprecated: 1, renamed: "init(deck:slideTransition:loop:defaultResolution:showCamera:cameraConfig:)")
17 | init(deck: Deck, slideDirection: SlideTransition, loop: Bool = false, defaultResolution: DefaultResolution = (width: 1920, height: 1080), showCamera: Bool = false, cameraConfig: CameraConfig = CameraConfig()) {
18 | self = Presenter(deck: deck, slideTransition: slideDirection, loop: loop, defaultResolution: defaultResolution, showCamera: showCamera, cameraConfig: cameraConfig)
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItems/Words.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Words.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct Words: ContentItem {
11 | public let id = UUID()
12 | let text: String
13 | let color: Color?
14 | let font: Font?
15 | let attributedText: AttributedString?
16 |
17 | public init(_ text: String, color: Color? = nil, font: Font? = nil, markdown: Bool = false) {
18 | if let markdown = try? AttributedString(markdown: text) {
19 | self.attributedText = markdown
20 | self.text = String(markdown.characters)
21 | } else {
22 | self.text = text
23 | self.attributedText = nil
24 | }
25 | self.color = color
26 | self.font = font
27 | }
28 |
29 | public init(_ attributedText: AttributedString, color: Color? = nil, font: Font? = nil) {
30 | self.text = String(attributedText.characters)
31 | self.color = color
32 | self.font = font
33 | self.attributedText = attributedText
34 | }
35 |
36 |
37 | // TODO: Use theme
38 | public func buildView(theme: Theme) -> AnyView {
39 | AnyView(
40 | ((attributedText != nil) ? Text(self.attributedText!) : Text(self.text))
41 | .font(self.font ?? theme.body.font)
42 | .foregroundColor(self.color ?? theme.body.color)
43 | )
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentItem.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 8/28/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public protocol ContentItem {
11 | var id: UUID { get }
12 | func buildView(theme: Theme) -> AnyView
13 | }
14 |
15 | @resultBuilder
16 | public enum ContentItemArrayBuilder {
17 | public static func buildEither(first component: [ContentItem]) -> [ContentItem] {
18 | return component
19 | }
20 |
21 | public static func buildEither(second component: [ContentItem]) -> [ContentItem] {
22 | return component
23 | }
24 |
25 | public static func buildBlock(_ components: [ContentItem]...) -> [ContentItem] {
26 | return components.flatMap { $0 }
27 | }
28 |
29 | public static func buildOptional(_ component: [ContentItem]?) -> [ContentItem] {
30 | return component ?? []
31 | }
32 |
33 | public static func buildExpression(_ expression: ContentItem) -> [ContentItem] {
34 | return [expression]
35 | }
36 |
37 | public static func buildExpression(_ expression: Void) -> [ContentItem] {
38 | return []
39 | }
40 |
41 | public static func buildExpression(_ expression: [ContentItem]) -> [ContentItem] {
42 | return expression
43 | }
44 |
45 | public static func buildArray(_ components: [[ContentItem]]) -> [ContentItem] {
46 | return components.flatMap { $0 }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/Demo/Tests iOS/Tests_iOS.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tests_iOS.swift
3 | // Tests iOS
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import XCTest
9 |
10 | class Tests_iOS: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use XCTAssert and related functions to verify your tests produce the correct results.
31 | }
32 |
33 | func testLaunchPerformance() throws {
34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
35 | // This measures how long it takes to launch your application.
36 | measure(metrics: [XCTApplicationLaunchMetric()]) {
37 | XCUIApplication().launch()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Examples/Demo/Tests macOS/Tests_macOS.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tests_macOS.swift
3 | // Tests macOS
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import XCTest
9 |
10 | class Tests_macOS: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use XCTAssert and related functions to verify your tests produce the correct results.
31 | }
32 |
33 | func testLaunchPerformance() throws {
34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
35 | // This measures how long it takes to launch your application.
36 | measure(metrics: [XCTApplicationLaunchMetric()]) {
37 | XCUIApplication().launch()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Syntax Highlight/CodeComponentFormat.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CodeComponentFormat.swift
3 | // DeckUI
4 | //
5 | // Created by Yonatan Mittlefehldt on 2022-09-11.
6 | //
7 |
8 | import Splash
9 |
10 | struct CodeComponentFormat: OutputFormat {
11 | func makeBuilder() -> Builder {
12 | return Builder()
13 | }
14 | }
15 |
16 | extension CodeComponentFormat {
17 | struct Builder: OutputBuilder {
18 | private var components: [[CodeComponent]]
19 |
20 | init() {
21 | self.components = [[CodeComponent]]()
22 | self.components.append([])
23 | }
24 |
25 | mutating func addToken(_ token: String, ofType type: TokenType) {
26 | components[components.count - 1].append(.token(token, type))
27 | }
28 |
29 | mutating func addPlainText(_ text: String) {
30 | components[components.count - 1].append(.plainText(text))
31 | }
32 |
33 | mutating func addWhitespace(_ whitespace: String) {
34 | let splitByNewLine = whitespace.split(separator: "\n", omittingEmptySubsequences: false)
35 | let lines = splitByNewLine.count
36 | for (i, blanks) in splitByNewLine.enumerated() {
37 | components[components.count - 1].append(.whitespace(String(blanks)))
38 | if i < (lines - 1) {
39 | components.append([])
40 | }
41 | }
42 | }
43 |
44 | func build() -> [[CodeComponent]] {
45 | components
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Compatibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Compatibility.swift
3 | // DeckUI
4 | //
5 | // Created by Alexandr Goncharov on 12.09.2022.
6 | //
7 |
8 | #if canImport(AppKit)
9 | import AppKit
10 | public typealias PlatformImage = NSImage
11 | public typealias PlatformView = NSView
12 | public typealias PlatformViewRepresentable = NSViewRepresentable
13 | #elseif canImport(UIKit)
14 | import UIKit
15 | public typealias PlatformImage = UIImage
16 | public typealias PlatformView = UIView
17 | public typealias PlatformViewRepresentable = UIViewRepresentable
18 | #endif
19 |
20 | import SwiftUI
21 |
22 | extension Image {
23 | init(platformImage: PlatformImage) {
24 | #if canImport(UIKit)
25 | self = Image(uiImage: platformImage)
26 | #elseif canImport(AppKit)
27 | self = Image(nsImage: platformImage)
28 | #endif
29 | }
30 | }
31 |
32 | // Taken from: https://gist.github.com/insidegui/97d821ca933c8627e7f614bc1d6b4983
33 |
34 | /// Implementers get automatic `UIViewRepresentable` conformance on iOS
35 | /// and `NSViewRepresentable` conformance on macOS.
36 | public protocol PlatformAgnosticViewRepresentable: PlatformViewRepresentable {
37 | associatedtype PlatformViewType
38 |
39 | func makePlatformView(context: Context) -> PlatformViewType
40 | func updatePlatformView(_ platformView: PlatformViewType, context: Context)
41 | }
42 |
43 | #if canImport(AppKit)
44 | public extension PlatformAgnosticViewRepresentable where NSViewType == PlatformViewType {
45 | func makeNSView(context: Context) -> NSViewType {
46 | makePlatformView(context: context)
47 | }
48 |
49 | func updateNSView(_ nsView: NSViewType, context: Context) {
50 | updatePlatformView(nsView, context: context)
51 | }
52 | }
53 | #elseif canImport(UIKit)
54 | public extension PlatformAgnosticViewRepresentable where UIViewType == PlatformViewType {
55 | func makeUIView(context: Context) -> UIViewType {
56 | makePlatformView(context: context)
57 | }
58 |
59 | func updateUIView(_ uiView: UIViewType, context: Context) {
60 | updatePlatformView(uiView, context: context)
61 | }
62 | }
63 | #endif
64 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Views/SlideNavigation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SlideNavigation.swift
3 | //
4 | //
5 | // Created by Zachary Brass on 3/18/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct SlideNavigationToolbarButtons: View {
11 | public var body: some View {
12 | Group {
13 | Button {
14 | withAnimation {
15 | PresentationState.shared.previousSlide()
16 | }
17 | } label: {
18 | Label("Previous", systemImage: "arrow.left")
19 | }.keyboardShortcut(.leftArrow, modifiers: [])
20 |
21 | Button {
22 | withAnimation {
23 | PresentationState.shared.nextSlide()
24 | }
25 | } label: {
26 | Label("Next", systemImage: "arrow.right")
27 | }.keyboardShortcut(.rightArrow, modifiers: [])
28 |
29 | Button {
30 | NotificationCenter.default.post(name: .keyDown, object: nil)
31 | } label: {
32 | Label("Down", systemImage: "arrow.down")
33 | }.keyboardShortcut(.downArrow, modifiers: [])
34 |
35 | Button {
36 | NotificationCenter.default.post(name: .keyUp, object: nil)
37 | } label: {
38 | Label("Up", systemImage: "arrow.up")
39 | }.keyboardShortcut(.upArrow, modifiers: [])
40 | }
41 | }
42 | public init() {
43 |
44 | }
45 | }
46 |
47 | extension View {
48 | public func slideNavigationGestures() -> some View {
49 | return gesture(DragGesture(minimumDistance: 3.0, coordinateSpace: .local)
50 | .onEnded { value in
51 | let tolerance: ClosedRange = -100...100
52 | switch(value.translation.width, value.translation.height) {
53 | case (tolerance, ...0): NotificationCenter.default.post(name: .keyUp, object: nil)
54 | case (tolerance, 0...): NotificationCenter.default.post(name: .keyDown, object: nil)
55 | case (...0, tolerance): PresentationState.shared.nextSlide()
56 | case (0..., tolerance): PresentationState.shared.previousSlide()
57 | default: break
58 | }
59 | }
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Syntax Highlight/CodeTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CodeTheme.swift
3 | // DeckUI
4 | //
5 | // Created by Yonatan Mittlefehldt on 2022-09-09.
6 | //
7 |
8 | import Splash
9 | import SwiftUI
10 |
11 | public struct CodeTheme {
12 | public var font: SwiftUI.Font
13 | public var plainTextColor: SwiftUI.Color
14 | public var backgroundColor: SwiftUI.Color
15 | public var tokenColors: [TokenType: SwiftUI.Color]
16 |
17 | public init(font: SwiftUI.Font, plainTextColor: SwiftUI.Color, backgroundColor: SwiftUI.Color, tokenColors: [TokenType : SwiftUI.Color]) {
18 | self.font = font
19 | self.plainTextColor = plainTextColor
20 | self.backgroundColor = backgroundColor
21 | self.tokenColors = tokenColors
22 | }
23 | }
24 |
25 | extension CodeTheme {
26 | func text(for code: CodeComponent) -> AttributedString {
27 | let foregroundColor: SwiftUI.Color
28 | let text: String
29 |
30 | switch code {
31 | case .token(let string, let token):
32 | foregroundColor = tokenColors[token] ?? plainTextColor
33 | text = string
34 | case .plainText(let string):
35 | foregroundColor = plainTextColor
36 | text = string
37 | case .whitespace(let string):
38 | foregroundColor = plainTextColor
39 | text = string
40 | }
41 |
42 | var attrString = AttributedString(stringLiteral: text)
43 | attrString.font = font
44 | attrString.backgroundColor = backgroundColor
45 | attrString.foregroundColor = foregroundColor
46 |
47 | return attrString
48 | }
49 | }
50 |
51 | extension CodeTheme {
52 | public static let xcodeDark: CodeTheme = CodeTheme(
53 | font: Font.system(size: 22, weight: .regular, design: .monospaced),
54 | plainTextColor: Color(hex: "#FFFFFF"),
55 | backgroundColor: .clear,
56 | tokenColors: [
57 | .keyword: Color(hex: "#ff79b3"),
58 | .string: Color(hex: "#ff8170"),
59 | .type: Color(hex: "#dabaff"),
60 | .call: Color(hex: "#78c2b4"),
61 | .number: Color(hex: "#dac87c"),
62 | .comment: Color(hex: "#808b98"),
63 | .property: Color(hex: "#79c2b4"),
64 | .dotAccess: Color(hex: "#79c2b4"),
65 | .preprocessing: Color(hex: "#ffa14f")
66 | ]
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItems/Media.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Media.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 9/1/22.
6 | //
7 |
8 | import SwiftUI
9 | import AVFoundation
10 | import Foundation
11 | import AVKit
12 |
13 | public struct Media: ContentItem {
14 | public enum Kind {
15 | case remoteImage(URL), assetImage(String), bundleImage(String)
16 | case bundleVideo(String)
17 |
18 | var view: some View {
19 | Group {
20 | switch self {
21 | case .remoteImage(let url):
22 | // TODO: This loads really weird with the slide transition
23 | AsyncImage(
24 | url: url,
25 | content: { image in
26 | image.resizable()
27 | .aspectRatio(contentMode: .fit)
28 | },
29 | placeholder: {
30 | ProgressView()
31 | }
32 | )
33 | case .assetImage(let name):
34 | Image(name)
35 | .resizable()
36 | .aspectRatio(contentMode: .fit)
37 | case .bundleImage(let name):
38 | #if canImport(AppKit)
39 | let platformImage = PlatformImage(named: name)
40 | #elseif canImport(UIKit)
41 | let path = Bundle.main.path(forResource: name, ofType: nil)!
42 | let platformImage = PlatformImage(contentsOfFile: path)
43 | #endif
44 | Image(platformImage: platformImage!)
45 | .resizable()
46 | .aspectRatio(contentMode: .fit)
47 | case .bundleVideo(let name):
48 | let url = Bundle.main.url(forResource: name, withExtension: nil)!
49 | let player = AVPlayer(url: url)
50 | VideoPlayer(player: player).task {
51 | await player.play()
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 | public let id = UUID()
59 | let kind: Kind
60 |
61 | public init(_ kind: Kind) {
62 | self.kind = kind
63 | }
64 |
65 | // TODO: Use theme
66 | public func buildView(theme: Theme) -> AnyView {
67 | AnyView(
68 | self.kind.view
69 | )
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItems/Bullets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bullets.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 9/1/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct Bullets: ContentItem {
11 | public enum Style {
12 | case bullet
13 | case dash
14 |
15 | var display: String {
16 | switch self {
17 | case .bullet:
18 | return "•"
19 | case .dash:
20 | return "–"
21 | }
22 | }
23 | }
24 |
25 | public let id = UUID()
26 | let style: Style
27 | @WordArrayBuilder var words: () -> [Words]
28 |
29 | public init(style: Style = .bullet, @WordArrayBuilder words: @escaping () -> [Words]) {
30 | self.style = style
31 | self.words = words
32 | }
33 |
34 | // TODO: Use theme
35 | public func buildView(theme: Theme) -> AnyView {
36 | AnyView(
37 | // TODO: Not sure if hardocing leading here is great
38 | VStack(alignment: .leading, spacing: 10) {
39 | ForEach(self.words(), id:\.id) { word in
40 | HStack(alignment: .center, spacing: 10) {
41 | Words(self.style.display, color: word.color, font: word.font).buildView(theme: theme)
42 | word.buildView(theme: theme)
43 | }
44 | }
45 | }
46 | )
47 | }
48 | }
49 |
50 | @resultBuilder
51 | public enum WordArrayBuilder {
52 | public static func buildEither(first component: [Words]) -> [Words] {
53 | return component
54 | }
55 |
56 | public static func buildEither(second component: [Words]) -> [Words] {
57 | return component
58 | }
59 |
60 | // Might only need this one
61 | public static func buildBlock(_ components: [Words]...) -> [Words] {
62 | return components.flatMap { $0 }
63 | }
64 |
65 | public static func buildOptional(_ component: [Words]?) -> [Words] {
66 | return component ?? []
67 | }
68 |
69 | // Might only need this one
70 | public static func buildExpression(_ expression: Words) -> [Words] {
71 | return [expression]
72 | }
73 |
74 | public static func buildExpression(_ expression: Void) -> [Words] {
75 | return []
76 | }
77 |
78 | public static func buildExpression(_ expression: [Words]) -> [Words] {
79 | return expression
80 | }
81 |
82 | public static func buildArray(_ components: [[Words]]) -> [Words] {
83 | return components.flatMap { $0 }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/Slide.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slide.swift
3 | // DeckUI (iOS)
4 | //
5 | // Created by Josh Holtz on 8/28/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct Slide: Identifiable {
11 | public let id = UUID()
12 |
13 | let alignment: Alignment
14 | let horizontalAlignment: HorizontalAlignment
15 | let padding: CGFloat
16 | let comment: String?
17 | let theme: Theme?
18 | let transition: SlideTransition?
19 | @ContentItemArrayBuilder var contentItems: () -> [ContentItem]
20 |
21 | public init(alignment: Alignment = .topLeading, padding: CGFloat = 40, comment: String? = nil, theme: Theme? = nil, transition: SlideTransition? = nil, @ContentItemArrayBuilder contentItems: @escaping () -> [ContentItem]) {
22 | self.alignment = alignment
23 | self.horizontalAlignment = .leading
24 | self.padding = padding
25 | self.comment = comment
26 | self.theme = theme
27 | self.transition = transition
28 | self.contentItems = contentItems
29 | }
30 |
31 | func buildContentItems(theme: Theme) -> AnyView {
32 | let contentViews = contentItems()
33 | return AnyView(
34 | ForEach(contentViews, id: \.id) {
35 | $0.buildView(theme: theme)
36 | }
37 | )
38 | }
39 |
40 | func buildView(theme: Theme) -> AnyView {
41 | let themeToUse = self.theme ?? theme
42 |
43 | let contentItemViews = self.buildContentItems(theme: themeToUse)
44 |
45 | return AnyView(
46 | VStack(alignment: self.horizontalAlignment) {
47 | contentItemViews
48 | }
49 | .padding(self.padding)
50 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: self.alignment)
51 | .background(themeToUse.background)
52 | )
53 | }
54 | }
55 |
56 | @resultBuilder
57 | public enum SlideArrayBuilder {
58 | public static func buildEither(first component: [Slide]) -> [Slide] {
59 | return component
60 | }
61 |
62 | public static func buildEither(second component: [Slide]) -> [Slide] {
63 | return component
64 | }
65 |
66 | // Might only need this one
67 | public static func buildBlock(_ components: [Slide]...) -> [Slide] {
68 | return components.flatMap { $0 }
69 | }
70 |
71 | public static func buildOptional(_ component: [Slide]?) -> [Slide] {
72 | return component ?? []
73 | }
74 |
75 | // Might only need this one
76 | public static func buildExpression(_ expression: Slide) -> [Slide] {
77 | return [expression]
78 | }
79 |
80 | public static func buildExpression(_ expression: Void) -> [Slide] {
81 | return []
82 | }
83 |
84 | public static func buildExpression(_ expression: [Slide]) -> [Slide] {
85 | return expression
86 | }
87 |
88 | public static func buildArray(_ components: [[Slide]]) -> [Slide] {
89 | return components.flatMap { $0 }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItems/Columns.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Columns.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct Columns: ContentItem {
11 | public let id = UUID()
12 | @ColumnArrayBuilder var columns: () -> [Column]
13 |
14 | public init(@ColumnArrayBuilder columns: @escaping () -> [Column]) {
15 | self.columns = columns
16 | }
17 |
18 | // TODO: Use theme
19 | public func buildView(theme: Theme) -> AnyView {
20 | AnyView(
21 | GeometryReader { proxy in
22 | HStack(alignment: .top, spacing: 0) {
23 | ForEach(Array(self.columns().enumerated()), id: \.offset) { index, column in
24 | // TODO: dont love this hardcoded
25 | VStack(alignment: .leading) {
26 | column.buildView(theme: theme)
27 | }.padding(.trailing, 20)
28 | .frame(width: proxy.size.width / CGFloat(self.columns().count), alignment: .leading)
29 | }
30 | }
31 | }.frame(maxWidth: .infinity, maxHeight: .infinity)
32 | )
33 | }
34 | }
35 |
36 | public struct Column: Identifiable {
37 | public let id = UUID()
38 | public let theme: Theme?
39 |
40 | @ContentItemArrayBuilder var contentItems: () -> [ContentItem]
41 |
42 | public func buildView(theme: Theme) -> AnyView {
43 | let contentItemViews = contentItems()
44 | return AnyView(
45 | ForEach(contentItemViews, id: \.id) {
46 | $0.buildView(theme: theme)
47 | }
48 | )
49 | }
50 |
51 | public init(theme: Theme? = nil, @ContentItemArrayBuilder contentItems: @escaping () -> [ContentItem]) {
52 | self.theme = theme
53 | self.contentItems = contentItems
54 | }
55 | }
56 |
57 | @resultBuilder
58 | public enum ColumnArrayBuilder {
59 | public static func buildEither(first component: [Column]) -> [Column] {
60 | return component
61 | }
62 |
63 | public static func buildEither(second component: [Column]) -> [Column] {
64 | return component
65 | }
66 |
67 | // Might only need this one
68 | public static func buildBlock(_ components: [Column]...) -> [Column] {
69 | return components.flatMap { $0 }
70 | }
71 |
72 | public static func buildOptional(_ component: [Column]?) -> [Column] {
73 | return component ?? []
74 | }
75 |
76 | // Might only need this one
77 | public static func buildExpression(_ expression: Column) -> [Column] {
78 | return [expression]
79 | }
80 |
81 | public static func buildExpression(_ expression: Void) -> [Column] {
82 | return []
83 | }
84 |
85 | public static func buildExpression(_ expression: [Column]) -> [Column] {
86 | return expression
87 | }
88 |
89 | public static func buildArray(_ components: [[Column]]) -> [Column] {
90 | return components.flatMap { $0 }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
1 | # DeckUI Project Rules for Claude
2 |
3 | ## Project Overview
4 | DeckUI is a Swift DSL for creating slide decks/presentations entirely in Swift code within Xcode. It uses SwiftUI and supports macOS 12+ and iOS 15+.
5 |
6 | ## Core Development Rules
7 |
8 | ### 1. Swift & SwiftUI Conventions
9 | - Use Swift 5.6+ features and syntax
10 | - Follow SwiftUI best practices and patterns
11 | - Maintain cross-platform compatibility (macOS and iOS)
12 | - Use `Compatibility.swift` for platform-specific code
13 |
14 | ### 2. DSL Pattern Rules
15 | - Always use `@resultBuilder` for DSL components
16 | - Maintain consistency with existing DSL syntax patterns
17 | - New content items must:
18 | - Conform to `ContentItem` protocol
19 | - Have unique IDs for SwiftUI's ForEach
20 | - Support theme customization where applicable
21 | - Follow the pattern established in existing content items
22 |
23 | ### 3. File Organization
24 | - Place DSL components in `Sources/DeckUI/DSL/`
25 | - Content items go in `Sources/DeckUI/DSL/ContentItems/`
26 | - View components go in `Sources/DeckUI/Views/`
27 | - Platform-specific code goes in `Compatibility.swift`
28 |
29 | ### 4. Theme System
30 | - All visual components should respect the theme system
31 | - Use theme colors (foreground, background, accent) consistently
32 | - Allow theme overrides at the slide level
33 | - Maintain compatibility with existing themes (dark, black, white)
34 |
35 | ### 5. Testing & Examples
36 | - Update the Demo app when adding new features
37 | - Test on both macOS and iOS platforms
38 | - Ensure Xcode preview functionality works
39 | - Test with different themes
40 |
41 | ### 6. Code Style
42 | - Match existing code style and formatting
43 | - Use descriptive names following Swift naming conventions
44 | - Keep DSL syntax clean and intuitive
45 | - Avoid unnecessary comments (code should be self-documenting)
46 |
47 | ### 7. Feature Implementation
48 | - New features should integrate seamlessly with existing DSL
49 | - Maintain backward compatibility
50 | - Consider both platforms when implementing features
51 | - Update result builders to support new content types
52 |
53 | ### 8. Navigation & Interaction
54 | - Respect existing keyboard shortcuts (arrow keys, up/down)
55 | - Maintain gesture support on iOS
56 | - Ensure new interactive elements don't conflict with navigation
57 |
58 | ### 9. Performance Considerations
59 | - Use SwiftUI's built-in optimization features
60 | - Avoid unnecessary re-renders
61 | - Keep slide transitions smooth
62 | - Optimize media loading and display
63 |
64 | ### 10. Dependencies
65 | - Minimize external dependencies
66 | - Only Splash is currently used for syntax highlighting
67 | - Any new dependencies must be justified and added via Swift Package Manager
68 |
69 | ## Build & Test Commands
70 | - Build: `swift build`
71 | - Test: `swift test`
72 | - Run Demo: Open `Examples/Demo/Demo.xcodeproj` in Xcode
73 |
74 | ## Common Tasks
75 | - Adding new content type: Create in `ContentItems/`, conform to `ContentItem`, update builders
76 | - Adding new theme: Update `Theme.swift`, test with all content types
77 | - Platform-specific features: Use `Compatibility.swift` for abstraction
--------------------------------------------------------------------------------
/Examples/Demo/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | },
93 | {
94 | "idiom" : "mac",
95 | "scale" : "1x",
96 | "size" : "16x16"
97 | },
98 | {
99 | "idiom" : "mac",
100 | "scale" : "2x",
101 | "size" : "16x16"
102 | },
103 | {
104 | "idiom" : "mac",
105 | "scale" : "1x",
106 | "size" : "32x32"
107 | },
108 | {
109 | "idiom" : "mac",
110 | "scale" : "2x",
111 | "size" : "32x32"
112 | },
113 | {
114 | "idiom" : "mac",
115 | "scale" : "1x",
116 | "size" : "128x128"
117 | },
118 | {
119 | "idiom" : "mac",
120 | "scale" : "2x",
121 | "size" : "128x128"
122 | },
123 | {
124 | "idiom" : "mac",
125 | "scale" : "1x",
126 | "size" : "256x256"
127 | },
128 | {
129 | "idiom" : "mac",
130 | "scale" : "2x",
131 | "size" : "256x256"
132 | },
133 | {
134 | "idiom" : "mac",
135 | "scale" : "1x",
136 | "size" : "512x512"
137 | },
138 | {
139 | "idiom" : "mac",
140 | "scale" : "2x",
141 | "size" : "512x512"
142 | }
143 | ],
144 | "info" : {
145 | "author" : "xcode",
146 | "version" : 1
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Sources/DeckUI/PresentationState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentationState.swift
3 | //
4 | //
5 | // Created by Zachary Brass on 3/12/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | open class PresentationState: ObservableObject {
11 | static let shared = PresentationState()
12 | @Published var slideIndex = 0
13 |
14 | var loop = false
15 | var slideTransition: SlideTransition? = .horizontal
16 |
17 | @Published var activeTransition: AnyTransition = .slideFromTrailing
18 |
19 | // Putting in sample Deck in case the user doesn't assign one. It instructs the user on how to add a Deck of their own
20 | open var deck: Deck = Deck(title: "DeckUI Example") {
21 | Slide(alignment: .center, comment: "Presenter notes are passed into the comment argument in the init method each of Slide") {
22 | Title("DeckUI Example")
23 | }
24 |
25 | Slide(alignment: .center) {
26 | Title("Getting Started")
27 | Columns {
28 | Column {
29 | Code(.swift) {
30 | """
31 | import SwiftUI
32 | import DeckUI
33 |
34 | struct ContentView: View {
35 | var body: some View {
36 | Presenter(deck: self.deck)
37 | }
38 | }
39 |
40 | extension ContentView {
41 | var deck: Deck {
42 | Deck(title: "SomeConf 2023") {
43 | Slide(alignment: .center) {
44 | Title("Welcome to DeckUI")
45 | }
46 |
47 | Slide {
48 | Title("Slide 1")
49 | Words("Some useful content")
50 | }
51 | }
52 | }
53 | }
54 | """
55 | }
56 | }
57 |
58 | Column {
59 | Bullets(style: .bullet) {
60 | Words("Create a `Deck` with multiple `Slide` ")
61 | Words("Create `Presenter` and give a deck")
62 | Words("`Presenter` is a SwiftUI View to present a `Deck`")
63 | }
64 | }
65 | }
66 | }
67 | }
68 | public func nextSlide() {
69 | let slides = self.deck.slides()
70 | if slideIndex >= (slides.count - 1) {
71 | if self.loop {
72 | slideIndex = 0
73 | }
74 | } else {
75 | slideIndex += 1
76 | }
77 |
78 | let nextSlide = slides[slideIndex]
79 |
80 | self.activeTransition = (nextSlide.transition ?? self.slideTransition).next
81 | NotificationCenter.default.post(name: .slideChanged, object: nextSlide)
82 |
83 | }
84 |
85 | public func previousSlide() {
86 | let slides = self.deck.slides()
87 |
88 | let currentSlide = slides[slideIndex]
89 |
90 | if slideIndex <= 0 {
91 | if self.loop {
92 | slideIndex = slides.count - 1
93 | }
94 | } else {
95 | slideIndex -= 1
96 | }
97 |
98 | let previousSlide = slides[slideIndex]
99 |
100 | self.activeTransition = (currentSlide.transition ?? self.slideTransition).previous
101 | NotificationCenter.default.post(name: .slideChanged, object: previousSlide)
102 |
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/ContentItems/Code.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Code.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import Splash
9 | import SwiftUI
10 |
11 | public struct Code: ContentItem {
12 | public let id = UUID()
13 | let text: String
14 | let enableHighlight: Bool
15 | let language: ProgrammingLanguage
16 |
17 | public init(_ language: ProgrammingLanguage = .none, enableHighlight: Bool = true, text: () -> String) {
18 | self.text = text()
19 | self.enableHighlight = enableHighlight
20 | self.language = language
21 | }
22 |
23 | public func buildView(theme: Theme) -> AnyView {
24 | let format = CodeComponentFormat()
25 | let highlighter = SyntaxHighlighter(format: format, grammar: self.language.grammar)
26 | let components = highlighter.highlight(self.text)
27 |
28 | return AnyView(
29 | CodeView(components: components, enableHighlight: self.enableHighlight, theme: theme)
30 | )
31 | }
32 | }
33 |
34 | struct CodeView: View {
35 | let components: [[CodeComponent]]
36 | let enableHighlight: Bool
37 | let theme: Theme
38 | let nonEmptyLineIndexes: [Int]
39 |
40 | @State var focusedLineIndex: Int?
41 |
42 | var focusedLine: Int? {
43 | guard let index = self.focusedLineIndex else {
44 | return nil
45 | }
46 | return self.nonEmptyLineIndexes[index]
47 | }
48 |
49 | init(components: [[CodeComponent]], enableHighlight: Bool, theme: Theme) {
50 | self.components = components
51 | self.enableHighlight = enableHighlight
52 | self.theme = theme
53 |
54 | self.nonEmptyLineIndexes = self.components.enumerated().compactMap { (index, line) -> Int? in
55 | if line.filter({ !$0.isWhitespace }).isEmpty {
56 | return nil
57 | } else {
58 | return index
59 | }
60 | }
61 | }
62 |
63 | var body: some View {
64 | VStack(alignment: .leading, spacing: 0) {
65 | ScrollView {
66 | ForEach(Array(self.components.enumerated()), id:\.offset) { index, line in
67 | Text(attributedString(for: line, highlight: isFocused(index)))
68 | .frame(maxWidth: .infinity, alignment: .leading)
69 | .padding(.vertical, 1)
70 | .background(isFocused(index) ? self.theme.codeHighlighted.backgroundColor : nil)
71 | }
72 | }
73 | }
74 | .onReceive(NotificationCenter.default.publisher(for: .keyUp), perform: { _ in
75 | if self.enableHighlight {
76 | self.previousLine()
77 | }
78 | }).onReceive(NotificationCenter.default.publisher(for: .keyDown), perform: { _ in
79 | if self.enableHighlight {
80 | self.nextLine()
81 | }
82 | })
83 | }
84 |
85 |
86 | private func attributedString(for line: [CodeComponent], highlight: Bool) -> AttributedString {
87 | let codeTheme = highlight ? theme.codeHighlighted : theme.code
88 | var attrStr = AttributedString()
89 | for component in line {
90 | attrStr += codeTheme.text(for: component)
91 | }
92 | return attrStr
93 | }
94 |
95 | private func nextLine() {
96 | if let index = self.focusedLineIndex {
97 | if index >= (nonEmptyLineIndexes.count - 1) {
98 | // No op
99 | } else {
100 | let newIndex = index + 1
101 | self.focusedLineIndex = newIndex
102 | }
103 | } else {
104 | self.focusedLineIndex = 0
105 | }
106 | }
107 |
108 | private func previousLine() {
109 | guard let index = self.focusedLineIndex else {
110 | return
111 | }
112 |
113 | if index <= 0 {
114 | // No op
115 | } else {
116 | self.focusedLineIndex = index - 1
117 | }
118 | }
119 |
120 | private func isFocused(_ line: Int) -> Bool {
121 | if let focusedLine = self.focusedLine {
122 | return focusedLine == line
123 | } else {
124 | return false
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Sources/DeckUI/DSL/Theme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Theme.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 9/5/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct Theme {
11 | var background: Color
12 | var title: Foreground
13 | var subtitle: Foreground
14 | var body: Foreground
15 |
16 | public var code: CodeTheme
17 | public var codeHighlighted: CodeTheme
18 |
19 | public init(background: Color, title: Foreground, subtitle: Foreground, body: Foreground, code: Foreground, codeHighlighted: (Color, Foreground)) {
20 | self.background = background
21 | self.title = title
22 | self.subtitle = subtitle
23 | self.body = body
24 | self.code = CodeTheme(font: code.font, plainTextColor: code.color, backgroundColor: .clear, tokenColors: [:])
25 | self.codeHighlighted = CodeTheme(font: codeHighlighted.1.font, plainTextColor: codeHighlighted.1.color, backgroundColor: codeHighlighted.0, tokenColors: [:])
26 | }
27 |
28 | public init(background: Color, title: Foreground, subtitle: Foreground, body: Foreground, code: CodeTheme, codeHighlighted: (Color, Foreground)) {
29 | self.background = background
30 | self.title = title
31 | self.subtitle = subtitle
32 | self.body = body
33 | self.code = code
34 | self.codeHighlighted = CodeTheme(font: codeHighlighted.1.font, plainTextColor: codeHighlighted.1.color, backgroundColor: codeHighlighted.0, tokenColors: [:])
35 | }
36 |
37 | public init(background: Color, title: Foreground, subtitle: Foreground, body: Foreground, code: CodeTheme, codeHighlighted: CodeTheme) {
38 | self.background = background
39 | self.title = title
40 | self.subtitle = subtitle
41 | self.body = body
42 | self.code = code
43 | self.codeHighlighted = codeHighlighted
44 | }
45 | }
46 |
47 | public struct Foreground {
48 | let color: Color
49 | let font: Font
50 |
51 | public init(color: Color, font: Font) {
52 | self.color = color
53 | self.font = font
54 | }
55 | }
56 |
57 | extension Theme {
58 | public static let standard: Theme = .black
59 |
60 | public static let dark: Theme = Theme(
61 | background: Color(hex: "#221d29"),
62 | title: Foreground(
63 | color: Color(hex: "#FFFFFF"),
64 | font: Font.system(size: 80, weight: .bold, design: .default)
65 | ),
66 | subtitle: Foreground(
67 | color: Color(hex: "#FFFFFF"),
68 | font: Font.system(size: 40, weight: .light, design: .default).italic()
69 | ),
70 | body: Foreground(
71 | color: Color(hex: "#FFFFFF"),
72 | font: Font.system(size: 40, weight: .regular, design: .default)
73 | ),
74 | code: .xcodeDark,
75 | codeHighlighted: (Color(hex: "#000000"), Foreground(
76 | color: Color(hex: "#FFFFFF"),
77 | font: Font.system(size: 22, weight: .heavy, design: .monospaced)
78 | ))
79 | )
80 |
81 | public static let black: Theme = Theme(
82 | background: Color(hex: "#000000"),
83 | title: Foreground(
84 | color: Color(hex: "#FFFFFF"),
85 | font: Font.system(size: 80, weight: .bold, design: .default)
86 | ),
87 | subtitle: Foreground(
88 | color: Color(hex: "#FFFFFF"),
89 | font: Font.system(size: 50, weight: .light, design: .default).italic()
90 | ),
91 | body: Foreground(
92 | color: Color(hex: "#FFFFFF"),
93 | font: Font.system(size: 40, weight: .regular, design: .default)
94 | ),
95 | code: Foreground(
96 | color: Color(hex: "#FFFFFF"),
97 | font: Font.system(size: 22, weight: .regular, design: .monospaced)
98 | ),
99 | codeHighlighted: (Color(hex: "#CCCCCC"), Foreground(
100 | color: Color(hex: "#000000"),
101 | font: Font.system(size: 22, weight: .heavy, design: .monospaced)
102 | ))
103 | )
104 |
105 | public static let white: Theme = Theme(
106 | background: Color(hex: "#FFFFFF"),
107 | title: Foreground(
108 | color: Color(hex: "#000000"),
109 | font: Font.system(size: 80, weight: .bold, design: .default)
110 | ),
111 | subtitle: Foreground(
112 | color: Color(hex: "#000000"),
113 | font: Font.system(size: 40, weight: .light, design: .default).italic()
114 | ),
115 | body: Foreground(
116 | color: Color(hex: "#000000"),
117 | font: Font.system(size: 40, weight: .regular, design: .default)
118 | ),
119 | code: Foreground(
120 | color: Color(hex: "#000000"),
121 | font: Font.system(size: 22, weight: .regular, design: .monospaced)
122 | ),
123 | codeHighlighted: (Color(hex: "#000000"), Foreground(
124 | color: Color(hex: "#FFFFFF"),
125 | font: Font.system(size: 22, weight: .heavy, design: .monospaced)
126 | ))
127 | )
128 | }
129 |
130 | public extension Color {
131 | init(hex: String) {
132 | let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
133 | var int: UInt64 = 0
134 | Scanner(string: hex).scanHexInt64(&int)
135 | let a, r, g, b: UInt64
136 | switch hex.count {
137 | case 3: // RGB (12-bit)
138 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
139 | case 6: // RGB (24-bit)
140 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
141 | case 8: // ARGB (32-bit)
142 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
143 | default:
144 | (a, r, g, b) = (1, 1, 1, 0)
145 | }
146 |
147 | self.init(
148 | .sRGB,
149 | red: Double(r) / 255,
150 | green: Double(g) / 255,
151 | blue: Double(b) / 255,
152 | opacity: Double(a) / 255
153 | )
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Views/CameraView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CameraView.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 9/7/22.
6 | //
7 |
8 | import Foundation
9 | import AVFoundation
10 | import SwiftUI
11 | import Combine
12 |
13 | // Taken from: https://benoitpasquier.com/webcam-utility-app-macos-swiftui/
14 |
15 | struct Camera: View {
16 | @StateObject var viewModel = ContentViewModel()
17 |
18 | var body: some View {
19 | ZStack {
20 | CameraContainerView(captureSession: viewModel.captureSession)
21 | .onAppear {
22 | self.viewModel.checkAuthorization()
23 | }
24 |
25 | // Hack: needed to make view clickable for context menu
26 | // There might be a better way to do this but meh
27 | Rectangle()
28 | .fill(.white.opacity(0.001))
29 | .frame(maxWidth: .infinity, maxHeight: .infinity)
30 | }
31 | .contextMenu {
32 | ForEach(self.viewModel.availableDevices, id:\.self) { device in
33 | Button {
34 | self.viewModel.startSessionForDevice(device)
35 | } label: {
36 | Label(device.localizedName, systemImage: "video.fill")
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 | final class CameraView: PlatformView {
44 |
45 | init(captureSession: AVCaptureSession) {
46 | #if canImport(AppKit)
47 | previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
48 | #endif
49 | super.init(frame: .zero)
50 |
51 | #if canImport(UIKit)
52 | let previewLayer = layer as? AVCaptureVideoPreviewLayer
53 | previewLayer?.session = captureSession
54 | #endif
55 |
56 | previewLayer?.frame = self.frame
57 | previewLayer?.contentsGravity = .resizeAspectFill
58 | previewLayer?.videoGravity = .resizeAspectFill
59 | previewLayer?.connection?.automaticallyAdjustsVideoMirroring = false
60 |
61 | #if canImport(AppKit)
62 | layer = previewLayer
63 | #endif
64 | }
65 |
66 | @available(*, unavailable, message: "Use init(captureSession:) instead")
67 | required init?(coder: NSCoder) {
68 | fatalError("init(coder:) has not been implemented")
69 | }
70 |
71 | #if canImport(AppKit)
72 | var previewLayer: AVCaptureVideoPreviewLayer?
73 | #elseif canImport(UIKit)
74 | override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self }
75 | #endif
76 | }
77 |
78 | struct CameraContainerView: PlatformAgnosticViewRepresentable {
79 |
80 | let captureSession: AVCaptureSession
81 |
82 | init(captureSession: AVCaptureSession) {
83 | self.captureSession = captureSession
84 | }
85 |
86 | func makePlatformView(context: Context) -> CameraView {
87 | CameraView(captureSession: captureSession)
88 | }
89 |
90 | func updatePlatformView(_ platformView: CameraView, context: Context) {}
91 | }
92 |
93 | final class ContentViewModel: ObservableObject {
94 |
95 | @Published var isGranted: Bool = false
96 | @Published var device: AVCaptureDevice? = nil
97 | @Published var availableDevices = [AVCaptureDevice]()
98 | var captureSession: AVCaptureSession!
99 | private var cancellables = Set()
100 |
101 | init() {
102 | captureSession = AVCaptureSession()
103 | setupBindings()
104 | }
105 |
106 | func setupBindings() {
107 | $isGranted
108 | .sink { [weak self] isGranted in
109 | if isGranted {
110 | self?.prepareCamera()
111 | } else {
112 | self?.stopSession()
113 | }
114 | }
115 | .store(in: &cancellables)
116 | }
117 |
118 | func fetchDevices() {
119 | var deviceTypes: [AVCaptureDevice.DeviceType] = [
120 | .builtInMicrophone,
121 | .builtInWideAngleCamera,
122 | ]
123 | #if os(macOS)
124 | deviceTypes.append(.externalUnknown)
125 | #endif
126 | let discoverySession = AVCaptureDevice.DiscoverySession(
127 | deviceTypes: deviceTypes,
128 | mediaType: .video,
129 | position: .unspecified
130 | )
131 | self.availableDevices = discoverySession.devices
132 | }
133 |
134 | func checkAuthorization() {
135 | switch AVCaptureDevice.authorizationStatus(for: .video) {
136 | case .authorized: // The user has previously granted access to the camera.
137 | self.isGranted = true
138 |
139 | case .notDetermined: // The user has not yet been asked for camera access.
140 | AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
141 | if granted {
142 | DispatchQueue.main.async {
143 | self?.isGranted = granted
144 | }
145 | }
146 | }
147 |
148 | case .denied: // The user has previously denied access.
149 | self.isGranted = false
150 | return
151 |
152 | case .restricted: // The user can't grant access due to restrictions.
153 | self.isGranted = false
154 | return
155 | @unknown default:
156 | fatalError()
157 | }
158 | }
159 |
160 | func startSession() {
161 | guard !captureSession.isRunning else { return }
162 | captureSession.startRunning()
163 | }
164 |
165 | func stopSession() {
166 | guard captureSession.isRunning else { return }
167 | captureSession.stopRunning()
168 | }
169 |
170 | func prepareCamera() {
171 | captureSession.sessionPreset = .high
172 |
173 | if let device = AVCaptureDevice.default(for: .video) {
174 | startSessionForDevice(device)
175 | }
176 |
177 | self.fetchDevices()
178 | }
179 |
180 | func startSessionForDevice(_ device: AVCaptureDevice) {
181 | do {
182 | for input in captureSession.inputs {
183 | captureSession.removeInput(input)
184 | }
185 |
186 | let input = try AVCaptureDeviceInput(device: device)
187 | captureSession.beginConfiguration()
188 | addInput(input)
189 | captureSession.commitConfiguration()
190 | startSession()
191 | self.device = device
192 | }
193 | catch {
194 | print("Something went wrong - ", error.localizedDescription)
195 | }
196 | }
197 |
198 | func addInput(_ input: AVCaptureInput) {
199 | guard captureSession.canAddInput(input) == true else {
200 | return
201 | }
202 | captureSession.addInput(input)
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/Sources/DeckUI/Views/Presenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Presenter.swift
3 | // DeckUI
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 | public struct Presenter: View {
11 | public typealias DefaultResolution = (width: Double, height: Double)
12 |
13 | @ObservedObject private var presentationState = PresentationState.shared
14 |
15 | let deck: Deck
16 | let defaultResolution: DefaultResolution
17 | let showCamera: Bool
18 | let cameraConfig: CameraConfig
19 |
20 | @State var isFullScreen = false
21 |
22 | public init(deck: Deck, slideTransition: SlideTransition? = .horizontal, loop: Bool = false, defaultResolution: DefaultResolution = (width: 1920, height: 1080), showCamera: Bool = false, cameraConfig: CameraConfig = CameraConfig()) {
23 |
24 | self.deck = deck
25 | self.defaultResolution = defaultResolution
26 | self.showCamera = showCamera
27 | self.cameraConfig = cameraConfig
28 | self.presentationState.deck = deck
29 | self.presentationState.loop = loop
30 | self.presentationState.slideTransition = slideTransition
31 | }
32 |
33 | // If we can turn this into an environment variable or observable object, presenter notes and potentially controls become way easier?
34 | var slide: Slide? {
35 | let slides = self.deck.slides()
36 | if slides.count > presentationState.slideIndex {
37 | return slides[presentationState.slideIndex]
38 | } else {
39 | return nil
40 | }
41 | }
42 |
43 | func scaleAmount(_ width: Double, _ height: Double) -> Double {
44 | let widthScale = width / self.defaultResolution.width
45 | let heightScale = height / self.defaultResolution.height
46 |
47 | let defaultResolution = self.defaultResolution.width / self.defaultResolution.height
48 | let frameResolution = width / height
49 |
50 | if defaultResolution < frameResolution {
51 | return heightScale
52 | } else {
53 | return widthScale
54 | }
55 | }
56 | public var body: some View {
57 | #if canImport(UIKit)
58 | if isExternalDisplayConnected && !isAirplayDisplayVersion {
59 | PresenterNotesView()
60 | } else {
61 | self.presenterViewBody
62 | .onReceive(
63 | screenDidConnectPublisher,
64 | perform: screenDidConnect
65 | )
66 | .onReceive(
67 | screenDidDisconnectPublisher,
68 | perform: screenDidDisconnect
69 | )
70 | }
71 | #else
72 | self.presenterViewBody
73 | #endif
74 |
75 | }
76 | public var presenterViewBody: some View {
77 | GeometryReader { proxy in
78 | ZStack(alignment: .center) {
79 | (slide?.theme ?? deck.theme).background
80 |
81 | self.bodyContents
82 | .clipped()
83 | .frame(width: self.defaultResolution.0, height: self.defaultResolution.1, alignment: .center)
84 | .scaleEffect(self.scaleAmount(proxy.size.width, proxy.size.height), anchor: .center)
85 |
86 | if self.showCamera {
87 | ZStack(alignment: cameraConfig.alignment) {
88 | Camera()
89 | .frame(width: cameraConfig.size, height: cameraConfig.size)
90 | .clipShape(Circle())
91 | .padding(cameraConfig.padding)
92 |
93 | Color.clear // Make full width and height
94 | }.frame(maxWidth: .infinity, maxHeight: .infinity)
95 | .scaleEffect(self.scaleAmount(proxy.size.width, proxy.size.height), anchor: .center)
96 | }
97 | }.frame(minWidth: 100, maxWidth: .infinity, minHeight: 100, maxHeight: .infinity)
98 | }
99 | }
100 |
101 | private var bodyContents: some View {
102 | ZStack {
103 | if self.isFullScreen {
104 | VStack {
105 | SlideNavigationToolbarButtons()
106 | }.opacity(0)
107 | }
108 |
109 | ForEach(Array(self.deck.slides().enumerated()), id: \.offset) { index, slide in
110 |
111 | if index == presentationState.slideIndex {
112 | slide.buildView(theme: self.deck.theme)
113 | .frame(maxWidth: .infinity, maxHeight: .infinity)
114 | .zIndex(Double(presentationState.slideIndex))
115 | }
116 | }.transition(presentationState.activeTransition)
117 | }
118 | .navigationTitle(self.deck.title)
119 | #if canImport(AppKit)
120 | .if(!self.isFullScreen) {
121 | $0.toolbar {
122 | ToolbarItemGroup {
123 | SlideNavigationToolbarButtons()
124 | }
125 | }
126 | }
127 | .onReceive(NotificationCenter.default.publisher(for: NSWindow.willEnterFullScreenNotification)) { _ in
128 | self.isFullScreen = true
129 | }
130 | .onReceive(NotificationCenter.default.publisher(for: NSWindow.willExitFullScreenNotification)) { _ in
131 | self.isFullScreen = false
132 | }
133 | #elseif canImport(UIKit)
134 | .slideNavigationGestures()
135 | #endif
136 | }
137 |
138 |
139 | #if canImport(UIKit)
140 | private func externalView() -> Presenter {
141 | var selfCopy = self
142 | selfCopy.isAirplayDisplayVersion = true
143 | return selfCopy
144 | }
145 |
146 | var isAirplayDisplayVersion = false
147 |
148 | @State var isExternalDisplayConnected: Bool = (UIApplication.shared.connectedScenes.count > 1)
149 | @State var additionalWindows: [UIWindow] = []
150 |
151 | private var screenDidConnectPublisher: AnyPublisher {
152 | NotificationCenter.default
153 | .publisher(for: UIScreen.didConnectNotification)
154 | .compactMap { $0.object as? UIScreen }
155 | .receive(on: RunLoop.main)
156 | .eraseToAnyPublisher()
157 | }
158 |
159 | private var screenDidDisconnectPublisher: AnyPublisher {
160 | NotificationCenter.default
161 | .publisher(for: UIScreen.didDisconnectNotification)
162 | .compactMap { $0.object as? UIScreen }
163 | .receive(on: RunLoop.main)
164 | .eraseToAnyPublisher()
165 | }
166 |
167 | private func screenDidConnect(_ screen: UIScreen) {
168 | let window = UIWindow(frame: screen.bounds)
169 |
170 | window.windowScene = UIApplication.shared.connectedScenes
171 | .first { ($0 as? UIWindowScene)?.screen == screen }
172 | as? UIWindowScene
173 |
174 | let controller = UIHostingController(rootView: self.externalView())
175 | window.rootViewController = controller
176 | window.isHidden = false
177 | additionalWindows.append(window)
178 | self.isExternalDisplayConnected = true
179 | }
180 |
181 | private func screenDidDisconnect(_ screen: UIScreen) {
182 | additionalWindows.removeAll { $0.screen == screen }
183 | self.isExternalDisplayConnected = false
184 | }
185 | #endif
186 | }
187 |
188 |
189 | public enum SlideTransition {
190 | case horizontal, vertical
191 | }
192 |
193 | extension Optional where Wrapped == SlideTransition {
194 | var next: AnyTransition {
195 | switch self {
196 | case .horizontal:
197 | return .slideFromTrailing
198 | case .vertical:
199 | return .slideFromBottom
200 | case .none:
201 | return .identity
202 | }
203 | }
204 |
205 | var previous: AnyTransition {
206 | switch self {
207 | case .horizontal:
208 | return .slideFromLeading
209 | case .vertical:
210 | return .slideFromTop
211 | case .none:
212 | return .identity
213 | }
214 | }
215 | }
216 |
217 | public struct CameraConfig {
218 | let size: CGFloat
219 | let padding: CGFloat
220 | let alignment: Alignment
221 |
222 | public init(size: CGFloat = 300, padding: CGFloat = 40, alignment: Alignment = .bottomTrailing) {
223 | self.size = size
224 | self.padding = padding
225 | self.alignment = alignment
226 | }
227 | }
228 |
229 | extension AnyTransition {
230 | static var slideFromBottom: AnyTransition {
231 | AnyTransition.asymmetric(
232 | insertion: .move(edge: .bottom),
233 | removal: .move(edge: .top))
234 |
235 | }
236 |
237 | static var slideFromTop: AnyTransition {
238 | AnyTransition.asymmetric(
239 | insertion: .move(edge: .top),
240 | removal: .move(edge: .bottom))
241 |
242 | }
243 |
244 | static var slideFromTrailing: AnyTransition {
245 | AnyTransition.asymmetric(
246 | insertion: .move(edge: .trailing),
247 | removal: .move(edge: .leading))
248 |
249 | }
250 |
251 | static var slideFromLeading: AnyTransition {
252 | AnyTransition.asymmetric(
253 | insertion: .move(edge: .leading),
254 | removal: .move(edge: .trailing))
255 |
256 | }
257 | }
258 |
259 | extension View {
260 | @ViewBuilder func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
261 | if condition() {
262 | transform(self)
263 | } else {
264 | self
265 | }
266 | }
267 | }
268 |
269 | extension NSNotification.Name {
270 | static let keyUp = NSNotification.Name("presenter_pressed_up")
271 | static let keyDown = NSNotification.Name("presenter_pressed_down")
272 | static let slideChanged = NSNotification.Name("slide_changed")
273 | }
274 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ✨ DeckUI
2 |
3 | [](https://swiftpackageindex.com/joshdholtz/DeckUI)
4 | [](https://swiftpackageindex.com/joshdholtz/DeckUI)
5 | 
6 | [](https://swift.org/package-manager/)
7 | []([https://twitter.com/joshdholtz](https://twitter.com/joshdholtz))
8 |
9 | DeckUI is a Swift DSL (domain specific language) for writing slide decks in Xcode. It allows for quick creation of slides and content in a language and environment you are familiar with.
10 |
11 | But _why_?
12 |
13 | Well, I made this because:
14 | - I was bored on an airplane
15 | - Wanted to use this as a demo for future conference talk on Swift DSLs
16 | - Need something more customizable than markdown for writing slide decks and more codey than Keynote
17 |
18 | 👉 Watch [Introducing DeckUI - Write slide decks in Swift](https://www.youtube.com/watch?v=FraeH6OeJPY) on my YouTube channel for more explaination and full demo
19 |
20 | ## ✨ Features
21 |
22 | - [x] Create slide decks in pure Swift code
23 | - [x] Decks are presented in SwiftUI with `Presenter`
24 | - [x] Build decks without knowing any SwiftUI
25 | - With `Deck`, `Slide`, `Title`, `Words`, `Bullets`, `Media`, `Columns`
26 | - [x] Use `RawView` to drop any SwiftUI view
27 | - Fully interactable and great for demos
28 | - [x] Display code with `Code`
29 | - Use up and down arrows to highlight lines of code as your talking about them
30 | - [x] Support videos on `Media`
31 |
32 | ### 🐌 Future Features
33 |
34 | - [ ] Support iOS and maybe tvOS
35 | - [ ] Fix bug with `Media` remote image loading and slide transitions
36 | - [ ] Animations within a slide
37 | - [ ] More customization on `Words`
38 | - [ ] Nesting of `Bullets`
39 | - [ ] Syntax highlighting for `Code`
40 | - [ ] Documentation
41 | - [ ] More examples
42 |
43 | ## Simple Demo
44 |
45 | ```swift
46 | import SwiftUI
47 | import DeckUI
48 |
49 | struct ContentView: View {
50 | var body: some View {
51 | Presenter(deck: self.deck)
52 | }
53 | }
54 |
55 | extension ContentView {
56 | var deck: Deck {
57 | Deck(title: "SomeConf 2023") {
58 | Slide(alignment: .center) {
59 | Title("Welcome to DeckUI")
60 | }
61 |
62 | Slide {
63 | Title("Quick Demo")
64 | Columns {
65 | Column {
66 | Bullets {
67 | Words("Bullets")
68 | Words("Words")
69 | Words("Images")
70 | Words("Columns")
71 | }
72 | }
73 |
74 | Column {
75 | Media(.remoteImage(URL(string: "https://www.fillmurray.com/g/200/300")!))
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 | ```
83 |
84 | https://user-images.githubusercontent.com/401294/189043329-fe75161c-17c1-4471-8632-07fb79d26d1a.mov
85 |
86 | ## 💻 Installing
87 |
88 | ### Swift Package Manager
89 |
90 | - File > Swift Packages > Add Package Dependency
91 | - Add `https://github.com/joshdholtz/DeckUI.git`
92 | - Select "Up to Next Major" with "1.0.0"
93 |
94 | ## 🚀 Getting Started
95 |
96 | There are no official "Getting Started" docs yet 😅 But look at...
97 |
98 | - [Demo app](https://github.com/joshdholtz/DeckUI/blob/main/Examples/Demo/Shared/ContentView.swift) for usage
99 | - [Sources/DeckUI/DSL](https://github.com/joshdholtz/DeckUI/tree/main/Sources/DeckUI/DSL) for how the `Deck`, `Slide`, and all slide components are built
100 | - [Sources/DeckUI/Views](https://github.com/joshdholtz/DeckUI/tree/main/Sources/DeckUI/Views) for how `Presenter` is built
101 |
102 | ## 📖 Documentation
103 |
104 | 100% not documented yet but I'll get there 🤷♂️
105 |
106 | ## 🏎 Performance
107 |
108 | Probably bad and never production ready 😈 Please only use DeckUI for a single presentation and never at any scale.
109 |
110 | ## 👨💻 Contributing
111 |
112 | Yes please! I'm happy to discuss issues and review/merge pull requests 🙂 I will do my best to get to the but I am a dad, work at [RevenueCat](https://www.revenuecat.com/), and the lead maintainer of [fastlane](https://github.com/fastlane/fastlane) so I might not respond right away.
113 |
114 | ## 📚 Examples
115 |
116 | ### Slide
117 |
118 | `Slide` can be used without any parameters but can be given a custom `alignment`, `padding`, and `theme`.
119 |
120 | ```swift
121 | Slide {
122 | // Content
123 | }
124 | ```
125 |
126 | ```swift
127 | Slide(alignment: .center, padding: 80, theme: .white) {
128 | // Content
129 | }
130 | ```
131 |
132 | ### Title
133 |
134 | `Title` can be used by itself or with an optional `subtitle`. It was real similar to `Words` but larger.
135 |
136 | ```swift
137 | Slide(alignment: .center) {
138 | Title("Introducing...")
139 | }
140 | ```
141 |
142 | ```swift
143 | Slide {
144 | Title("Introduction", subtitle: "What is it?")
145 | // Content
146 | }
147 | ```
148 |
149 | ### Words
150 |
151 | `Words` are similar to what a textbox would be in Keynote, PowerPoint, or Google Slides. There will eventually be more style configurations for words.
152 |
153 | ```swift
154 | Slide(alignment: .center) {
155 | Title("Center alignment")
156 | Words("Slides can be center aligned")
157 | Words("And more words")
158 | }
159 | ```
160 |
161 | ### Bullets
162 |
163 | `Bullets` turns `Words` into a list. It takes an optional `style` parameter where you can choose between `.bullets` and `.dash`. `Bullets` cannot be nested yet but soon™️.
164 |
165 | ```swift
166 | Slide {
167 | Title("Introduction", subtitle: "What is it?")
168 | Bullets {
169 | Words("A custom Swift DSL to make slide decks")
170 | Words("Distributed as a Swift Package")
171 | Words("Develop your slide deck in Xcode with Swift")
172 | }
173 | }
174 | ```
175 |
176 | ```swift
177 | Slide {
178 | Title("Introduction", subtitle: "What is it?")
179 | Bullets(style: .dash) {
180 | Words("A custom Swift DSL to make slide decks")
181 | Words("Distributed as a Swift Package")
182 | Words("Develop your slide deck in Xcode with Swift")
183 | }
184 | }
185 | ```
186 |
187 | ### Media
188 |
189 | `Media` provides a few ways to display images from various source types. This will eventually support videos.
190 |
191 | ```swift
192 | Slide {
193 | Media(.assetImage("some-asset-name"))
194 | Media(.bundleImage("some-file-name.jpg"))
195 | Media(.remoteImage(URL(string: "http://placekitten.com/g/200/300"))!)
196 | }
197 | ```
198 |
199 | ### Columns
200 |
201 | `Columns` allow you to use one to infinte `Column`s. Put other slide content in `Column`.
202 |
203 | ```swift
204 | Slide {
205 | Title("Columns")
206 | Columns {
207 | Column {
208 | // Content
209 | }
210 |
211 | Column {
212 | // Content
213 | }
214 | }
215 | }
216 | ```
217 |
218 | ```swift
219 | Slide {
220 | Title("Columns")
221 | Columns {
222 | Column {
223 | // Content
224 | }
225 |
226 | Column {
227 | // Content
228 | }
229 |
230 | Column {
231 | // Content
232 | }
233 |
234 | Column {
235 | // Content
236 | }
237 | }
238 | }
239 | ```
240 |
241 | ### Code
242 |
243 | `Code` is a super specifi version `Words`. It will:
244 | - Display text as monospace
245 | - Scroll vertical if bigger than screen
246 | - Highlight lines of code when up and down arrows are pressed
247 |
248 | ```swift
249 | Slide {
250 | Code("""
251 | struct ContentView: View {
252 | var body: some View {
253 | Text("Hello slides")
254 | }
255 | }
256 | """)
257 | }
258 | ```
259 |
260 | ```swift
261 | Slide {
262 | Code("""
263 | struct ContentView: View {
264 | var body: some View {
265 | Text("Hello slides")
266 | }
267 | }
268 | """, , enableHighlight: false)
269 | }
270 | ```
271 |
272 | ### RawView
273 |
274 | Drop any SwiftUI view inside of `RawView`. Could be built-in SwiftUI views like `Text` or `Button` but can also be any custom SwiftUI view.
275 |
276 | ```swift
277 | Slide {
278 | RawView {
279 | CounterView()
280 | }
281 | }
282 |
283 | struct CounterView: View {
284 | @State var count = 0
285 |
286 | var body: some View {
287 | Button {
288 | self.count += 1
289 | } label: {
290 | Text("Press me - \(self.count)")
291 | .font(.system(size: 60))
292 | .padding(.horizontal, 40)
293 | .padding(.vertical, 20)
294 | .foregroundColor(.white)
295 | .overlay(
296 | RoundedRectangle(cornerRadius: 25)
297 | .stroke(Color.white, lineWidth: 2)
298 | )
299 | }.buttonStyle(.plain)
300 | }
301 | }
302 | ```
303 |
304 | ### Themes
305 |
306 | A `Theme` can be set in `Presenter` or individually on `Slide`. There are three default themes (`.dark`, `.black`, `.white`) but feel free to use your own.
307 |
308 | ```swift
309 | struct ContentView: View {
310 | var body: some View {
311 | Presenter(deck: self.deck, showCamera: true)
312 | }
313 | }
314 |
315 | extension Theme {
316 | public static let venonat: Theme = Theme(
317 | background: Color(hex: "#624a7b"),
318 | title: Foreground(
319 | color: Color(hex: "#ff5a5a"),
320 | font: Font.system(size: 80,
321 | weight: .bold,
322 | design: .default)
323 | ),
324 | subtitle: Foreground(
325 | color: Color(hex: "#a48bbd"),
326 | font: Font.system(size: 50,
327 | weight: .light,
328 | design: .default).italic()
329 | ),
330 | body: Foreground(
331 | color: Color(hex: "#FFFFFF"),
332 | font: Font.system(size: 50,
333 | weight: .regular,
334 | design: .default)
335 | ),
336 | code: Foreground(
337 | color: Color(hex: "#FFFFFF"),
338 | font: Font.system(size: 26,
339 | weight: .regular,
340 | design: .monospaced)
341 | ),
342 | codeHighlighted: (Color(hex: "#312952"), Foreground(
343 | color: Color(hex: "#FFFFFF"),
344 | font: Font.system(size: 26,
345 | weight: .heavy,
346 | design: .monospaced)
347 | ))
348 | )
349 | }
350 | ```
351 |
352 | ## DeckUI in the real world
353 |
354 | 1. [FullQueueDeveloper](https://github.com/FullQueueDeveloper) presents "Pushing to the App Store using Swift" with [Swish](https://github.com/FullQueueDeveloper/Swish) & [Sh](https://github.com/FullQueueDeveloper/Sh) at GDG Omaha! [Watch the YouTube recording](https://www.youtube.com/watch?v=SxozYjWMhgU) of the presentation, and [checkout the source code on GitHub](https://github.com/FullQueueDeveloper/Deck-2022-09-13).
355 |
--------------------------------------------------------------------------------
/Examples/Demo/Shared/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Shared
4 | //
5 | // Created by Josh Holtz on 8/30/22.
6 | //
7 |
8 | import SwiftUI
9 | import DeckUI
10 |
11 | struct ContentView: View {
12 | var body: some View {
13 | Presenter(deck: self.deck, showCamera: true)
14 | }
15 | }
16 |
17 | extension ContentView {
18 | var deck: Deck {
19 | Deck(title: "DeckUI Demo") {
20 | Slide(alignment: .center, comment: "Here are some presenter notes") {
21 | Title("Introducing...")
22 | }
23 |
24 | Slide(alignment: .center) {
25 | RawView {
26 | Text("DeckUI")
27 | .font(.system(size: 200, weight: .bold, design: .monospaced))
28 | .foregroundColor(.white)
29 | .padding(60)
30 | .border(.white.opacity(0.5), width: 20)
31 | }
32 | }
33 |
34 | Slide {
35 | Title("Introduction", subtitle: "What is it?")
36 | Bullets(style: .bullet) {
37 | Words("A custom Swift DSL to make slide decks")
38 | Words("Distributed as a Swift Package")
39 | Words("Develop your slide deck in Xcode with Swift")
40 | }
41 | }
42 |
43 | Slide(alignment: .center, comment: "The presenter notes are back!") {
44 | Title("But why?")
45 | }
46 |
47 | Slide {
48 | Title("But why?", subtitle: "Because I can")
49 | Bullets(style: .bullet) {
50 | Words("Bored on a plane ride")
51 | Words("Future talk on writing Swift DSLs")
52 | Words("Markdown 👉 Swift 👈 Keynote")
53 | }
54 | }
55 |
56 | Slide(alignment: .center) {
57 | Title("What's all possible?")
58 | }
59 |
60 | Slide(alignment: .center) {
61 | Title("Center alignment")
62 | Words("Slides can be center aligned")
63 | }
64 |
65 | Slide(alignment: .top) {
66 | Title("Top alignment")
67 | Words("Slides also be top aligned")
68 | }
69 |
70 | Slide {
71 | Title("Images")
72 | Media(.bundleImage("bill-murray.jpeg"))
73 | }
74 |
75 | Slide {
76 | Title("Videos")
77 | Media(.bundleVideo("big-buck-bunny.mp4"))
78 | }
79 |
80 | Slide {
81 | Title("Multiple Columns", subtitle: "1, 2, or more")
82 |
83 | Columns {
84 | Column {
85 | Bullets(style: .bullet) {
86 | Words("Bill")
87 | Words("Murray")
88 | Words("Image")
89 | Words("👉")
90 | }
91 | }
92 |
93 | Column {
94 | Media(.assetImage("murray"))
95 | }
96 | }
97 | }
98 |
99 | Slide {
100 | Title("3 Bill Muarries", subtitle: "Don't know the plural of Murray")
101 |
102 | Columns {
103 | Column {
104 | Media(.assetImage("murray"))
105 | }
106 |
107 | Column {
108 | Media(.assetImage("murray"))
109 | }
110 |
111 | Column {
112 | Media(.assetImage("murray"))
113 | }
114 | }
115 | }
116 |
117 | Slide {
118 | Title("Code Blocks", subtitle: "")
119 | Columns {
120 | Column {
121 | Code(.swift) {
122 | """
123 | struct CounterView: View {
124 | @State var count = 0
125 |
126 | var body: some View {
127 | Button {
128 | self.count += 1
129 | } label: {
130 | Text("Press me - \\(self.count)")
131 | .font(.system(size: 60))
132 | .padding(.horizontal, 40)
133 | .padding(.vertical, 20)
134 | .foregroundColor(.white)
135 | .overlay(
136 | RoundedRectangle(cornerRadius: 25)
137 | .stroke(Color.white, lineWidth: 2)
138 | )
139 | }.buttonStyle(.plain)
140 | }
141 | }
142 | """
143 | }
144 | }
145 | Column {
146 | Bullets {
147 | Words("Press up and down arrows")
148 | Words("You can highlight lines")
149 | }
150 | }
151 | }
152 | }
153 |
154 | Slide {
155 | Title("Drop in any SwiftUI view", subtitle: "Do anything you want")
156 |
157 | Columns {
158 | Column {
159 | RawView {
160 | CounterView()
161 | }
162 | }
163 |
164 | Column {
165 | Code(.swift) {
166 | """
167 | struct CounterView: View {
168 | @State var count = 0
169 |
170 | var body: some View {
171 | Button {
172 | self.count += 1
173 | } label: {
174 | Text("Press me - \\(self.count)")
175 | .font(.system(size: 60))
176 | .padding(.horizontal, 40)
177 | .padding(.vertical, 20)
178 | .foregroundColor(.white)
179 | .overlay(
180 | RoundedRectangle(cornerRadius: 25)
181 | .stroke(Color.white, lineWidth: 2)
182 | )
183 | }.buttonStyle(.plain)
184 | }
185 | }
186 | """
187 | }
188 | }
189 | }
190 | }
191 |
192 | Slide(alignment: .center) {
193 | Title("Quick tutorial")
194 | }
195 |
196 | Slide {
197 | Title("Make Deck like...", subtitle: "Super simple")
198 | Columns {
199 | Column {
200 | Code(.swift) {
201 | """
202 | import SwiftUI
203 | import DeckUI
204 |
205 | struct ContentView: View {
206 | var body: some View {
207 | Presenter(deck: self.deck)
208 | }
209 | }
210 |
211 | extension ContentView {
212 | var deck: Deck {
213 | Deck(title: "SomeConf 2023") {
214 | Slide(alignment: .center) {
215 | Title("Welcome to DeckUI")
216 | }
217 |
218 | Slide {
219 | Title("Slide 1")
220 | Words("Some useful content")
221 | }
222 | }
223 | }
224 | }
225 | """
226 | }
227 | }
228 |
229 | Column {
230 | Bullets(style: .bullet) {
231 | Words("Create a `Deck` with multiple `Slide` ")
232 | Words("Create `Presenter` and give a deck")
233 | Words("`Presenter` is a SwiftUI to present a `Deck`")
234 | }
235 | }
236 | }
237 | }
238 |
239 | Slide(theme: .venonat) {
240 | Title("Change theme", subtitle: "On Deck or Slide")
241 | Columns {
242 | Column {
243 | Code(.swift) {
244 | """
245 | extension Theme {
246 | public static let venonat: Theme = Theme(
247 | background: Color(hex: "#624a7b"),
248 | title: Foreground(
249 | color: Color(hex: "#ff5a5a"),
250 | font: Font.system(size: 80,
251 | weight: .bold,
252 | design: .default)
253 | ),
254 | subtitle: Foreground(
255 | color: Color(hex: "#a48bbd"),
256 | font: Font.system(size: 50,
257 | weight: .light,
258 | design: .default).italic()
259 | ),
260 | body: Foreground(
261 | color: Color(hex: "#FFFFFF"),
262 | font: Font.system(size: 50,
263 | weight: .regular,
264 | design: .default)
265 | ),
266 | code: Foreground(
267 | color: Color(hex: "#FFFFFF"),
268 | font: Font.system(size: 26,
269 | weight: .regular,
270 | design: .monospaced)
271 | ),
272 | codeHighlighted: (Color(hex: "#312952"), Foreground(
273 | color: Color(hex: "#FFFFFF"),
274 | font: Font.system(size: 26,
275 | weight: .heavy,
276 | design: .monospaced)
277 | ))
278 | )
279 | }
280 | """
281 | }
282 | }
283 |
284 | Column {
285 | Code(.swift, enableHighlight: false) {
286 | """
287 | // Set theme on presenter
288 | var body: some View {
289 | Presenter(deck: self.deck, theme: .venonat)
290 | }
291 |
292 | // Or on individual slide
293 | Slide(theme: .venonat) {
294 | Title("Some slide")
295 | }
296 | """
297 | }
298 | }
299 | }
300 | }
301 |
302 | Slide {
303 | Title("Bullets", subtitle: "")
304 | Columns {
305 | Column {
306 | Code(.swift) {
307 | """
308 | Slide {
309 | Bullets {
310 | Words("")
311 | Words("")
312 | Words("")
313 | }
314 |
315 | Bullets(style: .dash) {
316 | Words("")
317 | Words("")
318 | Words("")
319 | }
320 | }
321 | """
322 | }
323 | }
324 |
325 | Column {
326 | Bullets {
327 | Words("Bullets take `Words`")
328 | Words("Default to circle/bullet")
329 | Words("Can change style")
330 | }
331 | }
332 | }
333 | }
334 |
335 | Slide {
336 | Title("Media", subtitle: "")
337 | Columns {
338 | Column {
339 | Code(.swift) {
340 | """
341 | Slide {
342 | Media(.assetImage(""))
343 | Media(.bundleImage(""))
344 | Media(.remoteImage(URL(string: ""))!)
345 | }
346 | """
347 | }
348 | }
349 |
350 | Column {
351 | Bullets {
352 | Words("Three media types")
353 | Words("Currently all images")
354 | Words("Video coming soon")
355 | }
356 | }
357 | }
358 | }
359 |
360 | Slide {
361 | Title("Columns", subtitle: "")
362 | Columns {
363 | Column {
364 | Code(.swift) {
365 | """
366 | Slide {
367 | Code(.swift) {
368 | \"\"\"
369 | Columns {
370 | Column {
371 | Bullets {
372 | Words("Left")
373 | Words("is")
374 | Words("cool")
375 | }
376 | }
377 | Column {
378 | Bullets {
379 | Words("Right")
380 | Words("is")
381 | Words("cooler")
382 | }
383 | }
384 | }
385 | \"\"\"
386 | }
387 | }
388 | """
389 | }
390 | }
391 |
392 | Column {
393 | Bullets {
394 | Words("Split slide into 1 to many columns")
395 | Words("No more explaination needed")
396 | }
397 | }
398 | }
399 | }
400 |
401 | Slide {
402 | Title("Code", subtitle: "")
403 | Columns {
404 | Column {
405 | Code(.swift) {
406 | """
407 | Slide {
408 | Code(.swift) {
409 | \"\"\"
410 | Columns {
411 | Column {
412 | Bullets {
413 | Words("Left")
414 | Words("is")
415 | Words("cool")
416 | }
417 | }
418 | Column {
419 | Bullets {
420 | Words("Right")
421 | Words("is")
422 | Words("cooler")
423 | }
424 | }
425 | }
426 | \"\"\"
427 | }
428 | }
429 | """
430 | }
431 | }
432 |
433 | Column {
434 | Bullets {
435 | Words("Easily drop in any code")
436 | Words("Up and down highlight lines")
437 | Words("Syntax highlight")
438 | }
439 | }
440 | }
441 | }
442 |
443 | Slide {
444 | Title("RawView", subtitle: "Power is all yours")
445 | Columns {
446 | Column {
447 | Code(.swift) {
448 | """
449 | Slide(alignment: .center) {
450 | RawView {
451 | Text("DeckUI")
452 | .font(.system(size: 200, weight: .bold, design: .monospaced))
453 | .foregroundColor(.white)
454 | .padding(60)
455 | .border(.white.opacity(0.5), width: 20)
456 | }
457 | }
458 | """
459 | }
460 | }
461 |
462 | Column {
463 | Bullets {
464 | Words("Put any SwiftUI view in `RawView`")
465 | Words("Native view or custom view")
466 | Words("Great for showing SwiftUI, WeatherKit, or any API or SDK examples")
467 | }
468 | }
469 | }
470 | }
471 |
472 | Slide(alignment: .center) {
473 | RawView {
474 | Text("https://github.com/joshdholtz/deckui")
475 | .underline()
476 | .font(.system(size: 60, weight: .bold, design: .monospaced))
477 | .foregroundColor(.white)
478 | }
479 | }
480 | }
481 | }
482 | }
483 |
484 | extension Theme {
485 | public static let venonat: Theme = Theme(
486 | background: Color(hex: "#624a7b"),
487 | title: Foreground(
488 | color: Color(hex: "#ff5a5a"),
489 | font: Font.system(size: 80,
490 | weight: .bold,
491 | design: .default)
492 | ),
493 | subtitle: Foreground(
494 | color: Color(hex: "#a48bbd"),
495 | font: Font.system(size: 50,
496 | weight: .light,
497 | design: .default).italic()
498 | ),
499 | body: Foreground(
500 | color: Color(hex: "#FFFFFF"),
501 | font: Font.system(size: 50,
502 | weight: .regular,
503 | design: .default)
504 | ),
505 | code: Foreground(
506 | color: Color(hex: "#FFFFFF"),
507 | font: Font.system(size: 26,
508 | weight: .regular,
509 | design: .monospaced)
510 | ),
511 | codeHighlighted: (Color(hex: "#312952"), Foreground(
512 | color: Color(hex: "#FFFFFF"),
513 | font: Font.system(size: 26,
514 | weight: .heavy,
515 | design: .monospaced)
516 | ))
517 | )
518 | }
519 |
520 | struct CounterView: View {
521 | @State var count = 0
522 |
523 | var body: some View {
524 | Button {
525 | self.count += 1
526 | } label: {
527 | Text("Press me - \(self.count)")
528 | .font(.system(size: 60))
529 | .padding(.horizontal, 40)
530 | .padding(.vertical, 20)
531 | .foregroundColor(.white)
532 | .overlay(
533 | RoundedRectangle(cornerRadius: 25)
534 | .stroke(Color.white, lineWidth: 2)
535 | )
536 | }.buttonStyle(.plain)
537 | }
538 | }
539 |
--------------------------------------------------------------------------------
/Examples/Demo/Demo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2C11907328BE84350038D4B9 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11907228BE84350038D4B9 /* Tests_iOS.swift */; };
11 | 2C11907528BE84350038D4B9 /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11907428BE84350038D4B9 /* Tests_iOSLaunchTests.swift */; };
12 | 2C11907F28BE84350038D4B9 /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11907E28BE84350038D4B9 /* Tests_macOS.swift */; };
13 | 2C11908128BE84350038D4B9 /* Tests_macOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11908028BE84350038D4B9 /* Tests_macOSLaunchTests.swift */; };
14 | 2C11908228BE84350038D4B9 /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11905A28BE84350038D4B9 /* DemoApp.swift */; };
15 | 2C11908328BE84350038D4B9 /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11905A28BE84350038D4B9 /* DemoApp.swift */; };
16 | 2C11908428BE84350038D4B9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11905B28BE84350038D4B9 /* ContentView.swift */; };
17 | 2C11908528BE84350038D4B9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C11905B28BE84350038D4B9 /* ContentView.swift */; };
18 | 2C11908628BE84350038D4B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C11905C28BE84350038D4B9 /* Assets.xcassets */; };
19 | 2C11908728BE84350038D4B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C11905C28BE84350038D4B9 /* Assets.xcassets */; };
20 | 2C1190C128C1A7030038D4B9 /* bill-murray.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 2C1190C028C1A7030038D4B9 /* bill-murray.jpeg */; };
21 | 2C1190C228C1A82D0038D4B9 /* bill-murray.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 2C1190C028C1A7030038D4B9 /* bill-murray.jpeg */; };
22 | 377A9A5828CA76D600AAA2E2 /* DeckUI in Frameworks */ = {isa = PBXBuildFile; productRef = 377A9A5728CA76D600AAA2E2 /* DeckUI */; };
23 | 377A9A5A28CA76DE00AAA2E2 /* DeckUI in Frameworks */ = {isa = PBXBuildFile; productRef = 377A9A5928CA76DE00AAA2E2 /* DeckUI */; };
24 | 37E2DBC828CA9A61006F664D /* big-buck-bunny.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 37E2DBC728CA9A61006F664D /* big-buck-bunny.mp4 */; };
25 | 37E2DBC928CA9A61006F664D /* big-buck-bunny.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 37E2DBC728CA9A61006F664D /* big-buck-bunny.mp4 */; };
26 | /* End PBXBuildFile section */
27 |
28 | /* Begin PBXContainerItemProxy section */
29 | 2C11906F28BE84350038D4B9 /* PBXContainerItemProxy */ = {
30 | isa = PBXContainerItemProxy;
31 | containerPortal = 2C11905528BE84340038D4B9 /* Project object */;
32 | proxyType = 1;
33 | remoteGlobalIDString = 2C11906028BE84350038D4B9;
34 | remoteInfo = "Demo (iOS)";
35 | };
36 | 2C11907B28BE84350038D4B9 /* PBXContainerItemProxy */ = {
37 | isa = PBXContainerItemProxy;
38 | containerPortal = 2C11905528BE84340038D4B9 /* Project object */;
39 | proxyType = 1;
40 | remoteGlobalIDString = 2C11906628BE84350038D4B9;
41 | remoteInfo = "Demo (macOS)";
42 | };
43 | /* End PBXContainerItemProxy section */
44 |
45 | /* Begin PBXFileReference section */
46 | 2C11905A28BE84350038D4B9 /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; };
47 | 2C11905B28BE84350038D4B9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
48 | 2C11905C28BE84350038D4B9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
49 | 2C11906128BE84350038D4B9 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 2C11906728BE84350038D4B9 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
51 | 2C11906928BE84350038D4B9 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; };
52 | 2C11906E28BE84350038D4B9 /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
53 | 2C11907228BE84350038D4B9 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; };
54 | 2C11907428BE84350038D4B9 /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = ""; };
55 | 2C11907A28BE84350038D4B9 /* Tests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
56 | 2C11907E28BE84350038D4B9 /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; };
57 | 2C11908028BE84350038D4B9 /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = ""; };
58 | 2C1190A628BE84D00038D4B9 /* DeckUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DeckUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
59 | 2C1190A828BE84D30038D4B9 /* DeckUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DeckUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
60 | 2C1190C028C1A7030038D4B9 /* bill-murray.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "bill-murray.jpeg"; sourceTree = ""; };
61 | 377A9A5628CA76AC00AAA2E2 /* DeckUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DeckUI; path = ../..; sourceTree = ""; };
62 | 37E2DBC728CA9A61006F664D /* big-buck-bunny.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "big-buck-bunny.mp4"; sourceTree = ""; };
63 | /* End PBXFileReference section */
64 |
65 | /* Begin PBXFrameworksBuildPhase section */
66 | 2C11905E28BE84350038D4B9 /* Frameworks */ = {
67 | isa = PBXFrameworksBuildPhase;
68 | buildActionMask = 2147483647;
69 | files = (
70 | 377A9A5A28CA76DE00AAA2E2 /* DeckUI in Frameworks */,
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | 2C11906428BE84350038D4B9 /* Frameworks */ = {
75 | isa = PBXFrameworksBuildPhase;
76 | buildActionMask = 2147483647;
77 | files = (
78 | 377A9A5828CA76D600AAA2E2 /* DeckUI in Frameworks */,
79 | );
80 | runOnlyForDeploymentPostprocessing = 0;
81 | };
82 | 2C11906B28BE84350038D4B9 /* Frameworks */ = {
83 | isa = PBXFrameworksBuildPhase;
84 | buildActionMask = 2147483647;
85 | files = (
86 | );
87 | runOnlyForDeploymentPostprocessing = 0;
88 | };
89 | 2C11907728BE84350038D4B9 /* Frameworks */ = {
90 | isa = PBXFrameworksBuildPhase;
91 | buildActionMask = 2147483647;
92 | files = (
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | /* End PBXFrameworksBuildPhase section */
97 |
98 | /* Begin PBXGroup section */
99 | 2C11905428BE84340038D4B9 = {
100 | isa = PBXGroup;
101 | children = (
102 | 377A9A5528CA76AC00AAA2E2 /* Packages */,
103 | 2C11905928BE84350038D4B9 /* Shared */,
104 | 2C1190BF28C1A7030038D4B9 /* Media */,
105 | 2C11906828BE84350038D4B9 /* macOS */,
106 | 2C11907128BE84350038D4B9 /* Tests iOS */,
107 | 2C11907D28BE84350038D4B9 /* Tests macOS */,
108 | 2C11906228BE84350038D4B9 /* Products */,
109 | 2C1190A528BE84D00038D4B9 /* Frameworks */,
110 | );
111 | sourceTree = "";
112 | };
113 | 2C11905928BE84350038D4B9 /* Shared */ = {
114 | isa = PBXGroup;
115 | children = (
116 | 2C11905A28BE84350038D4B9 /* DemoApp.swift */,
117 | 2C11905B28BE84350038D4B9 /* ContentView.swift */,
118 | 2C11905C28BE84350038D4B9 /* Assets.xcassets */,
119 | );
120 | path = Shared;
121 | sourceTree = "";
122 | };
123 | 2C11906228BE84350038D4B9 /* Products */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 2C11906128BE84350038D4B9 /* Demo.app */,
127 | 2C11906728BE84350038D4B9 /* Demo.app */,
128 | 2C11906E28BE84350038D4B9 /* Tests iOS.xctest */,
129 | 2C11907A28BE84350038D4B9 /* Tests macOS.xctest */,
130 | );
131 | name = Products;
132 | sourceTree = "";
133 | };
134 | 2C11906828BE84350038D4B9 /* macOS */ = {
135 | isa = PBXGroup;
136 | children = (
137 | 2C11906928BE84350038D4B9 /* macOS.entitlements */,
138 | );
139 | path = macOS;
140 | sourceTree = "";
141 | };
142 | 2C11907128BE84350038D4B9 /* Tests iOS */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 2C11907228BE84350038D4B9 /* Tests_iOS.swift */,
146 | 2C11907428BE84350038D4B9 /* Tests_iOSLaunchTests.swift */,
147 | );
148 | path = "Tests iOS";
149 | sourceTree = "";
150 | };
151 | 2C11907D28BE84350038D4B9 /* Tests macOS */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 2C11907E28BE84350038D4B9 /* Tests_macOS.swift */,
155 | 2C11908028BE84350038D4B9 /* Tests_macOSLaunchTests.swift */,
156 | );
157 | path = "Tests macOS";
158 | sourceTree = "";
159 | };
160 | 2C1190A528BE84D00038D4B9 /* Frameworks */ = {
161 | isa = PBXGroup;
162 | children = (
163 | 2C1190A828BE84D30038D4B9 /* DeckUI.framework */,
164 | 2C1190A628BE84D00038D4B9 /* DeckUI.framework */,
165 | );
166 | name = Frameworks;
167 | sourceTree = "";
168 | };
169 | 2C1190BF28C1A7030038D4B9 /* Media */ = {
170 | isa = PBXGroup;
171 | children = (
172 | 37E2DBC728CA9A61006F664D /* big-buck-bunny.mp4 */,
173 | 2C1190C028C1A7030038D4B9 /* bill-murray.jpeg */,
174 | );
175 | path = Media;
176 | sourceTree = "";
177 | };
178 | 377A9A5528CA76AC00AAA2E2 /* Packages */ = {
179 | isa = PBXGroup;
180 | children = (
181 | 377A9A5628CA76AC00AAA2E2 /* DeckUI */,
182 | );
183 | name = Packages;
184 | sourceTree = "";
185 | };
186 | /* End PBXGroup section */
187 |
188 | /* Begin PBXNativeTarget section */
189 | 2C11906028BE84350038D4B9 /* Demo (iOS) */ = {
190 | isa = PBXNativeTarget;
191 | buildConfigurationList = 2C11908A28BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Demo (iOS)" */;
192 | buildPhases = (
193 | 2C11905D28BE84350038D4B9 /* Sources */,
194 | 2C11905E28BE84350038D4B9 /* Frameworks */,
195 | 2C11905F28BE84350038D4B9 /* Resources */,
196 | );
197 | buildRules = (
198 | );
199 | dependencies = (
200 | );
201 | name = "Demo (iOS)";
202 | packageProductDependencies = (
203 | 377A9A5928CA76DE00AAA2E2 /* DeckUI */,
204 | );
205 | productName = "Demo (iOS)";
206 | productReference = 2C11906128BE84350038D4B9 /* Demo.app */;
207 | productType = "com.apple.product-type.application";
208 | };
209 | 2C11906628BE84350038D4B9 /* Demo (macOS) */ = {
210 | isa = PBXNativeTarget;
211 | buildConfigurationList = 2C11908D28BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Demo (macOS)" */;
212 | buildPhases = (
213 | 2C11906328BE84350038D4B9 /* Sources */,
214 | 2C11906428BE84350038D4B9 /* Frameworks */,
215 | 2C11906528BE84350038D4B9 /* Resources */,
216 | );
217 | buildRules = (
218 | );
219 | dependencies = (
220 | );
221 | name = "Demo (macOS)";
222 | packageProductDependencies = (
223 | 377A9A5728CA76D600AAA2E2 /* DeckUI */,
224 | );
225 | productName = "Demo (macOS)";
226 | productReference = 2C11906728BE84350038D4B9 /* Demo.app */;
227 | productType = "com.apple.product-type.application";
228 | };
229 | 2C11906D28BE84350038D4B9 /* Tests iOS */ = {
230 | isa = PBXNativeTarget;
231 | buildConfigurationList = 2C11909028BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Tests iOS" */;
232 | buildPhases = (
233 | 2C11906A28BE84350038D4B9 /* Sources */,
234 | 2C11906B28BE84350038D4B9 /* Frameworks */,
235 | 2C11906C28BE84350038D4B9 /* Resources */,
236 | );
237 | buildRules = (
238 | );
239 | dependencies = (
240 | 2C11907028BE84350038D4B9 /* PBXTargetDependency */,
241 | );
242 | name = "Tests iOS";
243 | productName = "Tests iOS";
244 | productReference = 2C11906E28BE84350038D4B9 /* Tests iOS.xctest */;
245 | productType = "com.apple.product-type.bundle.ui-testing";
246 | };
247 | 2C11907928BE84350038D4B9 /* Tests macOS */ = {
248 | isa = PBXNativeTarget;
249 | buildConfigurationList = 2C11909328BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Tests macOS" */;
250 | buildPhases = (
251 | 2C11907628BE84350038D4B9 /* Sources */,
252 | 2C11907728BE84350038D4B9 /* Frameworks */,
253 | 2C11907828BE84350038D4B9 /* Resources */,
254 | );
255 | buildRules = (
256 | );
257 | dependencies = (
258 | 2C11907C28BE84350038D4B9 /* PBXTargetDependency */,
259 | );
260 | name = "Tests macOS";
261 | productName = "Tests macOS";
262 | productReference = 2C11907A28BE84350038D4B9 /* Tests macOS.xctest */;
263 | productType = "com.apple.product-type.bundle.ui-testing";
264 | };
265 | /* End PBXNativeTarget section */
266 |
267 | /* Begin PBXProject section */
268 | 2C11905528BE84340038D4B9 /* Project object */ = {
269 | isa = PBXProject;
270 | attributes = {
271 | BuildIndependentTargetsInParallel = 1;
272 | LastSwiftUpdateCheck = 1340;
273 | LastUpgradeCheck = 1340;
274 | TargetAttributes = {
275 | 2C11906028BE84350038D4B9 = {
276 | CreatedOnToolsVersion = 13.4.1;
277 | };
278 | 2C11906628BE84350038D4B9 = {
279 | CreatedOnToolsVersion = 13.4.1;
280 | };
281 | 2C11906D28BE84350038D4B9 = {
282 | CreatedOnToolsVersion = 13.4.1;
283 | TestTargetID = 2C11906028BE84350038D4B9;
284 | };
285 | 2C11907928BE84350038D4B9 = {
286 | CreatedOnToolsVersion = 13.4.1;
287 | TestTargetID = 2C11906628BE84350038D4B9;
288 | };
289 | };
290 | };
291 | buildConfigurationList = 2C11905828BE84340038D4B9 /* Build configuration list for PBXProject "Demo" */;
292 | compatibilityVersion = "Xcode 13.0";
293 | developmentRegion = en;
294 | hasScannedForEncodings = 0;
295 | knownRegions = (
296 | en,
297 | Base,
298 | );
299 | mainGroup = 2C11905428BE84340038D4B9;
300 | productRefGroup = 2C11906228BE84350038D4B9 /* Products */;
301 | projectDirPath = "";
302 | projectRoot = "";
303 | targets = (
304 | 2C11906028BE84350038D4B9 /* Demo (iOS) */,
305 | 2C11906628BE84350038D4B9 /* Demo (macOS) */,
306 | 2C11906D28BE84350038D4B9 /* Tests iOS */,
307 | 2C11907928BE84350038D4B9 /* Tests macOS */,
308 | );
309 | };
310 | /* End PBXProject section */
311 |
312 | /* Begin PBXResourcesBuildPhase section */
313 | 2C11905F28BE84350038D4B9 /* Resources */ = {
314 | isa = PBXResourcesBuildPhase;
315 | buildActionMask = 2147483647;
316 | files = (
317 | 2C1190C128C1A7030038D4B9 /* bill-murray.jpeg in Resources */,
318 | 2C11908628BE84350038D4B9 /* Assets.xcassets in Resources */,
319 | 37E2DBC828CA9A61006F664D /* big-buck-bunny.mp4 in Resources */,
320 | );
321 | runOnlyForDeploymentPostprocessing = 0;
322 | };
323 | 2C11906528BE84350038D4B9 /* Resources */ = {
324 | isa = PBXResourcesBuildPhase;
325 | buildActionMask = 2147483647;
326 | files = (
327 | 2C1190C228C1A82D0038D4B9 /* bill-murray.jpeg in Resources */,
328 | 2C11908728BE84350038D4B9 /* Assets.xcassets in Resources */,
329 | 37E2DBC928CA9A61006F664D /* big-buck-bunny.mp4 in Resources */,
330 | );
331 | runOnlyForDeploymentPostprocessing = 0;
332 | };
333 | 2C11906C28BE84350038D4B9 /* Resources */ = {
334 | isa = PBXResourcesBuildPhase;
335 | buildActionMask = 2147483647;
336 | files = (
337 | );
338 | runOnlyForDeploymentPostprocessing = 0;
339 | };
340 | 2C11907828BE84350038D4B9 /* Resources */ = {
341 | isa = PBXResourcesBuildPhase;
342 | buildActionMask = 2147483647;
343 | files = (
344 | );
345 | runOnlyForDeploymentPostprocessing = 0;
346 | };
347 | /* End PBXResourcesBuildPhase section */
348 |
349 | /* Begin PBXSourcesBuildPhase section */
350 | 2C11905D28BE84350038D4B9 /* Sources */ = {
351 | isa = PBXSourcesBuildPhase;
352 | buildActionMask = 2147483647;
353 | files = (
354 | 2C11908428BE84350038D4B9 /* ContentView.swift in Sources */,
355 | 2C11908228BE84350038D4B9 /* DemoApp.swift in Sources */,
356 | );
357 | runOnlyForDeploymentPostprocessing = 0;
358 | };
359 | 2C11906328BE84350038D4B9 /* Sources */ = {
360 | isa = PBXSourcesBuildPhase;
361 | buildActionMask = 2147483647;
362 | files = (
363 | 2C11908528BE84350038D4B9 /* ContentView.swift in Sources */,
364 | 2C11908328BE84350038D4B9 /* DemoApp.swift in Sources */,
365 | );
366 | runOnlyForDeploymentPostprocessing = 0;
367 | };
368 | 2C11906A28BE84350038D4B9 /* Sources */ = {
369 | isa = PBXSourcesBuildPhase;
370 | buildActionMask = 2147483647;
371 | files = (
372 | 2C11907528BE84350038D4B9 /* Tests_iOSLaunchTests.swift in Sources */,
373 | 2C11907328BE84350038D4B9 /* Tests_iOS.swift in Sources */,
374 | );
375 | runOnlyForDeploymentPostprocessing = 0;
376 | };
377 | 2C11907628BE84350038D4B9 /* Sources */ = {
378 | isa = PBXSourcesBuildPhase;
379 | buildActionMask = 2147483647;
380 | files = (
381 | 2C11908128BE84350038D4B9 /* Tests_macOSLaunchTests.swift in Sources */,
382 | 2C11907F28BE84350038D4B9 /* Tests_macOS.swift in Sources */,
383 | );
384 | runOnlyForDeploymentPostprocessing = 0;
385 | };
386 | /* End PBXSourcesBuildPhase section */
387 |
388 | /* Begin PBXTargetDependency section */
389 | 2C11907028BE84350038D4B9 /* PBXTargetDependency */ = {
390 | isa = PBXTargetDependency;
391 | target = 2C11906028BE84350038D4B9 /* Demo (iOS) */;
392 | targetProxy = 2C11906F28BE84350038D4B9 /* PBXContainerItemProxy */;
393 | };
394 | 2C11907C28BE84350038D4B9 /* PBXTargetDependency */ = {
395 | isa = PBXTargetDependency;
396 | target = 2C11906628BE84350038D4B9 /* Demo (macOS) */;
397 | targetProxy = 2C11907B28BE84350038D4B9 /* PBXContainerItemProxy */;
398 | };
399 | /* End PBXTargetDependency section */
400 |
401 | /* Begin XCBuildConfiguration section */
402 | 2C11908828BE84350038D4B9 /* Debug */ = {
403 | isa = XCBuildConfiguration;
404 | buildSettings = {
405 | ALWAYS_SEARCH_USER_PATHS = NO;
406 | CLANG_ANALYZER_NONNULL = YES;
407 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
408 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
409 | CLANG_ENABLE_MODULES = YES;
410 | CLANG_ENABLE_OBJC_ARC = YES;
411 | CLANG_ENABLE_OBJC_WEAK = YES;
412 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
413 | CLANG_WARN_BOOL_CONVERSION = YES;
414 | CLANG_WARN_COMMA = YES;
415 | CLANG_WARN_CONSTANT_CONVERSION = YES;
416 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
418 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
419 | CLANG_WARN_EMPTY_BODY = YES;
420 | CLANG_WARN_ENUM_CONVERSION = YES;
421 | CLANG_WARN_INFINITE_RECURSION = YES;
422 | CLANG_WARN_INT_CONVERSION = YES;
423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
427 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
428 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
429 | CLANG_WARN_STRICT_PROTOTYPES = YES;
430 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
431 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
432 | CLANG_WARN_UNREACHABLE_CODE = YES;
433 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
434 | COPY_PHASE_STRIP = NO;
435 | DEBUG_INFORMATION_FORMAT = dwarf;
436 | ENABLE_STRICT_OBJC_MSGSEND = YES;
437 | ENABLE_TESTABILITY = YES;
438 | GCC_C_LANGUAGE_STANDARD = gnu11;
439 | GCC_DYNAMIC_NO_PIC = NO;
440 | GCC_NO_COMMON_BLOCKS = YES;
441 | GCC_OPTIMIZATION_LEVEL = 0;
442 | GCC_PREPROCESSOR_DEFINITIONS = (
443 | "DEBUG=1",
444 | "$(inherited)",
445 | );
446 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
447 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
448 | GCC_WARN_UNDECLARED_SELECTOR = YES;
449 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
450 | GCC_WARN_UNUSED_FUNCTION = YES;
451 | GCC_WARN_UNUSED_VARIABLE = YES;
452 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
453 | MTL_FAST_MATH = YES;
454 | ONLY_ACTIVE_ARCH = YES;
455 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
456 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
457 | };
458 | name = Debug;
459 | };
460 | 2C11908928BE84350038D4B9 /* Release */ = {
461 | isa = XCBuildConfiguration;
462 | buildSettings = {
463 | ALWAYS_SEARCH_USER_PATHS = NO;
464 | CLANG_ANALYZER_NONNULL = YES;
465 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
466 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
467 | CLANG_ENABLE_MODULES = YES;
468 | CLANG_ENABLE_OBJC_ARC = YES;
469 | CLANG_ENABLE_OBJC_WEAK = YES;
470 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
471 | CLANG_WARN_BOOL_CONVERSION = YES;
472 | CLANG_WARN_COMMA = YES;
473 | CLANG_WARN_CONSTANT_CONVERSION = YES;
474 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
475 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
476 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
477 | CLANG_WARN_EMPTY_BODY = YES;
478 | CLANG_WARN_ENUM_CONVERSION = YES;
479 | CLANG_WARN_INFINITE_RECURSION = YES;
480 | CLANG_WARN_INT_CONVERSION = YES;
481 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
482 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
483 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
484 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
485 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
486 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
487 | CLANG_WARN_STRICT_PROTOTYPES = YES;
488 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
489 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
490 | CLANG_WARN_UNREACHABLE_CODE = YES;
491 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
492 | COPY_PHASE_STRIP = NO;
493 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
494 | ENABLE_NS_ASSERTIONS = NO;
495 | ENABLE_STRICT_OBJC_MSGSEND = YES;
496 | GCC_C_LANGUAGE_STANDARD = gnu11;
497 | GCC_NO_COMMON_BLOCKS = YES;
498 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
499 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
500 | GCC_WARN_UNDECLARED_SELECTOR = YES;
501 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
502 | GCC_WARN_UNUSED_FUNCTION = YES;
503 | GCC_WARN_UNUSED_VARIABLE = YES;
504 | MTL_ENABLE_DEBUG_INFO = NO;
505 | MTL_FAST_MATH = YES;
506 | SWIFT_COMPILATION_MODE = wholemodule;
507 | SWIFT_OPTIMIZATION_LEVEL = "-O";
508 | };
509 | name = Release;
510 | };
511 | 2C11908B28BE84350038D4B9 /* Debug */ = {
512 | isa = XCBuildConfiguration;
513 | buildSettings = {
514 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
515 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
516 | CODE_SIGN_STYLE = Automatic;
517 | CURRENT_PROJECT_VERSION = 1;
518 | DEVELOPMENT_TEAM = 972KS36P2U;
519 | ENABLE_PREVIEWS = YES;
520 | GENERATE_INFOPLIST_FILE = YES;
521 | INFOPLIST_KEY_NSCameraUsageDescription = "This app use camera on the deck";
522 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
523 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
524 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
525 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
526 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
527 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
528 | LD_RUNPATH_SEARCH_PATHS = (
529 | "$(inherited)",
530 | "@executable_path/Frameworks",
531 | );
532 | MARKETING_VERSION = 1.0;
533 | PRODUCT_BUNDLE_IDENTIFIER = com.joshholtz.Demo;
534 | PRODUCT_NAME = Demo;
535 | SDKROOT = iphoneos;
536 | SWIFT_EMIT_LOC_STRINGS = YES;
537 | SWIFT_VERSION = 5.0;
538 | TARGETED_DEVICE_FAMILY = "1,2";
539 | };
540 | name = Debug;
541 | };
542 | 2C11908C28BE84350038D4B9 /* Release */ = {
543 | isa = XCBuildConfiguration;
544 | buildSettings = {
545 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
546 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
547 | CODE_SIGN_STYLE = Automatic;
548 | CURRENT_PROJECT_VERSION = 1;
549 | DEVELOPMENT_TEAM = 972KS36P2U;
550 | ENABLE_PREVIEWS = YES;
551 | GENERATE_INFOPLIST_FILE = YES;
552 | INFOPLIST_KEY_NSCameraUsageDescription = "This app use camera on the deck";
553 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
554 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
555 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
556 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
557 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
558 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
559 | LD_RUNPATH_SEARCH_PATHS = (
560 | "$(inherited)",
561 | "@executable_path/Frameworks",
562 | );
563 | MARKETING_VERSION = 1.0;
564 | PRODUCT_BUNDLE_IDENTIFIER = com.joshholtz.Demo;
565 | PRODUCT_NAME = Demo;
566 | SDKROOT = iphoneos;
567 | SWIFT_EMIT_LOC_STRINGS = YES;
568 | SWIFT_VERSION = 5.0;
569 | TARGETED_DEVICE_FAMILY = "1,2";
570 | VALIDATE_PRODUCT = YES;
571 | };
572 | name = Release;
573 | };
574 | 2C11908E28BE84350038D4B9 /* Debug */ = {
575 | isa = XCBuildConfiguration;
576 | buildSettings = {
577 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
578 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
579 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
580 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
581 | CODE_SIGN_STYLE = Automatic;
582 | COMBINE_HIDPI_IMAGES = YES;
583 | CURRENT_PROJECT_VERSION = 1;
584 | DEVELOPMENT_TEAM = 972KS36P2U;
585 | ENABLE_HARDENED_RUNTIME = YES;
586 | ENABLE_PREVIEWS = YES;
587 | GENERATE_INFOPLIST_FILE = YES;
588 | INFOPLIST_KEY_NSCameraUsageDescription = "Displaying face";
589 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
590 | LD_RUNPATH_SEARCH_PATHS = (
591 | "$(inherited)",
592 | "@executable_path/../Frameworks",
593 | );
594 | MACOSX_DEPLOYMENT_TARGET = 13.0;
595 | MARKETING_VERSION = 1.0;
596 | PRODUCT_BUNDLE_IDENTIFIER = com.joshholtz.Demo;
597 | PRODUCT_NAME = Demo;
598 | SDKROOT = macosx;
599 | SWIFT_EMIT_LOC_STRINGS = YES;
600 | SWIFT_VERSION = 5.0;
601 | };
602 | name = Debug;
603 | };
604 | 2C11908F28BE84350038D4B9 /* Release */ = {
605 | isa = XCBuildConfiguration;
606 | buildSettings = {
607 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
608 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
609 | CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements;
610 | CODE_SIGN_STYLE = Automatic;
611 | COMBINE_HIDPI_IMAGES = YES;
612 | CURRENT_PROJECT_VERSION = 1;
613 | DEVELOPMENT_TEAM = 972KS36P2U;
614 | ENABLE_HARDENED_RUNTIME = YES;
615 | ENABLE_PREVIEWS = YES;
616 | GENERATE_INFOPLIST_FILE = YES;
617 | INFOPLIST_KEY_NSCameraUsageDescription = "Displaying face";
618 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
619 | LD_RUNPATH_SEARCH_PATHS = (
620 | "$(inherited)",
621 | "@executable_path/../Frameworks",
622 | );
623 | MACOSX_DEPLOYMENT_TARGET = 13.0;
624 | MARKETING_VERSION = 1.0;
625 | PRODUCT_BUNDLE_IDENTIFIER = com.joshholtz.Demo;
626 | PRODUCT_NAME = Demo;
627 | SDKROOT = macosx;
628 | SWIFT_EMIT_LOC_STRINGS = YES;
629 | SWIFT_VERSION = 5.0;
630 | };
631 | name = Release;
632 | };
633 | 2C11909128BE84350038D4B9 /* Debug */ = {
634 | isa = XCBuildConfiguration;
635 | buildSettings = {
636 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
637 | CODE_SIGN_STYLE = Automatic;
638 | CURRENT_PROJECT_VERSION = 1;
639 | DEVELOPMENT_TEAM = 972KS36P2U;
640 | GENERATE_INFOPLIST_FILE = YES;
641 | IPHONEOS_DEPLOYMENT_TARGET = 15.5;
642 | MARKETING_VERSION = 1.0;
643 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshholtz.Tests-iOS";
644 | PRODUCT_NAME = "$(TARGET_NAME)";
645 | SDKROOT = iphoneos;
646 | SWIFT_EMIT_LOC_STRINGS = NO;
647 | SWIFT_VERSION = 5.0;
648 | TARGETED_DEVICE_FAMILY = "1,2";
649 | TEST_TARGET_NAME = "Demo (iOS)";
650 | };
651 | name = Debug;
652 | };
653 | 2C11909228BE84350038D4B9 /* Release */ = {
654 | isa = XCBuildConfiguration;
655 | buildSettings = {
656 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
657 | CODE_SIGN_STYLE = Automatic;
658 | CURRENT_PROJECT_VERSION = 1;
659 | DEVELOPMENT_TEAM = 972KS36P2U;
660 | GENERATE_INFOPLIST_FILE = YES;
661 | IPHONEOS_DEPLOYMENT_TARGET = 15.5;
662 | MARKETING_VERSION = 1.0;
663 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshholtz.Tests-iOS";
664 | PRODUCT_NAME = "$(TARGET_NAME)";
665 | SDKROOT = iphoneos;
666 | SWIFT_EMIT_LOC_STRINGS = NO;
667 | SWIFT_VERSION = 5.0;
668 | TARGETED_DEVICE_FAMILY = "1,2";
669 | TEST_TARGET_NAME = "Demo (iOS)";
670 | VALIDATE_PRODUCT = YES;
671 | };
672 | name = Release;
673 | };
674 | 2C11909428BE84350038D4B9 /* Debug */ = {
675 | isa = XCBuildConfiguration;
676 | buildSettings = {
677 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
678 | CODE_SIGN_STYLE = Automatic;
679 | CURRENT_PROJECT_VERSION = 1;
680 | DEVELOPMENT_TEAM = 972KS36P2U;
681 | GENERATE_INFOPLIST_FILE = YES;
682 | MACOSX_DEPLOYMENT_TARGET = 12.3;
683 | MARKETING_VERSION = 1.0;
684 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshholtz.Tests-macOS";
685 | PRODUCT_NAME = "$(TARGET_NAME)";
686 | SDKROOT = macosx;
687 | SWIFT_EMIT_LOC_STRINGS = NO;
688 | SWIFT_VERSION = 5.0;
689 | TEST_TARGET_NAME = "Demo (macOS)";
690 | };
691 | name = Debug;
692 | };
693 | 2C11909528BE84350038D4B9 /* Release */ = {
694 | isa = XCBuildConfiguration;
695 | buildSettings = {
696 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
697 | CODE_SIGN_STYLE = Automatic;
698 | CURRENT_PROJECT_VERSION = 1;
699 | DEVELOPMENT_TEAM = 972KS36P2U;
700 | GENERATE_INFOPLIST_FILE = YES;
701 | MACOSX_DEPLOYMENT_TARGET = 12.3;
702 | MARKETING_VERSION = 1.0;
703 | PRODUCT_BUNDLE_IDENTIFIER = "com.joshholtz.Tests-macOS";
704 | PRODUCT_NAME = "$(TARGET_NAME)";
705 | SDKROOT = macosx;
706 | SWIFT_EMIT_LOC_STRINGS = NO;
707 | SWIFT_VERSION = 5.0;
708 | TEST_TARGET_NAME = "Demo (macOS)";
709 | };
710 | name = Release;
711 | };
712 | /* End XCBuildConfiguration section */
713 |
714 | /* Begin XCConfigurationList section */
715 | 2C11905828BE84340038D4B9 /* Build configuration list for PBXProject "Demo" */ = {
716 | isa = XCConfigurationList;
717 | buildConfigurations = (
718 | 2C11908828BE84350038D4B9 /* Debug */,
719 | 2C11908928BE84350038D4B9 /* Release */,
720 | );
721 | defaultConfigurationIsVisible = 0;
722 | defaultConfigurationName = Release;
723 | };
724 | 2C11908A28BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Demo (iOS)" */ = {
725 | isa = XCConfigurationList;
726 | buildConfigurations = (
727 | 2C11908B28BE84350038D4B9 /* Debug */,
728 | 2C11908C28BE84350038D4B9 /* Release */,
729 | );
730 | defaultConfigurationIsVisible = 0;
731 | defaultConfigurationName = Release;
732 | };
733 | 2C11908D28BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Demo (macOS)" */ = {
734 | isa = XCConfigurationList;
735 | buildConfigurations = (
736 | 2C11908E28BE84350038D4B9 /* Debug */,
737 | 2C11908F28BE84350038D4B9 /* Release */,
738 | );
739 | defaultConfigurationIsVisible = 0;
740 | defaultConfigurationName = Release;
741 | };
742 | 2C11909028BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Tests iOS" */ = {
743 | isa = XCConfigurationList;
744 | buildConfigurations = (
745 | 2C11909128BE84350038D4B9 /* Debug */,
746 | 2C11909228BE84350038D4B9 /* Release */,
747 | );
748 | defaultConfigurationIsVisible = 0;
749 | defaultConfigurationName = Release;
750 | };
751 | 2C11909328BE84350038D4B9 /* Build configuration list for PBXNativeTarget "Tests macOS" */ = {
752 | isa = XCConfigurationList;
753 | buildConfigurations = (
754 | 2C11909428BE84350038D4B9 /* Debug */,
755 | 2C11909528BE84350038D4B9 /* Release */,
756 | );
757 | defaultConfigurationIsVisible = 0;
758 | defaultConfigurationName = Release;
759 | };
760 | /* End XCConfigurationList section */
761 |
762 | /* Begin XCSwiftPackageProductDependency section */
763 | 377A9A5728CA76D600AAA2E2 /* DeckUI */ = {
764 | isa = XCSwiftPackageProductDependency;
765 | productName = DeckUI;
766 | };
767 | 377A9A5928CA76DE00AAA2E2 /* DeckUI */ = {
768 | isa = XCSwiftPackageProductDependency;
769 | productName = DeckUI;
770 | };
771 | /* End XCSwiftPackageProductDependency section */
772 | };
773 | rootObject = 2C11905528BE84340038D4B9 /* Project object */;
774 | }
775 |
--------------------------------------------------------------------------------