Navigation repeats itself several times after clicking the object - swiftui

I just shared this Bug with Apple. I want to share with you.
Application Follow
1 - After the user logs on to the onBoardingView page, they are directed to ContentView with fullScreenCover.
2 - ContentView page contains objects in TabView that are repeated with ForEach. Clicking on these objects will take you to the DetailView page.
3 - However, Navigation repeats itself several times after clicking the object.
My English is bad. Sorry for this.
Video is here
Project file is here
struct OnboardView: View {
#State var isLogin: Bool = false
var body: some View {
Button(action: {self.isLogin = true}) {
Text("Login")
}
.fullScreenCover(isPresented: self.$isLogin) {
ContentView()
}
}
}
struct ContentView: View {
#State var selected: String = ""
var items: [String] = ["1","2","3","4","5","6","7","8","9","10"]
var body: some View {
NavigationView {
TabView(selection: $selected) {
ForEach(items, id: \.self) { item in
NavigationLink(
destination: DetailView(),
label: {
Text(item)
.foregroundColor(.white)
.padding()
.background(Color.orange)
.cornerRadius(10)
})
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}

When working with ForEach in SwiftUI, you have to be extra careful on the ids.
Try changing items to items.indices instead:
ForEach(items.indices, id: \.self) { item in
NavigationLink(
destination: Text("Detail View"),
label: {
Text(items[item])
.foregroundColor(.white)
.padding()
.background(Color.orange)
.cornerRadius(10)
}
)
}

Related

onTapGesture To Dismiss Keyboard Makes View Inoperable

I am working on a SwiftUI app. In the app I have a custom coded List that acts as a Form. The reason it is custom coded is because I am using a custom color. I have TextField rows among other rows that act as Navigation Links. My issue is that when I add a onTapGesture to dismiss the keyboard all other row functions stop working. For example the NavigationLinks.
NavigationLink Work Here
ZStack(alignment: .leading, content: {
Color.pacificBlue
.edgesIgnoringSafeArea(.all)
List {
Section(header: Text("Header") {
NavigationLink(
destination: SecondaryView(),
label: {
Text("Secondary View")
})
TextField("MyField", text: self.$myField)
}
}
}
NavigationLink Does Not Work Here
ZStack(alignment: .leading, content: {
Color.pacificBlue
.edgesIgnoringSafeArea(.all)
List {
Section(header: Text("Header") {
NavigationLink(
destination: SecondaryView(),
label: {
Text("Secondary View")
})
TextField("MyField", text: self.$myField)
}
}
}
.onTapGesture {
self.dismissKeyboard()
}
Dismiss Keyboard
extension View {
func dismissKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
}
Is there a way to work around this without having to move to a secondary view to add text. Any help would be appreciated.
Taking the answer from: SwiftUI NavigationLink in list
the following works for me for NavigationLink. If you have
a Button for example, this will not work.
struct ContentView: View {
#State var myField = ""
#State private var showIt: Int? = 0 // <-- here
var body: some View {
NavigationView {
ZStack(alignment: .leading) {
Color.blue.edgesIgnoringSafeArea(.all)
List {
Section(header: Text("Header")) {
// -- here --
NavigationLink(destination: Text("destination view"), tag: 1, selection: $showIt) {
Text("Secondary View")
}.disabled(true) // <-- here
.onTapGesture { showIt = 1 } // <-- here
TextField("MyField", text: $myField)
}
}.listStyle(.plain)
}
.onTapGesture {
self.dismissKeyboard()
}
}.navigationViewStyle(.stack)
}
}

Weird fullScreenCover and sheet behaviour in iOS 15

I have some problem with presenting sheet, sheet that use Identifiable item binding and fullScreenCover.
Main issue is that:
I choose identifiable item, corresponding sheet appears
I close that sheet with swipe
After it's dismiss i open normal sheet
It open again identifiable item sheet content
Another weird behaviour is if I open fullScreenCover after dismissing sheet it's open sheet but with content of fullScreenCover
This never happens on iOS 14. I think that is states that responsible for presenting those views not have time to update
You can checkout minimal gist reproduction or see it here
import SwiftUI
struct ContentView: View {
enum Sheet: String, Identifiable {
var id: String {
rawValue
}
case one, two
}
#State var isFullScreenPresented: Bool = false
#State var isSheetPresented: Bool = false
#State var isItemSheetPresented: Sheet?
var body: some View {
VStack {
HStack {
Button(action: {isFullScreenPresented.toggle()}, label: {
Text("fullScreen")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isSheetPresented.toggle()}, label: {
Text("sheet")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Button(action: {isItemSheetPresented = .one}, label: {
Text("one")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isItemSheetPresented = .two}, label: {
Text("two")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Text(isFullScreenPresented.description)
Text(isSheetPresented.description)
}
Text(isItemSheetPresented.debugDescription)
Spacer()
}
.sheet(item: $isItemSheetPresented, onDismiss: {isItemSheetPresented = nil}, content: {item in
Text(item.id)
})
.sheet(isPresented: $isSheetPresented, onDismiss: { isSheetPresented = false}, content: {
Text("sheet")
})
.fullScreenCover(isPresented: $isFullScreenPresented, onDismiss: {isFullScreenPresented = false }, content: {FullScreenContent()})
}
}
struct FullScreenContent: View {
#Environment(\.presentationMode) var dismiss
var body: some View {
VStack {
Button("close", action: {dismiss.wrappedValue.dismiss()})
Spacer()
}
}
}

SwiftUI NavigationLink in WatchOS8

I am just finishing up an iOS/watchOS app. After upgrading my watch to WatchOS8 I discovered a new glitch that was not present with WatchOS7. I have a list of regions each containing some locations and a link in each cell of the list to navigate to a list of these locations. In OS8 I find that the first time I tap a region name it navigates properly to the location list, but after that it does not. Rather it seems to store the link so that when I tab to another screen (TripView or ExcursionView in the code below) it briefly navigates to the correct location list then immediately back to the region list (not to TripView or ExcursionView). Here is the relevant part of the code.
struct ContentView: View {
var body: some View {
TabView {
WatchCurrentView()
WatchTripView(tripType:.trip)
WatchTripView(tripType:.interval)
WatchRegionView()
WatchExcursionView()
}
}
}
struct WatchRegionView: View {
#FetchRequest(fetchRequest:Region.fetch())
var regions: FetchedResults<Region>
var body: some View {
VStack{
Spacer()
.frame(height:5)
List{ForEach(regions, id:\.self){region in
ZStack(alignment: Alignment(horizontal: .leading, vertical: .center)) {
RegionCell(region:region)
.frame(height:14)
NavigationLink(
destination: WatchLocationsInRegionView(region:region),
label: {
EmptyView()
}
)
}
}
.environment(\.defaultMinListRowHeight, 0.1)
Spacer()
}
.navigationBarTitle("Regions")
}
}
}
struct RegionCell:View {
#EnvironmentObject var prefs: Prefs
var region:Region
var body: some View {
HStack{
Spacer()
.frame(width:15)
Text(region.name)
Spacer()
}
.frame(height:25)
.background(Color(prefs.colorTheme.navBarFG))
.foregroundColor(Color(prefs.colorTheme.navBarBG))
}
}
struct WatchLocationsInRegionView:View {
#State var region:Region
var body: some View {
List{ForEach(region.locations, id:\.self)
{location in
ZStack{
WatchLocationView (aLocation: InternalLocation(location: location), tripType:.trip,locationType: .list)
NavigationLink(
destination: BigWatchLocationView(aLocation: InternalLocation(location: location)),
label: {
EmptyView()
})
}
}
.font(.system(size: 14))
.navigationBarTitle("\(region.name)")
.onAppear(){prefs.watchDisplayMode = .coordinates}
}
}
}

SwiftUI, setting title to child views of TabView inside of NavigationView does not work

Why I am putting TabView into a NavigationView is because I need to hide the bottom tab bar when user goes into 2nd level 'detail' views which have their own bottom action bar.
But doing this leads to another issue: all the 1st level 'list' views hosted by TabView no longer display their titles. Below is a sample code:
import SwiftUI
enum Gender: String {
case female, male
}
let members: [Gender: [String]] = [
Gender.female: ["Emma", "Olivia", "Ava"], Gender.male: ["Liam", "Noah", "William"]
]
struct TabItem: View {
let image: String
let label: String
var body: some View {
VStack {
Image(systemName: image).imageScale(.large)
Text(label)
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
ListView(gender: .female).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
ListView(gender: .male).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
}
}
}
struct ListView: View {
let gender: Gender
var body: some View {
let names = members[gender]!
return List {
ForEach(0..<names.count, id: \.self) { index in
NavigationLink(destination: DetailView(name: names[index])) {
Text(names[index])
}
}
}.navigationBarTitle(Text(gender.rawValue), displayMode: .inline)
}
}
struct DetailView: View {
let name: String
var body: some View {
ZStack {
VStack {
Text("profile views")
}
VStack {
Spacer()
HStack {
Spacer()
TabItem(image: "pencil.circle", label: "Edit")
Spacer()
TabItem(image: "minus.circle", label: "Delete")
Spacer()
}
}
}
.navigationBarTitle(Text(name), displayMode: .inline)
}
}
What I could do is to have a #State var title in the root view and pass the binding to all the list views, then have those list views to set their title back to root view on appear. But I just don't feel so right about it, is there any better way of doing this? Thanks for any help.
The idea is to join TabView selection with NavigationView content dynamically.
Demo:
Here is simplified code depicting approach (with using your views). The NavigationView and TabView just position independently in ZStack, but content of NavigationView depends on the selection of TabView (which content is just stub), thus they don't bother each other. Also in such case it becomes possible to hide/unhide TabView depending on some condition - in this case, for simplicity, presence of root list view.
struct TestTabsOverNavigation: View {
#State private var tabVisible = true
#State private var selectedTab: Int = 0
var body: some View {
ZStack(alignment: .bottom) {
contentView
tabBar
}
}
var contentView: some View {
NavigationView {
ListView(gender: selectedTab == 0 ? .female : .male)
.onAppear {
withAnimation {
self.tabVisible = true
}
}
.onDisappear {
withAnimation {
self.tabVisible = false
}
}
}
}
var tabBar: some View {
TabView(selection: $selectedTab) {
Rectangle().fill(Color.clear).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
Rectangle().fill(Color.clear).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
.frame(height: 50) // << !! might be platform dependent
.opacity(tabVisible ? 1.0 : 0.0)
}
}
This maybe a late answer, but the TabView items need to be assigned tag number else binding selection parameter won't happen. Here is how I do the same thing on my project:
#State private var selectedTab:Int = 0
private var pageTitles = ["Home", "Customers","Sales", "More"]
var body: some View {
NavigationView{
TabView(selection: $selectedTab, content:{
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text(pageTitles[0])
}.tag(0)
CustomerListView()
.tabItem {
Image(systemName: "rectangle.stack.person.crop.fill")
Text(pageTitles[1])
}.tag(1)
SaleView()
.tabItem {
Image(systemName: "tag.fill")
Text(pageTitles[2])
}.tag(2)
MoreView()
.tabItem {
Image(systemName: "ellipsis.circle.fill")
Text(pageTitles[3])
}.tag(3)
})
.navigationBarTitle(Text(pageTitles[selectedTab]),displayMode:.inline)
.font(.headline)
}
}

ScrollView acting weired (Xcode 11 GM seed - SwiftUI)

I was trying to make a custom list. And its acting weired if we add Encapsulated VStack in scrollView and try to add new row from that VStack. But we have to encapsulate because in Xcode will give "complex view complier error". I am providing full code for better understanding. Please try to run it. New element is not added as expected and its pushing everything upward.
struct RowView: View {
var body: some View {
VStack{
HStack{
Spacer()
.foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}}
struct cView:View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
VStack{
Button(action: {
self.array.append("Test")
}, label: {
Text("Add")
})
VStack{
ForEach(array, id: \.self){_ in
RowView()
}
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}}
struct ContentView : View {
#State var array: [String] = []
var body: some View {
ScrollView{
VStack{
Text("d")
.frame(height: 90)
VStack{
cView()
}
}
}
.navigationBarTitle("Test", displayMode: .automatic)
}}
When I reformatted and removed unused stuff I got:
struct RowView: View {
let text: String
var body: some View {
VStack{
HStack{
Spacer()
Text(text).foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}
}
struct cView:View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
VStack{
Button(
action: { self.array.append("Test") },
label: { Text("Add") }
)
ForEach(array, id: \.self){text in
RowView(text: text)
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}
}
struct ContentView : View {
var body: some View {
List {
VStack{
Text("d")
cView()
}
}
}
}
ScrollView is a real PITA and it hates Text which is why I replaced it with a List. RowView was missing a Text IMHO so I put one in. The array in ContentView was never used so I removed it, similarly a navigatinBarTitle needs a NavigationView.
This isn't really an answer as it uses List instead of ScrollView but it does point to where your problems lie. It is also very strange as every thing is in a single List row but I tried to change as little as possible.
You might like to try running SwiftLint on your code. I often swear at it, especially when it complains about the cyclomatic complexity of my enum switches but it does improve my code.
Most likely a bug, but I did not need to encapsulate. And if I don't, the code works as expected:
struct RowView: View {
var body: some View {
VStack{
HStack{
Spacer()
.foregroundColor(Color.black)
Spacer()
}
}
.background(Color.white)
.cornerRadius(13)
.padding()
}
}
struct ContentView : View {
#State var array: [String] = []
#State var height: CGFloat = 60
var body: some View {
ScrollView{
VStack{
Text("d")
.frame(height: 90)
VStack{
Button(action: {
self.array.append("Test")
}, label: {
Text("Add")
})
VStack{
ForEach(array, id: \.self){_ in
RowView()
}
}
.background(Color.red)
.cornerRadius(13)
.padding()
}
}
}
.navigationBarTitle("Test", displayMode: .automatic)
}
}