diff --git a/.gitignore b/.gitignore
index 63d8a20..de011f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,3 +94,6 @@ iOSInjectionProject/
SimpleBudget.xcodeproj
buildServer.json
+.compile
+Sources/Info.plist
+Sources/simple-budget.entitlements
diff --git a/Info.plist b/Info.plist
deleted file mode 100644
index 9523b46..0000000
--- a/Info.plist
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- com.corybuecker.SimpleBudget
- CFBundleName
- Simple Budget
- CFBundleShortVersionString
- 1
- CFBundleVersion
- 1.0
-
-
diff --git a/Sources/Assets.xcassets/AccentColor.colorset/Contents.json b/Sources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..35bfc1f
--- /dev/null
+++ b/Sources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "display-p3",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.227",
+ "green" : "0.160",
+ "red" : "0.127"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..ebf6057
--- /dev/null
+++ b/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,14 @@
+{
+ "images" : [
+ {
+ "filename" : "piggy_bank_icon_1024x1024.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/Assets.xcassets/AppIcon.appiconset/piggy_bank_icon_1024x1024.png b/Sources/Assets.xcassets/AppIcon.appiconset/piggy_bank_icon_1024x1024.png
new file mode 100644
index 0000000..f9fb86d
Binary files /dev/null and b/Sources/Assets.xcassets/AppIcon.appiconset/piggy_bank_icon_1024x1024.png differ
diff --git a/Sources/Assets.xcassets/Contents.json b/Sources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Sources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift
index 626988f..21be05c 100644
--- a/Sources/ContentView.swift
+++ b/Sources/ContentView.swift
@@ -1,13 +1,27 @@
+import SwiftData
import SwiftUI
struct ContentView: View {
- init() {
- print("test")
- }
+ @Environment(\.modelContext) var context: ModelContext
var body: some View {
- return VStack {
- Text("Hello, World!")
+ TabView {
+ Reports()
+ .tabItem {
+ Label("Reports", systemImage: "chart.bar")
+ }
+ AccountList()
+ .tabItem {
+ Label("Accounts", systemImage: "building.columns")
+ }
+ SavingList()
+ .tabItem {
+ Label("Savings", systemImage: "dollarsign.circle")
+ }
+ GoalList()
+ .tabItem {
+ Label("Goals", systemImage: "target")
+ }
}
}
}
diff --git a/Sources/Models/Account.swift b/Sources/Models/Account.swift
new file mode 100644
index 0000000..c380fd5
--- /dev/null
+++ b/Sources/Models/Account.swift
@@ -0,0 +1,14 @@
+import SwiftData
+
+@Model
+class Account {
+ var name: String = ""
+ var balance: Double = 0.0
+ var debt: Bool = false
+
+ init(name: String, balance: Double, debt: Bool) {
+ self.name = name
+ self.balance = balance
+ self.debt = debt
+ }
+}
diff --git a/Sources/Models/Goal.swift b/Sources/Models/Goal.swift
new file mode 100644
index 0000000..77df9a6
--- /dev/null
+++ b/Sources/Models/Goal.swift
@@ -0,0 +1,24 @@
+import SwiftData
+import SwiftUI
+
+enum GoalRecurrence: Codable {
+ case daily
+ case weekly
+ case monthly
+ case yearly
+}
+
+@Model
+class Goal {
+ var name: String = ""
+ var amount: Double = 0.0
+ var recurrence: GoalRecurrence = GoalRecurrence.monthly
+ var targetDate: Date = Date()
+
+ init(name: String, amount: Double, recurrence: GoalRecurrence, targetDate: Date) {
+ self.name = name
+ self.amount = amount
+ self.recurrence = recurrence
+ self.targetDate = targetDate
+ }
+}
diff --git a/Sources/Models/Saving.swift b/Sources/Models/Saving.swift
new file mode 100644
index 0000000..4b323be
--- /dev/null
+++ b/Sources/Models/Saving.swift
@@ -0,0 +1,12 @@
+import SwiftData
+
+@Model
+class Saving {
+ var name: String = ""
+ var amount: Double = 0.0
+
+ init(name: String, amount: Double) {
+ self.name = name
+ self.amount = amount
+ }
+}
diff --git a/Sources/Services/DateService.swift b/Sources/Services/DateService.swift
new file mode 100644
index 0000000..8e44e1e
--- /dev/null
+++ b/Sources/Services/DateService.swift
@@ -0,0 +1,92 @@
+import SwiftUI
+
+struct DateService {
+ enum DateServiceError: Error {
+ case unexpectedDate
+ }
+
+ private let from: Date
+ private let calendar: Calendar
+
+ init() {
+ self.from = Date()
+ self.calendar = Calendar.current
+ }
+
+ init(_ from: Date) {
+ self.from = from
+ self.calendar = Calendar.current
+ }
+
+ func startOfMonth() throws -> Date {
+ let components = self.calendar.dateComponents([.year, .month], from: self.from)
+ guard let optDate = self.calendar.date(from: components) else {
+ throw DateServiceError.unexpectedDate
+ }
+ return optDate
+ }
+
+ func endOfMonth() throws -> Date {
+ let startOfMonth = try startOfMonth()
+ let components = DateComponents(month: 1, second: -1)
+ guard let endOfMonth = self.calendar.date(byAdding: components, to: startOfMonth) else {
+ throw DateServiceError.unexpectedDate
+ }
+ return endOfMonth
+ }
+
+ func startOfNextMonth() throws -> Date {
+ let addComponents = DateComponents(month: 1)
+
+ guard let nextMonth = self.calendar.date(byAdding: addComponents, to: self.from) else {
+ throw DateServiceError.unexpectedDate
+ }
+
+ let nextMonthComponents = self.calendar.dateComponents([.year, .month], from: nextMonth)
+
+ guard let optDate = self.calendar.date(from: nextMonthComponents) else {
+ throw DateServiceError.unexpectedDate
+ }
+ return optDate
+ }
+
+ func endOfNextMonth() throws -> Date {
+ let startOfNextMonth = try startOfNextMonth()
+ let components = DateComponents(month: 1, second: -1)
+ guard let endOfNextMonth = self.calendar.date(byAdding: components, to: startOfNextMonth) else {
+ throw DateServiceError.unexpectedDate
+ }
+ return endOfNextMonth
+ }
+
+ func isEndOfMonth() throws -> Bool {
+ let dateComponents = self.calendar.dateComponents([.year, .month, .day], from: self.from)
+ let endOfMonthComponents = try self.calendar.dateComponents(
+ [.year, .month, .day], from: endOfMonth())
+
+ return dateComponents.year == endOfMonthComponents.year
+ && dateComponents.month == endOfMonthComponents.month
+ && dateComponents.day == endOfMonthComponents.day
+ }
+
+ func daysUntilEndOfMonth() throws -> Int {
+ if try isEndOfMonth() {
+ guard
+ let days = try self.calendar.dateComponents(
+ [.day], from: self.from, to: endOfNextMonth()
+ ).day
+ else {
+ throw DateServiceError.unexpectedDate
+ }
+ return days
+ }
+
+ guard
+ let days = try self.calendar.dateComponents([.day], from: self.from, to: endOfMonth())
+ .day
+ else {
+ throw DateServiceError.unexpectedDate
+ }
+ return days
+ }
+}
diff --git a/Sources/Services/GoalService.swift b/Sources/Services/GoalService.swift
new file mode 100644
index 0000000..4e7dbd8
--- /dev/null
+++ b/Sources/Services/GoalService.swift
@@ -0,0 +1,52 @@
+import SwiftUI
+
+struct GoalService {
+ private let goal: Goal
+ private let day: Double = 60 * 60 * 24.0
+
+ init(goal: Goal) {
+ self.goal = goal
+ }
+
+ func amortized() -> Decimal {
+ if Date() >= self.goal.targetDate {
+ return Decimal(self.goal.amount)
+ }
+
+ return dailyAmount() * Decimal(elapsedDays())
+ }
+
+ func dailyAmount() -> Decimal {
+ if Date() >= self.goal.targetDate {
+ return Decimal(0)
+ }
+
+ if Date() < startDate() {
+ return Decimal(0)
+ }
+
+ return Decimal(self.goal.amount)
+ / Decimal(DateInterval(start: startDate(), end: self.goal.targetDate).duration / day)
+ }
+
+ private func elapsedDays() -> Double {
+ if Date() < startDate() {
+ return 0
+ }
+
+ return DateInterval(start: startDate(), end: Date()).duration / day
+ }
+
+ private func startDate() -> Date {
+ switch self.goal.recurrence {
+ case GoalRecurrence.daily:
+ return Calendar.current.date(byAdding: .day, value: -1, to: self.goal.targetDate)!
+ case GoalRecurrence.weekly:
+ return Calendar.current.date(byAdding: .weekOfYear, value: -1, to: self.goal.targetDate)!
+ case GoalRecurrence.monthly:
+ return Calendar.current.date(byAdding: .month, value: -1, to: self.goal.targetDate)!
+ case GoalRecurrence.yearly:
+ return Calendar.current.date(byAdding: .year, value: -1, to: self.goal.targetDate)!
+ }
+ }
+}
diff --git a/Sources/SimpleBudget.swift b/Sources/SimpleBudget.swift
index 76d5a6a..aa2db65 100644
--- a/Sources/SimpleBudget.swift
+++ b/Sources/SimpleBudget.swift
@@ -1,10 +1,11 @@
+import SwiftData
import SwiftUI
@main
struct SimpleBudget: App {
- var body: some Scene {
- WindowGroup {
- ContentView()
- }
+ var body: some Scene {
+ WindowGroup {
+ ContentView().modelContainer(for: [Account.self, Saving.self, Goal.self])
}
+ }
}
diff --git a/Sources/Views/AccountForm.swift b/Sources/Views/AccountForm.swift
new file mode 100644
index 0000000..88e584c
--- /dev/null
+++ b/Sources/Views/AccountForm.swift
@@ -0,0 +1,43 @@
+import SwiftData
+import SwiftUI
+
+struct AccountForm: View {
+ let account: Account?
+
+ @State private var name: String = ""
+ @State private var balance: Double = 0.0
+ @State private var debt: Bool = false
+
+ @Environment(\.modelContext) var modelContext: ModelContext
+ @Environment(\.dismiss) private var dismiss
+
+ var body: some View {
+ Form {
+ TextField("Name", text: $name)
+ TextField("Balance", value: $balance, format: .number)
+ Picker("Debt", selection: $debt) {
+ Text("No").tag(false)
+ Text("Yes").tag(true)
+ }.pickerStyle(.segmented)
+ }.navigationBarTitle("New Account").toolbar {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button("Save") {
+ if let account {
+ account.name = name
+ account.balance = balance
+ account.debt = debt
+ } else {
+ modelContext.insert(Account(name: name, balance: balance, debt: debt))
+ }
+ dismiss()
+ }
+ }
+ }.onAppear {
+ if let account {
+ name = account.name
+ balance = account.balance
+ debt = account.debt
+ }
+ }
+ }
+}
diff --git a/Sources/Views/AccountList.swift b/Sources/Views/AccountList.swift
new file mode 100644
index 0000000..1d4d40b
--- /dev/null
+++ b/Sources/Views/AccountList.swift
@@ -0,0 +1,34 @@
+import SwiftData
+import SwiftUI
+
+struct AccountList: View {
+ @Environment(\.modelContext) var modelContext: ModelContext
+ @Query private var accounts: [Account]
+
+ var body: some View {
+ VStack {
+ NavigationStack {
+ List {
+ ForEach(accounts, id: \.self) { account in
+ NavigationLink(destination: AccountForm(account: account)) {
+ Text(account.name)
+ }
+ }.onDelete(perform: deleteAccount)
+ }.navigationBarTitle("Accounts").toolbar {
+ ToolbarItem {
+ NavigationLink(destination: AccountForm(account: nil)) {
+ Image(systemName: "plus")
+ }
+ }
+ }
+ }
+ Spacer()
+ }
+ }
+
+ func deleteAccount(at offsets: IndexSet) {
+ for offset in offsets {
+ modelContext.delete(accounts[offset])
+ }
+ }
+}
diff --git a/Sources/Views/GoalForm.swift b/Sources/Views/GoalForm.swift
new file mode 100644
index 0000000..09beb69
--- /dev/null
+++ b/Sources/Views/GoalForm.swift
@@ -0,0 +1,53 @@
+import SwiftData
+import SwiftUI
+
+struct GoalForm: View {
+ let goal: Goal?
+
+ @State private var name: String = ""
+ @State private var amount: Double = 0.0
+ @State private var recurrence: GoalRecurrence = .monthly
+ @State private var targetDate: Date = Date()
+
+ @Environment(\.modelContext) var modelContext: ModelContext
+ @Environment(\.dismiss) var dismiss
+
+ var body: some View {
+ Form {
+ TextField("Name", text: $name)
+ TextField("Amount", value: $amount, format: .number)
+ Picker("Recurrance", selection: $recurrence) {
+ Text("Daily").tag(GoalRecurrence.daily)
+ Text("Weekly").tag(GoalRecurrence.weekly)
+ Text("Monthly").tag(GoalRecurrence.monthly)
+ Text("Yearly").tag(GoalRecurrence.yearly)
+ }
+ DatePicker("Target Date", selection: $targetDate, displayedComponents: [.date])
+ }.navigationBarTitle(goal?.name ?? "New Goal").toolbar {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button("Save") {
+ let startOfDay = Calendar.current.startOfDay(for: targetDate)
+
+ if let goal = goal {
+ goal.name = name
+ goal.amount = amount
+ goal.recurrence = recurrence
+ goal.targetDate = startOfDay
+ } else {
+ modelContext.insert(
+ Goal(name: name, amount: amount, recurrence: recurrence, targetDate: startOfDay))
+ }
+
+ dismiss()
+ }
+ }
+ }.onAppear {
+ if let goal {
+ name = goal.name
+ amount = goal.amount
+ recurrence = goal.recurrence
+ targetDate = goal.targetDate
+ }
+ }
+ }
+}
diff --git a/Sources/Views/GoalList.swift b/Sources/Views/GoalList.swift
new file mode 100644
index 0000000..888d0eb
--- /dev/null
+++ b/Sources/Views/GoalList.swift
@@ -0,0 +1,67 @@
+import SwiftData
+import SwiftUI
+
+struct GoalList: View {
+ @Environment(\.modelContext) var modelContext: ModelContext
+ @Query private var goals: [Goal]
+
+ @State private var lastRendered: Date = Date()
+
+ var body: some View {
+ VStack {
+ NavigationStack {
+ List {
+ ForEach(goals, id: \.self) { goal in
+ NavigationLink(destination: GoalForm(goal: goal)) {
+ GoalListItem(goal: goal, lastRendered: lastRendered)
+ }
+ }
+ .onDelete(perform: deleteGoal)
+ }
+ .navigationBarTitle("Goals")
+ .toolbar {
+ ToolbarItem {
+ NavigationLink(destination: GoalForm(goal: nil)) {
+ Image(systemName: "plus")
+ }
+ }
+ }
+ .refreshable {
+ lastRendered = Date()
+ }
+ }
+ Spacer()
+ }
+ }
+
+ func deleteGoal(at offsets: IndexSet) {
+ for offset in offsets {
+ modelContext.delete(goals[offset])
+ }
+ }
+}
+
+struct GoalListItem: View {
+ let goal: Goal
+ let lastRendered: Date
+
+ var body: some View {
+ VStack(alignment: .leading) {
+ Text(goal.name)
+ Text(goal.targetDate.formatted(date: .abbreviated, time: .omitted))
+ .foregroundColor(.secondary)
+ HStack {
+ Text(
+ GoalService(goal: goal).dailyAmount(),
+ format: .currency(code: "USD").precision(.fractionLength(2)))
+ Text("/")
+ Text(
+ GoalService(goal: goal).amortized(),
+ format: .currency(code: "USD").precision(.fractionLength(5)))
+ Text("/")
+ Text(goal.amount, format: .currency(code: "USD").precision(.fractionLength(0)))
+ }
+ .foregroundColor(.secondary)
+ }
+ }
+}
diff --git a/Sources/Views/Reports.swift b/Sources/Views/Reports.swift
new file mode 100644
index 0000000..590d57e
--- /dev/null
+++ b/Sources/Views/Reports.swift
@@ -0,0 +1,85 @@
+import SwiftData
+import SwiftUI
+
+struct Reports: View {
+ @Environment(\.modelContext) var modelContext: ModelContext
+ @Query var accounts: [Account]
+ @Query var savings: [Saving]
+ @Query var goals: [Goal]
+
+ var total: Decimal {
+ let accountsTotal: Decimal = accounts.reduce(
+ 0,
+ { accumulator, account in
+ account.debt
+ ? accumulator - Decimal(account.balance) : accumulator + Decimal(account.balance)
+ })
+
+ let savingsTotal: Decimal = savings.reduce(
+ 0,
+ { accumulator, saving in
+ accumulator - Decimal(saving.amount)
+ })
+
+ let goalsTotal: Decimal = goals.reduce(
+ 0,
+ { accumulator, goal in
+ accumulator - GoalService(goal: goal).amortized()
+ })
+
+ return accountsTotal + savingsTotal + goalsTotal
+ }
+
+ var daysRemaining: Int? = try? DateService().daysUntilEndOfMonth()
+
+ func dailySaving() -> Decimal {
+ goals.reduce(
+ 0,
+ { accumulator, goal in
+ accumulator + GoalService(goal: goal).dailyAmount()
+ })
+ }
+
+ func remainingAmount() -> Decimal {
+ if let daysRemaining {
+ total / Decimal(daysRemaining)
+ } else {
+ 0
+ }
+ }
+
+ @State private var lastRendered = Date()
+
+ var body: some View {
+ VStack {
+ Breakdown(
+ lastRendered: lastRendered, total: total, daysRemaining: daysRemaining,
+ remainingAmount: remainingAmount(), dailySaving: dailySaving())
+ Button("Refresh") {
+ lastRendered = Date()
+ }
+ }
+ }
+}
+
+struct Breakdown: View {
+ let lastRendered: Date
+ let total: Decimal
+ let daysRemaining: Int?
+ let remainingAmount: Decimal
+ let dailySaving: Decimal
+
+ var body: some View {
+ Text("Reports").foregroundColor(
+ Color(
+ red: Double.random(in: 0...1),
+ green: Double.random(in: 0...1),
+ blue: Double.random(in: 0...1)))
+ Text(total, format: .currency(code: "USD"))
+ if let daysRemaining {
+ Text("Days remaining: \(daysRemaining)")
+ }
+ Text(remainingAmount, format: .currency(code: "USD").precision(.fractionLength(4)))
+ Text(dailySaving, format: .currency(code: "USD"))
+ }
+}
diff --git a/Sources/Views/SavingForm.swift b/Sources/Views/SavingForm.swift
new file mode 100644
index 0000000..c1db071
--- /dev/null
+++ b/Sources/Views/SavingForm.swift
@@ -0,0 +1,37 @@
+import SwiftData
+import SwiftUI
+
+struct SavingForm: View {
+ let saving: Saving?
+
+ @State private var name: String = ""
+ @State private var amount: Double = 0.0
+
+ @Environment(\.modelContext) var modelContext: ModelContext
+ @Environment(\.dismiss) var dismiss
+
+ var body: some View {
+ Form {
+ TextField("Name", text: $name)
+ TextField("Amount", value: $amount, format: .number)
+ }.navigationBarTitle(saving?.name ?? "New Saving").toolbar {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button("Save") {
+ if let saving = saving {
+ saving.name = name
+ saving.amount = amount
+ } else {
+ modelContext.insert(Saving(name: name, amount: amount))
+ }
+
+ dismiss()
+ }
+ }
+ }.onAppear {
+ if let saving {
+ name = saving.name
+ amount = saving.amount
+ }
+ }
+ }
+}
diff --git a/Sources/Views/SavingList.swift b/Sources/Views/SavingList.swift
new file mode 100644
index 0000000..0ef7890
--- /dev/null
+++ b/Sources/Views/SavingList.swift
@@ -0,0 +1,34 @@
+import SwiftData
+import SwiftUI
+
+struct SavingList: View {
+ @Environment(\.modelContext) var modelContext: ModelContext
+ @Query private var savings: [Saving]
+
+ var body: some View {
+ VStack {
+ NavigationStack {
+ List {
+ ForEach(savings, id: \.self) { saving in
+ NavigationLink(destination: SavingForm(saving: saving)) {
+ Text(saving.name)
+ }
+ }.onDelete(perform: deleteSaving)
+ }.navigationBarTitle("Savings").toolbar {
+ ToolbarItem {
+ NavigationLink(destination: SavingForm(saving: nil)) {
+ Image(systemName: "plus")
+ }
+ }
+ }
+ }
+ Spacer()
+ }
+ }
+
+ func deleteSaving(at offsets: IndexSet) {
+ for offset in offsets {
+ modelContext.delete(savings[offset])
+ }
+ }
+}
diff --git a/launch.sh b/launch.sh
index 1c7f9ab..d9ab73e 100755
--- a/launch.sh
+++ b/launch.sh
@@ -1,14 +1,7 @@
#!/bin/bash -ex
-#rm -rf SimpleBudget.xcodeproj
-#
-#xcodegen
-#
-#xcodebuild clean build -project SimpleBudget.xcodeproj -allowProvisioningUpdates -sdk iphonesimulator
-#xcrun simctl install MyPhone build/Debug-iphonesimulator/SimpleBudget.app
-
-#xcode-build-server config -project SimpleBudget.xcodeproj -scheme SimpleBudget
-
-xcodebuild clean build -project SimpleBudget.xcodeproj -allowProvisioningUpdates -sdk iphonesimulator -configuration Debug
-xcrun simctl install MyPhone build/Debug-iphonesimulator/SimpleBudget.app
-xcrun simctl launch --console-pty --terminate-running-process MyPhone com.corybuecker.SimpleBudget
+xcodegen
+xcodebuild build -project SimpleBudget.xcodeproj -sdk iphonesimulator -configuration Debug -scheme simple-budget -derivedDataPath ./.build | xcode-build-server parse -a
+#xcrun simctl uninstall MyPhone com.corybuecker.SimpleBudget
+xcrun simctl install MyPhone .build/Build/Products/Debug-iphonesimulator/simple-budget.app
+xcrun simctl launch --console-pty --terminate-running-process MyPhone dev.corybuecker.simple-budget
diff --git a/project.yml b/project.yml
index 3c98bcb..53cb2a2 100644
--- a/project.yml
+++ b/project.yml
@@ -1,13 +1,34 @@
name: SimpleBudget
options:
- bundleIdPrefix: com.corybuecker
+ bundleIdPrefix: dev.corybuecker
settings:
DEVELOPMENT_TEAM: ${DEVELOPMENT_TEAM}
- INFOPLIST_FILE: Info.plist
targets:
- SimpleBudget:
+ simple-budget:
type: application
platform: iOS
deploymentTarget: "17.2"
+ supportedDestinations: [iOS]
sources:
- - Sources
+ - path: Sources
+ info:
+ path: Sources/Info.plist
+ properties:
+ CFBundleDevelopmentRegion: en
+ CFBundleIdentifier: dev.corybuecker.simple-budget
+ CFBundleName: Simple Budget
+ CFBundlePackageType: APPL
+ CFBundleShortVersionString: 1.0.1
+ CFBundleVersion: 1.0.1
+ ITSAppUsesNonExemptEncryption: false
+ UIBackgroundModes: [remote-notification]
+ UILaunchScreen: {}
+ UIRequiresFullScreen: true
+ UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait]
+ entitlements:
+ path: Sources/simple-budget.entitlements
+ properties:
+ aps-environment: development
+ com.apple.developer.icloud-container-identifiers: [iCloud.dev.corybuecker.simple-budget]
+ com.apple.developer.icloud-services: [CloudKit]
+