Post

Replies

Boosts

Views

Activity

Reply to SQLite - Select statement to extract 1 year of data
Not a perfect solution but I use the SQLite function date and now to get the current days date and subtract one year from that. The ideal solution would for Saturdays and Sundays to always return as the preceding Friday. Will work on that and If I can determine a solution will post it. let queryTradingDaysStatement = """ Select FundName, TimeStamp, Close FROM TradingDays WHERE FundName = '\(fundName)' AND TimeStamp >= date('now', 'start of day', '-1 year') ORDER By TimeStamp ASC ; """
Jul ’23
Reply to CoreData Failure During Entity Creation in the ViewContext
I updated my code but used persistentContainer.newBackgroundContext(). Also got rid of the Task and async. The code runs without failure and is very fast. Thank you again for the assistance. Oh, and I learned that I do not have to save twice. class UpdateCoreData: ObservableObject { let persistentContainer = CoreDataStack.shared.persistentContainer @Published var loadingData: Bool = true @Published var updateStatus: String = "" init() { let bgContext = persistentContainer.newBackgroundContext() bgContext.performAndWait { do { var latestDate: Date = Date() var decodedJSON: TradingDays = TradingDays.init(tradingday: []) latestDate = LatestCoreDataDate(fundName: "Fund1", moc: bgContext) decodedJSON = DecodeJSONFile(fileName: "Fund1") AddRecordsToCoreData(jsonData: decodedJSON, fundName: "Fund1", latestDate: latestDate, moc: bgContext) latestDate = Date() decodedJSON = TradingDays.init(tradingday: []) latestDate = LatestCoreDataDate(fundName: "Fund2", moc: bgContext) decodedJSON = DecodeJSONFile(fileName: "Fund2") AddRecordsToCoreData(jsonData: decodedJSON, fundName: "Fund2", latestDate: latestDate, moc: bgContext) latestDate = Date() decodedJSON = TradingDays.init(tradingday: []) latestDate = LatestCoreDataDate(fundName: "Fund3", moc: bgContext) decodedJSON = DecodeJSONFile(fileName: "Fund3") AddRecordsToCoreData(jsonData: decodedJSON, fundName: "Fund3", latestDate: latestDate, moc: bgContext) try bgContext.save() } catch let error { print("error in background context = \(error)") } } // end perform and wait 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 LatestCoreDataDate(fundName: String, moc: NSManagedObjectContext) -> 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) { print("\(fundName)") for item in jsonData.tradingday { if item.timeStamp > latestDate { let newRecord = TradingDayClose(context: moc) newRecord.fundName = fundName newRecord.id = UUID() newRecord.timeStamp = item.timeStamp newRecord.close = (item.close) as NSDecimalNumber } else { break } } }
Jul ’23
Reply to How do I extract HTML table data from a string with regex builder
After some additional research and testing I discovered that the error in my RegEx Builder was related to attempting to capture the last number in the pattern (x,***,***). So I broke it out into 3 captures and then perform some math in the for loop. There is probably a better way but I have not yet figured it out. In addition, thanks to esilks input, I removed the line feeds from the multi line variable and converted it to a regular string. Clearly a better way. Below is the updated function. func GetHTMLTableData3() { 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 { "<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>" Capture { Repeat(.digit, count: 1) }transform: { Int($0) } "," Capture { Repeat(.digit, count: 3) }transform: { Int($0) } "," Capture { Repeat(.digit, count: 3) } transform: { Int($0) } "</span>" } for match in stringData.matches(of: tradingDayPattern) { let (line, date, open, high, low, close, adjClose, v1, v2, v3) = match.output let vol1 = v1! * 1000000 let vol2 = v2! * 1000 let volume = vol1 + vol2 + v3! print("\(date) - \(close) - \(volume)") } }
Jul ’23
Reply to How do I disable a button while it has focus
After some additional testing I am convinced that the error message is a result of disabling a button while it has focus. So I added a state variable of type boolean and toggle it in every button's action. Then I watch the state variable with an .onChange in the DataTable view. In the .onChange I test to see if a button should no longer be enabled and set the focus to a button that should still be enabled. At the end of this .onChange I toggle a second state variable of type boolean. Then I watch this state variable with a second .onChange. In the second .onChange I have logic that disables the appropriate buttons based on where the user is in the data being displayed. Doing it this way assures that no button that is being disabled has focus. Below are the 2 on change sections of code. .onChange(of: updateFocus, perform: { newValue in switch buttonWithFocus { case .btnUp200: if vm.startIndex - 200 <= 0 { buttonWithFocus = .btnUp25 } case .btnUp25: if vm.startIndex == 0 { buttonWithFocus = .btnDn25 } case .btnDn25: if vm.endIndex == closingValues.count - 1 { buttonWithFocus = .btnUp25 } case .btnDn200: if (closingValues.count - 1) - (vm.startIndex + 200) < 25 { buttonWithFocus = .btnDn25 } case .none: print("no button has focus") } updateEnabledButtons.toggle() }) .onChange(of: updateEnabledButtons) { newValue in if vm.startIndex + 25 <= closingValues.count - 1 { vm.pageDownDisabled25 = false } else { vm.pageDownDisabled25 = true } if (closingValues.count - 1) - (vm.startIndex + 200) >= 25 { vm.pageDownDisabled200 = false } else { vm.pageDownDisabled200 = true } if vm.startIndex > 0 { vm.pageUpDisabled25 = false } else { vm.pageUpDisabled25 = true } if vm.startIndex - 200 >= 0 { vm.pageUpDisabled200 = false } else { vm.pageUpDisabled200 = true } }
Jun ’23
Reply to How do I disable a button while it has focus
Closing value count variable holds the number of rows in the underlying data array. It is about 1200 rows. My table is set up to only show 25 rows from this array at a time. The 4 buttons, Page Up 200, Page Up 25, Page Down 25 and Page Down 200 allow the user to move up or down in the data. The start index variable holds the first row in the underlying array that will be displayed in the table. Upon initial display of the table, rows 0 thru 24 of the underlying array are displayed. If the user presses the Page Down 25 button the start index variable is now 25 and rows 25 thru 49 are displayed in the table. What I want to do is disable buttons that cannot do anything. For example if rows 0 thru 24 of the underlying array are being displayed then Page Up 25 and Page Up 200 should be disabled since there is no data above row 0 of the underlying array. In the logic I test to see if the start index variable is > 24. If so then the Page Up 25 button should no longer be disabled. Same for the Page Up 200 button. If the user has moved down through the underlying array and are now 200 or more rows from row 0 in the underlying array, then that button should no longer be disabled. In the initial if statement I am testing to see if I can no longer press the Page Down 200 button and fill the data table with 25 values, i.e. advancing 200 rows would place us closer to the last row in the underlying data array than 25 rows. In this case I want to disable the Page Down 200 button. I use the vm.pageDownDisabled200 (a boolean) to enable and disable the Page Down 200 button, which is a vew. If the value is true then the button should become disabled. It is used in the last line of the button view as .disabled(vm.pageDownDisabled200). This is where I seem to get into trouble. If the Page Down 200 button is enabled and has focus, trying to disable it appears to be the source of the error message "AttributeGraph: cycle detected through attribute 864480". But I could be wrong. Thank you for responding. Any insight you can provide will be appreciated.
Jun ’23
Reply to How do I initialize a view models variables in a view
I moved the on appear to the h stack but the table rows portion of the table still ran before the on appear resulting in an index out of range error. So I added the suggested state variable and if statement. The view now works perfectly. Thank you again for providing great advice. Below is the updated code. struct DataTable: View { @ObservedObject var vm: ButtonsViewModel = ButtonsViewModel.shared var closingValues: [TradingDayClose] var heading: String = "" @State private var hasAppeared = false 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]) } } } .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) 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 .onAppear { vm.showData = [] vm.showData = Array(repeating: false, count: 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
Jun ’23
Reply to How do I animate a Swift line mark chart
It works. My memory was wrong on assigning values to a @State variable in the init but after some research I corrected my error. Below is the updated view. Thank you for your input. 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 = "" var renderRate: Double @State var showData: [Bool] init(fundName: String, numYears: Int, closingValues: [TradingDayClose], renderRate: Double) { self.heading = fundName + String(" - \(numYears) Year") self.closingValues = closingValues _showData = State(initialValue: Array(repeating: false, count: closingValues.count)) self.renderRate = renderRate } 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(Array(closingValues.enumerated()), id: \.offset ) { (index, value) in if showData[index] { LineMark( x: .value("Time", value.timeStamp!), y: .value("Closing Value", value.close) ) .foregroundStyle(Color.blue) .lineStyle(StrokeStyle(lineWidth: 1.25)) } } } .onAppear { for i in 0...closingValues.count - 1 { DispatchQueue.main.asyncAfter(deadline: .now() + renderRate * Double(i)) { showData[i] = true } } } .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()) } }
Jun ’23
Reply to Swift - access attribute in array of core data entities in a table column via a variable
I went about solving the problem a little differently. After fetching the core data into an array, I use the map functionality to generate arrays based the structure below, all in the view model. I can then make the Data Table structure generic, i.e. work with any array of objects based on the structure. Works. struct ClosingValue: Identifiable { var timeStamp: Date var close: Double var id = UUID() } // partial code in my view model. tempValues is array of objects based on structure above. tempValues = closingValues.map { tempEntity = ClosingValue(timeStamp: $0.timeStamp!, close: $0.s1Close) } return tempEntity } // Data Table structure struct DataTable: View { var closingValues: Array<ClosingValue> var heading: String init(closingValues: Array<ClosingValue> , heading: String) { self.closingValues = closingValues self.heading = heading } var body: some View { Text(heading) .foregroundColor(.black) .font(Font.custom("Arial", size: 18)) .bold() .padding(.top, 10) Table (closingValues) { TableColumn("Date") { value in HStack { Spacer() Text(dateToStringFormatter.string(from: value.timeStamp)) Spacer() } } .width(100) TableColumn("ClosingValue") { value in HStack { Spacer() Text(String(format: "$ %.2f", value.close)) Spacer() } } .width(100) } .foregroundColor(.black) .font(Font.custom("Arial", size: 16)) .frame(width: 250) Spacer() .frame(height: 30) } }
Apr ’23
Reply to Swift - Map two elements from an array to a new array
I did not properly set up my closingValues variable to be an array, which is what I wanted. In addition, I went with a for loop to populate the closingValues array instead of the map functionality. The code below works. Thank you for your assistance DMG. struct FundValues: Identifiable { var timeStamp: Date var close: Double var id = UUID() } struct DataTable: View { var allClosingValues: [TradingDayPrices] var closingValues: Array<FundValues> = Array() var selection: DataDisplayed init(selection: DataDisplayed, allClosingValues: [TradingDayPrices]) { self.selection = selection self.allClosingValues = allClosingValues switch selection { case .VFIAXDT: for value in allClosingValues { closingValues.append(FundValues(timeStamp: value.timeStamp!, close: value.vfiaxClose)) } case .PrinDT: for value in allClosingValues { closingValues.append(FundValues(timeStamp: value.timeStamp!, close: value.prinClose)) } case .VOODT: for value in allClosingValues { closingValues.append(FundValues(timeStamp: value.timeStamp!, close: value.vooClose)) } default: closingValues = Array() } } var body: some View { Table(closingValues) { TableColumn("Date") { value in Text(dateToStringFormatter.string(from: value.timeStamp)) } TableColumn("Closing Value") { value in Text(currencyFormatter.string(from: value.close as NSNumber)!) } } } }
Apr ’23
Reply to SwiftUI - Accessing StateObject's Object Without Being Installed on a View
Thank you MobileTen. I needed to use async and await. Below are the main call, view model and data table structure that work. @main struct NavigationStackApp: App { let coreDataManager = CoreDataManager() var body: some Scene { WindowGroup { ContentView(fetchedCoreData: FetchCoreData(persistentContainer: coreDataManager.persistentContainer)) } } } class FetchCoreData: ObservableObject { @Published var closingValues: [TradingDayPrices] = [] private (set) var persistentContainer: NSPersistentContainer init(persistentContainer: NSPersistentContainer) { self.persistentContainer = persistentContainer Task { closingValues = await FetchResults(moc: persistentContainer.viewContext) } } } func FetchResults(moc: NSManagedObjectContext) async -> [TradingDayPrices] { var coreDataValues: [TradingDayPrices] = [] let fetchRequest : NSFetchRequest<TradingDayPrices> = TradingDayPrices.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timeStamp", ascending: true)] fetchRequest.predicate = NSPredicate(format: "netWorth > %d", 0.0) do { coreDataValues = try moc.fetch(fetchRequest) } catch let error { print("Error fetching max date. \(error.localizedDescription)") } return coreDataValues } struct DataTable: View { var closingValues: [TradingDayPrices] = [] init(closingValues: [TradingDayPrices]) { self.closingValues = closingValues } var body: some View { List(closingValues, id: \.self) { item in Text("\(dateToStringFormatter.string(from: item.timeStamp!)) - ") + Text("\(item.vooClose) ") } } }
Apr ’23
Reply to Swift: Create a range of angles around a circle between 235 and 35
After some thought I realized I could use an array instead of a range. I created a function that that filters out the unwanted angles and returns the filtered array. I then use array.randomElement() to get a random angle with in the range desired. let localRange = RemoveAngles(start: 36, end: 234) randomAngle = localRange.randomElement()! func RemoveAngles(start: Int, end: Int) -> Array<Int> { let fullRange: ClosedRange<Int> = 0...360 let filteredRange = fullRange.filter { $0 < start || $0 > end } return filteredRange }
Mar ’23
Reply to Creating Instance of Class of type NSWindowController
Looks like I was heading down the wrong path. I find that I need to work with NSWindow, not NSWindowController, and among other things, its style mask. I found some code on line that lets me position the window on the screen. I made some modifications to it and added a function setStyleMask which is an extension to NSWndow. It allows me to make the modifications I want. Below is the updated code. My ContentView does not change with the exception of the elimination of @State private var winController = WinController(). import SwiftUI @main struct BouncingWheelApp: App { var body: some Scene { WindowGroup { ContentView() .hostingWindowPosition( vertical: .center, horizontal: .center, screen: .main) } } } import SwiftUI import AppKit extension NSWindow { func setStyleMask() { titlebarAppearsTransparent = true titleVisibility = .hidden backgroundColor = .gray styleMask.remove(.resizable) } func setPosition(_ position: Position, in screen: NSScreen?) { setStyleMask() guard let visibleFrame = (screen ?? self.screen)?.visibleFrame else { return } let origin = position.value(forWindow: frame, inScreen: visibleFrame) let myRect = CGRect(x: origin.x, y: origin.y, width: 800.0, height: 800.0) setFrame(myRect, display: true) } // end function set position struct Position { static let defaultPadding: CGFloat = 16 var vertical: Vertical var horizontal: Horizontal var padding = Self.defaultPadding enum Horizontal { case left, center, right } enum Vertical { case top, center, bottom } func value(forWindow windowRect: CGRect, inScreen screenRect: CGRect) -> CGPoint { let xPosition = horizontal.valueFor(screenRange: screenRect.minX..<screenRect.maxX, width: windowRect.width, padding: padding) let yPosition = vertical.valueFor(screenRange: screenRect.minY..<screenRect.maxY, height: windowRect.height, padding: padding) return CGPoint(x: xPosition, y: yPosition) } } // end structure Position } extension NSWindow.Position.Horizontal { func valueFor(screenRange: Range<CGFloat>, width: CGFloat, padding: CGFloat) -> CGFloat { switch self { case .left: return screenRange.lowerBound + padding case .center: return (screenRange.upperBound + screenRange.lowerBound - width) / 2 case .right: return screenRange.upperBound - width - padding } } } extension NSWindow.Position.Vertical { func valueFor(screenRange: Range<CGFloat>, height: CGFloat, padding: CGFloat) -> CGFloat { switch self { case .top: return screenRange.upperBound - height - padding case .center: return (screenRange.upperBound + screenRange.lowerBound - height) / 2 case .bottom: return screenRange.lowerBound + padding } } } struct HostingWindowFinder: 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) } } } private struct WindowPositionModifier: ViewModifier { let position: NSWindow.Position let screen: NSScreen? func body(content: Content) -> some View { content .background(HostingWindowFinder { $0?.setPosition(position, in: screen) } ) } } extension View { func hostingWindowPosition(vertical: NSWindow.Position.Vertical, horizontal: NSWindow.Position.Horizontal, padding: CGFloat = NSWindow.Position.defaultPadding, screen: NSScreen? = nil) -> some View { modifier( WindowPositionModifier( position: NSWindow.Position(vertical: vertical, horizontal: horizontal, padding: padding), screen: screen ) ) } }
Mar ’23
Reply to Charts: How do I position x axis value labels below the associated grid lines
After some additional research I was able to position values with the AxisValueLabel horizontal and vertical spacing parameters. I was not able to figure out how to get Charts to place a value under the far right vertical grid line, i.e. my 5th date. So I resolved the problem by removing the AxisValueLabel code and using an overlay along with x & y positioning of the values. See updated code below. struct LineChart: View { private var localArray: [TradingDayPrices] private var chartTitle: String private var numYears: Int init(passedInArray: [TradingDayPrices], chartTitle: String, numYears: Int) { self.localArray = passedInArray self.chartTitle = chartTitle self.numYears = numYears } var body: some View { let minCloseValue: Double = localArray.map { $0.close }.min()! let maxCloseValue: Double = localArray.map { $0.close }.max()! let minDate: Date = localArray[0].timeStamp! let itemCount: Int = localArray.count - 1 let maxDate: Date = localArray[itemCount].timeStamp! let dateArray: [Date] = GetXAxisLabels(min: minDate, max: maxDate, numYears: numYears) Spacer() GroupBox (label: Text("\(chartTitle)") .font(Font.custom("Arial", size: 20)) .frame(width: 700, height: 50, alignment: .center)) { Chart { ForEach(localArray) { item in LineMark ( x: .value("TimeStamp", item.timeStamp!), y: .value("Close", item.close) ) .foregroundStyle(Color.blue) .lineStyle(StrokeStyle(lineWidth: 1.25)) } // end for each } // end chart .padding(50) .chartBackground { item in Color.white } .chartXAxisLabel(position: .bottom, alignment: .center, spacing: 25) { Text("") } .chartYAxisLabel(position: .leading, alignment: .center, spacing: 25) { Text("Closing Value") .font(.system(size: 14)) .foregroundColor(Color.black) } .chartXAxis { AxisMarks (values: dateArray) { value in AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5)) .foregroundStyle(Color.gray) AxisTick(centered: true, length: 7 , stroke: StrokeStyle(lineWidth: 0.5)) .foregroundStyle(Color.gray) } // end axis marks } // end chart x axis .chartYAxis { AxisMarks (position: .leading, values: GetYAxisLabels(min: minCloseValue, max: maxCloseValue)) { value in AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5)) .foregroundStyle(Color.gray) AxisTick(centered: true, length: 7 , stroke: StrokeStyle(lineWidth: 0.5)) .foregroundStyle(Color.gray) AxisValueLabel(verticalSpacing: 10) { if let localValue = value.as(Double.self) { let formattedValue = String(format: "$%.2f", localValue) Text(formattedValue) .font(.system(size: 12)) .foregroundColor(Color.black) } } } // end axis marks } // end chart y axis .chartXScale(domain: minDate...maxDate) .chartYScale(domain: minCloseValue...maxCloseValue) .frame(width: 700, height: 700, alignment: .center) .overlay { let dateValue0 = dateToStringFormatter.string(from: dateArray[0]) Text(dateValue0).position(x: 155, y: 620) let dateValue1 = dateToStringFormatter.string(from: dateArray[1]) Text(dateValue1).position(x: 275, y: 620) let dateValue2 = dateToStringFormatter.string(from: dateArray[2]) Text(dateValue2).position(x: 398, y: 620) let dateValue3 = dateToStringFormatter.string(from: dateArray[3]) Text(dateValue3).position(x: 522, y: 620) let dateValue4 = dateToStringFormatter.string(from: dateArray[4]) Text(dateValue4).position(x: 654, y: 620) Text("Date").position(x: 398, y: 655) .font(.system(size: 14)) .foregroundColor(Color.black) } // end overlay } // end group box frame Spacer().frame(height:65) } // end of body } // end of structure
Nov ’22
Reply to Create Spreadsheet Like Grid with SwiftUI
It appears that I should have been using GridItems. They have solved my problem. Below is the code: struct DateAndCloseTable: View { let containerData: [CommonAttributes] let heading: String let verticalSpacing: CGFloat = 0 let frameWidth: CGFloat = 100 let frameHeight: CGFloat = 25 let horizontalSpacing: CGFloat = 0 let trailingPadding: CGFloat = 45 let bottomPadding: CGFloat = -2 let columns = [ GridItem(.fixed(75), spacing: 0), GridItem(.fixed(75), spacing: 0) ] var body: some View { Text("") Text(heading) .font(.system(size: 18, weight: .bold, design: .default)) .foregroundColor(.blue) .padding(.trailing, 24) Text("") ScrollView { let lastRowIndex: Int = containerData.count - 1 LazyVGrid(columns: columns, alignment: .center, spacing: verticalSpacing){ ForEach((0...lastRowIndex), id: \.self) { index in let theDate = dateToStringFormatter.string(from: containerData[index].date!) let theClose = String(format: "$%.2f", containerData[index].close ) Text(theDate) .font(.system(size: 14, weight: .regular, design: .default)) .frame(width: frameWidth, height: frameHeight, alignment: .center) .background(.white) .border(.black, width: 1) .padding(.trailing, trailingPadding) .padding(.bottom, bottomPadding) Text(theClose) .font(.system(size: 14, weight: .regular, design: .default)) .frame(width: frameWidth, height: frameHeight, alignment: .center) .background(.white) .border(.black, width: 1) .padding(.bottom, bottomPadding) } Text("") Text("") Text("") } // end lazy v grid .padding(.horizontal) } // end of scroll view } }
Aug ’22