Adding Navigationlink to view clears results - swift

I added a navigationLink to the view and it now comes up empty. If I place the navigationLink below the scrollview, it works, but I cannot pass the required value from the foreach loop to the next view.
import SwiftUI
struct CurrentRatesView: View {
let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 1), count: 5)
#StateObject private var lendersViewModel = LendersViewModel()
var body: some View {
ScrollView{
LazyVGrid(columns: columns, spacing: 1, pinnedViews: .sectionHeaders) {
ForEach(lendersViewModel.lenders, id: \.key) { lender in
NavigationLink {
HistoricalRatesView(lender: lender.financial_institution)
}label: {
Section(header: Text(lender.financial_institution)
.font(Font.custom(Design.PrimaryFontSemiBold, size: 20))
.frame(maxWidth: .infinity)
.background(Color.gray)
){
// table cell headers
TBLCellHeader(title:"1 Year")
TBLCellHeader(title:"2 Year")
TBLCellHeader(title:"3 Year")
TBLCellHeader(title:"4 Year")
TBLCellHeader(title:"5 Year")
// table cell values
TBLCellValue(value: lender.one_year)
TBLCellValue(value: lender.two_year)
TBLCellValue(value: lender.three_year)
TBLCellValue(value: lender.four_year)
TBLCellValue(value: lender.five_year)
}
}
}
.padding(10.0)
}
.onAppear{
lendersViewModel.fetchLenders()
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
VStack{
Text("Mortgage Rates")
.font(Font.custom(Design.PrimaryFontSemiBold, size: 20))
.font(Font.custom(Design.PrimaryFontSemiBold, size: 14))
}
}
}
}
}

Related

My SwiftUI View doesn't update navigation title for third level

