sheet inside ForEach doesn't loop over id's - ios

Each link has an id, the output I get when I click on the button is the wrong id, it repeats the first id on the first 4 buttons and then the others are random.
struct PricesList: View {
#ObservedObject var viewModel = ViewModel()
#State var isSheetPresented = false
var body: some View{
NavigationView{
ScrollView {
LazyVGrid(columns: gridLayout, spacing: 15, content: {
ForEach(viewModel.items, id: \.id) { item in
VStack(alignment: .leading, spacing: 5){
Button(action: {
self.isSheetPresented.toggle()
}, label: {
Image(item.imageUrl)
.resizable()
.scaledToFit()
.padding(10)
}).sheet(isPresented: $isSheetPresented, content: {
WebView(url: item.link)
})
}//:VSTACK
.scaledToFit()
.padding()
.background(Color.white.cornerRadius(12))
.background(RoundedRectangle(cornerRadius: 12).stroke(Color.gray, lineWidth: 1))
}//: LOOP FOR EACH
}).padding(5)
.onAppear(perform: {
viewModel.loadData()
viewModel.postData()
})
}
.navigationBarHidden(true)
}//: NAVIGATION VIEW
} //: BODY
}

You have sheet for each item in your list, and all of them are getting isSheetPresented value. Which one will be displayed is undefined
Instead you need to store selectedItem and pass it to single sheet, like this:
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
#State var selectedItem: Item?
var body: some View{
NavigationView{
ScrollView {
LazyVGrid(columns: gridLayout, spacing: 15, content: {
ForEach(viewModel.items, id: \.id) { item in
VStack(alignment: .leading, spacing: 5){
Button(action: {
selectedItem = item
}, label: {
Image(item.imageUrl)
.resizable()
.scaledToFit()
.padding(10)
})
}//:VSTACK
.scaledToFit()
.padding()
.background(Color.white.cornerRadius(12))
.background(RoundedRectangle(cornerRadius: 12).stroke(Color.gray, lineWidth: 1))
}//: LOOP FOR EACH
}).padding(5)
.onAppear(perform: {
viewModel.loadData()
viewModel.postData()
})
.sheet(item: $selectedItem, content: { selectedItem in
WebView(url: selectedItem.link)
})
}
.navigationBarHidden(true)
}//: NAVIGATION VIEW
} //: BODY
}

Related

How to hide TabView in NavigationLink?

First of all I did a full research for my problem and NOTHING on Google helped me,
the problem is simple, it seems that the solution should also be simple, but I can't hide the tabbar in NavigationLink, and if something works out, then wierd behavior of the buttons and the transition back, etc...
TabView itself
import SwiftUI
struct Main: View {
#State var currentTab: Int = 0
var body: some View {
TabView(selection: $currentTab) {
HomeView().tag(0)
AccountInfoView().tag(1)
SettingsView().tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.bottom)
.overlay(
TabBarView(currentTab: $currentTab),
alignment: .top
)
}
}
struct TabBarView: View {
#Binding var currentTab: Int
#Namespace var namespace
var tabBarOptions: [String] = ["Search", "Items", "Account"]
var body: some View {
HStack(spacing: 0) {
ForEach(Array(zip(self.tabBarOptions.indices,
self.tabBarOptions)),
id: \.0,
content: {
index, name in
TabBarItem(currentTab: self.$currentTab,
namespace: namespace.self,
tabBarItemName: name,
tab: index)
})
}
.padding(.top)
.background(Color.clear)
.frame(height: 100)
.edgesIgnoringSafeArea(.all)
}
}
struct TabBarItem: View {
#Binding var currentTab: Int
let namespace: Namespace.ID
var tabBarItemName: String
var tab: Int
var body: some View {
Button {
self.currentTab = tab
} label: {
VStack {
Spacer()
Text(tabBarItemName)
if currentTab == tab {
CustomColor.myColor
.frame(height: 2)
.matchedGeometryEffect(id: "underline",
in: namespace,
properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: self.currentTab)
}
.fontWeight(.heavy)
.buttonStyle(.plain)
}
}
NavigationLink -> this is just the part of the code that contains the NavigationLink, this VStack of course is inside the NavigationView.
struct HomeView: View {
NavigationView {
...
VStack(spacing: 15) {
ForEach(self.data.datas.filter {(self.search.isEmpty ? true : $0.title.localizedCaseInsensitiveContains(self.search))}, id: \.id) { rs in
NavigationLink(
destination: ItemDetails(data: rs)
){
RecentItemsView(data: rs)
}
.buttonStyle(PlainButtonStyle())
}
}
}
}
ItemDetails
struct ItemDetails: View {
let data: DataType
var body : some View {
NavigationView {
VStack {
AsyncImage(url: URL(string: data.pic), content: { image in
image.resizable()
}, placeholder: {
ProgressView()
})
.aspectRatio(contentMode: .fill)
.frame(width: 250, height: 250)
.clipShape(RoundedRectangle(cornerRadius: 12.5))
.padding(10)
VStack(alignment: .leading, spacing: 8, content: {
Text(data.title)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .center)
Text(data.description)
.font(.caption)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .center)
})
.padding(20)
}
.padding(.horizontal)
}
.navigationBarBackButtonHidden(true)
}
}
I apologize for the garbage in the code, it seemed to me that there is not much of it and it does not interfere with understanding the code, also during the analysis of this problem on the Google\SO, I did not need to work with other parts of the code anywhere, except for those that I provided above, but if I missed something, then please let me know, thanks.

