├── .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://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fjoshdholtz%2FDeckUI%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/joshdholtz/DeckUI) 4 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fjoshdholtz%2FDeckUI%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/joshdholtz/DeckUI) 5 | ![](https://img.shields.io/github/license/joshdholtz/DeckUI) 6 | [![](https://img.shields.io/badge/SPM-supported-DE5C43.svg?style=flat)](https://swift.org/package-manager/) 7 | [![](https://img.shields.io/badge/twitter-@joshdholtz-blue.svg?style=flat)]([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 | --------------------------------------------------------------------------------