if let primaryController = self.storyboard?.instantiateController(withIdentifier: "ViewController") as? NSViewController {
self.view.window?.contentViewController = primaryController
}
This code appears to work.
Thank you for the input.
Chris
Post
Replies
Boosts
Views
Activity
Thank you for the response but the code appears to only take me to the second view. The button text does not change and it does not take me back to the first view. Does the var firstPass have to be changed to false once the NextContentView() is displayed?
Chris
After some additional testing, it appears that when an @State variable changes the view is redrawn. So it would appear that all I need to do is toggle firstPass when the navigation button is selected. Unfortunately I have yet to find a way to do that.
If anyone knows, please let me know.
Chris
Claude,
I have attempted to add a line beneath navigation link as follows:
self.firstPass.toggle()
It does not work.
Any ideas.
Sorry I overlooked you last response.
Chris
I have looked at using observable objects and adjusted my code as shown below. Xcode does not like any line with the word ObservableObject in it.
The attempt I am trying to make is to change the firstPass variable so that the navigation panel in ContentView redraws.
I was going also link the observable object to the NextContentView view, toggle it there then hope that the ContentView navigation panel finally redraws and shows the updated button text and NavigationLink destination.
My hope is that I have made some simple stupid error.
Any help would be appreciated.
Chris
import SwiftUI
import Combine
class SelectedView: ObservedObject {
@Published var firstPass : Bool = true
}
struct ContentView: View {
@ObservedObject var theSelection = SelectedView()
var body: some View {
NavigationView {
if (self.theSelection.firstPass){
NavigationLink(destination: NextContentView()){
Text ("Next View")
}
}
else {
NavigationLink(destination: ContentView()) {
Text ("Previous View")
}
}
}
}
}
Yes, between those two lazyVgrids.
I ended up adding negative padding right after the closing } of the first lazyVgrid and it removed the gap.
.padding(.bottom, - 7.5)
As far as a screen shot I know how to cut out a section with shift + command + 4. When I select the paper clip below and add image I select the associated png file from my desktop but I do no see it appear in this box. I clearly do not understand how to do it.
Below is the section of code that includes the two lazyVgrids and the added padding. Thank you for giving me the heads up on paste and match style. It sure looks a lot cleaner.
Regards,
Chris
LazyVGrid(
columns: columns,
alignment: .center,
spacing: 0)
{
ForEach(0..<1) {row in
ForEach(0..<numElementsPerArray) {col in
Rectangle()
.foregroundColor(.white)
.overlay (Text(principalData.csvData[row][col]).bold())
.frame(height: 30)
.border(Color.gray, width: 2)
}
}
}.padding(.bottom, -7.5)
ScrollView{
LazyVGrid(
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
}
}
After some additional research I discovered that the solution is to make a struct instead of a class and utilize a static variable. The Boolean variable updated is now accessible directly in all three models as CSVData.updated. It does not need to be passed in. The struct is shown below.
struct CSVData {
static var updated: Bool = false
}
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
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
)
)
}
}
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
}
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) ")
}
}
}
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)!)
}
}
}
}
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)
}
}
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())
}
}