How do you make a tab bar where it will not mess up your views

I have tried multiple different ways to make a tab bar but for some reason, it messes up my views either preventing them from scrolling or their functionality completely corrupts here are two of the ways I tried why is this happening? They both allow me to switch between views but they bot alter the functionality or the performance of my views and it is confusing as to why it should just go from one view to the other no problem.
import SwiftUI
struct Home: View {
#State var selectedTab = "Global"
var edges = UIApplication.shared.windows.first?.safeAreaInsets
var body: some View {
GeometryReader { geometry in
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
ZStack{
SettingsView()
.opacity(selectedTab == "Settings" ? 1 : 0)
Ether()
.opacity(selectedTab == "Ether" ? 1 : 0)
PostView()
.opacity(selectedTab == "Global" ? 1 : 0)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.frame(width: geometry.size.width, height: geometry.size.height)
CustomTabbar(selectedTab: $selectedTab)
.padding(.bottom,edges!.bottom == 0 ? 15 : 0)
}
.background(Color(.black).ignoresSafeArea(.all, edges: .all))
.ignoresSafeArea(.all, edges: .bottom)
.navigationBarHidden(true)
}
}
}
import SwiftUI
struct Home: View {
// #State var selectedTab = "Global"
var edges = UIApplication.shared.windows.first?.safeAreaInsets
var body: some View {
TabView {
PostView()
.tabItem {
Image(systemName: "flame")
Text("Global")
}
Ether()
.tabItem {
Image(systemName: "flame.fill")
Text("Ether")
}
SettingsView()
.tabItem {
Image(systemName: "person")
Text("Profile")
}
}
}
}
import SwiftUI
struct CustomTabbar: View {
#Binding var selectedTab : String
var body: some View {
HStack(spacing: 0){
TabButton(title: "Global", selectedTab: $selectedTab)
TabButton(title: "Ether", selectedTab: $selectedTab)
TabButton(title: "Settings", selectedTab: $selectedTab)
}
.padding(.horizontal)
.background(Color.white)
.clipShape(Capsule())
}
}
struct TabButton : View {
var title : String
#Binding var selectedTab : String
var body: some View{
Button(action: {selectedTab = title}) {
VStack(spacing: 5){
Image(title)
.renderingMode(.template)
Text(title)
.font(.caption)
.fontWeight(.bold)
.foregroundColor(selectedTab == title ? Color(.blue) : .gray)
.padding(.horizontal)
.padding(.vertical,10)
}
}
}
}

For Each view pushing content away

I'm working on a profile page and it has to render a list of previous runs and so I'm using a for each to do that. But for some reason, this view pushes all the other information out of view and I'm not sure why. Screenshot of the issue:
Here's the code:
struct AccountView: View {
#EnvironmentObject var session: SessionStore
#State var isShowingAccountDetails = false
#ObservedObject var tracks = RunsService()
var body: some View {
VStack(alignment: .leading) {
AccountHeader(tracks: tracks)
Text("This Week")
.font(.title)
.bold()
.padding(.leading)
PersonalWeeklyCard()
.frame(height: 150)
.padding(.horizontal)
Text("Previous runs")
.font(.title)
.bold()
.padding(.leading)
Spacer()
VStack{
ForEach(tracks.myRuns, id: \.id) { track in
RunCard(track: track)
}
}
}
.onAppear(perform: tracks.fetchRuns)
.navigationBarHidden(true)
.navigationBarItems(trailing: Button(action: {
isShowingAccountDetails = true
}, label: {Text("Details")}))
.sheet(isPresented: $isShowingAccountDetails) {
AccountDetails(session: session)
}
}
}

Presenting a sheet pops views from NavigationView