I have prepared a simple example with three levels:
struct Element: Identifiable {
let id = UUID()
let text: String
init(text: String) {
self.text = text
}
}
struct FirstView: View {
#State var elements = [Element(text: "a"), Element(text: "b")]
var body: some View {
NavigationView {
List(elements) { element in
NavigationLink {
SecondView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("First")
}
}
}
struct SecondView: View {
#State var elements = [Element(text: "1"), Element(text: "2")]
var body: some View {
NavigationView {
List(elements) { element in
NavigationLink {
ThirdView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("Second")
}
}
}
struct ThirdView: View {
#State var elements = [Element(text: "+"), Element(text: "-")]
var body: some View {
NavigationView {
List(elements) { element in
NavigationLink {
SecondView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("Third")
}
}
}
And this is what happens in Apple Watch when I go to the next levels:
As you can see for third view,navigation title was not updated. Why?
Is there also something wrong with rendering? When I swipe down second list it ends up like:
You just need to remove NavigationView from around the SecondView and ThirdView, i.e.
struct SecondView: View {
#State var elements = [Element(text: "1"), Element(text: "2")]
var body: some View {
List(elements) { element in
NavigationLink {
ThirdView()
} label: {
VStack(alignment: .leading, spacing: 3, content: {
Text(element.text)
.font(.system(size: 12))
.foregroundColor(.white)
})
}
}
.navigationTitle("Second")
}
}
If you're building for watchOS 9.0+, you should probably use the new NavigationStack

Swift/SwiftUI: Using AppStorage to hold list of IDs

I am trying to use AppStorage to hold a list of id's for my Country struct. I want to persist this list of id's as favorites for a user and if it's a favorite, have the heart next to each country filled.
If they tap the heart to unfavorite it, it will remove the id from the favLists and if they tap it to favorite it, it will add it to the list.
This is not working as I expect and I am not sure what I am doing wrong here.
struct Number: Identifiable, Codable {
var id: Int
}
struct ContentView: View {
#Binding var countries: [Country]
#Namespace var namespace;
#AppStorage("favLists") var favLists: [Number] = [];
var body: some View {
GeometryReader { bounds in
NavigationView {
ScrollView {
ForEach(Array(filteredCountries.enumerated()), id: \.1.id) { (index,country) in
LazyVStack {
ZStack(alignment: .bottom) {
HStack {
NavigationLink(
destination: CountryView(country: country),
label: {
HStack {
Image(country.image)
.resizable()
.frame(width: 50, height: 50)
Text(country.display_name)
.foregroundColor(Color.black)
.padding(.leading)
Spacer()
}
.padding(.top, 12.0)
}
).buttonStyle(FlatLinkStyle())
if (favLists.filter{$0.id == country.id}.count > 0) {
Image(systemName: "heart.fill").foregroundColor(.red).onTapGesture {
let index = favLists.firstIndex{ $0.id == country.id}
if let index = index {
favLists.remove(at: index)
}
}
.padding(.top, 12)
} else {
Image(systemName: "heart").foregroundColor(.red).onTapGesture {
favLists.append(Number(id: country.id))
}
.padding(.top, 12)
}
}
.padding(.horizontal, 16.0)
}
}
}
}
.frame(maxWidth: bounds.size.width)
.navigationTitle("Countries")
.font(Font.custom("Avenir-Book", size: 28))
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
}

Utilize buttons as pickers to pick between map views

So I want to be able to utilize a picker so that I can choose between different views, but the only option that I found was a picker, which outputs the following output:
I want to have a custom solution where I can have three buttons, and choose between those similar to how a picker is chosen, here is the look that I'm attempting to achieve:
The code below renders the buttons perfectly, but the Picker don't render the contents of the button nor the modifiers, how would I be able to achieve clicking on each individual button similar to how a picker is selected?
The Picker also have a .tag(0), .tag(1) that can be utilized, how would this work on buttons?
Here is the code:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var mapType = 0
#State var allItems: [String] = [
"Standard",
"Hybrid",
"Image",
]
var body: some View {
VStack(spacing: 0) {
// MARK: Map Type
HStack {
Picker(
"Map Type",
selection: $mapType,
content: {
ForEach(allItems, id: \.self) { item in
VStack {
HStack {
VStack {
Text(item)
.font(.footnote.weight(.bold))
.frame(maxWidth: .infinity, alignment: .leading)
.clipped()
.foregroundColor(.primary)
}
}
.padding(8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
}
.border(.red)
.background {
RoundedRectangle(cornerRadius: 4, style: .continuous)
.foregroundColor(Color(.systemFill))
}
.foregroundColor(Color(.quaternaryLabel))
.cornerRadius(8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
}
})
.pickerStyle(SegmentedPickerStyle())
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.clipped()
.padding(.bottom, 4)
}
.padding(16)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
}
}
Here is what I attempted, but it's not working, I am not getting a log.info() output when clicking buttons:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var mapType = 0
#State var allItems: [String] = [
"Standard",
"Hybrid",
"Image",
]
var body: some View {
VStack(spacing: 0) {
HStack {
ForEach(allItems, id: \.self) { item in
VStack {
HStack {
VStack {
Button(action: {
// Action
}, label: {
Text(item)
})
.tag(mapSettings.mapType)
.onChange(of: mapType) { newValue in
mapSettings.mapType = newValue
log.info("We have selected \(newValue)")
}
}
}
}
}
}
}
}
}
you could try something like this:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var allItems: [String] = ["Standard", "Hybrid", "Image"]
var body: some View {
HStack {
ForEach(allItems, id: \.self) { item in
Button(action: {
mapSettings.mapType = item
print("We have selected \(item)")
}, label: {
Text(item)
})
.buttonStyle(.bordered)
}
}
}
}
EDIT-1:
if the mapType is an Int, then use:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var allItems: [String] = ["Standard", "Hybrid", "Image"]
var body: some View {
HStack {
ForEach(allItems, id: \.self) { item in
Button(action: {
switch item {
case "Standard": mapSettings.mapType = 0
case "Hybrid": mapSettings.mapType = 1
case "Image": mapSettings.mapType = 2
default: mapSettings.mapType = 0
}
print("We have selected \(item) mapSettings: \(mapSettings.mapType) ")
}, label: {
Text(item)
})
.buttonStyle(.bordered)
}
}
}
}

SwiftUI: using MatchedGeometryEffect for a scrollView?

I am trying to understand MatchGeometryEffect a little better. I am trying to put it on each cell of a list in a scrollView. I want it to be where if showAlternate is false, the main cell is show but if showAlternate is true , then the individual cell will show the else of the conditional which would be a larger cell ( not part of the code yet). I have the matchedGeometryEffect on the ForEach right now but how can I do each cell indvidually?
struct ContentView: View {
#Binding var countries: [Country]
#State var showAlternate = false;
#Namespace var namespace;
var body: some View {
NavigationView {
ScrollView {
ForEach(Array(countries.enumerated()), id: \.1.id) { (index,country) in
LazyVStack {
HStack {
NavigationLink(
destination: CountryView(country: country),
label: {
HStack {
Image(country.image)
.resizable()
.scaledToFit()
Text(country.display_name)
.foregroundColor(Color.black)
.padding(.leading)
Spacer()
}
.padding(.top, 12.0)
.frame(height: 80)
}
).buttonStyle(FlatLinkStyle())
}
.padding(.horizontal, 16.0)
.overlay(Divider(), alignment: .top)
}
}
.background(Rectangle().opacity(0.2).matchedGeometryEffect(id: "shape", in: namespace))
}
}
.font(Font.custom("Avenir", size: 22))
}
}

How do I fix buttons not being tappable in a ForEach loop?

I have a view which contains a button. The view with the button is created from a forEach loop. For some reason only some buttons are tappable and others are not.
The parent view contains a NavigationView, a scroll view inside the NavigationView, a lazyVStack inside of the scroll view, a forEachloop in that lazyVStack and in that for loop is the child view that contains the button.
struct ContentView: View {
let peoples:[Person] = Bundle.main.decode("data.json")
var body: some View {
let columns = [
GridItem(.flexible(minimum: 300), spacing: 10)
]
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.self) { person in
PersonView(name: person.Name, age: person.Age)
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}
The child view is bellow. I suspect the scroll view is stealing the user input, but I am not sure why or how to overcome it. Some buttons are tapeable and some are not.
struct PersonView: View {
#Environment(\.colorScheme) var colorScheme
var name: String
var age: Int
var body: some View {
VStack(alignment:.leading) {
Image("randoPerson")
.resizable()
.scaledToFill()
.frame(minWidth: nil, idealWidth: nil,
maxWidth: UIScreen.main.bounds.width, minHeight: nil,
idealHeight: nil, maxHeight: 300, alignment: .center)
.clipped()
VStack(alignment: .leading, spacing: 6) {
Text("name")
.fontWeight(.heavy)
.padding(.leading)
Text("Age \(age)")
.foregroundColor(Color.gray)
.padding([.leading,.bottom])
Button(action: { print("I was tapped") }) {
HStack {
Image(systemName: "message.fill")
.font(.title)
.foregroundColor(.white)
.padding(.leading)
Text("Message them")
.font(.subheadline)
.foregroundColor(.white)
.padding()
}
.background(Color.blue)
}
.padding()
}
.background(Color(UIColor.systemBackground).cornerRadius(15))
.shadow(color:colorScheme == .dark
? Color.white.opacity(0.2)
: Color.black.opacity(0.2),
radius: 7, x: 0, y: 2)
}
}
}
To fix the issue, you can add an id: UUID associated to a Person and iterate between the Person, inside the ForEach using their ID.
You will also noticed that I added the value as lowercases to respect the Swift convention.
struct Person {
let id: UUID // Add this value
var name: String
var age: Int
}
So here is the ContentView with the id replacing \.self:
struct ContentView: View {
let peoples: [Person] = Bundle.main.decode("data.json")
var body: some View {
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.id) { person in // id replace \.self here
PersonView(name: person.name, age: person.age) // removed uppercase
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}