SwiftUI Tabview inside TabView and List items highlight behaviour strange? - swiftui

I am new in Swift, please help me a hand
The first question is Can use TabView inside a TabView?
If it can be, I got an issue related to List, items inside List is highlighted when I tap into another place
// This tab will have many pages and can swap left/right to change tabs
struct Tab1: View {
var body: some View {
TabView(selection: $otherSelected) {
SubOfTab1()
.tag("tag1")
... other tabs
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
// This tabview will display tab item as bottom
struct ContentView: View {
var body: some View {
TabView(selection: $selected) {
var body: some View {
Tab1()
.tag("tagParent1")
... other tabs
}
}
}
}
struct SubOfTab1: View {
VStack {
Text("Try to tab this text") ------> Try to tab this and all list items below is hightlighted??? why???
.frame(height: 100)
List() {
Text("XX")
Text("XX")
Text("XX")
Text("XX")
...
}
}
}
First run
Tapped to the text above, all items in List are highlighted??

I had similar issue with List of .plain style inside a TabView, tapping outside list resulted to selection all visible list items.
Setting .listRowBackground() of list cell fixed it.
In your case:
List() {
Text("XX")
.listRowBackground(Color.black)
Text("XX")
.listRowBackground(Color.black)
Text("XX")
.listRowBackground(Color.black)
Text("XX")
.listRowBackground(Color.black)
...
}

Related

SwiftUI NavigationView cannot appears multiple NavigationLinks

I have a navigation view and each NavigationLink jumps to a color view:
struct ContentView: View {
private let navigationLinks: [NavigationItem] = [
NavigationItem("Red", AnyView(Color.red.edgesIgnoringSafeArea(.all))),
NavigationItem("Orange", AnyView(Color.orange.edgesIgnoringSafeArea(.all))),
NavigationItem("Yellow", AnyView(Color.yellow.edgesIgnoringSafeArea(.all))),
NavigationItem("Green", AnyView(Color.green.edgesIgnoringSafeArea(.all))),
NavigationItem("Blue", AnyView(Color.blue.edgesIgnoringSafeArea(.all))),
NavigationItem("Purple", AnyView(Color.purple.edgesIgnoringSafeArea(.all))),
NavigationItem("Pink", AnyView(Color.pink.edgesIgnoringSafeArea(.all))),
NavigationItem("Cyan", AnyView(Color.cyan.edgesIgnoringSafeArea(.all))),
NavigationItem("Teal", AnyView(Color.teal.edgesIgnoringSafeArea(.all))),
NavigationItem("Black", AnyView(Color.black.edgesIgnoringSafeArea(.all))),
NavigationItem("Gray", AnyView(Color.gray.edgesIgnoringSafeArea(.all))),
]
var body: some View {
NavigationView {
ForEach(self.navigationLinks, id:\.key) { item in
NavigationLink(destination: item.value) {
Text(item.key)
}
}
}
}
}
struct NavigationItem {
let key: String
let value: AnyView
init(_ key: String, _ value: AnyView) {
self.key = key
self.value = value
}
}
The result of running the program is just an Orange Item, and the other NavigationItems have disappeared.
When I click the back button in the upper left corner it will go back to the Red Item.
Is there any work around for this?
Your current ForEach will directly add all Text to NavigationView and this makes NavigationView confused. Wrap your ForEach to either List or VStack.
List { //Or VStack {
ForEach(self.navigationLinks, id:\.key) { item in
NavigationLink(destination: item.value) {
Text(item.key)
}
}
}
Note: With VStack you don't have scroll so if you are planning to add more items or a dynamic list then it's better to use List View only.

Combining Navigation Bar, TabView and searchable causes the NavigationBar UI issue with lists

Combining Navigation Bar, TabView and searchable causes the NavigationBar and Search InputField to stay stationary when scrolling up on the second selected tab.
If I run the code below and first click on the Bookmark tab and scroll the list up, I get the desired results as shown in Figure 1.
If I immediately click the Home tab after the Bookmark tab and scroll the list up, I get an undesirable effect of the list displaying underneath the navigation header as shown in Figure 2.
The order that you click on the tabs produces different effects, and the position you last left the list before going to the next tab also has some strange influence on the behavior.
I need to use the TabView because it "remembers" the position of the list when you move from tab to tab. Creating my own tab control causes the list to reset everytime its displayed and I understand why. We also need to wrap the TabView under the NavigationView because our application subviews need to display their own tabs.
My questions, what am I doing wrong that is causing these inconsistencies in the navigation header. I have tried putting each list in it's own Stack but no joy, same issue keeps happening.
Any assistance would be greatly appreciated, we are currently blocked on our navigation design because of this anomaly. Hoping it's out fault so we can correct it.
----> the complete code <-------------------------------------------------------
struct ContentView: View {
#State var selectedTab: String = "Home"
#State var searchText: String = ""
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
ListView(itemTitle: "Home List", itemCount: 50)
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}.tag("Home")
ListView(itemTitle: "Bookmark List", itemCount: 20)
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("Bookmark")
}.tag("Bookmark")
Text("Profile Tab")
.tabItem {
Image(systemName: "person.crop.circle")
Text("Profile")
}.tag("Profile")
}
.navigationTitle(selectedTab)
}
.searchable(text: $searchText)
.onSubmit(of: .search) {
// Do something
}
}
}
struct ListView: View {
var itemTitle: String
var itemCount: Int
var body: some View {
List(){
ForEach(1...itemCount,id: \.self){ i in
NavigationLink(destination: ListViewDetailView("\(itemTitle) \(i)")) {
VStack(alignment: .leading){
Text("\(itemTitle) \(i)").padding()
}
}
}
}
}
}
struct ListViewDetailView: View {
var text:String
init(_ text: String){
self.text = text
}
var body: some View {
Text(text).navigationTitle(Text("\(text) Detail"))
}
}

