It appears that the problem is related to displaying the table before the variable it is based on, fundData, is updated / populated. I solved the problem as follows. 1) I moved the initial sql query from the task tied to Progress View in the main view to the view models init (I could have probably just added isLoading = true to the ProgressView task). This along with the isLoading variable assure that initially fundData is populated with data before the table is displayed. 2) I added isLoading = true to the button action. This once again assures that that fundData is updated before the table is displayed (fundData and isLoading are updated on the main actor at the end of the view models function). Along with these two changes I pulled QueryDatabase into the view model but it is not part of the solution. It just seemed cleaner. Oh, and I made QueryDatabase (function in view model) async since I changed from Dispatch.main.async to MainActor(run:. I do not think this is part of the solution. Below is the updated main view and view model. The button works and it is very fast.
// main view
struct ContentView: View {
@ObservedObject var vm: SQLiteViewModel = SQLiteViewModel()
var body: some View {
if vm.isLoading == true {
ProgressView()
} else {
HStack {
Spacer()
.frame(width: 135, height: 200, alignment: .center)
Table(vm.fundData) {
TableColumn("Fund") { record in
Text(record.fundName)
.frame(width: 60, height: 15, alignment: .center)
}
.width(60)
TableColumn("Date") { record in
Text(sqlDateFormatter.string(from: record.timeStamp))
.frame(width: 120, height: 15, alignment: .center)
}
.width(120)
TableColumn("Close") { record in
Text(String(format: "$%.2f", record.close))
.frame(width: 65, height: 15, alignment: .trailing)
}
.width(65)
} // end table
.font(.system(size: 14, weight: .regular, design: .monospaced))
.frame(width: 330, height: 565, alignment: .center)
Spacer()
.frame(width: 30, height: 30, alignment: .center)
VStack {
Button(action: {
Task {
vm.isLoading = true
await vm.QueryDatabase(fundName: "Fund1", numYears: 1, sortOrder: "main.TimeStamp DESC")
}
}) {
Text("Date Descending")
} // end button
.frame(width: 140)
} // end v stack
.font(.system(size: 12, weight: .regular, design: .monospaced))
} // end horizontal stack
.frame(width: 600, height: 600, alignment: .center)
}
} // end view
}
// view model
class SQLiteViewModel: ObservableObject {
var db: OpaquePointer?
@Published var fundData: [TradingDay] = []
@Published var isLoading: Bool = true
var tempTradingDays: [TradingDay] = []
init() {
db = OpenDatabase()
Task {
await QueryDatabase(fundName: "Fund1", numYears: 1, sortOrder: "main.TimeStamp ASC")
}
}
func QueryDatabase(fundName: String, numYears: Int, sortOrder: String) async {
tempTradingDays = []
let daysBetween4713And2001: Double = 2451910.500000
let secondsPerDay: Double = 86400.00
var queryTradingDaysCPtr: OpaquePointer?
sqlite3_exec(db, SQLStmts.beginTransaction, nil, nil, nil);
if sqlite3_prepare_v2(db, SQLStmts.QueryTradingDays(fundName: fundName, numYears: numYears, sortOrder: sortOrder), -1, &queryTradingDaysCPtr, nil) == SQLITE_OK {
while (sqlite3_step(queryTradingDaysCPtr) == SQLITE_ROW) {
let fundName = sqlite3_column_text(queryTradingDaysCPtr, 0)
let daysSince4713BC = sqlite3_column_double(queryTradingDaysCPtr, 1)
let close = sqlite3_column_double(queryTradingDaysCPtr, 2)
let fundNameAsString = String(cString: fundName!)
let daysSinceJanOne2001 = daysSince4713BC - daysBetween4713And2001
let secondsSinceJanOne2001 = daysSinceJanOne2001 * secondsPerDay
let timeStamp = Date(timeIntervalSinceReferenceDate: secondsSinceJanOne2001)
var tempTradingDay: TradingDay = TradingDay(fundName: "", timeStamp: Date(), close: 0.0)
tempTradingDay.fundName = fundNameAsString
tempTradingDay.timeStamp = timeStamp
tempTradingDay.close = close
tempTradingDays.append(tempTradingDay)
} // end while loop
} else {
let errorMessage = String(cString: sqlite3_errmsg(db))
print("\nQuery is not prepared \(errorMessage)")
}
sqlite3_finalize(queryTradingDaysCPtr)
sqlite3_exec(db, SQLStmts.commitTransaction, nil, nil, nil);
await MainActor.run(body: {
self.fundData = self.tempTradingDays
self.isLoading = false
})
}
}