├── images
└── home.png
├── Cedar
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Color+Cedar.swift
├── OperationQueue+Serial.swift
├── NSPersistentContainer+BackgroundSave.swift
├── CalendarView.swift
├── Date+IntervalOfComponent.swift
├── HabitViewModel.swift
├── AppDelegate.swift
├── HabitDetailView.swift
├── AsyncOperation.swift
├── Cedar.xcdatamodeld
│ └── Cedar.xcdatamodel
│ │ └── contents
├── ToggleHabitCompletionOperation.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── HabitListView.swift
├── NewHabitView.swift
├── Info.plist
├── SceneDelegate.swift
├── HabitRowView.swift
└── HabitsStore.swift
├── README.md
├── CedarTests
├── HabitsStoreTests.swift
└── Info.plist
├── Cedar.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ └── Cedar.xcscheme
└── project.pbxproj
├── CedarUITests
├── Info.plist
└── CedarUITests.swift
└── .gitignore
/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmick/cedar/HEAD/images/home.png
--------------------------------------------------------------------------------
/Cedar/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cedar
2 | A habit tracker built using SwiftUI
3 |
4 | 
5 |
--------------------------------------------------------------------------------
/Cedar/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/CedarTests/HabitsStoreTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | @testable import Cedar
4 |
5 | final class HabitStoreTests: XCTestCase {
6 | let sut = HabitsStore()
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/Cedar/Color+Cedar.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | extension Color {
4 | static var cedarGreen: Color {
5 | return Color(hue: 0.41, saturation: 0.40, brightness: 0.72)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Cedar.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Cedar/OperationQueue+Serial.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension OperationQueue {
4 | static var serial: OperationQueue {
5 | let q = OperationQueue()
6 | q.maxConcurrentOperationCount = 1
7 | return q
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Cedar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Cedar/NSPersistentContainer+BackgroundSave.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 |
3 | extension NSPersistentContainer {
4 | func performBackgroundTaskAndSave(_ closure: @escaping (NSManagedObjectContext) -> Void) {
5 | performBackgroundTask { context in
6 | closure(context)
7 |
8 | do {
9 | try context.save()
10 | } catch {
11 | print("Error saving habit", error)
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Cedar/CalendarView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 |
4 | struct CalendarView: UIViewRepresentable {
5 | typealias UIViewType = UICollectionView
6 |
7 | func makeUIView(context: UIViewRepresentableContext) -> CalendarView.UIViewType {
8 | let v = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
9 |
10 | return v
11 | }
12 |
13 | func updateUIView(_ uiView: CalendarView.UIViewType, context: UIViewRepresentableContext) {
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Cedar/Date+IntervalOfComponent.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // from: https://stackoverflow.com/questions/40075850/swift-3-find-number-of-calendar-days-between-two-dates
4 | extension Date {
5 | func interval(ofComponent comp: Calendar.Component, fromDate date: Date) -> Int {
6 |
7 | let currentCalendar = Calendar.current
8 |
9 | guard let start = currentCalendar.ordinality(of: comp, in: .era, for: date) else { return 0 }
10 | guard let end = currentCalendar.ordinality(of: comp, in: .era, for: self) else { return 0 }
11 |
12 | return end - start
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/CedarTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/CedarUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Cedar/HabitViewModel.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import SwiftUI
3 |
4 | struct HabitViewModel: Identifiable {
5 | let id: NSManagedObjectID
6 | let title: String
7 | let reason: String
8 | let isComplete: Bool
9 | let completionDaysAgo: Set
10 |
11 | init?(habit: Habit) {
12 | guard let title = habit.title, let reason = habit.reason else { return nil }
13 | guard let completions = habit.completions?.allObjects as? [HabitCompletion] else { return nil }
14 |
15 | // This probably won't scale to many completions, but it works 🤷♂️
16 | let daysAgoSet = Set(completions.compactMap { completion -> Int? in
17 | return Date().interval(ofComponent: .day, fromDate: completion.date!)
18 | })
19 |
20 | self.init(id: habit.objectID, title: title, reason: reason, isComplete: daysAgoSet.contains(0), completionDaysAgo: daysAgoSet)
21 | }
22 |
23 | init(id: NSManagedObjectID, title: String, reason: String, isComplete: Bool, completionDaysAgo: Set) {
24 | self.id = id
25 | self.title = title
26 | self.reason = reason
27 | self.isComplete = isComplete
28 | self.completionDaysAgo = completionDaysAgo
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/CedarUITests/CedarUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CedarUITests.swift
3 | // CedarUITests
4 | //
5 | // Created by Patrick Mick on 7/21/19.
6 | // Copyright © 2019 Patrick Mick. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class CedarUITests: XCTestCase {
12 | func testHabitCreationAndDeletion() {
13 | // UI tests must launch the application that they test.
14 | let app = XCUIApplication()
15 | app.launch()
16 |
17 | XCTContext.runActivity(named: "Add new habit") { _ in
18 | app.buttons["Add New Habit"].tap()
19 | app.textFields["Title"].tap()
20 | app.typeText("Test")
21 | app.textFields["Reason"].tap()
22 | app.typeText("A cool reason")
23 | app.buttons["Add"].tap()
24 | }
25 |
26 | XCTContext.runActivity(named: "Delete habit") { _ in
27 | app.buttons["Test\nA cool reason"].tap()
28 | app.buttons["Delete"].tap()
29 | XCTAssertFalse(app.buttons["Test\nA cool reason"].exists)
30 | }
31 | }
32 |
33 | func testLaunchPerformance() {
34 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
35 | XCUIApplication().launch()
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Cedar/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import UIKit
3 |
4 | @UIApplicationMain
5 | class AppDelegate: UIResponder, UIApplicationDelegate {
6 |
7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
8 | // Override point for customization after application launch.
9 | return true
10 | }
11 |
12 | // MARK: UISceneSession Lifecycle
13 |
14 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
15 | // Called when a new scene session is being created.
16 | // Use this method to select a configuration to create the new scene with.
17 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
18 | }
19 |
20 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
21 | // Called when the user discards a scene session.
22 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
23 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
24 | }
25 |
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/Cedar/HabitDetailView.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 | import SwiftUI
3 |
4 | struct HabitDetailView: View {
5 | let habitsStore: HabitsStore
6 | let habitViewModel: HabitViewModel
7 | let onClose: () -> Void
8 |
9 | var body: some View {
10 | NavigationView {
11 | Button(action: {
12 | self.habitsStore.deleteHabit(with: self.habitViewModel.id)
13 | self.onClose()
14 | }) {
15 | Text("Delete")
16 | .fontWeight(.bold)
17 | .foregroundColor(Color.red)
18 | }
19 | .navigationBarTitle(habitViewModel.title)
20 | .navigationBarItems(trailing: Button(action: {
21 | self.onClose()
22 | }, label: {
23 | Text("Close")
24 | .fontWeight(.bold)
25 | .foregroundColor(.cedarGreen)
26 | }))
27 | }
28 | }
29 | }
30 |
31 | #if DEBUG
32 | extension HabitViewModel {
33 | static var test: HabitViewModel {
34 | return HabitViewModel(id: NSManagedObjectID(), title: "Workout", reason: "Build confidence", isComplete: true, completionDaysAgo: Set())
35 | }
36 | }
37 |
38 | struct HabitDetailView_Previews: PreviewProvider {
39 | static var previews: some View {
40 | HabitDetailView(habitsStore: HabitsStore(), habitViewModel: .test, onClose: { })
41 | }
42 | }
43 | #endif
44 |
--------------------------------------------------------------------------------
/Cedar/AsyncOperation.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Subclasses should implement main, and not call super. Super will switch `state` to executing.
4 | /// When you're finished with async work, switch the operation `state` to `.finished`.
5 | /// Finally, when switching threads or between subtasks check if the operation is cancelled. If it is switch the state to `.finished` and return.
6 | class AsyncOperation: Operation {
7 | enum State {
8 | case waiting
9 | case executing
10 | case finished
11 |
12 | var kvoKey: String? {
13 | switch self {
14 | case .executing: return "isExecuting"
15 | case .finished: return "isFinished"
16 | default: return nil
17 | }
18 | }
19 | }
20 |
21 | var state: State = .waiting {
22 | willSet {
23 | if let kvoKey = state.kvoKey { willChangeValue(forKey: kvoKey) }
24 | if let kvoKey = newValue.kvoKey { willChangeValue(forKey: kvoKey) }
25 | }
26 | didSet {
27 | if let kvoKey = oldValue.kvoKey { didChangeValue(forKey: kvoKey) }
28 | if let kvoKey = state.kvoKey { didChangeValue(forKey: kvoKey) }
29 | }
30 | }
31 |
32 | override var isAsynchronous: Bool { return true }
33 | override var isExecuting: Bool { return state == .executing }
34 | override var isFinished: Bool { return state == .finished }
35 |
36 | override func start() {
37 | guard !isCancelled else { state = .finished; return }
38 | state = .executing
39 | main()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Cedar/Cedar.xcdatamodeld/Cedar.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Cedar/ToggleHabitCompletionOperation.swift:
--------------------------------------------------------------------------------
1 | import CoreData
2 |
3 | final class ToggleHabitCompleteOperation: AsyncOperation {
4 | let persistentContainer: NSPersistentContainer
5 | let habitId: NSManagedObjectID
6 |
7 | init(persistentContainer: NSPersistentContainer, habitId: NSManagedObjectID) {
8 | self.persistentContainer = persistentContainer
9 | self.habitId = habitId
10 | }
11 |
12 | override func main() {
13 | persistentContainer.performBackgroundTask { context in
14 | guard !self.isCancelled else { self.state = .finished; return }
15 | guard let habit = context.object(with: self.habitId) as? Habit else { self.state = .finished; return }
16 |
17 | let startOfToday = Calendar.current.startOfDay(for: Date())
18 | let request: NSFetchRequest = HabitCompletion.fetchRequest()
19 | request.predicate = NSPredicate(format: "habit == %@ && date > %@", habit, startOfToday as NSDate)
20 |
21 | if let result = try? context.fetch(request), result.count > 0 {
22 | for completion in result {
23 | context.delete(completion)
24 | }
25 | } else {
26 | let completion = HabitCompletion(context: context)
27 | completion.date = Date()
28 | habit.addToCompletions(completion)
29 | }
30 |
31 | do {
32 | try context.save()
33 | } catch {
34 | print("Toggle habit completion save error", error)
35 | }
36 |
37 | self.state = .finished
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 |
--------------------------------------------------------------------------------
/Cedar/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Cedar/HabitListView.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 | //import CoreData
3 | import SwiftUI
4 |
5 | struct HabitListView: View {
6 | @ObservedObject var habitsStore = HabitsStore()
7 |
8 | var body: some View {
9 | ZStack {
10 | Rectangle()
11 | .foregroundColor(Color(hue: 0.72, saturation: 0.02, brightness: 0.98))
12 | .edgesIgnoringSafeArea(.all)
13 | ScrollView() {
14 | VStack {
15 | Text("cedar")
16 | .font(.largeTitle)
17 | .fontWeight(.bold)
18 | .foregroundColor(.cedarGreen)
19 | ForEach(habitsStore.habits) { habit in
20 | HabitRowView(habitsStore: self.habitsStore, habit: habit)
21 | }
22 | }
23 | .padding(.top, 16)
24 | }
25 | VStack {
26 | Spacer()
27 | AddButton(habitsStore: habitsStore)
28 | }
29 | }
30 | }
31 | }
32 |
33 | #if DEBUG
34 | struct HabitListView_Previews: PreviewProvider {
35 | static var previews: some View {
36 | HabitListView()
37 | }
38 | }
39 | #endif
40 |
41 | struct AddButton: View {
42 | let habitsStore: HabitsStore
43 | @State var isPresented = false
44 |
45 | var body: some View {
46 | Button(action: {
47 | self.isPresented.toggle()
48 | }) {
49 | ZStack {
50 | Circle()
51 | .foregroundColor(.cedarGreen)
52 | .frame(width: 64, height: 64)
53 |
54 | Image(systemName: "plus")
55 | .font(.largeTitle)
56 | .foregroundColor(.white)
57 | }
58 | }.sheet(isPresented: $isPresented) {
59 | return NewHabitView(habitsStore: self.habitsStore, isPresented: self.$isPresented)
60 | }.accessibility(label: Text("Add New Habit"))
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Cedar/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Cedar/NewHabitView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct NewHabitView: View {
4 | let habitsStore: HabitsStore
5 |
6 | @Binding var isPresented: Bool
7 |
8 | @State var title: String = ""
9 | @State var reason: String = ""
10 |
11 | var isFormComplete: Bool {
12 | return !title.isEmpty && !reason.isEmpty
13 | }
14 |
15 | var body: some View {
16 | NavigationView {
17 | VStack {
18 | EntryFieldView(title: "Title", binding: $title)
19 | EntryFieldView(title: "Reason", binding: $reason)
20 | Spacer()
21 | }
22 | .padding()
23 | .navigationBarTitle("Add Habit")
24 | .navigationBarItems(
25 | leading:
26 | Button(action: {
27 | self.isPresented = false
28 | }, label: {
29 | Text("Cancel")
30 | .foregroundColor(.cedarGreen)
31 | }),
32 | trailing:
33 | Button(action: {
34 | self.habitsStore.createHabit(with: self.title, reason: self.reason)
35 | self.isPresented = false
36 | }, label: {
37 | Text("Add")
38 | .foregroundColor(isFormComplete ? Color.cedarGreen : Color.cedarGreen.opacity(0.3))
39 | .fontWeight(.bold)
40 | }).disabled(!isFormComplete)
41 | )
42 | }
43 | }
44 | }
45 |
46 | #if DEBUG
47 | struct NewHabitView_Previews: PreviewProvider {
48 | static var previews: some View {
49 | NewHabitView(habitsStore: HabitsStore(), isPresented: Binding(get: { return true }, set: { _ in }))
50 | }
51 | }
52 | #endif
53 |
54 | struct EntryFieldView: View {
55 | let title: String
56 | @Binding var binding: String
57 |
58 | var body: some View {
59 | HStack {
60 | Text(title)
61 | .fontWeight(.bold)
62 | .baselineOffset(2.0)
63 | TextField(title, text: $binding)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Cedar/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UILaunchStoryboardName
33 | LaunchScreen
34 | UISceneConfigurationName
35 | Default Configuration
36 | UISceneDelegateClassName
37 | $(PRODUCT_MODULE_NAME).SceneDelegate
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UISupportedInterfaceOrientations~ipad
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationPortraitUpsideDown
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Cedar/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Cedar
4 | //
5 | // Created by Patrick Mick on 7/21/19.
6 | // Copyright © 2019 Patrick Mick. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
21 |
22 | // Use a UIHostingController as window root view controller
23 | if let windowScene = scene as? UIWindowScene {
24 | let window = UIWindow(windowScene: windowScene)
25 | window.rootViewController = UIHostingController(rootView: HabitListView())
26 | self.window = window
27 | window.makeKeyAndVisible()
28 | }
29 | }
30 |
31 | func sceneDidDisconnect(_ scene: UIScene) {
32 | // Called as the scene is being released by the system.
33 | // This occurs shortly after the scene enters the background, or when its session is discarded.
34 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
35 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
36 | }
37 |
38 | func sceneDidBecomeActive(_ scene: UIScene) {
39 | // Called when the scene has moved from an inactive state to an active state.
40 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
41 | }
42 |
43 | func sceneWillResignActive(_ scene: UIScene) {
44 | // Called when the scene will move from an active state to an inactive state.
45 | // This may occur due to temporary interruptions (ex. an incoming phone call).
46 | }
47 |
48 | func sceneWillEnterForeground(_ scene: UIScene) {
49 | // Called as the scene transitions from the background to the foreground.
50 | // Use this method to undo the changes made on entering the background.
51 | }
52 |
53 | func sceneDidEnterBackground(_ scene: UIScene) {
54 | // Called as the scene transitions from the foreground to the background.
55 | // Use this method to save data, release shared resources, and store enough scene-specific state information
56 | // to restore the scene back to its current state.
57 | }
58 |
59 |
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/Cedar/HabitRowView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct HabitRowView: View {
4 | let habitsStore: HabitsStore
5 | let habit: HabitViewModel
6 | @State var isPresented = false
7 |
8 | var body: some View {
9 | Button(action: {
10 | self.isPresented.toggle()
11 | }) {
12 | ZStack {
13 | RoundedRectangle(cornerRadius: 8, style: .circular)
14 | .foregroundColor(.white)
15 | .shadow(color: Color(.sRGB, white: 0, opacity: 0.05), radius: 8, x: 0, y: 8)
16 | VStack {
17 | HStack {
18 | VStack(alignment: .leading) {
19 | Text(habit.title)
20 | .font(.headline)
21 | .foregroundColor(Color(.sRGB, white: 0.33, opacity: 1))
22 | Text(habit.reason)
23 | .font(.subheadline)
24 | .foregroundColor(Color(.sRGB, white: 0.66, opacity: 1))
25 | }
26 | Spacer()
27 | VStack(alignment: .trailing) {
28 | Button(action: {
29 | self.habitsStore.complete(habitWithId: self.habit.id)
30 | }) {
31 | ZStack {
32 | Circle()
33 | .foregroundColor(habit.isComplete ? Color.cedarGreen : Color(.sRGB, white: 0, opacity: 0.10))
34 | .frame(width: 44, height: 44)
35 | Image(systemName: "checkmark")
36 | .font(.headline)
37 | .foregroundColor(.white)
38 | }
39 | }
40 | Spacer()
41 | HStack {
42 | ForEach(0..<5) { idx in
43 | Circle()
44 | .foregroundColor(self.habit.completionDaysAgo.contains(4 - idx) ? Color.cedarGreen : Color(.sRGB, white: 0, opacity: 0.10))
45 | .frame(width: 6, height: 6)
46 | }
47 | }
48 | }
49 | }
50 | }
51 | .padding()
52 | }
53 | .padding(.horizontal, 24)
54 | .padding(.vertical, 8)
55 | }.sheet(isPresented: $isPresented) {
56 | HabitDetailView(habitsStore: self.habitsStore, habitViewModel: self.habit) { self.isPresented.toggle() }
57 | }
58 | }
59 | }
60 |
61 | #if DEBUG
62 | struct HabitRowView_Previews: PreviewProvider {
63 | static var previews: some View {
64 | HabitRowView(habitsStore: HabitsStore(), habit: .test)
65 | .frame(height: 100)
66 | }
67 | }
68 | #endif
69 |
--------------------------------------------------------------------------------
/Cedar/HabitsStore.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 | import CoreData
3 | import SwiftUI
4 |
5 | final class HabitsStore: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
6 | var objectWillChange = PassthroughSubject()
7 | private lazy var habitMutationQueue: OperationQueue = .serial
8 |
9 | var habits: [HabitViewModel] {
10 | let habits = fetchedResultsController.fetchedObjects ?? []
11 | let viewModels = habits.compactMap(HabitViewModel.init)
12 | return viewModels
13 | }
14 |
15 | override init() {
16 | super.init()
17 |
18 | try? self.fetchedResultsController.performFetch()
19 | }
20 |
21 | func createHabit(with title: String, reason: String) {
22 | assert(!title.isEmpty)
23 | assert(!reason.isEmpty)
24 |
25 | persistentContainer.performBackgroundTaskAndSave { context in
26 | let habit = Habit(context: context)
27 | habit.title = title
28 | habit.reason = reason
29 | habit.createdAt = Date()
30 | }
31 | }
32 |
33 | func deleteHabit(with id: NSManagedObjectID) {
34 | persistentContainer.performBackgroundTaskAndSave { context in
35 | let habit = context.object(with: id)
36 | context.delete(habit)
37 | }
38 | }
39 |
40 | func complete(habitWithId habitId: NSManagedObjectID) {
41 | let operation = ToggleHabitCompleteOperation(persistentContainer: persistentContainer, habitId: habitId)
42 | habitMutationQueue.addOperation(operation)
43 | }
44 |
45 | // MARK: - NSFetchedResultsControllerDelegate
46 |
47 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
48 | objectWillChange.send(())
49 | }
50 |
51 | // MARK: - Core Data stack
52 |
53 | private lazy var persistentContainer: NSPersistentCloudKitContainer = {
54 | let container = NSPersistentCloudKitContainer(name: "Cedar")
55 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in
56 | if let error = error as NSError? {
57 | // Replace this implementation with code to handle the error appropriately.
58 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
59 |
60 | /*
61 | Typical reasons for an error here include:
62 | * The parent directory does not exist, cannot be created, or disallows writing.
63 | * The persistent store is not accessible, due to permissions or data protection when the device is locked.
64 | * The device is out of space.
65 | * The store could not be migrated to the current model version.
66 | Check the error message to determine what the actual problem was.
67 | */
68 | fatalError("Unresolved error \(error), \(error.userInfo)")
69 | }
70 | })
71 | container.viewContext.automaticallyMergesChangesFromParent = true
72 | return container
73 | }()
74 |
75 | private lazy var fetchedResultsController: NSFetchedResultsController = {
76 | let request: NSFetchRequest = Habit.fetchRequest()
77 | request.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]
78 | let frc = NSFetchedResultsController(fetchRequest: request, managedObjectContext: persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
79 | frc.delegate = self
80 | return frc
81 | }()
82 | }
83 |
--------------------------------------------------------------------------------
/Cedar.xcodeproj/xcshareddata/xcschemes/Cedar.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
36 |
42 |
43 |
44 |
48 |
54 |
55 |
56 |
57 |
58 |
68 |
70 |
76 |
77 |
78 |
79 |
85 |
87 |
93 |
94 |
95 |
96 |
98 |
99 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/Cedar.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | CD0B86122309B83A00FB420B /* Color+Cedar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0B86112309B83A00FB420B /* Color+Cedar.swift */; };
11 | CD0B86142309D96A00FB420B /* HabitRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0B86132309D96A00FB420B /* HabitRowView.swift */; };
12 | CD3BC5A522E56EAB00896FE7 /* HabitsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3BC5A422E56EAB00896FE7 /* HabitsStoreTests.swift */; };
13 | CD3BC5A722E572EA00896FE7 /* NewHabitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3BC5A622E572EA00896FE7 /* NewHabitView.swift */; };
14 | CD505E5922E80D400009580B /* HabitsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD505E5822E80D400009580B /* HabitsStore.swift */; };
15 | CD682E5022E5420100705A23 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD682E4F22E5420100705A23 /* AppDelegate.swift */; };
16 | CD682E5222E5420100705A23 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD682E5122E5420100705A23 /* SceneDelegate.swift */; };
17 | CD682E5422E5420100705A23 /* HabitListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD682E5322E5420100705A23 /* HabitListView.swift */; };
18 | CD682E5622E5420300705A23 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD682E5522E5420300705A23 /* Assets.xcassets */; };
19 | CD682E5922E5420300705A23 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD682E5822E5420300705A23 /* Preview Assets.xcassets */; };
20 | CD682E5C22E5420300705A23 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD682E5A22E5420300705A23 /* LaunchScreen.storyboard */; };
21 | CD682E7222E5420300705A23 /* CedarUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD682E7122E5420300705A23 /* CedarUITests.swift */; };
22 | CDA17DA423087BB600A7C6A0 /* HabitDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA17DA323087BB600A7C6A0 /* HabitDetailView.swift */; };
23 | CDA17DA62308EEE000A7C6A0 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA17DA52308EEE000A7C6A0 /* CalendarView.swift */; };
24 | CDDC456022EE90B0009DF238 /* Cedar.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CDDC455E22EE90B0009DF238 /* Cedar.xcdatamodeld */; };
25 | CDFF346E2309EF2900271184 /* HabitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFF346D2309EF2900271184 /* HabitViewModel.swift */; };
26 | CDFF34702309EF4500271184 /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFF346F2309EF4500271184 /* AsyncOperation.swift */; };
27 | CDFF34722309EF9A00271184 /* ToggleHabitCompletionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFF34712309EF9A00271184 /* ToggleHabitCompletionOperation.swift */; };
28 | CDFF34742309EFB800271184 /* Date+IntervalOfComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFF34732309EFB800271184 /* Date+IntervalOfComponent.swift */; };
29 | CDFF34762309F03B00271184 /* NSPersistentContainer+BackgroundSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFF34752309F03B00271184 /* NSPersistentContainer+BackgroundSave.swift */; };
30 | CDFF34782309F0BB00271184 /* OperationQueue+Serial.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFF34772309F0BB00271184 /* OperationQueue+Serial.swift */; };
31 | /* End PBXBuildFile section */
32 |
33 | /* Begin PBXContainerItemProxy section */
34 | CD682E6322E5420300705A23 /* PBXContainerItemProxy */ = {
35 | isa = PBXContainerItemProxy;
36 | containerPortal = CD682E4422E5420100705A23 /* Project object */;
37 | proxyType = 1;
38 | remoteGlobalIDString = CD682E4B22E5420100705A23;
39 | remoteInfo = Cedar;
40 | };
41 | CD682E6E22E5420300705A23 /* PBXContainerItemProxy */ = {
42 | isa = PBXContainerItemProxy;
43 | containerPortal = CD682E4422E5420100705A23 /* Project object */;
44 | proxyType = 1;
45 | remoteGlobalIDString = CD682E4B22E5420100705A23;
46 | remoteInfo = Cedar;
47 | };
48 | /* End PBXContainerItemProxy section */
49 |
50 | /* Begin PBXFileReference section */
51 | CD0B86112309B83A00FB420B /* Color+Cedar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Cedar.swift"; sourceTree = ""; };
52 | CD0B86132309D96A00FB420B /* HabitRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HabitRowView.swift; sourceTree = ""; };
53 | CD3BC5A422E56EAB00896FE7 /* HabitsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HabitsStoreTests.swift; path = ../../../../../System/Volumes/Data/Users/patrickmick/Desktop/Cedar/CedarTests/HabitsStoreTests.swift; sourceTree = ""; };
54 | CD3BC5A622E572EA00896FE7 /* NewHabitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = NewHabitView.swift; path = ../../../../../System/Volumes/Data/Users/patrickmick/Desktop/Cedar/Cedar/NewHabitView.swift; sourceTree = ""; };
55 | CD505E5822E80D400009580B /* HabitsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HabitsStore.swift; path = ../../../../../System/Volumes/Data/Users/patrickmick/Desktop/Cedar/Cedar/HabitsStore.swift; sourceTree = ""; };
56 | CD682E4C22E5420100705A23 /* Cedar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cedar.app; sourceTree = BUILT_PRODUCTS_DIR; };
57 | CD682E4F22E5420100705A23 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
58 | CD682E5122E5420100705A23 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
59 | CD682E5322E5420100705A23 /* HabitListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HabitListView.swift; sourceTree = ""; };
60 | CD682E5522E5420300705A23 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
61 | CD682E5822E5420300705A23 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
62 | CD682E5B22E5420300705A23 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
63 | CD682E5D22E5420300705A23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
64 | CD682E6222E5420300705A23 /* CedarTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CedarTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
65 | CD682E6822E5420300705A23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
66 | CD682E6D22E5420300705A23 /* CedarUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CedarUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
67 | CD682E7122E5420300705A23 /* CedarUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CedarUITests.swift; sourceTree = ""; };
68 | CD682E7322E5420300705A23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
69 | CDA17DA323087BB600A7C6A0 /* HabitDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HabitDetailView.swift; sourceTree = ""; };
70 | CDA17DA52308EEE000A7C6A0 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = ""; };
71 | CDDC455F22EE90B0009DF238 /* Cedar.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Cedar.xcdatamodel; sourceTree = ""; };
72 | CDFF346D2309EF2900271184 /* HabitViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HabitViewModel.swift; sourceTree = ""; };
73 | CDFF346F2309EF4500271184 /* AsyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = ""; };
74 | CDFF34712309EF9A00271184 /* ToggleHabitCompletionOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleHabitCompletionOperation.swift; sourceTree = ""; };
75 | CDFF34732309EFB800271184 /* Date+IntervalOfComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+IntervalOfComponent.swift"; sourceTree = ""; };
76 | CDFF34752309F03B00271184 /* NSPersistentContainer+BackgroundSave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPersistentContainer+BackgroundSave.swift"; sourceTree = ""; };
77 | CDFF34772309F0BB00271184 /* OperationQueue+Serial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OperationQueue+Serial.swift"; sourceTree = ""; };
78 | /* End PBXFileReference section */
79 |
80 | /* Begin PBXFrameworksBuildPhase section */
81 | CD682E4922E5420100705A23 /* Frameworks */ = {
82 | isa = PBXFrameworksBuildPhase;
83 | buildActionMask = 2147483647;
84 | files = (
85 | );
86 | runOnlyForDeploymentPostprocessing = 0;
87 | };
88 | CD682E5F22E5420300705A23 /* Frameworks */ = {
89 | isa = PBXFrameworksBuildPhase;
90 | buildActionMask = 2147483647;
91 | files = (
92 | );
93 | runOnlyForDeploymentPostprocessing = 0;
94 | };
95 | CD682E6A22E5420300705A23 /* Frameworks */ = {
96 | isa = PBXFrameworksBuildPhase;
97 | buildActionMask = 2147483647;
98 | files = (
99 | );
100 | runOnlyForDeploymentPostprocessing = 0;
101 | };
102 | /* End PBXFrameworksBuildPhase section */
103 |
104 | /* Begin PBXGroup section */
105 | CD682E4322E5420100705A23 = {
106 | isa = PBXGroup;
107 | children = (
108 | CD682E4E22E5420100705A23 /* Cedar */,
109 | CD682E6522E5420300705A23 /* CedarTests */,
110 | CD682E7022E5420300705A23 /* CedarUITests */,
111 | CD682E4D22E5420100705A23 /* Products */,
112 | );
113 | sourceTree = "";
114 | };
115 | CD682E4D22E5420100705A23 /* Products */ = {
116 | isa = PBXGroup;
117 | children = (
118 | CD682E4C22E5420100705A23 /* Cedar.app */,
119 | CD682E6222E5420300705A23 /* CedarTests.xctest */,
120 | CD682E6D22E5420300705A23 /* CedarUITests.xctest */,
121 | );
122 | name = Products;
123 | sourceTree = "";
124 | };
125 | CD682E4E22E5420100705A23 /* Cedar */ = {
126 | isa = PBXGroup;
127 | children = (
128 | CD682E4F22E5420100705A23 /* AppDelegate.swift */,
129 | CD682E5122E5420100705A23 /* SceneDelegate.swift */,
130 | CD682E5322E5420100705A23 /* HabitListView.swift */,
131 | CD3BC5A622E572EA00896FE7 /* NewHabitView.swift */,
132 | CDA17DA323087BB600A7C6A0 /* HabitDetailView.swift */,
133 | CD505E5822E80D400009580B /* HabitsStore.swift */,
134 | CDDC455E22EE90B0009DF238 /* Cedar.xcdatamodeld */,
135 | CD682E5522E5420300705A23 /* Assets.xcassets */,
136 | CD682E5A22E5420300705A23 /* LaunchScreen.storyboard */,
137 | CD682E5D22E5420300705A23 /* Info.plist */,
138 | CD682E5722E5420300705A23 /* Preview Content */,
139 | CDA17DA52308EEE000A7C6A0 /* CalendarView.swift */,
140 | CD0B86112309B83A00FB420B /* Color+Cedar.swift */,
141 | CD0B86132309D96A00FB420B /* HabitRowView.swift */,
142 | CDFF346D2309EF2900271184 /* HabitViewModel.swift */,
143 | CDFF346F2309EF4500271184 /* AsyncOperation.swift */,
144 | CDFF34712309EF9A00271184 /* ToggleHabitCompletionOperation.swift */,
145 | CDFF34732309EFB800271184 /* Date+IntervalOfComponent.swift */,
146 | CDFF34752309F03B00271184 /* NSPersistentContainer+BackgroundSave.swift */,
147 | CDFF34772309F0BB00271184 /* OperationQueue+Serial.swift */,
148 | );
149 | path = Cedar;
150 | sourceTree = "";
151 | };
152 | CD682E5722E5420300705A23 /* Preview Content */ = {
153 | isa = PBXGroup;
154 | children = (
155 | CD682E5822E5420300705A23 /* Preview Assets.xcassets */,
156 | );
157 | path = "Preview Content";
158 | sourceTree = "";
159 | };
160 | CD682E6522E5420300705A23 /* CedarTests */ = {
161 | isa = PBXGroup;
162 | children = (
163 | CD682E6822E5420300705A23 /* Info.plist */,
164 | CD3BC5A422E56EAB00896FE7 /* HabitsStoreTests.swift */,
165 | );
166 | path = CedarTests;
167 | sourceTree = "";
168 | };
169 | CD682E7022E5420300705A23 /* CedarUITests */ = {
170 | isa = PBXGroup;
171 | children = (
172 | CD682E7122E5420300705A23 /* CedarUITests.swift */,
173 | CD682E7322E5420300705A23 /* Info.plist */,
174 | );
175 | path = CedarUITests;
176 | sourceTree = "";
177 | };
178 | /* End PBXGroup section */
179 |
180 | /* Begin PBXNativeTarget section */
181 | CD682E4B22E5420100705A23 /* Cedar */ = {
182 | isa = PBXNativeTarget;
183 | buildConfigurationList = CD682E7622E5420300705A23 /* Build configuration list for PBXNativeTarget "Cedar" */;
184 | buildPhases = (
185 | CD682E4822E5420100705A23 /* Sources */,
186 | CD682E4922E5420100705A23 /* Frameworks */,
187 | CD682E4A22E5420100705A23 /* Resources */,
188 | );
189 | buildRules = (
190 | );
191 | dependencies = (
192 | );
193 | name = Cedar;
194 | productName = Cedar;
195 | productReference = CD682E4C22E5420100705A23 /* Cedar.app */;
196 | productType = "com.apple.product-type.application";
197 | };
198 | CD682E6122E5420300705A23 /* CedarTests */ = {
199 | isa = PBXNativeTarget;
200 | buildConfigurationList = CD682E7922E5420300705A23 /* Build configuration list for PBXNativeTarget "CedarTests" */;
201 | buildPhases = (
202 | CD682E5E22E5420300705A23 /* Sources */,
203 | CD682E5F22E5420300705A23 /* Frameworks */,
204 | CD682E6022E5420300705A23 /* Resources */,
205 | );
206 | buildRules = (
207 | );
208 | dependencies = (
209 | CD682E6422E5420300705A23 /* PBXTargetDependency */,
210 | );
211 | name = CedarTests;
212 | productName = CedarTests;
213 | productReference = CD682E6222E5420300705A23 /* CedarTests.xctest */;
214 | productType = "com.apple.product-type.bundle.unit-test";
215 | };
216 | CD682E6C22E5420300705A23 /* CedarUITests */ = {
217 | isa = PBXNativeTarget;
218 | buildConfigurationList = CD682E7C22E5420300705A23 /* Build configuration list for PBXNativeTarget "CedarUITests" */;
219 | buildPhases = (
220 | CD682E6922E5420300705A23 /* Sources */,
221 | CD682E6A22E5420300705A23 /* Frameworks */,
222 | CD682E6B22E5420300705A23 /* Resources */,
223 | );
224 | buildRules = (
225 | );
226 | dependencies = (
227 | CD682E6F22E5420300705A23 /* PBXTargetDependency */,
228 | );
229 | name = CedarUITests;
230 | productName = CedarUITests;
231 | productReference = CD682E6D22E5420300705A23 /* CedarUITests.xctest */;
232 | productType = "com.apple.product-type.bundle.ui-testing";
233 | };
234 | /* End PBXNativeTarget section */
235 |
236 | /* Begin PBXProject section */
237 | CD682E4422E5420100705A23 /* Project object */ = {
238 | isa = PBXProject;
239 | attributes = {
240 | LastSwiftUpdateCheck = 1100;
241 | LastUpgradeCheck = 1100;
242 | ORGANIZATIONNAME = "Patrick Mick";
243 | TargetAttributes = {
244 | CD682E4B22E5420100705A23 = {
245 | CreatedOnToolsVersion = 11.0;
246 | };
247 | CD682E6122E5420300705A23 = {
248 | CreatedOnToolsVersion = 11.0;
249 | TestTargetID = CD682E4B22E5420100705A23;
250 | };
251 | CD682E6C22E5420300705A23 = {
252 | CreatedOnToolsVersion = 11.0;
253 | TestTargetID = CD682E4B22E5420100705A23;
254 | };
255 | };
256 | };
257 | buildConfigurationList = CD682E4722E5420100705A23 /* Build configuration list for PBXProject "Cedar" */;
258 | compatibilityVersion = "Xcode 9.3";
259 | developmentRegion = en;
260 | hasScannedForEncodings = 0;
261 | knownRegions = (
262 | en,
263 | Base,
264 | );
265 | mainGroup = CD682E4322E5420100705A23;
266 | productRefGroup = CD682E4D22E5420100705A23 /* Products */;
267 | projectDirPath = "";
268 | projectRoot = "";
269 | targets = (
270 | CD682E4B22E5420100705A23 /* Cedar */,
271 | CD682E6122E5420300705A23 /* CedarTests */,
272 | CD682E6C22E5420300705A23 /* CedarUITests */,
273 | );
274 | };
275 | /* End PBXProject section */
276 |
277 | /* Begin PBXResourcesBuildPhase section */
278 | CD682E4A22E5420100705A23 /* Resources */ = {
279 | isa = PBXResourcesBuildPhase;
280 | buildActionMask = 2147483647;
281 | files = (
282 | CD682E5C22E5420300705A23 /* LaunchScreen.storyboard in Resources */,
283 | CD682E5922E5420300705A23 /* Preview Assets.xcassets in Resources */,
284 | CD682E5622E5420300705A23 /* Assets.xcassets in Resources */,
285 | );
286 | runOnlyForDeploymentPostprocessing = 0;
287 | };
288 | CD682E6022E5420300705A23 /* Resources */ = {
289 | isa = PBXResourcesBuildPhase;
290 | buildActionMask = 2147483647;
291 | files = (
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | };
295 | CD682E6B22E5420300705A23 /* Resources */ = {
296 | isa = PBXResourcesBuildPhase;
297 | buildActionMask = 2147483647;
298 | files = (
299 | );
300 | runOnlyForDeploymentPostprocessing = 0;
301 | };
302 | /* End PBXResourcesBuildPhase section */
303 |
304 | /* Begin PBXSourcesBuildPhase section */
305 | CD682E4822E5420100705A23 /* Sources */ = {
306 | isa = PBXSourcesBuildPhase;
307 | buildActionMask = 2147483647;
308 | files = (
309 | CDA17DA423087BB600A7C6A0 /* HabitDetailView.swift in Sources */,
310 | CD3BC5A722E572EA00896FE7 /* NewHabitView.swift in Sources */,
311 | CD0B86142309D96A00FB420B /* HabitRowView.swift in Sources */,
312 | CDFF34762309F03B00271184 /* NSPersistentContainer+BackgroundSave.swift in Sources */,
313 | CDA17DA62308EEE000A7C6A0 /* CalendarView.swift in Sources */,
314 | CDDC456022EE90B0009DF238 /* Cedar.xcdatamodeld in Sources */,
315 | CDFF34742309EFB800271184 /* Date+IntervalOfComponent.swift in Sources */,
316 | CDFF34782309F0BB00271184 /* OperationQueue+Serial.swift in Sources */,
317 | CDFF346E2309EF2900271184 /* HabitViewModel.swift in Sources */,
318 | CD682E5022E5420100705A23 /* AppDelegate.swift in Sources */,
319 | CD682E5222E5420100705A23 /* SceneDelegate.swift in Sources */,
320 | CD0B86122309B83A00FB420B /* Color+Cedar.swift in Sources */,
321 | CD505E5922E80D400009580B /* HabitsStore.swift in Sources */,
322 | CDFF34722309EF9A00271184 /* ToggleHabitCompletionOperation.swift in Sources */,
323 | CDFF34702309EF4500271184 /* AsyncOperation.swift in Sources */,
324 | CD682E5422E5420100705A23 /* HabitListView.swift in Sources */,
325 | );
326 | runOnlyForDeploymentPostprocessing = 0;
327 | };
328 | CD682E5E22E5420300705A23 /* Sources */ = {
329 | isa = PBXSourcesBuildPhase;
330 | buildActionMask = 2147483647;
331 | files = (
332 | CD3BC5A522E56EAB00896FE7 /* HabitsStoreTests.swift in Sources */,
333 | );
334 | runOnlyForDeploymentPostprocessing = 0;
335 | };
336 | CD682E6922E5420300705A23 /* Sources */ = {
337 | isa = PBXSourcesBuildPhase;
338 | buildActionMask = 2147483647;
339 | files = (
340 | CD682E7222E5420300705A23 /* CedarUITests.swift in Sources */,
341 | );
342 | runOnlyForDeploymentPostprocessing = 0;
343 | };
344 | /* End PBXSourcesBuildPhase section */
345 |
346 | /* Begin PBXTargetDependency section */
347 | CD682E6422E5420300705A23 /* PBXTargetDependency */ = {
348 | isa = PBXTargetDependency;
349 | target = CD682E4B22E5420100705A23 /* Cedar */;
350 | targetProxy = CD682E6322E5420300705A23 /* PBXContainerItemProxy */;
351 | };
352 | CD682E6F22E5420300705A23 /* PBXTargetDependency */ = {
353 | isa = PBXTargetDependency;
354 | target = CD682E4B22E5420100705A23 /* Cedar */;
355 | targetProxy = CD682E6E22E5420300705A23 /* PBXContainerItemProxy */;
356 | };
357 | /* End PBXTargetDependency section */
358 |
359 | /* Begin PBXVariantGroup section */
360 | CD682E5A22E5420300705A23 /* LaunchScreen.storyboard */ = {
361 | isa = PBXVariantGroup;
362 | children = (
363 | CD682E5B22E5420300705A23 /* Base */,
364 | );
365 | name = LaunchScreen.storyboard;
366 | sourceTree = "";
367 | };
368 | /* End PBXVariantGroup section */
369 |
370 | /* Begin XCBuildConfiguration section */
371 | CD682E7422E5420300705A23 /* Debug */ = {
372 | isa = XCBuildConfiguration;
373 | buildSettings = {
374 | ALWAYS_SEARCH_USER_PATHS = NO;
375 | CLANG_ANALYZER_NONNULL = YES;
376 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
377 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
378 | CLANG_CXX_LIBRARY = "libc++";
379 | CLANG_ENABLE_MODULES = YES;
380 | CLANG_ENABLE_OBJC_ARC = YES;
381 | CLANG_ENABLE_OBJC_WEAK = YES;
382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
383 | CLANG_WARN_BOOL_CONVERSION = YES;
384 | CLANG_WARN_COMMA = YES;
385 | CLANG_WARN_CONSTANT_CONVERSION = YES;
386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
388 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
389 | CLANG_WARN_EMPTY_BODY = YES;
390 | CLANG_WARN_ENUM_CONVERSION = YES;
391 | CLANG_WARN_INFINITE_RECURSION = YES;
392 | CLANG_WARN_INT_CONVERSION = YES;
393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
398 | CLANG_WARN_STRICT_PROTOTYPES = YES;
399 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
400 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
401 | CLANG_WARN_UNREACHABLE_CODE = YES;
402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
403 | COPY_PHASE_STRIP = NO;
404 | DEBUG_INFORMATION_FORMAT = dwarf;
405 | ENABLE_STRICT_OBJC_MSGSEND = YES;
406 | ENABLE_TESTABILITY = YES;
407 | GCC_C_LANGUAGE_STANDARD = gnu11;
408 | GCC_DYNAMIC_NO_PIC = NO;
409 | GCC_NO_COMMON_BLOCKS = YES;
410 | GCC_OPTIMIZATION_LEVEL = 0;
411 | GCC_PREPROCESSOR_DEFINITIONS = (
412 | "DEBUG=1",
413 | "$(inherited)",
414 | );
415 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
416 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
417 | GCC_WARN_UNDECLARED_SELECTOR = YES;
418 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
419 | GCC_WARN_UNUSED_FUNCTION = YES;
420 | GCC_WARN_UNUSED_VARIABLE = YES;
421 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
422 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
423 | MTL_FAST_MATH = YES;
424 | ONLY_ACTIVE_ARCH = YES;
425 | SDKROOT = iphoneos;
426 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
427 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
428 | };
429 | name = Debug;
430 | };
431 | CD682E7522E5420300705A23 /* Release */ = {
432 | isa = XCBuildConfiguration;
433 | buildSettings = {
434 | ALWAYS_SEARCH_USER_PATHS = NO;
435 | CLANG_ANALYZER_NONNULL = YES;
436 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
437 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
438 | CLANG_CXX_LIBRARY = "libc++";
439 | CLANG_ENABLE_MODULES = YES;
440 | CLANG_ENABLE_OBJC_ARC = YES;
441 | CLANG_ENABLE_OBJC_WEAK = YES;
442 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
443 | CLANG_WARN_BOOL_CONVERSION = YES;
444 | CLANG_WARN_COMMA = YES;
445 | CLANG_WARN_CONSTANT_CONVERSION = YES;
446 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
447 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
448 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
449 | CLANG_WARN_EMPTY_BODY = YES;
450 | CLANG_WARN_ENUM_CONVERSION = YES;
451 | CLANG_WARN_INFINITE_RECURSION = YES;
452 | CLANG_WARN_INT_CONVERSION = YES;
453 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
454 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
455 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
456 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
457 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
458 | CLANG_WARN_STRICT_PROTOTYPES = YES;
459 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
460 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
461 | CLANG_WARN_UNREACHABLE_CODE = YES;
462 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
463 | COPY_PHASE_STRIP = NO;
464 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
465 | ENABLE_NS_ASSERTIONS = NO;
466 | ENABLE_STRICT_OBJC_MSGSEND = YES;
467 | GCC_C_LANGUAGE_STANDARD = gnu11;
468 | GCC_NO_COMMON_BLOCKS = YES;
469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
471 | GCC_WARN_UNDECLARED_SELECTOR = YES;
472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
473 | GCC_WARN_UNUSED_FUNCTION = YES;
474 | GCC_WARN_UNUSED_VARIABLE = YES;
475 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
476 | MTL_ENABLE_DEBUG_INFO = NO;
477 | MTL_FAST_MATH = YES;
478 | SDKROOT = iphoneos;
479 | SWIFT_COMPILATION_MODE = wholemodule;
480 | SWIFT_OPTIMIZATION_LEVEL = "-O";
481 | VALIDATE_PRODUCT = YES;
482 | };
483 | name = Release;
484 | };
485 | CD682E7722E5420300705A23 /* Debug */ = {
486 | isa = XCBuildConfiguration;
487 | buildSettings = {
488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
489 | CODE_SIGN_STYLE = Automatic;
490 | DEVELOPMENT_ASSET_PATHS = "Cedar/Preview\\ Content";
491 | ENABLE_PREVIEWS = YES;
492 | INFOPLIST_FILE = Cedar/Info.plist;
493 | LD_RUNPATH_SEARCH_PATHS = (
494 | "$(inherited)",
495 | "@executable_path/Frameworks",
496 | );
497 | PRODUCT_BUNDLE_IDENTIFIER = com.patrickmick.Cedar;
498 | PRODUCT_NAME = "$(TARGET_NAME)";
499 | SWIFT_VERSION = 5.0;
500 | TARGETED_DEVICE_FAMILY = "1,2";
501 | };
502 | name = Debug;
503 | };
504 | CD682E7822E5420300705A23 /* Release */ = {
505 | isa = XCBuildConfiguration;
506 | buildSettings = {
507 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
508 | CODE_SIGN_STYLE = Automatic;
509 | DEVELOPMENT_ASSET_PATHS = "Cedar/Preview\\ Content";
510 | ENABLE_PREVIEWS = YES;
511 | INFOPLIST_FILE = Cedar/Info.plist;
512 | LD_RUNPATH_SEARCH_PATHS = (
513 | "$(inherited)",
514 | "@executable_path/Frameworks",
515 | );
516 | PRODUCT_BUNDLE_IDENTIFIER = com.patrickmick.Cedar;
517 | PRODUCT_NAME = "$(TARGET_NAME)";
518 | SWIFT_VERSION = 5.0;
519 | TARGETED_DEVICE_FAMILY = "1,2";
520 | };
521 | name = Release;
522 | };
523 | CD682E7A22E5420300705A23 /* Debug */ = {
524 | isa = XCBuildConfiguration;
525 | buildSettings = {
526 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
527 | BUNDLE_LOADER = "$(TEST_HOST)";
528 | CODE_SIGN_STYLE = Automatic;
529 | INFOPLIST_FILE = CedarTests/Info.plist;
530 | LD_RUNPATH_SEARCH_PATHS = (
531 | "$(inherited)",
532 | "@executable_path/Frameworks",
533 | "@loader_path/Frameworks",
534 | );
535 | PRODUCT_BUNDLE_IDENTIFIER = com.patrickmick.CedarTests;
536 | PRODUCT_NAME = "$(TARGET_NAME)";
537 | SWIFT_VERSION = 5.0;
538 | TARGETED_DEVICE_FAMILY = "1,2";
539 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Cedar.app/Cedar";
540 | };
541 | name = Debug;
542 | };
543 | CD682E7B22E5420300705A23 /* Release */ = {
544 | isa = XCBuildConfiguration;
545 | buildSettings = {
546 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
547 | BUNDLE_LOADER = "$(TEST_HOST)";
548 | CODE_SIGN_STYLE = Automatic;
549 | INFOPLIST_FILE = CedarTests/Info.plist;
550 | LD_RUNPATH_SEARCH_PATHS = (
551 | "$(inherited)",
552 | "@executable_path/Frameworks",
553 | "@loader_path/Frameworks",
554 | );
555 | PRODUCT_BUNDLE_IDENTIFIER = com.patrickmick.CedarTests;
556 | PRODUCT_NAME = "$(TARGET_NAME)";
557 | SWIFT_VERSION = 5.0;
558 | TARGETED_DEVICE_FAMILY = "1,2";
559 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Cedar.app/Cedar";
560 | };
561 | name = Release;
562 | };
563 | CD682E7D22E5420300705A23 /* Debug */ = {
564 | isa = XCBuildConfiguration;
565 | buildSettings = {
566 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
567 | CODE_SIGN_STYLE = Automatic;
568 | INFOPLIST_FILE = CedarUITests/Info.plist;
569 | LD_RUNPATH_SEARCH_PATHS = (
570 | "$(inherited)",
571 | "@executable_path/Frameworks",
572 | "@loader_path/Frameworks",
573 | );
574 | PRODUCT_BUNDLE_IDENTIFIER = com.patrickmick.CedarUITests;
575 | PRODUCT_NAME = "$(TARGET_NAME)";
576 | SWIFT_VERSION = 5.0;
577 | TARGETED_DEVICE_FAMILY = "1,2";
578 | TEST_TARGET_NAME = Cedar;
579 | };
580 | name = Debug;
581 | };
582 | CD682E7E22E5420300705A23 /* Release */ = {
583 | isa = XCBuildConfiguration;
584 | buildSettings = {
585 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
586 | CODE_SIGN_STYLE = Automatic;
587 | INFOPLIST_FILE = CedarUITests/Info.plist;
588 | LD_RUNPATH_SEARCH_PATHS = (
589 | "$(inherited)",
590 | "@executable_path/Frameworks",
591 | "@loader_path/Frameworks",
592 | );
593 | PRODUCT_BUNDLE_IDENTIFIER = com.patrickmick.CedarUITests;
594 | PRODUCT_NAME = "$(TARGET_NAME)";
595 | SWIFT_VERSION = 5.0;
596 | TARGETED_DEVICE_FAMILY = "1,2";
597 | TEST_TARGET_NAME = Cedar;
598 | };
599 | name = Release;
600 | };
601 | /* End XCBuildConfiguration section */
602 |
603 | /* Begin XCConfigurationList section */
604 | CD682E4722E5420100705A23 /* Build configuration list for PBXProject "Cedar" */ = {
605 | isa = XCConfigurationList;
606 | buildConfigurations = (
607 | CD682E7422E5420300705A23 /* Debug */,
608 | CD682E7522E5420300705A23 /* Release */,
609 | );
610 | defaultConfigurationIsVisible = 0;
611 | defaultConfigurationName = Release;
612 | };
613 | CD682E7622E5420300705A23 /* Build configuration list for PBXNativeTarget "Cedar" */ = {
614 | isa = XCConfigurationList;
615 | buildConfigurations = (
616 | CD682E7722E5420300705A23 /* Debug */,
617 | CD682E7822E5420300705A23 /* Release */,
618 | );
619 | defaultConfigurationIsVisible = 0;
620 | defaultConfigurationName = Release;
621 | };
622 | CD682E7922E5420300705A23 /* Build configuration list for PBXNativeTarget "CedarTests" */ = {
623 | isa = XCConfigurationList;
624 | buildConfigurations = (
625 | CD682E7A22E5420300705A23 /* Debug */,
626 | CD682E7B22E5420300705A23 /* Release */,
627 | );
628 | defaultConfigurationIsVisible = 0;
629 | defaultConfigurationName = Release;
630 | };
631 | CD682E7C22E5420300705A23 /* Build configuration list for PBXNativeTarget "CedarUITests" */ = {
632 | isa = XCConfigurationList;
633 | buildConfigurations = (
634 | CD682E7D22E5420300705A23 /* Debug */,
635 | CD682E7E22E5420300705A23 /* Release */,
636 | );
637 | defaultConfigurationIsVisible = 0;
638 | defaultConfigurationName = Release;
639 | };
640 | /* End XCConfigurationList section */
641 |
642 | /* Begin XCVersionGroup section */
643 | CDDC455E22EE90B0009DF238 /* Cedar.xcdatamodeld */ = {
644 | isa = XCVersionGroup;
645 | children = (
646 | CDDC455F22EE90B0009DF238 /* Cedar.xcdatamodel */,
647 | );
648 | currentVersion = CDDC455F22EE90B0009DF238 /* Cedar.xcdatamodel */;
649 | name = Cedar.xcdatamodeld;
650 | path = /System/Volumes/Data/Users/patrickmick/Desktop/Cedar/Cedar/Cedar.xcdatamodeld;
651 | sourceTree = "";
652 | versionGroupType = wrapper.xcdatamodel;
653 | };
654 | /* End XCVersionGroup section */
655 | };
656 | rootObject = CD682E4422E5420100705A23 /* Project object */;
657 | }
658 |
--------------------------------------------------------------------------------