Navigation + Tabview + Sheet broken in iOS 15

It looks like Navigation + TabView + Sheet is broken in iOS 15.
When I do this:
ContentView -> DetailView -> Bottom Sheet
When the bottom sheet comes up, the Detail view is automatically popped off the stack:
https://www.youtube.com/watch?v=gguLptAx0l4
I expect the Detail view to stay there even when the bottom sheet appears. Does anyone have any idea on why this happens and how to fix it?
Here is my sample code:
import Combine
import SwiftUI
import RealmSwift
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
TabItemView(num: 1)
.tabItem {
Text("One")
}
TabItemView(num: 2)
.tabItem {
Text("Two")
}
}
}
}
}
struct TabItemView: View {
private let num: Int
init(num: Int) {
self.num = num
}
var body: some View {
NavigationLink(destination: DetailView(text: "Detail View \(num)")) {
Text("Go to Detail View")
}
}
}
struct DetailView: View {
#State private var showingSheet = false
private let text: String
init(text: String) {
self.text = text
}
var body: some View {
Button("Open Sheet") {
showingSheet.toggle()
}.sheet(isPresented: $showingSheet) {
Text("Sheet Text")
}
}
}
This works on iOS 14 btw
UPDATE 1:
Tried #Sebastian's suggestion of putting NavigationView inside of TabView. While this fixed the nav bug, it fundamentally changed the behavior (I don't want to show the tabs in DetailView).
Also tried his suggestion of using Introspect to set navigationController.hidesBottomBarWhenPushed = true on the NavigationLink destination, but that didn't do anything:
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TabItemView(num: 1)
}.tabItem {
Text("One")
}
NavigationView {
TabItemView(num: 2)
}.tabItem {
Text("Two")
}
}
}
}
struct TabItemView: View {
private let num: Int
init(num: Int) {
self.num = num
}
var body: some View {
NavigationLink(destination: DetailView(text: "Detail View \(num)").introspectNavigationController { navigationController in
navigationController.hidesBottomBarWhenPushed = true
}) {
Text("Go to Detail View")
}
}
}
struct DetailView: View {
#State private var showingSheet = false
private let text: String
init(text: String) {
self.text = text
}
var body: some View {
Button("Open Sheet") {
showingSheet.toggle()
}.sheet(isPresented: $showingSheet) {
Text("Sheet Text")
}
}
}
You need to flip how you nest TabView & NavigationView. Instead of nesting several TabView views inside a NavigationView, use the TabView as the parent component, with a NavigationView for each tab.
This is how the updated ContentView would look like:
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TabItemView(num: 1)
}
.tabItem {
Text("One")
}
NavigationView {
TabItemView(num: 2)
}
.tabItem {
Text("Two")
}
}
}
}
This makes sense and is more correct: The tabs should always be visible, but you want to show a different navigation stack with different content in each tab.
That it worked previously doesn't make it more correct - SwiftUI probably just changed its mind on dealing with unexpected situations. That, and the lack of error messages in these situations, is the downside of using a framework that tries to render anything you throw at it!
If the goal is specifically to hide the tabs when pushing a new view on a NavigationView (e.g., when tapping on a conversation in a messaging app), you have to use a different solution. Apple added the UIViewController.hidesBottomBarWhenPushed property to UIKit to support this specific use case.
This property is set on the UIViewController that, when presented, should not show a toolbar. In other words: Not the UINavigationController or the UITabBarController, but the child UIViewController that you push onto the UINavigationController.
This property is not supported in SwiftUI natively. You could set it using SwiftUI-Introspect, or simply write the navigation structure of your application using UIKit and write the views inside in SwiftUI, linking them using UIHostingViewController.

