Environment: Xcode 11.1 running on Catalina (19A573a) and building iOS only app.
I have the following code that should be fairly simple.
- I have buttons A - H (8 buttons)
- When I tap on a button I expect to be taken to a respective view (“View A”, “View B”, etc) as they are embedded in a NavigationView.
I run into several issues
- With the code shown tapping button for “View A” does nothing but the other buttons work.
- After re-running a few times tapping on button A will work some times but fail most of the time
- If I disable displaying of all the buttons except button A tapping on button A works.
- If I disable displaying of any single button (again there are 8 buttons, A-H) then tapping on the first button works.
I understand there is a technical limit in VStack of more than 10 view and I tried the suggestion made here:
https://www.hackingwithswift.com/quick-start/swiftui/how-to-group-views-together
But that does not work
I have tried variations of putting the buttons in the list and that doesn’t work.
Anyone who wants to test can create a new project and copy the entire code contents into the ContentView.swift file and run.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
HStack {
Spacer()
VStack {
Spacer()
Group {
NavigationLink(destination: ViewA()) {
BasicButton(buttonName: "View A", buttonColor: .orange)
}
NavigationLink(destination: ViewB()) {
BasicButton(buttonName: "View B", buttonColor: .red)
}
NavigationLink(destination: ViewC()) {
BasicButton(buttonName: "View C", buttonColor: .green)
}
NavigationLink(destination: ViewD()) {
BasicButton(buttonName: "View D", buttonColor: .blue)
}
}
Group {
NavigationLink(destination: ViewE()) {
BasicButton(buttonName: "View E", buttonColor: .pink)
}
NavigationLink(destination: ViewF()) {
BasicButton(buttonName: "View F", buttonColor: .gray)
}
NavigationLink(destination: ViewG()) {
BasicButton(buttonName: "View G", buttonColor: .purple)
}
NavigationLink(destination: ViewH()) {
BasicButton(buttonName: "View H", buttonColor: .yellow)
}
}
Spacer()
}
Spacer()
}
.background(Color.black).edgesIgnoringSafeArea(.all)
}
}
}
struct BasicButton: View {
var buttonName: String
var buttonColor: Color
var body: some View {
Text(buttonName)
.font(.title)
.multilineTextAlignment(.center)
.foregroundColor(.white)
.frame(width: 300, height: 60)
.background(buttonColor)
.cornerRadius(5)
.padding()
}
}
struct ViewA: View {
var body: some View {
Text("View A").font(.largeTitle)
}
}
struct ViewB: View {
var body: some View {
Text("View B").font(.largeTitle)
}
}
struct ViewC: View {
var body: some View {
Text("View C").font(.largeTitle)
}
}
struct ViewD: View {
var body: some View {
Text("View D").font(.largeTitle)
}
}
struct ViewE: View {
var body: some View {
Text("View E").font(.largeTitle)
}
}
struct ViewF: View {
var body: some View {
Text("View F").font(.largeTitle)
}
}
struct ViewG: View {
var body: some View {
Text("View G").font(.largeTitle)
}
}
struct ViewH: View {
var body: some View {
Text("View H").font(.largeTitle)
}
}
When you open Debug View Hierarchy you will notice that View A is completely obstructed by a invisible NavigationBar, which even though it is invisible is blocking all the touches from reaching View A.
Related
I have two views embedded in a TabView and a third view activated by a ToolbarItem in a navigationStack.
problem 1)
When I tap on plus button I navigate to my addView, but I still can see the tabs at the bottom.
problem 2)
after many test I found that if put the tabView code in MainView Inside a NavigationStack, I solve problem 1) but each time I dismiss from a detailView from a row in ContentView, the navigation Item disappears.
the main view for the tabview
struct MainView: View {
var body: some View {
TabView {
ContentView()
.tabItem {
Label("List", systemImage: "list.dash")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape.fill")
}
}
}
}
the ContentView (a list of lessons, navigationDestination goes to a detail view)
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest (sortDescriptors: [
SortDescriptor(\.lessonNuber, order: .reverse)
], predicate: nil) var lessons: FetchedResults<Lesson>
#State var showAddView = false
var body: some View {
NavigationStack {
VStack {
List {
ForEach(lessons, id: \.self) { lesson in
NavigationLink {
DetailView(lesson: lesson)
} label: {
HStack {
Text("\(lesson.lessonNuber)")
.font(.title)
Text( "\(lesson.un_notion)")
.font(.body)
}
}
}
}
// .background(
// NavigationLink(destination: AddView(), isActive: $showAddView) {
// AddView()
// }
// )
.navigationDestination(isPresented: $showAddView) {
AddView()
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
showAddView = true
} label: {
Label("Add Lesson", systemImage: "plus")
}
}
}
}
.padding()
}
}
}
I am still learning SwiftUI and I have come across a small problem.
In my app, I have a main view. On the top is a search bar and at the bottom, a menu with different buttons. I want to change views when clicking those buttons. However, I only want to change the middle section.
No big deal, I will just put the middle part into a NavigationView. That works alright and I am able to change my views. My problem is that the buttons below do not have any impact on the new view.
To try to simplify: Let’s say I’m on home page. I then click the grocery list button (guess what I’m making school projects lol). My navigation link works just fine and goes to the list. So, now I’m on view 2 let’s say. When I press the home button, it doesn’t close that view and go to my main one. Here is my code setup:
import SwiftUI
struct ContentView: View {
#State private var searchText: String = ""
#State private var action: Int? = 0
var body: some View {
ZStack {
// Top Menu
VStack{
HStack {
Spacer()
TextField("Search",
text: $searchText)
.background(Color.white)
Button(action: {
self.action = 1
}, label: {
Image(systemName: "magnifyingglass.circle")
.font(.largeTitle)
})
Spacer()
}
// Body
NavigationView {
VStack {
Text("Can I See Something")
NavigationLink(destination: SearchView(), tag: 1, selection: $action) {
}
Text("Yes/No")
}
}
Spacer()
// Bottom Menu
HStack (alignment: .top) {
Spacer()
VStack {
Button(action: {
}, label: {
Image(systemName: "house.fill")
.font(.largeTitle)
})
.padding(.top)
Text("Home")
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "newspaper")
.font(.largeTitle)
})
.padding(.top)
Text("Weekly\nAd")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "checklist")
.font(.largeTitle)
})
.padding(.top)
Text("Grocery\nList")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "person.crop.circle")
.font(.largeTitle)
})
.padding(.top)
Text("Account")
}
Spacer()
}
}
}
}
}
struct SearchView: View {
var body: some View {
ZStack {
Text("Nothing to see here!")
}
.navigationBarBackButtonHidden(true)
}
}
SearchView is a separate view (in its own file) in the app that opens up when the magnifying glass button is pressed. Currently it does not do anything. However I want to be able to press those buttons on this view above to still navigate the app.
Also, on another note, is there anyway to get rid of the back button?
In your code the buttons do not have any function.
Instead of creating a tab bar on your own, I'd rather take something like:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
MainView()
.tabItem {
Label("Home", systemImage: "house.fill")
}
NewsView()
.tabItem {
Label("Weekly\nAd", systemImage: "newspaper")
}
OrderView()
.tabItem {
Label("Grocery\nList", systemImage: "checklist")
}
AccountView()
.tabItem {
Label("Account", systemImage: "person.crop.circle")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MainView: View {
var body: some View {
Text("Home View")
}
}
struct NewsView: View {
var body: some View {
Text("News View")
}
}
struct OrderView: View {
var body: some View {
Text("Order View")
}
}
struct AccountView: View {
var body: some View {
Text("Account View")
}
}
In that case you'll have to create a view for each tab you are configuring (see the last 4 structs).
If you want to do it with a Stack with your own created buttons, I think you should create al 4 views as well and then you either hide them or put them out of focus by using an offset. In that case the buttons should hide/show the specific views or change the offset accordingly to move the specific views into the visible area. With the offset you also can add some animation.
Regarding the search bar on top of your app, since the views are all different, I wouldn't keep the same search bar everywhere, but if you really want to have it that way, you can embed the code + your search bar into a VStack (as you did it in your example).
I am having trouble with a return from a navigation view within a tabbed view. My project has a Settings tab where the user may select via navigation link "View Entries". And from there another navigation link to "Add New Entry". Returning from Add New Entry should bring you to View Entries but instead is return another level to the Setting Menu.
I am seeing a warning on the console stating "trying to pop to a missing destination at /Library/Caches/com.apple...". Using the tabbed view sample code at SwiftUI NavigationView trying to pop to missing destination (Monoceros?) I no longer get the "pop-to-missing-destination" warning but I still have the same problem with the navigation return.
The sample code below is ready to run and test in Xcode 12.
In the sample code below, tap settings and select the navigation view "View Entries". This would be a screen where entries are displayed in a list. Tapping the plus button is where new entries could be added. The textfield on the "Add New Entry" screen doesn't do anything. Clicking the Save or Back buttons should return you to "View Entries" screen but instead returns you to the Setting Menu. The Save button uses presentationMode.wrappedValue.dismiss to dismiss the view.
The fact that two different version of the tab view logic didn't have any impact on my navigation view return logic leads me to believe that I just have some kind on plain old bug in my navigation view logic but I sure don't see one. The sample code below is using the standard tab view logic.
struct ContentView: View {
#State private var selection = 0
var body: some View {
NavigationView {
TabView (selection: $selection) {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}.tag(1)
AView()
.tabItem {
Label("A", systemImage: "a.circle")
}.tag(2)
BView()
.tabItem {
Label("B", systemImage: "b.circle")
}.tag(3)
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape")
}.tag(4)
}
}
}
}
struct HomeView: View {
var body: some View {
Text("Home Screen")
}
}
struct AView: View {
var body: some View {
Text("A Screen")
}
}
struct BView: View {
var body: some View {
Text("B Screen")
}
}
struct SettingsView: View {
var body: some View {
VStack (alignment: .leading) {
List {
Text("Settings")
.font(.title)
.fontWeight(.bold)
.padding(.leading, 15)
NavigationLink(destination: SetAView()) {Text("View Entries")}
}
}
.font(.body)
}
}
struct SetAView: View {
var body: some View {
List {
Text("View Entries")
.padding(.vertical, 10)
Text("Normally entires would be displayed here")
Text("Should return here upon adding new entry")
.padding(.vertical, 10)
Text("Click the + button to add new entry")
}
.navigationBarItems(trailing: NavigationLink (destination: AddTestView()) {
Image(systemName: "plus")
.resizable()
.foregroundColor(Color(.systemBlue))
.frame(width: 18, height: 18)
} // body
)
}
}
struct AddTestView: View {
#Environment(\.presentationMode) var presentationMode
#State private var catSelect: String = ""
var body: some View {
NavigationView {
VStack {
Form {
Section {
TextField("Enter Entry Name", text: $catSelect)
.padding(.horizontal, 20)
.keyboardType(.default)
}
}
}
.navigationBarTitle(Text("Add new Entry"), displayMode: .inline)
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarItems(trailing: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text ("Save")
})
}
}
}
After considerable analysis I discovered in my actual code that I had two copies of NavigationView––nothing wrong with the TabView code. Removing the one NavigationView not in contentView then caused several functions from working so rebuilt them from scratch. Now have everything working with TabView and NaigationView including the back buttons.
I'm having a problem where when I close the app to send it to the background and then reopen it, it doesn't go to the last screen. Instead it seems to reset just like if the app were being opened from scratch.
I've included an example below. Run it on an iPad in landscape mode, select "Favorites". The details controller will show a red screen. Close the app to send it to the background, open any other app, then go back to the test app. You'll see that it reset itself to the green view. It should stay on the red view.
I've taken all of my code straight from the Fruta example project which doesn't have this behavior so I have no idea what's going on.
EDIT
I've made SideBar a standalone list like Asperi suggested, and I'm also now using SceneStorage as Apple recommends. Using SceneStorage solves the sidebar issue at first, but the core problem is still there when I'm multiple levels deep in a navigation stack.
In this updated example if you tap on Numbers in the sidebar, then select a row, leave the app, and come back after doing something else, the sidebar selection resets.
I have discovered that this is only a problem if your app supports multiple windows. If you uncheck that box none of this seems to be necessary.
The example below has the most recent code edits.
EDIT
I reached out to Apple Code Level Support and they re-debited my account telling me that my issue might be a bug and advised me to file a radar. I’m not entirely sure that it’s a framework bug though.
struct ContentView: View {
#if os(iOS)
#Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
var body: some View {
#if os(iOS)
if horizontalSizeClass == .compact {
AppTabNavigation()
} else {
AppSidebarNavigation()
}
#else
AppSidebarNavigation()
#endif
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// MARK: - AppTabNavigation
struct AppTabNavigation: View {
#State private var selection: Tab = .menu
enum Tab {
case menu
case favorites
case rewards
case recipes
}
var body: some View {
TabView(selection: $selection) {
NavigationView {
Color.purple
}
.tabItem {
Label("Menu", systemImage: "list.bullet")
.accessibility(label: Text("Menu"))
}
.tag(Tab.menu)
NavigationView {
Color.orange
}
.tabItem {
Label("Favorites", systemImage: "heart.fill")
.accessibility(label: Text("Favorites"))
}
.tag(Tab.favorites)
NavigationView {
Color.red
}
.tabItem {
Label("Numbers", systemImage: "book.closed.fill")
.accessibility(label: Text("Numbers"))
}
.tag(Tab.recipes)
}
}
}
enum NavigationItem: String {
case menu
case favorites
case recipes
}
struct SideBar: View{
#Binding var selection: String?
var body: some View {
List(selection: $selection) {
NavigationLink(destination: Color.green, tag: NavigationItem.menu.rawValue, selection: $selection) {
Label("Menu", systemImage: "list.bullet")
}
NavigationLink(destination: Color.red, tag: NavigationItem.favorites.rawValue, selection: $selection) {
Label("Favorites", systemImage: "heart")
}
NavigationLink(destination: ListView(), tag: NavigationItem.recipes.rawValue, selection: $selection) {
Label("Numbers", systemImage: "book.closed")
}
}
.listStyle(SidebarListStyle())
}
}
struct AppSidebarNavigation: View {
#SceneStorage("ContentView.selection") private var selection: String?
var body: some View {
NavigationView {
SideBar(selection: $selection)
Text("Select a category")
.foregroundColor(.secondary)
}
}
}
struct ListView: View{
#SceneStorage("ListView.selection") private var selection: String?
var body: some View{
List(0..<20){ num in
NavigationLink(destination: ListDetails(num: num), tag: num.description, selection: $selection) {
Text(num.description)
}
}
}
}
struct ListDetails: View{
let num: Int
var body: some View{
Text(num.description)
.font(.title)
}
}
Computable property sidebar reevaluated causing List rebuilding, use instead standalone view, like
enum NavigationItem {
case menu
case favorites
case recipes
}
struct AppSidebarNavigation: View {
#State private var selection: NavigationItem? = .menu
#State private var presentingRewards = false
var body: some View {
NavigationView {
SideBarView(selection: $selection)
Text("Select a category")
.foregroundColor(.secondary)
}
}
}
struct SideBarView: View {
#Binding var selection: NavigationItem?
var body: some View {
List(selection: $selection) {
NavigationLink(destination: Color.green, tag: NavigationItem.menu, selection: $selection) {
Label("Menu", systemImage: "list.bullet")
}
.tag(NavigationItem.menu)
NavigationLink(destination: Color.red, tag: NavigationItem.favorites, selection: $selection) {
Label("Favorites", systemImage: "heart")
}
.tag(NavigationItem.favorites)
NavigationLink(destination: Color.blue, tag: NavigationItem.recipes, selection: $selection) {
Label("Recipes", systemImage: "book.closed")
}
.tag(NavigationItem.recipes)
}
.listStyle(SidebarListStyle())
}
}
I am trying to get a context menu to navigate to another view using the following code
var body: some View
{
VStack
{
Text(self.event.name).font(.body)
...
Spacer()
NavigationLink(destination: EditView(event: self.event))
{
Image(systemName: "pencil")
}
}
.navigationBarTitle(Text(appName))
.contextMenu
{
NavigationLink(destination: EditView(event: self.event))
{
Image(systemName: "pencil")
}
}
}
The NavigationLink within the VStack works as expected and navigates to the edit view but I want to use a contextMenu. Although the context menu displays the image, when I tap on it it doesn't navigate to the edit view, instead it just cancels the context menu.
I am doing this within a watch app but don't think that should make a difference, is there anything special I have to do with context menu navigation?
I would use the isActive variant of NavigationLink that you can trigger by setting a state variable. Apple documents this here
This variant of NavigationLink is well fit for dynamic/programatic navigation.
Your .contextMenu sets the state variable to true and that activates the NavigationLink. Because you don't want the link to be visible, set the label view to EmptyView
Here's an example, not identical to your post but hopefully makes it clear.
struct ContentView: View {
#State private var showEditView = false
var body: some View {
NavigationView {
VStack {
Text("Long Press Me")
.contextMenu {
Button(action: {
self.showEditView = true
}, label: {
HStack {
Text("Edit")
Image(systemName: "pencil")
}
})
}
NavigationLink(destination: Text("Edit Mode View Here"), isActive: $showEditView) {
EmptyView()
}
}
.navigationBarTitle("Context Menu")
}
}
}
In Xcode 11.4 it's now possible to do this with sensible NavigationLink buttons. Yay! 🎉
.contextMenu {
NavigationLink(destination: VisitEditView(visit: visit)) {
Text("Edit visit")
Image(systemName: "square.and.pencil")
}
NavigationLink(destination: SegmentsEditView(timelineItem: visit)) {
Text("Edit individual segments")
Image(systemName: "ellipsis")
}
}
This works on Xcode 11.6
struct ContentView: View {
#State var isActiveFromContextMenu = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : detailTwo(), isActive: $isActiveFromContextMenu ){
EmptyView()
}
List{
NavigationLink(destination: detail() ){
row(isActiveFromContextMenu: $isActiveFromContextMenu)
}
NavigationLink(destination: detail() ){
row(isActiveFromContextMenu: $isActiveFromContextMenu)
}
NavigationLink(destination: detail() ){
row(isActiveFromContextMenu: $isActiveFromContextMenu)
}
}
}
}
}
}
struct detail: View {
var body: some View{
Text("Detail view")
}
}
struct detailTwo: View {
var body: some View{
Text("DetailTwo view")
}
}
struct row: View {
#Binding var isActiveFromContextMenu : Bool
var body: some View {
HStack{
Text("item")
}.contextMenu{
Button(action: {
self.isActiveFromContextMenu = true
})
{
Text("navigate to")
}
}
}
}
I found success in masking the NavigationLink in the background and switching the context with a Button as the shortest yet simplest alternative.
struct ContentView: View {
#State private var isShowing = false
var body: some View {
NavigationView {
Text("Hello")
.background(NavigationLink("", destination: Text("World!"), isActive: $isShowing))
.contextMenu {
Button {
isShowing = true
} label: {
Label("Switch to New View", systemImage: "chevron.forward")
}
}
}
}
}