I have a view A that contains a NavigationView. A pushes View B by activating a NavigationLink. B pushes View C by activating a NavigationLink. C pushes View D by tapping a NavigationLink. D then presents a View as a sheet. If i dismiss the sheet the current top view is B. For some reason presenting the sheet pops 2 screens.
Can someone explain this behaviour and how i can fix it?
I'm guessing somehow the navigation link in view B (that pushed view C) get deActivated but I cant figure out why
From what i tested it seems when the sheet gets presented ViewB gets reinitialized which will reinit my vm and so deactivate the link. How to i stop the view from being reinitialized?
struct ViewA: View {
#SwiftUI.Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
#ObservedObject private var viewModel: ViewAVM
var body: some View {
NavigationView {
GeometryReader { fullView in
ScrollView(.vertical, showsIndicators: false) {
if !self.viewModel.loading {
VStack(alignment: .leading, spacing: 0) {
DetailsView(car: self.$viewModel.data)
Spacer(minLength: 20)
NavigationLink(destination: ViewB(rootActive: self.$viewModel.showB), isActive: self.$viewModel.showB) {
Button(action: {
self.viewModel.showB = true
}) {
SecondaryButtonView(enabled: true, title: "Go")
}
}.isDetailLink(false).padding([.leading, .trailing], 20)
}.frame(minHeight: fullView.size.height)
}
}
}
.navigationBarBackButtonHidden(true)
.navigationBarTitle("View A")
.navigationBarItems(leading: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image("LeftArrow").renderingMode(.original)
})
.onAppear {
self.viewModel.update()
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
struct ViewBUI: View {
#SwiftUI.Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
#ObservedObject private var viewModel: ViewBVM
private let rootActive: Binding<Bool>
var body: some View {
GeometryReader { fullView in
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
Text("").font(.regularParagraph).padding(.top, 15).padding(.bottom, 10)
CustomEntryView()
Spacer(minLength: 20)
NavigationLink(destination: ViewC(rootActive: self.rootActive), isActive: self.$viewModel.showC) {
Button(action: {
self.hideKeyboard()
self.viewModel.validate()
}) {
PrimaryButtonView(enabled: self.viewModel.valid, title: "Continue")
}.disabled(!self.viewModel.valid)
}.isDetailLink(false).padding(.bottom, 16)
}.padding([.leading, .trailing], 20).frame(minHeight: fullView.size.height)
}
}
.navigationBarBackButtonHidden(true)
.navigationBarTitle(viewModel.new ? "New" : "Old")
.navigationBarItems(leading: Button(action: {
self.viewModel.rootActive.wrappedValue = false
}) {
Image("LeftArrow").renderingMode(.original)
})
}
}
struct ViewDUI: View {
#SwiftUI.Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let isDriver: Bool
let car: Binding<Car?>
let rootActive: Binding<Bool>
var body: some View {
GeometryReader { fullView in
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
Text("").font(.regularParagraph).foregroundColor(Color.black).padding([.leading, .trailing], 20).padding(.top, 15)
CarDetailsView(car: self.car)
Spacer(minLength: 20)
if self.car.wrappedValue?.mot == true && self.car.wrappedValue?.taxed == true {
if self.isDriver {
Button(action: {
self.rootActive.wrappedValue = false
}) {
PrimaryButtonView(enabled: true, title: "Save")
}.padding(.bottom, 16).padding([.leading, .trailing], 20)
} else {
NavigationLink(destination: ViewE(rootActive: self.$viewModel.showB)) {
PrimaryButtonView(enabled: true, title: "Continue")
}.isDetailLink(false).padding(.bottom, 16).padding([.leading, .trailing], 20)
}
} else {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
PrimaryButtonView(enabled: true, title: "")
}.padding(.bottom, 16).padding([.leading, .trailing], 20)
}
}.frame(minHeight: fullView.size.height)
}
}
.navigationBarBackButtonHidden(true)
.navigationBarTitle("")
.navigationBarItems(leading: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image("LeftArrow").renderingMode(.original)
})
}
}
struct ViewEUI: View {
#SwiftUI.Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
private let rootActive: Binding<Bool>
private let isDriver: Bool
#State private var showingScanner = false
#State private var scanHandler: LicenseScanHandler!
#State private var scanCompleted = false
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text("")
.font(.regularParagraph)
.padding(.top, 15)
.padding([.leading, .trailing], 20)
ZStack(alignment: .topLeading) {
VStack(alignment: .leading, spacing: 0) {
Image("sample")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(20)
}
.overlay(RoundedRectangle(cornerRadius: 3).stroke(Color.azure, lineWidth: 1))
.background(Color.white.edgesIgnoringSafeArea(.all))
.padding(.top, 12)
Text("")
.font(.tinyParagraph)
.foregroundColor(.azure)
.frame(width: 80, height: 24)
.background(Color.white.edgesIgnoringSafeArea(.all))
.cornerRadius(8)
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.azure, lineWidth: 1))
.padding(.leading, 20)
}
.padding([.leading, .trailing], 40)
.padding(.top, 16)
Spacer(minLength: 20)
Button(action: {
self.showingScanner = true
}) {
PrimaryButtonView(enabled: true, title: "Scan")
}.padding(.bottom, 16).padding([.leading, .trailing], 20)
.sheet(isPresented: $showingScanner) {
ScannerWrapper(handler: self.scanHandler)
}
NavigationLink(destination: InfoUI(), isActive: $scanCompleted) {
Text("")
}.isDetailLink(false)
}
.background(Color.offWhite.edgesIgnoringSafeArea(.all))
.navigationBarBackButtonHidden(true)
.navigationBarTitle("")
.navigationBarItems(leading: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image("LeftArrow").renderingMode(.original)
})
.onAppear {
self.scanHandler = LicenseScanHandler(delegate: self)
}
}
}
Although it is not documented, I realised that presenting a .sheet() while the view itself is set to a "no detail view" via .isDetailLink(false) causes this issue.
I can see why, as if the view is not a detail view it cannot present a sheet, hence the sheet is actually presented by the first detail view and that is why it goes back to that view right after the sheet is presented.
Try removing .isDetailLink(false) and finding a different workaround if you really need it to be not a detail link.