SwiftUI List with ForEach works in one VStack but not in second one

I'm at a complete loss for why a List I'm trying to create (with a ForEach) in SwiftUI is behaving the way that it is. I have an ObservedObject that I verified in the inspector has the data that I'm expecting. In the first VStack, I can create the List with the ForEach and it outputs just fine. In the second VStack, I get no data output.
struct WorkoutPreview: View {
#Environment(\.managedObjectContext) var managedObjectContext
#ObservedObject var workoutSessionExercises: WorkoutSessionExercises
var body: some View {
NavigationView {
VStack {
Text("Welcome to your workout! Below are the exercises and bands you've selected for each.")
.padding()
Text("We found \(workoutSessionExercises.workoutExercises.count) workouts.")
.padding()
// This List (with ForEach) works fine and produces output.
List {
ForEach(0..<workoutSessionExercises.workoutExercises.count) { index in
Text(self.workoutSessionExercises.workoutExercises[index].exerciseName)
}
}
}
// The exact same List (with ForEach) in this VStack produces no results.
VStack {
List {
ForEach(0..<workoutSessionExercises.workoutExercises.count) { index in
Text(self.workoutSessionExercises.workoutExercises[index].exerciseName)
}
}
}
Spacer()
}
}
}
the problem is you have 2 VStacks that are not one below the other in the View, and so you can't see the second list.
To fix your problem wrap your VStacks in another VStack, such as:
var body: some View {
NavigationView {
VStack { // <------
VStack {
Text("Welcome to your workout! Below are the exercises and bands you've selected for each.").padding()
Text("We found \(workoutSessionExercises.workoutExercises.count) workouts.").padding()
// This List (with ForEach) works fine and produces output.
List {
ForEach(0..<workoutSessionExercises.workoutExercises.count) { index in
Text(self.workoutSessionExercises.workoutExercises[index].exerciseName)
}
}
}
// The exact same List (with ForEach) in this VStack produces no results.
VStack {
List {
ForEach(0..<workoutSessionExercises.workoutExercises.count) { index in
Text(self.workoutSessionExercises.workoutExercises[index].exerciseName)
}
}
}
Spacer()
} // <------
}
}

SwiftUI List is not showing any items

I want to use NavigationView together with the ScrollView, but I am not seeing List items.
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView{
VStack {
Text("Some stuff 1")
List{
Text("one").padding()
Text("two").padding()
Text("three").padding()
}
Text("Some stuff 2")
}
}
}
}
}
All I see is the text. If I remove ScrollView I see it all, but the text is being pushed to the very bottom. I simply want to be able to add List and Views in a nice scrollable page.
The ScrollView expects dimension from content, but List expects dimension from container - as you see there is conflict, so size for list is undefined, and a result rendering engine just drop it to avoid disambiguty.
The solution is to define some size to List, depending of your needs, so ScrollView would now how to lay out it, so scroll view could scroll entire content and list could scroll internal content.
Eg.
struct ContentView: View {
#Environment(\.defaultMinListRowHeight) var minRowHeight
var body: some View {
NavigationView {
ScrollView{
VStack {
Text("Some stuff 1")
List {
Text("one").padding()
Text("two").padding()
Text("three").padding()
}.frame(minHeight: minRowHeight * 3).border(Color.red)
Text("Some stuff 2")
}
}
}
}
}
Just wanted to throw out an answer that fixed what I was seeing very similar to the original problem - I had put a Label() item ahead of my List{ ... } section, and when I deleted that Label() { } I was able to see my List content again. Possibly List is buggy with other items surrounding it (Xcode 13 Beta 5).