Posts

Post marked as solved
1 Replies
331 Views
I have a program that uses 3 different windows, each in their own window group. In a button I can use openWindow() to call another window and it works perfectly. I watched the video at the link below and now am trying to convert my program to use a navigation stack instead of buttons. I got things to work using the code listed at the link with a few changes but am unable to get the navigation destination to use openWindow. I attempted to create a view builder but could not get it to work. After some additional research I am starting to think that the navigation destination expects to use the same window as the root view not a separate window/view and therefore, navigation destination wants just a view. But I am unsure. If someone could provide some guidance it would be appreciated. https://developer.apple.com/videos/play/wwdc2022/10054/ struct ContentView: View { @Environment (\.openWindow) private var openWindow @StateObject private var dataModel = DataModel() var body: some View { VStack(alignment: .center) { NavigationStack() { List(Sections.allCases) { section in Section(section.localizedName) { ForEach(dataModel.stocks(in: section)) { stock in NavigationLink(stock.listingName, value: stock) .font(Font.custom("Arial", size: 16)) } } // end section } // end list .scrollContentBackground(.hidden) .background(Color.mint) .navigationTitle("Stocks") .navigationDestination(for: StockData.self) { stock in Text("\(stock.listingName) - \(stock.name)") // GenericTableView(content: openWindow(id: "table", value: stock.name)) // Cannot convert value of type '()' to expected argument type '() -> ContentView' } } // end navigation stack } // end v stack } } struct GenericTableView<Content:View>: View { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { content } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
5 Replies
539 Views
I have a program that utilizes a Navigation Stack. I want each view to be centered on the screen and be a specific size. I accomplished this with some position logic which sets the views frame size and origin. There is unique position logic for each of the 3 view sizes, the Content View and 2 navigation destination views. When the Content View is first displayed the associated position logic code runs and the view is the correct size and centered. The same is true every time one of the 2 navigation destination views is displayed. Unfortunately, when I return to the Content View from a navigation destination view the position logic does not run again and the Content View is now the same size and position as the previous navigation destination view. How do I resolve this problem? Below is the position logic code associated with the Content View and how it is called. @main struct TableViewTestApp: App { var body: some Scene { WindowGroup { ContentView() .hostingWindowPositionHome( screen: .main ) } } } extension View { func hostingWindowPositionHome( screen: NSScreen? = nil ) -> some View { modifier( WindowPositionModifierHome( screen: screen ) ) } } private struct WindowPositionModifierHome: ViewModifier { let screen: NSScreen? func body(content: Content) -> some View { content.background( HostingWindowFinderHome { $0?.setPositionHome(in: screen) } ) } } private struct HostingWindowFinderHome: NSViewRepresentable { var callback: (NSWindow?) -> () func makeNSView(context: Self.Context) -> NSView { let view = NSView() DispatchQueue.main.async { self.callback(view.window) } return view } func updateNSView(_ nsView: NSView, context: Context) { DispatchQueue.main.async { self.callback(nsView.window) } } } extension NSWindow { func setPositionHome(in screen: NSScreen?) { let nsRectangle: NSRect = NSRect(x: 1055.0, y: 370.0, width: 450, height: 700) setFrame(nsRectangle, display: true) } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
2 Replies
1.1k Views
I have a swift program that displays a chart using Chart. The code includes an X Axis Scale parameter but for some reason the last value (right most) on the x axis does not display. It should display Aug 2023. In checking the array used for the x axis labels I find that the last value is Aug 2023. I do not know how to overcome this obstacle. Any assistance will be appreciated. Below is a picture of the bottom of the chart and the code. struct CustomChartView: View { let vm: SQLDataVM = SQLDataVM.shared let closingValues: [TradingDay] let fundName: String let numYears: Int let maxCloseStruct: TradingDay let maxClose: Double let minCloseStruct: TradingDay let minClose: Double let yIncrment: Double let yAxisValues: [Double] let minTimeStampStruct: TradingDay let minTimeStamp: Date var dateComponent = DateComponents() let maxTimeStampStruct: TradingDay let maxTimeStamp: Date var xAxisValues: [Date] = [] init (fundName: String, numYears: Int) { self.fundName = fundName self.numYears = numYears closingValues = self.vm.QueryDatabase(fundName: fundName, numYears: numYears) maxCloseStruct = self.closingValues.max(by: { (tradingDay1, tradingDay2) -> Bool in return tradingDay1.close < tradingDay2.close })! maxClose = maxCloseStruct.close minCloseStruct = closingValues.min(by: { (tradingDay1, tradingDay2) -> Bool in return tradingDay1.close < tradingDay2.close })! minClose = minCloseStruct.close yIncrment = (maxClose - minClose)/4 yAxisValues = [ minClose, minClose + (1 * yIncrment), minClose + (2 * yIncrment), minClose + (3 * yIncrment), maxClose ] minTimeStampStruct = closingValues.min(by: { (tradingDay1, tradingDay2) -> Bool in return tradingDay1.timeStamp < tradingDay2.timeStamp })! minTimeStamp = minTimeStampStruct.timeStamp maxTimeStampStruct = closingValues.max(by: { (tradingDay1, tradingDay2) -> Bool in return tradingDay1.timeStamp < tradingDay2.timeStamp })! maxTimeStamp = maxTimeStampStruct.timeStamp xAxisValues.append(minTimeStamp) for i in 1...11 { dateComponent.month = i let nextMonth = Calendar.current.date(byAdding: dateComponent, to: minTimeStamp) xAxisValues.append(nextMonth!) } xAxisValues.append(maxTimeStamp) print("\(xAxisValues[12])") // prints 2023-08-04 00:00:00 +0000 } // end init var body: some View { HStack(alignment: .center) { VStack(alignment: .center) { Chart(closingValues, id:\.id) { LineMark( x: .value("Date", $0.timeStamp, unit: .day), y: .value("Closing", $0.close) ) .foregroundStyle(.blue) } // end chart .frame(width: 1000, height: 700, alignment: .center) .chartXAxisLabel(position: .bottom, alignment: .center, spacing: 15) { Text("Date") .font(.custom("Arial", size: 20)) } .chartYAxisLabel(position: .leading, alignment: .center, spacing: 20) { Text("Closing Value") .font(.custom("Arial", size: 20)) } .chartXAxis { AxisMarks(values: xAxisValues) { value in if let date = value.as(Date.self) { AxisValueLabel(horizontalSpacing: -14, verticalSpacing: 10) { VStack(alignment: .leading) { Text(ChartMonthFormatter.string(from: date)) .font(.custom("Arial", size: 14)) Text(ChartYearFormatter.string(from: date)) .font(.custom("Arial", size: 14)) } // end v stack } // end axis label } // end if statement AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1)) .foregroundStyle(Color.black) AxisTick(centered: true, length: 0, stroke: .none) } } // end chart x axis .chartXScale(domain: [xAxisValues[0], xAxisValues[12]]) .chartYAxis { AxisMarks(position: .leading, values: yAxisValues) { value in AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1)) .foregroundStyle(Color.black) AxisTick(centered: true, length: 0, stroke: .none) AxisValueLabel(horizontalSpacing: 10) { if let yAxisValue = value.as(Double.self) { let stringValue = String(format: "$%.02f", yAxisValue) Text(stringValue) .font(.custom("Arial", size: 14)) } } } } .chartYScale(domain: [minClose, maxClose]) .chartPlotStyle { plotArea in plotArea.background(.white.opacity(0.9)) .border(.black, width: 1) } // end chart plot style } // end v stack .frame(width: 1200, height: 900, alignment: .center) } // end h stack } // end some view }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
1 Replies
458 Views
I am creating a user interface for a working program. I am able to lay things out with offsets but am trying as a learning experience to do the same with alignment guides. For the section heading labeled "Group 1" I am able to get the layout I want. What I cannot get to work are alignment guides for the associated rounded rectangle. It appears that the alignment guides with the rounded rectangle only affect the contents i.e. the text "Data Table". Below is a picture of the code output and the code. How, using alignment guides, can move the top rounded rectangle? To keep the picture small I just copied the left side of the output window. The top rounded rectangle is approximately centered in this window. struct ContentView: View { var body: some View { NavigationStack { Text("Group1") .font(Font.custom("Arial", size: 16)) .alignmentGuide(HorizontalAlignment.center, computeValue: { d in print("\(d[HorizontalAlignment.leading])") // print leading = 0.0 // print center = 27 // print trailing = 54 return d[HorizontalAlignment.leading] + 255 }) ZStack (alignment: Alignment(horizontal: .center, vertical: .center)) { RoundedRectangle(cornerRadius: 10) .fill(.white) .alignmentGuide(HorizontalAlignment.center, computeValue: { d in return d[HorizontalAlignment.leading] + 45 }) VStack (alignment: HorizontalAlignment.leading) { NavigationLink(value: "Table1") { Text("Data Table") } .font(Font.custom("Arial", size: 14)) .buttonStyle(.link) .underline() .focusable(false) } // end vstack } // end zStack .frame(width: 200, height: 60) Text("Group2") .font(Font.custom("Arial", size: 16)) .frame(width: 600, height: 20, alignment: .leading) .padding(.leading, 35) .padding(.top, 20) .offset(x: 30) ZStack { RoundedRectangle(cornerSize: CGSize(width: 200, height: 60)) .stroke(Color(.clear), lineWidth: 1) .frame(width: 200, height: 60, alignment: .leading) .background(.white) .cornerRadius(10) .offset(x: -160) VStack () { NavigationLink(value: "Table2") { Text("Data Table") } .font(Font.custom("Arial", size: 14)) .buttonStyle(.link) .underline() .padding(.leading, 8) .frame(width: 200, height: 20, alignment: .leading) .offset(x: -160) .focusable(false) } } Text("Group3") .font(Font.custom("Arial", size: 16)) .frame(width: 600, height: 20, alignment: .leading) .padding(.leading, 35) .padding(.top, 20) .offset(x: 30) ZStack { RoundedRectangle(cornerSize: CGSize(width: 200, height: 60)) .stroke(Color(.clear), lineWidth: 1) .frame(width: 200, height: 60, alignment: .leading) .background(.white) .cornerRadius(10) .offset(x: -160) VStack () { NavigationLink(value: "Table3") { Text("Data Table") } .font(Font.custom("Arial", size: 14)) .buttonStyle(.link) .underline() .padding(.leading, 8) .frame(width: 200, height: 20, alignment: .leading) .offset(x: -160) .focusable(false) } } .padding(.bottom, 20) .navigationDestination(for: String.self) { command in switch command { case "Table1": HStack { CustomTableView(fundName: "Table1", numYears: 1) .frame(width: 320, height: 345, alignment: .center) .padding(.leading, 120) .padding(.trailing, 160) } case "Table2": HStack { CustomTableView(fundName: "Table2", numYears: 1) .frame(width: 320, height: 345, alignment: .center) .padding(.leading, 120) .padding(.trailing, 160) } case "Table3": HStack { CustomTableView(fundName: "Table3", numYears: 1) .frame(width: 320, height: 345, alignment: .center) .padding(.leading, 120) .padding(.trailing, 160) } default: let _ = print("problem in main view switch statement") } // end switch } // end navigation destination } .frame(width: 600, height: 400, alignment: .trailing) } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
2 Replies
515 Views
I have a program that creates a custom table view. In the delegate that populates the cells with data I create an instance of a custom NSTableCellView. It works. What I would like to be able to do is pass in a value when making an instance so that I can set the width of the frame (var rectWidth in the custom NSTableCellView). I am currently using two different custom NSTableCellViews, each with a different value for var rectWidth. Below is my delegate and custom NSTableCellView code. func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { if (tableColumn?.identifier)!.rawValue == "fund" { let dataCell = CustomTableCellView() dataCell.textField?.stringValue = closingValues[row].fundName return dataCell } else if (tableColumn?.identifier)!.rawValue == "date" { let dataCell = CustomTableCellViewDate() dataCell.textField?.stringValue = SQLDateFormatter.string(from: closingValues[row].timeStamp) return dataCell } else { let dataCell = CustomTableCellView() dataCell.textField?.stringValue = String(format: "$%.2f", closingValues[row].close) return dataCell } } class CustomTableCellView: NSTableCellView { override init(frame frameRect: NSRect) { super.init(frame: frameRect) let rectWidth: CGFloat = 100 self.autoresizingMask = .width let nsRectangle = NSMakeRect(0, 0, rectWidth, 24) let customTextField: NSTextField = CustomTextField(frame: nsRectangle) self.textField = customTextField self.addSubview(customTextField) } required init?(coder decoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
1 Replies
579 Views
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 } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
4 Replies
567 Views
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 } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
1 Replies
392 Views
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 } } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
1 Replies
363 Views
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?
Posted
by ChrisMH.
Last updated
.
Post marked as solved
2 Replies
616 Views
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) }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
5 Replies
465 Views
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 }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
1 Replies
668 Views
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 }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
2 Replies
510 Views
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") } } }
Posted
by ChrisMH.
Last updated
.
Post not yet marked as solved
1 Replies
410 Views
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 } }
Posted
by ChrisMH.
Last updated
.
Post marked as solved
2 Replies
505 Views
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)") } }
Posted
by ChrisMH.
Last updated
.