@Chris_Parker sorry about that, here are the other bits of code for you (for the colors I have just removed these below to stop any errors).
import SwiftUI
import UniformTypeIdentifiers
struct MainMenuView: View {
@StateObject var store = DataStore()
@State private var showingAlert = false
@State private var showingExporter = false
@State private var showingImporter = false
@State var fileName = ""
let selectedFont = "DINAlternate-Bold"
func load() async throws -> [BudgetDetails] {
try await withCheckedThrowingContinuation { continuation in
load {result in
switch result {
case .failure(let error):
continuation.resume(throwing: error)
case .success(let budget):
continuation.resume(returning: budget)
}
}
}
}
func load(completion: @escaping (Result<[BudgetDetails], Error>)->Void) {
DispatchQueue.main.async {
do {
let fileURL = URL(string: fileName)
guard let file = try? FileHandle(forReadingFrom: fileURL!) else {
DispatchQueue.main.async {
completion(.success([]))
}
return
}
let budgetMaster = try JSONDecoder().decode([BudgetDetails].self, from: file.availableData)
DispatchQueue.main.async {
completion(.success(budgetMaster))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
var body: some View {
ZStack {
ZStack {
Color.white.ignoresSafeArea()
List{
Section{
HStack{
Image(systemName: "arrow.up.doc").foregroundColor(Color.black)
Text("Export").font(.custom(selectedFont, size: 15)).foregroundColor(Color.black)
}
.overlay(
Button("", action: {
Task{
DataStore.save(budget: store.budget) { result in
if case .failure(let error) = result {
fatalError(error.localizedDescription)
}
}
}
showingExporter.toggle()
})
)
}
.listRowBackground(.white)
Section{
HStack{
Image(systemName: "arrow.down.doc").foregroundColor(.black)
Text("Import").font(.custom(selectedFont, size: 15)).foregroundColor(.black)
}
.overlay(
Button("", action: {
showingImporter.toggle()
})
)
}
.listRowBackground(.white)
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack{
Text("Menu").font(.custom(selectedFont, size: 30)) .font(.title).fontWeight(.bold)
.foregroundColor(.black)
Spacer()
EditButton()
}
}
}
}
}
.fileImporter(isPresented: $showingImporter, allowedContentTypes: [.json], onCompletion: { result in
switch result {
case .success(let url):
fileName = url.absoluteString
Task {
load { result in
switch result {
case .failure(let error):
fatalError(error.localizedDescription)
case .success(let budget):
store.budget = budget
}
}
}
case .failure(let error):
print(error.localizedDescription)
}
})
.fileExporter(isPresented: $showingExporter, document: Doc(url: try! DataStore.fileURL()), contentType: .json) { (result) in
do {
let fileURL = try result.get()
print(fileURL)
}
catch {
print("Cannot Save Document")
print(error.localizedDescription)
}
}
}
}
struct Doc: FileDocument {
var url: URL
static var readableContentTypes: [UTType]{[.json]}
init(url: URL) {
self.url = url
}
init(configuration: ReadConfiguration) throws {
url = try DataStore.fileURL()
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let file = try! FileWrapper(url: url, options: .immediate)
return file
}
}
struct MainMenuView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
MainMenuView()
}
}
}
import Foundation
import SwiftUI
class DataStore: ObservableObject {
@Published var budget: [BudgetDetails] = []
static func fileURL() throws -> URL {
try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
.appendingPathComponent("BudgetMaster.data")
}
static func load() async throws -> [BudgetDetails] {
try await withCheckedThrowingContinuation { continuation in
load {result in
switch result {
case .failure(let error):
continuation.resume(throwing: error)
case .success(let budget):
continuation.resume(returning: budget)
}
}
}
}
static func load(completion: @escaping (Result<[BudgetDetails], Error>)->Void) {
DispatchQueue.global(qos: .background).async {
do {
let fileURL = try fileURL()
guard let file = try? FileHandle(forReadingFrom: fileURL) else {
DispatchQueue.main.async {
completion(.success([]))
}
return
}
let budgetMaster = try JSONDecoder().decode([BudgetDetails].self, from: file.availableData)
DispatchQueue.main.async {
completion(.success(budgetMaster))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
@discardableResult
static func save(budget: [BudgetDetails]) async throws -> Int {
try await withCheckedThrowingContinuation { continuation in
save(budget: budget) { result in
switch result {
case .failure(let error):
continuation.resume(throwing: error)
case .success(let budgetSaved):
continuation.resume(returning: budgetSaved)
}
}
}
}
static func save(budget: [BudgetDetails], completion: @escaping (Result<Int, Error>)->Void) {
DispatchQueue.global(qos: .background).async {
do {
let data = try JSONEncoder().encode(budget)
let outfile = try fileURL()
try data.write(to: outfile)
DispatchQueue.main.async {
completion(.success(budget.count))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
}
import Foundation
struct BudgetDetails: Identifiable, Hashable, Equatable, Codable {
let id: UUID
var categoryDetails: String
var incomeDetails: String
var incomeCategory: String
var incomeFrequency: Period
var incomeAmount: Int
var incomeAnnual: Int {
switch incomeFrequency {
case .Daily:
return incomeAmount * 365
case .Weekly:
return incomeAmount * 52
case .Fortnightly:
return incomeAmount * 26
case .Monthly:
return incomeAmount * 12
case .Quarterly:
return incomeAmount * 4
case .Annually:
return incomeAmount
case .Annually1:
return incomeAmount
}
}
var expenseDetails: String
var expenseCategory: String
var expenseFrequency: Period
var expenseAmount: Int
var expenseAnnual: Int {
switch expenseFrequency {
case .Daily:
return expenseAmount * 365
case .Weekly:
return expenseAmount * 52
case .Fortnightly:
return expenseAmount * 26
case .Monthly:
return expenseAmount * 12
case .Quarterly:
return expenseAmount * 4
case .Annually:
return expenseAmount
case .Annually1:
return expenseAmount
}
}
var budgetType: String {
if incomeCategory == "" && expenseCategory == "" {
return "Category"
}
else if incomeCategory == "" && categoryDetails == "" {
return "Expense"
}
else {
return "Income"
}
}
init(id: UUID = UUID(),categoryDetails: String, incomeDetails: String, incomeCategory: String, incomeFrequency: Period, incomeAmount: Int, incomeAnnual: Int, expenseDetails: String, expenseCategory: String, expenseFrequency: Period, expenseAmount: Int, expenseAnnual: Int, budgetType: String) {
self.id = id
self.categoryDetails = categoryDetails
self.incomeDetails = incomeDetails
self.incomeCategory = incomeCategory
self.incomeFrequency = incomeFrequency
self.incomeAmount = incomeAmount
self.expenseDetails = expenseDetails
self.expenseCategory = expenseCategory
self.expenseFrequency = expenseFrequency
self.expenseAmount = expenseAmount
}
}
extension BudgetDetails {
struct Data {
var categoryDetails: String = ""
var incomeDetails: String = ""
var incomeCategory: String = ""
var incomeFrequency: Period = .Annually1
var incomeAmount: Int = 0
var incomeAnnual: Int = 0
var expenseDetails: String = ""
var expenseCategory: String = ""
var expenseFrequency: Period = .Annually1
var expenseAmount: Int = 0
var expenseAnnual: Int = 0
var budgetType: String = ""
}
var bData: Data {
Data(categoryDetails: categoryDetails, incomeDetails: incomeDetails, incomeCategory: incomeCategory, incomeFrequency: incomeFrequency, incomeAmount: incomeAmount, incomeAnnual: incomeAnnual, expenseDetails: expenseDetails, expenseCategory: expenseCategory, expenseFrequency: expenseFrequency, expenseAmount: expenseAmount, expenseAnnual: expenseAnnual, budgetType: budgetType)
}
mutating func update(from bData: Data) {
categoryDetails = bData.categoryDetails
incomeDetails = bData.incomeDetails
incomeCategory = bData.incomeCategory
incomeFrequency = bData.incomeFrequency
incomeAmount = Int(bData.incomeAmount)
expenseDetails = bData.expenseDetails
expenseCategory = bData.expenseCategory
expenseFrequency = bData.expenseFrequency
expenseAmount = Int(bData.expenseAmount)
}
init(bData: Data) {
id = UUID()
categoryDetails = bData.categoryDetails
incomeDetails = bData.incomeDetails
incomeCategory = bData.incomeCategory
incomeFrequency = bData.incomeFrequency
incomeAmount = Int(bData.incomeAmount)
expenseDetails = bData.expenseDetails
expenseCategory = bData.expenseCategory
expenseFrequency = bData.expenseFrequency
expenseAmount = Int(bData.expenseAmount)
}
}
extension BudgetDetails {
static let sampleData: [BudgetDetails] =
[
BudgetDetails(categoryDetails: "Work", incomeDetails: "", incomeCategory: "", incomeFrequency: .Annually1, incomeAmount: 0, incomeAnnual: 0, expenseDetails: "",expenseCategory: "", expenseFrequency: .Annually1, expenseAmount: 0, expenseAnnual: 0, budgetType: "Category"),
BudgetDetails(categoryDetails: "Investments", incomeDetails: "", incomeCategory: "", incomeFrequency: .Annually1, incomeAmount: 0, incomeAnnual: 0, expenseDetails: "",expenseCategory: "", expenseFrequency: .Annually1, expenseAmount: 0, expenseAnnual: 0, budgetType: "Category"),
BudgetDetails(categoryDetails: "Food", incomeDetails: "", incomeCategory: "", incomeFrequency: .Annually1, incomeAmount: 0, incomeAnnual: 0, expenseDetails: "",expenseCategory: "", expenseFrequency: .Annually1, expenseAmount: 0, expenseAnnual: 0, budgetType: "Category"),
BudgetDetails(categoryDetails: "House", incomeDetails: "", incomeCategory: "", incomeFrequency: .Annually1, incomeAmount: 0, incomeAnnual: 0, expenseDetails: "",expenseCategory: "", expenseFrequency: .Annually1, expenseAmount: 0, expenseAnnual: 0, budgetType: "Category"),
BudgetDetails(categoryDetails: "", incomeDetails: "Wages", incomeCategory: "Work", incomeFrequency: .Weekly, incomeAmount: 1000, incomeAnnual: 52000, expenseDetails: "",expenseCategory: "", expenseFrequency: .Weekly, expenseAmount: 0, expenseAnnual: 0, budgetType: "Income"),
BudgetDetails(categoryDetails: "", incomeDetails: "Dividends", incomeCategory: "Investments", incomeFrequency: .Quarterly, incomeAmount: 500, incomeAnnual: 2000, expenseDetails: "",expenseCategory: "", expenseFrequency: .Weekly, expenseAmount: 0, expenseAnnual: 0, budgetType: "Income"),
BudgetDetails(categoryDetails: "", incomeDetails: "", incomeCategory: "", incomeFrequency: .Weekly, incomeAmount: 0, incomeAnnual: 0, expenseDetails: "Rent",expenseCategory: "House", expenseFrequency: .Weekly, expenseAmount: 500, expenseAnnual: 26000, budgetType: "Expense"),
BudgetDetails(categoryDetails: "", incomeDetails: "", incomeCategory: "", incomeFrequency: .Weekly, incomeAmount: 0, incomeAnnual: 0, expenseDetails: "Coffee",expenseCategory: "Food", expenseFrequency: .Daily, expenseAmount: 5, expenseAnnual: 1825, budgetType: "Expense")
]
}