The below code works. I pass in an array of core data entities and display attributes in a table. In the second table column I use a switch case statement to handle the 3 possible attributes I want to display. I could eliminate the switch case if I could figure out how to access a given core data attribute via a variable that contains the specific attribute name but have been unable to determine how to do so. Two attempts are commented out in the code.
struct DataTable: View {
private var closingValues: Array<TradingDayPrices>
var heading: String
var attribute: String
init(closingValues: [TradingDayPrices], heading: String, attribute: String) {
self.closingValues = closingValues
self.heading = heading
self.attribute = attribute
}
var body: some View {
Text(heading)
.foregroundColor(.black)
.font(Font.custom("Arial", size: 18))
.bold()
.padding(.top, 10)
Table(self.closingValues) {
TableColumn("Date") { value in
HStack {
Spacer()
Text(dateToStringFormatter.string(from: value.timeStamp!))
Spacer()
}
}
.width(100)
TableColumn("Closing Value") { value in
HStack {
Spacer()
// Text(String(format: "$ %.2f", value(forKey: attribute)))
// Text(String(format: "$ %.2f", value(attribute)))
switch attribute {
case "s1Close":
Text(String(format: "$ %.2f", value.s1Close))
case "s2Close":
Text(String(format: "$ %.2f", value.s2Close))
default:
Text(String(format: "$ %.2f", value.s3Close))
}
Spacer()
}
}
.width(100)
}
.foregroundColor(.black)
.font(Font.custom("Arial", size: 16))
.frame(width: 250)
Spacer()
.frame(height: 30)
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have a program that adds data from CSV files to core data. Once I have saved the managed object context to the persistent store I want to run a checkpoint to flush the contents of the WAL file into the SQLite file. I have added code to my class in an attempt to do so but am unable to determine on the line "sqlite3_wal_checkpoint( ...)" what to put in the unsafe pointer parameter (denoted with ??????? in the code).
class UpdateCoreData: ObservableObject {
@Published var isLoading: Bool = true
var moc: NSManagedObjectContext
init(moc: NSManagedObjectContext) {
self.moc = moc
Task {
await IsDataStoreEmpty(moc: self.moc)
let fund1 = CSVtoCoreData(fundName: "Fund1", fileName: "Fund1.csv")
let fund2 = CSVtoCoreData(fundName: "Fund2", fileName: "Fund2.csv")
let fund3 = CSVtoCoreData(fundName: "Fund3", fileName: "Fund3.csv")
await fund1.CSVtoCoreDataG(moc: self.moc)
await fund2.CSVtoCoreDataG(moc: self.moc)
await fund3.CSVtoCoreDataG(moc: self.moc)
do {
try moc.save()
} catch {
print("Error saving. \(error)")
}
--- start of added code
let fileURL = URL(fileURLWithPath: "/Users/Chris/Downloads/Persistent Store 2/Funds.sqlite")
var dataBase: OpaquePointer?
sqlite3_open(fileURL.path, &dataBase )
if sqlite3_open(fileURL.path, &dataBase) != SQLITE_OK {
print("Unable to open sqlite file")
} else {
print("Succesfully opened sqlite file")
}
sqlite3_wal_checkpoint(dataBase, ??????? )
sqlite3_close(dataBase)
--- end of added code
DispatchQueue.main.async {
self.isLoading = false
}
}
} // end init
}
I have a view that displays a chart based on an array. Each element in the array is all of the attributes associated with a core data entity. The data is properly displayed. I would like to animate the rendering of the charts data but cannot seem to figure out how to do it. If someone could point me in the right direction it would be appreciated. Below is the code.
struct ClosingValuesChart: View {
@State private var selectedDate: Date? = nil
@State private var selectedClose: Float? = nil
@State private var xAxisLabels: [Date] = []
var closingValues: [TradingDayClose] = []
var heading: String = ""
init(fundName: String, numYears: Int, closingValues: [TradingDayClose]) {
self.heading = fundName + String(" - \(numYears) Year")
self.closingValues = closingValues
}
var body: some View {
GroupBox (heading) {
let xMin = closingValues.first?.timeStamp
let xMax = closingValues.last?.timeStamp
let yMin = closingValues.map { $0.close }.min()!
let yMax = closingValues.map { $0.close }.max()!
let xAxisLabels: [Date] = GetXAxisLabels(xMin: xMin!, xMax: xMax!)
var yAxisLabels: [Float] {
stride(from: yMin, to: yMax + ((yMax - yMin)/7), by: (yMax - yMin) / 7).map { $0 }
}
Chart {
ForEach(closingValues) { value in
LineMark(
x: .value("Time", value.timeStamp!),
y: .value("Closing Value", value.close)
)
.foregroundStyle(Color.blue)
.lineStyle(StrokeStyle(lineWidth: 1.25))
}
}
.chartXScale(domain: xMin!...xMax!)
.chartXAxisLabel(position: .bottom, alignment: .center, spacing: 25) {
Text("Date")
.textFormatting(fontSize: 14)
}
.chartXAxis {
AxisMarks(position: .bottom, values: xAxisLabels) { value in
AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1))
AxisValueLabel(anchor: .top) {
if value.as(Date.self) != nil {
Text("")
}
}
}
}
.chartYScale(domain: yMin...yMax)
.chartYAxisLabel(position: .leading, alignment: .center, spacing: 25) {
Text("Closing Value")
.font(Font.custom("Arial", size: 14))
.foregroundColor(.black)
}
.chartYAxis {
AxisMarks(position: .leading, values: yAxisLabels) { value in
AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1))
AxisValueLabel() {
if let labelValue = value.as(Double.self) {
Text(String(format: "$ %.2f", labelValue))
.textFormatting(fontSize: 12)
}
}
}
}
.chartOverlay { proxy in
GeometryReader { geometry in
ChartOverlayRectangle(selectedDate: $selectedDate, selectedClose: $selectedClose, proxy: proxy, geometry: geometry, xMin: xMin!, xMax: xMax!, closingValues: closingValues)
}
}
.overlay {
XAxisDates(dateLabels: xAxisLabels)
}
.overlay {
DateAndClosingValue(selectedDate: selectedDate ?? Date(), selectedClose: selectedClose ?? 0.0)
}
}
.groupBoxStyle(ChartGroupBoxStyle())
}
}
I have a view that displays core data values (passed in as closingValues) in a data table. To make the view generic, so I can pass in arrays of different lengths (all are from the same entity and thus attributes) and account for the view having already been called with a different set of values, I need to initialize the showData array contained in the associated view model. I attempt to do so in the init of the view but I get an error message "publishing changes from within a view updates is not allowed. This will cause undefined behavior". It should be noted that in the view model showData is Published and initialized = []. I attempted to do the initialization in the views onappear but the tables rows: ForEach runs before onappear and I get an index out of range on the first call to the view. I tried adding @MainActor to the view models class, which also failed to solve the problem. I am clearly not understanding a concept. Below is the code for the view.
struct DataTable: View {
@ObservedObject var vm: ButtonsViewModel = ButtonsViewModel.shared
var closingValues: [TradingDayClose]
var heading: String = ""
init(fundName: String, closingValues: [TradingDayClose]) {
self.heading = fundName
self.closingValues = closingValues
vm.showData = []
vm.showData = Array(repeating: false, count: closingValues.count)
// Publishing changes from within view updates is not allowed, this will cause undefined behavior.
}
var body: some View {
HStack {
Spacer()
.frame(width: 150)
GroupBox(heading) {
Table (of: TradingDayClose.self) {
TableColumn("") { closingValue in
Text(dateToStringFormatter.string(from: closingValue.timeStamp!))
.id(closingValue.timeStamp!)
.textFormatting(fontSize: 14)
.frame(width: 100, alignment: .center)
} // end table column
TableColumn("") { closingValue in
Text(String(format: "$ %.2f", closingValue.close))
.textFormatting(fontSize: 14)
.frame(width: 100, alignment: .center)
} // end table column
} rows: {
ForEach((closingValues.indices), id: \.self) { index in
if vm.showData[index] == true {
TableRow(closingValues[index])
}
}
}
.onAppear {
vm.InitializeIndexes()
vm.InitializeButtons()
for i in vm.startIndex...vm.endIndex {
DispatchQueue.main.asyncAfter(deadline: .now() + vm.renderRate * Double(i)) {
vm.showData[i] = true
} // end dispatch queue main async
}
}
.frame(width: 250)
.overlay {
let tempValue1: String = "Date"
let tempValue2: String = "Closing Value"
Text(tempValue1).position(x: 63, y: 15)
.textFormatting(fontSize: 16)
Text(tempValue2).position(x: 180, y: 15)
.textFormatting(fontSize: 16)
}
} // end group box
.groupBoxStyle(Table2GroupBoxStyle())
Spacer()
.frame(width: 50)
VStack {
Spacer()
.frame(height: 100)
ButtonUp25(closingValuesCount: closingValues.count)
ButtonUp200(closingValuesCount: closingValues.count)
Spacer()
.frame(height: 20)
ButtonDown25(closingValuesCount: closingValues.count)
ButtonDown200(closingValuesCount: closingValues.count)
Spacer()
.frame(height: 100)
} // end v stack
Spacer()
.frame(width: 50)
} // end h stack
} // end body
} // end struct
I have a view that shows a table and 4 buttons. Each button allows the user to step forward and backwards through the data. Buttons are enabled and disabled based on where you are in the data. If you are less than 200 values to the end of the data for example, the "Page Down - 200" button is disabled. Everything works fine if the mouse is used to run the code associated with each button. But in this case I think the buttons never get focus. Focus I think remains with the sidebar content item that brought up the table view (am using a navigation split view). If I tab over to the page down 200 button and use the space bar to run its associated code I get the error "AttributeGraph: cycle detected through attribute 864480". I think the problem lies with attempting to disable the button while it has focus but am not 100% sure. I have tried to change the focus prior to disabling the button but I get the same error. I think there is some fundamental that I am missing. Below is my table view along with the page down 200 button view.
struct DataTable: View {
@FocusState var buttonWithFocus: Field?
@ObservedObject var vm: ButtonsViewModel = ButtonsViewModel.shared
@State private var hasAppeared = false
var closingValues: [TradingDayClose]
var heading: String = ""
init(fundName: String, closingValues: [TradingDayClose]) {
self.heading = fundName
self.closingValues = closingValues
}
var body: some View {
HStack {
Spacer()
.frame(width: 150)
GroupBox(heading) {
if hasAppeared {
Table (of: TradingDayClose.self) {
TableColumn("") { closingValue in
Text(dateToStringFormatter.string(from: closingValue.timeStamp!))
.id(closingValue.timeStamp!)
.textFormatting(fontSize: 14)
.frame(width: 100, alignment: .center)
} // end table column
TableColumn("") { closingValue in
Text(String(format: "$ %.2f", closingValue.close))
.textFormatting(fontSize: 14)
.frame(width: 100, alignment: .center)
} // end table column
} rows: {
ForEach((closingValues.indices), id: \.self) { index in
if vm.showData[index] == true {
TableRow(closingValues[index])
}
}
}
.focusable(false)
.frame(width: 250)
.overlay {
let tempValue1: String = "Date"
let tempValue2: String = "Closing Value"
Text(tempValue1).position(x: 63, y: 15)
.textFormatting(fontSize: 16)
Text(tempValue2).position(x: 180, y: 15)
.textFormatting(fontSize: 16)
}
} // end has appeared
} // end group box
.groupBoxStyle(Table2GroupBoxStyle())
Spacer()
.frame(width: 50)
VStack {
Spacer()
.frame(height: 100)
Form {
Section {
ButtonUp25(closingValuesCount: closingValues.count)
.focused($buttonWithFocus, equals: .btnUp25)
ButtonUp200(closingValuesCount: closingValues.count)
.focused($buttonWithFocus, equals: .btnUp200)
} header: {
Text("Page Up")
}
Spacer()
.frame(height: 20)
Section {
ButtonDown25(closingValuesCount: closingValues.count)
.focused($buttonWithFocus, equals: .btnDn25)
ButtonDown200(buttonWithFocus: $buttonWithFocus, closingValuesCount: closingValues.count)
.focused($buttonWithFocus, equals: .btnDn200)
} header: {
Text("Page Down")
}
} // end form
.navigationTitle("Data Table")
Spacer()
.frame(height: 100)
} // end v stack
Spacer()
.frame(width: 50)
} // end h stack
.onAppear {
vm.InitializeShowData(closingValueCount: closingValues.count)
vm.InitializeIndexes()
vm.InitializeButtons()
for i in vm.startIndex...vm.endIndex {
DispatchQueue.main.asyncAfter(deadline: .now() + vm.renderRate * Double(i)) {
vm.showData[i] = true
} // end dispatch queue main async
}
hasAppeared = true
}
} // end body
} // end struct
struct ButtonDown200: View {
var buttonWithFocus: FocusState<Field?>.Binding
@ObservedObject var vm: ButtonsViewModel = ButtonsViewModel.shared
var closingValuesCount: Int
var body: some View {
Button("Page Down - 200") {
for i in vm.startIndex...vm.endIndex {
vm.showData[i] = false
}
vm.startIndex = vm.startIndex + 200
vm.endIndex = vm.startIndex + 24
var j: Int = 0
for i in vm.startIndex...vm.endIndex {
DispatchQueue.main.asyncAfter(deadline: .now() + vm.renderRate * Double(j)) {
vm.showData[i] = true
}
j = j + 1
}
if (closingValuesCount - 1) - (vm.startIndex + 200) < 25 {
// buttonWithFocus.wrappedValue = .btnDn25
vm.pageDownDisabled200 = true
}
if vm.startIndex > 24 {
vm.pageUpDisabled25 = false
}
if vm.startIndex - 200 >= 0 {
vm.pageUpDisabled200 = false
}
}
.controlSize(.large)
.buttonStyle(.borderedProminent)
.disabled(vm.pageDownDisabled200)
}
}
I am attempting to learn regex builder. I scape data from a web site, pull out just the table rows and table data and place them into a string. I have attempted to extract table data with regex builder with no success. For testing I placed 3 scrapped table rows into a multi line string and apply my regex pattern in a for loop. It does not appear to find any matches. I am clearly overlooking something. Below is my code:
func GetHTMLTableData() {
let stringData = """
<tr class=BdT Bdc($seperatorColor) Ta(end) Fz(s) Whs(nw)><td class=Py(10px) Ta(start) Pend(10px)><span>Jun 30, 2023</span></td><td class=Py(10px) Pstart(10px)><span>405.40</span></td><td class=Py(10px) Pstart(10px)><span>408.22</span></td><td class=Py(10px) Pstart(10px)><span>405.29</span></td><td class=Py(10px) Pstart(10px)><span>407.28</span></td><td class=Py(10px) Pstart(10px)><span>407.28</span></td><td class=Py(10px) Pstart(10px)><span>5,160,100</span></td></tr>
<tr class=BdT Bdc($seperatorColor) Ta(end) Fz(s) Whs(nw)><td class=Py(10px) Ta(start) Pend(10px)><span>Jun 29, 2023</span></td><td class=Py(10px) Pstart(10px)><span>400.60</span></td><td class=Py(10px) Pstart(10px)><span>402.67</span></td><td class=Py(10px) Pstart(10px)><span>400.19</span></td><td class=Py(10px) Pstart(10px)><span>402.51</span></td><td class=Py(10px) Pstart(10px)><span>402.51</span></td><td class=Py(10px) Pstart(10px)><span>3,914,800</span></td></tr>
<tr class=BdT Bdc($seperatorColor) Ta(end) Fz(s) Whs(nw)><td class=Py(10px) Ta(start) Pend(10px)><span>Jun 28, 2023</span></td><td class=Py(10px) Pstart(10px)><span>401.35</span></td><td class=Py(10px) Pstart(10px)><span>403.49</span></td><td class=Py(10px) Pstart(10px)><span>400.71</span></td><td class=Py(10px) Pstart(10px)><span>402.55</span></td><td class=Py(10px) Pstart(10px)><span>400.97</span></td><td class=Py(10px) Pstart(10px)><span>4,320,700</span></td></tr>
"""
let tradingDayPattern = Regex {
"<tr class=BdT Bdc($seperatorColor) Ta(end) Fz(s) Whs(nw)>"
"<td class=Py(10px) Ta(start) Pend(10px)><span>"
Capture(.date(format: "\(month: .abbreviated) \(day: .twoDigits), \(year: .extended(minimumLength: 4))", locale: Locale(identifier: "en_US_POSIX") , timeZone: .gmt))
"</span></td><td class=Py(10px) Pstart(10px)><span>"
TryCapture {
OneOrMore(.digit)
"."
Repeat(.digit, count: 2)
} transform: {
Double($0)
}
"</span></td><td class=Py(10px) Pstart(10px)><span>"
TryCapture {
OneOrMore(.digit)
"."
Repeat(.digit, count: 2)
} transform: {
Double($0)
}
"</span></td><td class=Py(10px) Pstart(10px)><span>"
TryCapture {
OneOrMore(.digit)
"."
Repeat(.digit, count: 2)
} transform: {
Double($0)
}
"</span></td><td class=Py(10px) Pstart(10px)><span>"
TryCapture {
OneOrMore(.digit)
"."
Repeat(.digit, count: 2)
} transform: {
Double($0)
}
"</span></td><td class=Py(10px) Pstart(10px)><span>"
TryCapture {
OneOrMore(.digit)
"."
Repeat(.digit, count: 2)
} transform: {
Double($0)
}
"</span></td><td class=Py(10px) Pstart(10px)><span>"
TryCapture {
OneOrMore(.digit)
","
Repeat(.digit, count: 3)
","
Repeat(.digit, count: 3)
} transform: {
Int($0)
}
"</span></td></tr>"
}
for match in stringData.matches(of: tradingDayPattern) {
let (line, date, open, high, low, close, adjClose, volume ) = match.output
print("\(date) - \(close)")
}
}
I have a program that reads in CSV data from a text file on disk. It converts it to an array. It then encodes this array into a JSON string. It all works. The problem I have is the during the encoding process a double like 402.71 is converted to 402.70999999. How do I control the encoding process so that this number is 402.71 in the JSON string. Below is a line of CSV data, a line of the resulting array element and the resulting JSON object, the encoding function and the structures used for creating the array from the CSV data.
CSV data:
07/07/23, 403.03, 406.679, 402.71, 402.89, 3668080
Resulting array data:
TradingDays(tradingday: [DownloadHTML.Tradingday(timeStamp: 2023-07-07 00:00:00 +0000, open: 403.03, high: 406.679, low: 402.71, close: 402.89, volume: 3668080),
Resulting JSON object:
{
"tradingday" : [
{
"timeStamp" : "2023-07-07T00:00:00Z",
"low" : 402.70999999999998,
"high" : 406.67899999999997,
"volume" : 3668080,
"open" : 403.02999999999997,
"close" : 402.88999999999999
},
Function:
func EncodeTradingDays (tradingDays: TradingDays) async -> String {
var jsonData: Data?
var jsonString: String
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .iso8601
jsonEncoder.outputFormatting = [.prettyPrinted]
do {
jsonData = try jsonEncoder.encode(tradingDays)
} catch {
print("error encoding json data")
}
jsonString = String(data: jsonData!, encoding: .utf8)!
return jsonString
}
Data structures:
struct TradingDays: Codable {
var tradingday: [Tradingday]
}
struct Tradingday: Codable {
var timeStamp: Date
var open: Double
var high: Double
var low: Double
var close: Double
let volume: Int
enum CodingKeys: String, CodingKey {
case timeStamp
case open, high, low, close, volume
}
}
I have a program that reads in 3 json files and updates CoreData if one or more of the JSON files contains new data. It runs successfully 9 times out of 10. The failure occurs during the creation of an entity in the managed object context. The failure occurs in the function AddRecordsToCoreData. I have placed the error messages below the offending line of code. Further, it appears to only fail during the third call to UpdateCoreDataRecoreds which in turn calls AddRecordsToCoreData. Setting up the calls to run in series I thought I could eliminate any problems but clearly not the case. What error am I making in the code? Or is there some other approach I should utilize? Below is the code in question.
class UpdateCoreData: ObservableObject {
let persistentContainer = CoreDataStack.shared.persistentContainer
@Published var loadingData: Bool = true
@Published var updateStatus: String = ""
init() {
Task {
async let fund1Complete: Bool = UpdateCoreDataRecords(fundName: "Fund1", moc: persistentContainer.viewContext)
let _ = await (fund1Complete)
async let fund2Complete: Bool = UpdateCoreDataRecords(fundName: "Fund2", moc: persistentContainer.viewContext)
let _ = await (fund2Complete)
async let fund3Complete: Bool = UpdateCoreDataRecords(fundName: "Fund3", moc: persistentContainer.viewContext)
let _ = await (fund3Complete)
persistentContainer.viewContext.vacuum()
let persistentStore = persistentContainer.persistentStoreCoordinator.persistentStores.first
do {
try persistentContainer.persistentStoreCoordinator.remove(persistentStore!)
} catch {
print("Unable to remove store -> \(error)")
}
DispatchQueue.main.async {
self.loadingData = false
self.updateStatus = "Core Date is Updated"
}
}
}
}
func UpdateCoreDataRecords(fundName: String, moc: NSManagedObjectContext) async -> Bool {
var latestDate: Date = Date()
var decodedJSON: TradingDays = TradingDays.init(tradingday: [])
latestDate = await LatestCoreDataDate(fundName: fundName, moc: moc)
decodedJSON = await DecodeJSONFile(fileName: fundName)
await AddRecordsToCoreData(jsonData: decodedJSON, fundName: fundName, latestDate: latestDate, moc: moc)
return true
}
func LatestCoreDataDate(fundName: String, moc: NSManagedObjectContext) async -> Date {
var coreDataValues: [TradingDayClose] = []
var latestDate: Date = Date()
let fetchRequest: NSFetchRequest<TradingDayClose> = TradingDayClose.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timeStamp", ascending: false)]
fetchRequest.predicate = NSPredicate(format: "fundName = %@", fundName)
fetchRequest.fetchLimit = 1
do {
coreDataValues = try moc.fetch(fetchRequest)
} catch let error {
print("Error fetching max date. \(error.localizedDescription)")
}
if coreDataValues.isEmpty {
latestDate = Calendar.current.date(byAdding: DateComponents(year: -6), to: latestDate)!
} else {
latestDate = coreDataValues[0].timeStamp!
}
return latestDate
}
func AddRecordsToCoreData(jsonData: TradingDays, fundName: String, latestDate: Date, moc: NSManagedObjectContext) async {
print("\(fundName)")
for item in jsonData.tradingday {
if item.timeStamp > latestDate {
let newRecord = TradingDayClose(context: moc)
// Thread 4: EXC_BAD_ACCESS (code=1, address=0x8)
// thread 3 signal Sigbrt
// Thread 2: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
newRecord.fundName = fundName
newRecord.id = UUID()
newRecord.timeStamp = item.timeStamp
newRecord.close = (item.close) as NSDecimalNumber
} else {
break
}
}
if moc.hasChanges {
do {
print("Saving moc")
try moc.save()
} catch {
print("Errors attempting to save moc")
}
}
}
I am learning how to access SQLite without the use of Swift Data. I have a function that tests to see if a table exists and it works. The problem is that I do not understand why it works. It is my understanding that the SELECT statement returns a 0 if the table does not exist and 1 if it does. But the API step statement returns 101 (SQLITE_Done) if the table does not exist. But if the table does exist it does not return 101. Per the SQLite documentation it would appear that 101 means the operation has completed. What am I missing here and is there a way to capture the underlying SQLite 0 or 1 which would allow me to test for that? Below is my function.
func doesTableExist(db: OpaquePointer?) -> Bool {
var tableExists: Bool = true
let testForTable = """
SELECT name FROM sqlite_master
WHERE type='table'
AND name='Contact';
"""
var testForTablePtr: OpaquePointer?
if sqlite3_prepare_v2(db, testForTable, -1, &testForTablePtr, nil) == SQLITE_OK {
if sqlite3_step(testForTablePtr) == SQLITE_DONE {
tableExists = false
}
} else {
print("unable to compile sql statement testing to see if table exists")
}
return tableExists
}
I have an SQLite table that holds a fund name, as a string, a time stamp, as an iso8601 string, and a close, as a double. SQLite appears to call strings text and a double a real. In the function below, I query this table with a select statement to return the last year of data based on the latest date in the table. To get the latest date I have a separate function that finds the latest date, converts if from a string to a date, subtracts 1 year from the date then converts it back to an iso8601 date string. It is this date string that is being passed into the function below as the variable startingDate. The function below works just fine and is relatively fast. What I am trying to determine is there a way to modify the select statement in the function below such that it does everything and I will no longer have to separately calculate the starting date and pass it in?
func QuerySQLiteData(db: OpaquePointer?, fundName: String, startingDate: String) -> [TradingDay] {
var queryResults: [TradingDay] = []
let timeStampFormatter = ISO8601DateFormatter()
let queryTradingDaysStatement = """
Select
FundName,
TimeStamp,
Close
FROM
TradingDays
WHERE
FundName = '\(fundName)'
AND
TimeStamp >= '\(startingDate)'
ORDER By
TimeStamp ASC
;
"""
var queryTradingDaysCPtr: OpaquePointer?
var tradingDay: TradingDay = TradingDay(fundName: "", timeStamp: Date(), close: 0.0)
if sqlite3_prepare_v2(db, queryTradingDaysStatement, -1, &queryTradingDaysCPtr, nil) == SQLITE_OK {
while (sqlite3_step(queryTradingDaysCPtr) == SQLITE_ROW) {
let fundName = sqlite3_column_text(queryTradingDaysCPtr, 0)
let timeStamp = sqlite3_column_text(queryTradingDaysCPtr, 1)
let close = sqlite3_column_double(queryTradingDaysCPtr, 2)
let fundNameAsString = String(cString: fundName!)
let timeStampAsString = String(cString: timeStamp!)
let timeStampAsDate = timeStampFormatter.date(from: timeStampAsString)!
tradingDay.fundName = fundNameAsString
tradingDay.timeStamp = timeStampAsDate
tradingDay.close = close
queryResults.append(tradingDay)
} // end while loop
} else {
let errorMessage = String(cString: sqlite3_errmsg(db))
print("\nQuery is not prepared \(errorMessage)")
}
sqlite3_finalize(queryTradingDaysCPtr)
return queryResults
}
I have a function that queries an SQLite database and returns names, dates and values. In SQLite the dates are in Julian format. I convert the dates in the SQLite query to a date time string which give me "2022-08-01 00:00:00". I want to display this date as the date string "Aug 1, 2022". The only way I have been able to achieve this is to convert the first date string to a date via a date formatter then convert this date to the desired date string via a second date formatter. Is there a more direct way to do this?
func AccessSQLiteDB(db: OpaquePointer?) {
let queryTradingDaysStatement = """
WITH
TempTable1
AS
(
SELECT
max(TimeStamp) - 365.25 as StartingDate
FROM
TradingDays
WHERE
FundName = 'Fund1'
),
TempTable2
AS
(
SELECT
main.FundName,
datetime(main.TimeStamp) as SQLDateString,
main.Close
FROM
TradingDays main, TempTable1 as temp
WHERE
main.FundName = 'Fund1'
AND
main.TimeStamp >= temp.StartingDate
)
SELECT
FundName,
SQLDateString,
Close
FROM
TempTable2
ORDER By
SQLDateString ASC
;
"""
let sqlDateStringFormatter = DateFormatter()
sqlDateStringFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
sqlDateStringFormatter.timeZone = .gmt
var queryTradingDaysCPtr: OpaquePointer?
if sqlite3_prepare_v2(db, queryTradingDaysStatement, -1, &queryTradingDaysCPtr, nil) == SQLITE_OK {
while (sqlite3_step(queryTradingDaysCPtr) == SQLITE_ROW) {
let fundName = sqlite3_column_text(queryTradingDaysCPtr, 0)
let timeStamp = sqlite3_column_text(queryTradingDaysCPtr, 1)
let close = sqlite3_column_double(queryTradingDaysCPtr, 2)
let fundNameAsString = String(cString: fundName!)
let timeStampAsString = String(cString: timeStamp!)
print(timeStampAsString) // returns this format 2022-08-01 00:00:00
let timeStampAsDate: Date = sqlDateStringFormatter.date(from: timeStampAsString)!
print("\(timeStampAsDate)") // returns this format 2022-08-01 00:00:00 +0000
let mediumDataFormatter = DateFormatter()
mediumDataFormatter.dateStyle = .medium
mediumDataFormatter.timeZone = .gmt
let dateString = mediumDataFormatter.string(from: timeStampAsDate)
print(dateString) // returns this format Aug 1, 2022
let closeAsString = String(format: "$%.2f", close)
print(fundNameAsString + " - " + dateString + " - " + closeAsString)
} // end while loop
} else {
let errorMessage = String(cString: sqlite3_errmsg(db))
print("\nQuery is not prepared \(errorMessage)")
}
sqlite3_finalize(queryTradingDaysCPtr)
}
I am learning SQLite queries. I use prepare statement and one or more calls to a step statement. The prepare statement knows the pointer to the database, the UFT8 select statement and the name of the pointer which will point to the complied SQL select statement. The first step call runs the compiled select statement and if data is returned, the first row is made available via column calls. It seems logical to me that the select statement returns all matching rows but if the first step statement only has access to the first row of data, the balance of matching rows must be stored somewhere. Is this the case and if so where are the query results stored? Or is does this work in some other manner?
I have a program that displays data in a table in the main view. It gets this data by running a C API Select statement. On the main view there is also a button which allows me to change the sort order to descending. To do so, this button calls the same function initially called to display data but with a different order by string for the Select statement. Selecting the button causes the data to be displayed in descending order and it is very fast. Unfortunately selecting the button also causes a warning to be displayed in Xcode "Application performed a reentrant operation in its NSTableView delegate". I do not understand what to do to resolve the issue. Any assistance will be appreciated. Below is the Context View and the View Model. The QueryDatabase function called in the view model is just the C api calls including Prepare, Step in a while loop and Finalize. If it would be helpful I can add it.
// Main View:
struct ContentView: View {
@ObservedObject var vm: SQLiteViewModel = SQLiteViewModel()
var body: some View {
if vm.loadingData == true {
ProgressView()
.task {
vm.GetSQLData(fundName: "Fund1", numYears: 1, sortOrder: "main.TimeStamp ASC")
}
} 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: {
vm.GetSQLData(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 else statement
} // end view
}
// View Model:
class SQLiteViewModel: ObservableObject {
var db: OpaquePointer?
@Published var fundData: [TradingDay] = []
@Published var loadingData: Bool = true
init() {
db = OpenDatabase()
}
func GetSQLData(fundName: String, numYears: Int, sortOrder: String) {
DispatchQueue.main.async {
self.fundData = QueryDatabase(db: self.db, fundName: fundName, numYears: numYears, sortOrder: sortOrder)
self.loadingData = false
}
}
}
I am creating my own NSTableView for macOS. I can display formatted data and provide scrolling by embedding it in a ScrollView. When the table is displayed there is a horizontal gap between adjacent cells. The same gap occurs between the ScrollView header cells. I do not know how to. solve this problem. There is one other issue, I do not know how to set the vertical height of the ScrollView header cells. I would like to make them taller. Below is my code along with the test data.
struct ClosingValue: Identifiable {
var id: UUID
var name: String
var date: String
var close: Float
static var closingValues = [
ClosingValue(id: UUID(), name: "Stock1", date: "Nov 01, 2009", close: 1.10),
ClosingValue(id: UUID(), name: "Stock1", date: "Nov 02, 2009", close: 2.10),
ClosingValue(id: UUID(), name: "Stock1", date: "Nov 03, 2009", close: 3.10),
ClosingValue(id: UUID(), name: "Stock1", date: "Nov 04, 2009", close: 4.10),
ClosingValue(id: UUID(), name: "Stock1", date: "Nov 05, 2009", close: 5.10)
]
}
struct BetterTableView: NSViewRepresentable {
class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
@State var closingValues: [ClosingValue] = ClosingValue.closingValues
func numberOfRows(in tableView: NSTableView) -> Int {
closingValues.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var nsView = NSView()
let tableColumnWidth: CGFloat = 135
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes1: [NSAttributedString.Key: Any] = [
.foregroundColor: NSColor.blue,
.paragraphStyle: paragraphStyle,
.font: NSFont(name: "Arial", size: 14.0) as Any
]
switch tableColumn {
case tableView.tableColumns[0]:
tableColumn?.width = tableColumnWidth
let attributedString = NSAttributedString(string: closingValues[row].name, attributes: attributes1)
let tempNSView = NSTextField()
tempNSView.backgroundColor = NSColor.white
tempNSView.isBordered = true
tempNSView.isEditable = false
tempNSView.attributedStringValue = attributedString
nsView = tempNSView
case tableView.tableColumns[1]:
tableColumn?.width = tableColumnWidth
let attributedString = NSAttributedString(string: closingValues[row].date, attributes: attributes1)
let tempNSView = NSTextField()
tempNSView.backgroundColor = NSColor.white
tempNSView.isBordered = true
tempNSView.isEditable = false
tempNSView.attributedStringValue = attributedString
nsView = tempNSView
case tableView.tableColumns[2]:
tableColumn?.width = tableColumnWidth
let closeAsString = String(format: "$%.2f", closingValues[row].close)
let attributedString = NSAttributedString(string: closeAsString, attributes: attributes1)
let tempNSView = NSTextField()
tempNSView.backgroundColor = NSColor.white
tempNSView.isBordered = true
tempNSView.isEditable = false
tempNSView.attributedStringValue = attributedString
nsView = tempNSView
default:
print("problem in table view switch statement")
}
return nsView
}
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeNSView(context: Context) -> NSScrollView {
let tableView = NSTableView()
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.addTableColumn(NSTableColumn())
tableView.addTableColumn(NSTableColumn())
tableView.addTableColumn(NSTableColumn())
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: NSColor.black,
.paragraphStyle: paragraphStyle,
.font: NSFont.systemFont(ofSize: 14)
]
let column0AttributedString = NSAttributedString(string: "Stock", attributes: attributes)
let column0Header = tableView.tableColumns[0]
column0Header.headerCell.drawsBackground = true
column0Header.headerCell.backgroundColor = NSColor.systemMint
column0Header.headerCell.alignment = .center
column0Header.headerCell.attributedStringValue = column0AttributedString
let column1AttributedString = NSAttributedString(string: "Date", attributes: attributes)
let column1Header = tableView.tableColumns[1]
column1Header.headerCell.drawsBackground = true
column1Header.headerCell.backgroundColor = NSColor.systemMint
column1Header.headerCell.alignment = .center
column1Header.headerCell.attributedStringValue = column1AttributedString
let column2AttributedString = NSAttributedString(string: "Closing Value", attributes: attributes)
let column2Header = tableView.tableColumns[2]
column2Header.headerCell.drawsBackground = true
column2Header.headerCell.backgroundColor = NSColor.systemMint
column2Header.headerCell.alignment = .center
column2Header.headerCell.attributedStringValue = column2AttributedString
let scrollView = NSScrollView()
scrollView.documentView = tableView
return scrollView
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
let tableView = (nsView.documentView as! NSTableView)
// work on this section
}
}
I have code that displays an NSTableView. I have the functionality I want but am having problems with the formatting. In the makeNSView function I can set the header cell text, background color and alignment. What I have been unable to determine is how to add what I would term padding to the header cell background color such that it is taller, i.e. extends further above and below the text. As a secondary issue, I would like to be able to control the border widths of the table cells i.e. the NSViews, such that they appear all the same. Right now, adjacent cell edges have double width borders and non adjacent cell edges have single width borders. Below are the displayed results and code.
struct BetterTableView: NSViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
var dateSorting: String = "ASC"
var closingSorting: String = "ASC"
var closingValues: [ClosingValue] = []
override init() {
super.init()
self.closingValues = GetValues()
}
func tableView(_ tableView: NSTableView, mouseDownInHeaderOf tableColumn: NSTableColumn) {
switch tableColumn.title {
case "Date":
if dateSorting == "ASC" {
closingValues.sort {
$0.date > $1.date
}
dateSorting = "DESC"
} else {
closingValues.sort {
$0.date < $1.date
}
dateSorting = "ASC"
}
case "Closing":
if closingSorting == "ASC" {
closingValues.sort {
$0.date > $1.date
}
closingSorting = "DESC"
} else {
closingValues.sort {
$0.date < $1.date
}
closingSorting = "ASC"
}
default:
print("default")
}
tableView.reloadData()
tableView.scrollRowToVisible(0)
}
func numberOfRows(in tableView: NSTableView) -> Int {
closingValues.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var nsView = NSView()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes1: [NSAttributedString.Key: Any] = [
.foregroundColor: NSColor.blue,
.paragraphStyle: paragraphStyle,
.font: NSFont(name: "Arial", size: 14.0) as Any
]
var attributedString = NSAttributedString()
let tempNSView = NSTextField()
switch tableColumn {
case tableView.tableColumns[0]:
attributedString = NSAttributedString(string: closingValues[row].name, attributes: attributes1)
case tableView.tableColumns[1]:
attributedString = NSAttributedString(string: closingValues[row].date, attributes: attributes1)
case tableView.tableColumns[2]:
let closeAsString = String(format: "$%.2f", closingValues[row].close)
attributedString = NSAttributedString(string: closeAsString, attributes: attributes1)
default:
print("problem in table view switch statement")
}
tempNSView.backgroundColor = NSColor.white
tempNSView.isBordered = true
tempNSView.isEditable = false
tempNSView.attributedStringValue = attributedString
nsView = tempNSView
return nsView
}
} // end of coordinator class
func makeNSView(context: Context) -> NSScrollView {
let tableView = NSTableView()
tableView.style = .plain
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.addTableColumn(NSTableColumn())
tableView.addTableColumn(NSTableColumn())
tableView.addTableColumn(NSTableColumn())
tableView.intercellSpacing = NSSize(width: 0.0, height: 0.0)
tableView.headerView?.frame.size.height = 20.0
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: NSColor.black,
.paragraphStyle: paragraphStyle,
.font: NSFont.systemFont(ofSize: 18)
]
let column0AttributedString = NSAttributedString(string: "Stock", attributes: attributes)
let column0Header = tableView.tableColumns[0]
column0Header.headerCell.drawsBackground = true
column0Header.headerCell.backgroundColor = NSColor.systemMint
column0Header.headerCell.alignment = .center
column0Header.headerCell.attributedStringValue = column0AttributedString
column0Header.sizeToFit()
column0Header.minWidth = 90.0
column0Header.maxWidth = 90.0
let column1AttributedString = NSAttributedString(string: "Date", attributes: attributes)
let column1Header = tableView.tableColumns[1]
column1Header.headerCell.drawsBackground = true
column1Header.headerCell.backgroundColor = NSColor.systemMint
column1Header.headerCell.alignment = .center
column1Header.headerCell.attributedStringValue = column1AttributedString
column1Header.sizeToFit()
column1Header.minWidth = 125.0
column1Header.maxWidth = 125.0
let column2AttributedString = NSAttributedString(string: "Closing", attributes: attributes)
let column2Header = tableView.tableColumns[2]
column2Header.headerCell.drawsBackground = true
column2Header.headerCell.backgroundColor = NSColor.systemMint
column2Header.headerCell.alignment = .center
column2Header.headerCell.attributedStringValue = column2AttributedString
column2Header.sizeToFit()
column2Header.minWidth = 90.0
column2Header.maxWidth = 90.0
let scrollView = NSScrollView()
scrollView.documentView = tableView
return scrollView
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
let tableView = (nsView.documentView as! NSTableView)
print("in update ns view")
// work on this section
}
}