├── 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 | ![Home](https://github.com/pmick/cedar/raw/master/images/home.png) 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 | --------------------------------------------------------------------------------