SwiftUI list showing an array is not updated

In my app in SwiftUI, there is a list showing all items in an array. When I click on one item, its details are shown and can be modified. Those changes are stored in the array, but when I go back to the list view, the changes are only reflected after I made a change to that list array, like adding or moving an item. Can I make this list refresh when it re-appears?
My main view looks like this:
import SwiftUI
struct ContentView: View {
#State var items: [Item]
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink(destination: ItemDetailView(items: self.$items, index: self.items.firstIndex(of: item)!)) {
HStack {
VStack(alignment: .leading) {
Text(item.name).font(.title)
if item.serialNumber != nil {
Text(item.serialNumber!)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
Spacer()
Text("\(item.valueInDollars)$").font(.title)
}
}
}
.onDelete(perform: delete)
.onMove(perform: move)
Text("No more items!")
}
.navigationBarTitle(Text("Homepwner"), displayMode: .inline)
.navigationBarItems(leading: EditButton(), trailing: Button(action: addItem) { Text("Add") })
}
}
//... functions
}
The detail view looks like this:
import SwiftUI
struct ItemDetailView: View {
#Binding var items: [Item]
let index: Int
var body: some View {
VStack {
VStack(alignment: .leading) {
HStack {
Text("Name: ")
TextField("Item Name", text: $items[index].name)
}
//... more TextFields
}
.padding(.all, 8.0)
VStack(alignment: .center) {
//... button
Image(systemName: "photo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}.navigationBarTitle(items[index].name)
}
}
The class Item is Identifiable and Equatable and only holds the necessary members, the class ItemStore only holds an array of Items.
i just tried this (had to enhance code so it was compilable at all) and it works:
import SwiftUI
struct Item : Equatable, Identifiable, Hashable {
var id = UUID()
var name: String
var serialNumber : String?
var valueInDollars : Int = 5
}
struct ContentView: View {
#State var items: [Item] = [Item(name: "hi"), Item(name: "ho")]
var body: some View {
NavigationView {
List {
ForEach(items, id: \.self) { item in
NavigationLink(destination: ItemDetailView(items: self.$items, index: self.items.firstIndex(of: item)!)) {
HStack {
VStack(alignment: .leading) {
Text(item.name).font(.title)
if item.serialNumber != nil {
Text(item.serialNumber!)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
Spacer()
Text("\(item.valueInDollars)$").font(.title)
}
}
}
// .onDelete(perform: delete)
// .onMove(perform: move)
Text("No more items!")
}
.navigationBarTitle(Text("Homepwner"), displayMode: .inline)
// .navigationBarItems(leading: EditButton(), trailing: Button(action: addItem) { Text("Add") })
}
}
//... functions
}
struct ItemDetailView: View {
#Binding var items: [Item]
let index: Int
var body: some View {
VStack {
VStack(alignment: .leading) {
HStack {
Text("Name: ")
TextField("Item Name", text: $items[index].name)
}
//... more TextFields
}
.padding(.all, 8.0)
VStack(alignment: .center) {
//... button
Image(systemName: "photo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}.navigationBarTitle(items[index].name)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Resources