I have like to use the native navigation menu bar in swiftui but somehow I could not get it to navigate to the settings page.
When I click on Settings menu item, it does show "at setting" but it does not navigate to the view.
Any help would be greatly appreciated.
struct ContentView: View {
#StateObject var vm = LoginViewModel()
#State var selection: Int? = 0
var body: some View {
NavigationView {
ZStack {
if !vm.log_status {
Home()
.toolbar{
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Button {
print("at home")
} label: {
Label("Home", systemImage: "house.fill")
}
Button {
print("at setting")
self.selection = 1
} label: {
NavigationLink(destination: Text("Settings Page"), tag:1, selection: $selection) { Label("Settings", systemImage: "gearshape.fill")}
}
} label: {
Label(title: { Text("Menu")}, icon: {Image(systemName: "filemenu.and.selection").foregroundColor(Color.black).font(.system(size: 24))})
}
}
}
} else {
Login()
}
}
.navigationTitle("App")
}
}
}
It is not necessary to have the NavigationLink inside a button itself. You can move it within the ZStack and get rid of any label you defined. By doing so, when you tap on the button where "at setting" is printed, you still change the selection value which in return triggers the navigation call. I've made a few changes to your code (Read the comments):
NavigationView {
ZStack {
NavigationLink(destination: Text("Settings Page"), tag:1, selection: $selection) {} // Move the NavigationLink to the ZStack and get rid of any labels set.
if !vm.log_status {
Text("Home")
.toolbar{
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Button {
print("at home")
} label: {
Label("Home", systemImage: "house.fill")
}
Button {
print("at setting")
self.selection = 1
} label: {
Label("Settings", systemImage: "gearshape.fill") // Keep ONLY the label of the Previous NavigationLink
}
} label: {
Label(title: { Text("Menu")}, icon: {Image(systemName: "filemenu.and.selection").foregroundColor(Color.black).font(.system(size: 24))})
}
}
}
} else {
Text("Login")
}
}
.navigationTitle("App")
}
The result:
Related
I would like to change the color of navigationTitle inside NavigationStack.
struct InterestView: View {
var body: some View {
NavigationStack {
VStack {
}
.navigationTitle("interest".uppercased())
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
print("Image tapped!")
} label: {
Text("add".uppercased())
.foregroundColor(Color.primaryColor)
.font(.headline)
}
}
}
}
}
}
I could not find any modifier to change it.
In SwiftUI, you cannot change title color with a simple modifier. You need to change UINavigation Appearance but this will effect globally.
Alternative solution:
You are already using toolbar, so adding title to toolbar is easy as follow. I hope this solves your problem.
struct InterestView: View {
var body: some View {
NavigationStack {
VStack {
}
.toolbar {
ToolbarItemGroup(placement: .principal) {
Text("interest".uppercased())
.foregroundColor(.red)
}
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
print("Image tapped!")
} label: {
Text("add".uppercased())
.foregroundColor(Color.red)
.font(.headline)
}
}
}
}
}
}
I am making an app where the first view the users see is a home screen with buttons that takes them to a second view. One of the second views present the user with a list of items. When the user clicks on one of these items the user comes to a detailed view of the item. When the user comes to the detailed view he is unfortunately presented with two toolbar buttons in the corner as can be seen here:
.
I know that one of the solutions is to only have one navigationview and that solves my problem. But I need to have toolbar items in my listview to be able to add more items, sort the list and have the list searchable which I'm not able to do without navigationView. I Have tried using scrollView and NavigationStack but it comes out blank.
Does anyone have an idea how to work with mulitple views, not getting double up "back buttons" on the toolbar and still have other toolbar items?
View one: (Home Screen):
NavigationView {
ZStack {
VStack {
Text(title)
.font(.custom("Glacial Indifference", size: 34, relativeTo: .headline).weight(.bold))
.multilineTextAlignment(.leading)
.foregroundColor(.white)
.tracking(10)
.padding(8)
.background(
Rectangle()
.fill(.gray)
.frame(width: 1000, height: 150)
.ignoresSafeArea()
.opacity(0.5))
Spacer()
}
VStack {
NavigationLink {
MapView()
} label: {
Buttons(str: "Cheese Map")
}
.padding(.bottom, 200)
}
VStack {
NavigationLink {
TabView()
} label: {
Buttons(str: "Cheese List")
}
.padding(.bottom, 400)
}
Second View (list):
NavigationView {
List {
ForEach(items, id: \.id) { item in
NavigationLink {
ItemView(item: item)
} label: {
ListItem(item: item)
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
showingAddItem = true
} label: {
Image(systemName: "plus")
Text("Add Item")
.font(.footnote)
.italic()
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Sort") {
Picker("Filter Options", selection: $selectedSort) {
ForEach(sortOptions, id: \.self) {
value in
Text(value)
.tag(value)
}
}
}
.onChange(of: selectedSort) { _ in
let sortBy = sorts[sortOptions.firstIndex(of: selectedSort)!]
items.sortDescriptors = sortBy.descriptors
}
}
}
.sheet(isPresented: $showingAddItems) {
AddItemsView(items: Items())
}
.navigationTitle("Item List")
.searchable(text: $searchText)
}
}
}
DetailView:
ScrollView {
ZStack {
VStack {
//More code...
Both .toolbar and .searchable find the nearest enclosing NavigationView automatically. You do not need a NavigationView in your list view.
Here's a self-contained demo. It looks like this:
Here's the code:
import SwiftUI
import PlaygroundSupport
struct HomeScreen: View {
var body: some View {
NavigationView {
List {
NavigationLink("Cheese Map") { Text("Map") }
NavigationLink("Cheese List") { ListView() }
}
.navigationTitle("Home Screen")
}
.navigationViewStyle(.stack)
}
}
struct ListView: View {
#State var items = ["Cheddar", "Swiss", "Edam"]
#State var search: String = ""
var filteredItems: [String] {
return items.filter {
search.isEmpty
|| $0.localizedCaseInsensitiveContains(search)
}
}
var body: some View {
List(filteredItems, id: \.self) {
Text($0)
}
.searchable(text: $search)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
items.append("Gouda")
}
} label: {
Label("Add Item", systemImage: "plus")
}
.disabled(items.contains("Gouda"))
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Sort") {
Button("Ascending") {
withAnimation {
items.sort()
}
}
Button("Descending") {
withAnimation {
items.sort()
items.reverse()
}
}
}
}
}
.navigationTitle("Cheese List")
}
}
PlaygroundPage.current.setLiveView(HomeScreen())
I have a problem regarding a TabView that is displayed inside of a NavigationStack. The TabView contains multiple views. Each view has its own navigationBarTitle and toolbar. The problem is that these views toolbar and navigation title are not shown. Just the content that is defined inside the views. I wrote the following code:
struct Home : View {
var body some : View {
NavigationStack {
TabView(selection: $router.currentTab) {
First()
.tag(0)
Second()
.tag(1)
Third()
.tag(2)
Fourth()
.tag(3)
Fifth()
.tag(4)
}
}
}
}
The First() view is defined the following (all other views are structured similar):
struct First: View {
var body: some View {
ZStack {
Color("background").ignoresSafeArea(.all)
ScrollView {
VStack(spacing: 15) {
WhatsNewView()
FavoriteView()
Spacer()
}
}
.refreshable {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
newsService.getArticles()
simpleSuccess()
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
UserButton()
}
ToolbarItem(placement: .navigationBarLeading) {
ToolbarRanking()
}
ToolbarItem(placement: .navigationBarLeading) {
ToolbarCalendar()
}
ToolbarItem(placement: .navigationBarLeading) {
ToolbarSearch()
}
}
.navigationBarTitle("Home")
}
}
Does anyone know how to fix that problem?
The TabView should be your top-level view.
Each tab should then contain its own NavigationStack. e.g.
enum Router {
case screenA, screenB
}
struct ContentView: View {
var body: some View {
TabView() {
FirstScreen()
NavigationStack {
Text("second")
.navigationTitle("second")
}
.tabItem {
Label("second", systemImage: "2.circle")
}
NavigationStack {
Text("third")
.navigationTitle("third")
}
.tabItem {
Label("third", systemImage: "3.circle")
}
NavigationStack {
Text("fourth")
.navigationTitle("fourth")
}
.tabItem {
Label("fourth", systemImage: "4.circle")
}
NavigationStack {
Text("fifth")
.navigationTitle("fifth")
}
.tabItem {
Label("fifth", systemImage: "5.circle")
}
}
}
}
struct FirstScreen: View {
#State private var path: [Router] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("first")
NavigationLink("Screen A", value: Router.screenA)
}
.navigationDestination(for: Router.self) { router in
switch router {
case .screenA:
VStack {
NavigationLink("Screen B", value: Router.screenB)
}
.navigationTitle("Screen A")
case .screenB:
VStack {
Button("Pop to root") {
path = []
}
}
.navigationTitle("Screen B")
}
}
.navigationTitle("first")
}
.tabItem {
Label("first", systemImage: "1.circle")
}
.task {
print("First screen task")
}
.onAppear {
print("First screen appears")
}
}
}
I would like to navigate between different NavigationLinks in NavigationView while some part of the main window stay the same. For example, I want to make a music app and I want to let the play controller always on top, while I can display different navigation contents (songs page, artists page...) using the rest of the window.
Like what's showed in the picture below, I want to keep the red part always there while the blue part changes.
Navigation Example Picture
My code would be like below, but it won't work correctly. The AlwaysStayView() disappears when I click any NavigationLink on sidebar. So, how can I correct it or is there any solution (prefer in SwiftUI, but framework like UIKit would also be OK). I would appreciate it.
NavigationView {
List {
NavigationLink { DiscoverView() }
label: { Label("Discover", systemImage: "magnifyingglass") }
NavigationLink { SongsView() }
label: { Label("Songs", systemImage: "music.note") }
NavigationLink { ArtistsView() }
label: { Label("Artists", systemImage: "music.mic") }
}
}
.listStyle(SidebarListStyle())
VStack {
AlwaysStayView()
SongsView()
}
}
In this case the default details view and navigated details view should be the same, but updated content can be injected in it in navigation link.
Here is a demo. Tested with Xcode 13.3 / iPadOS 15.4
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink { DetailsView { DiscoverView() } }
label: { Label("Discover", systemImage: "magnifyingglass") }
NavigationLink { DetailsView { SongsView() } }
label: { Label("Songs", systemImage: "music.note") }
NavigationLink { DetailsView { ArtistsView() } }
label: { Label("Artists", systemImage: "music.mic") }
}
.navigationBarHidden(true)
.listStyle(SidebarListStyle())
DetailsView { SongsView() } // << here default !!
}
}
}
struct DetailsView<V: View>: View {
#ViewBuilder var content: () -> V // << injected via builder !!
var body: some View {
VStack(spacing: 0) {
AlwaysStayView()
content() // << changed part here !!
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.navigationBarHidden(true)
}
}
The NavigationLink from sidebar always exchanges the whole right screen area. So you would have to put your AlwaysStayView inside the navigation links – in each. Either on top level or inside the respective detail views. Here is one example:
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Discover", color: .blue) }
label: { Label("Discover", systemImage: "magnifyingglass") }
NavigationLink {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Songs", color: .teal) }
label: { Label("Songs", systemImage: "music.note") }
NavigationLink {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Artists", color: .mint) }
label: { Label("Artists", systemImage: "music.mic") }
}
.listStyle(.sidebar)
// Standard view if no item is lelected
VStack {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Songs", color: .teal)
}
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("Toolbar")
}
}
}
}
struct DetailView: View {
let title: String
let color: Color
var body: some View {
Text(title).font(.title)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(color)
}
}
This is a beginner question
I want to move to the next view controller (OptionsView) when the button in the toolbar tapped, how can I do it?
var body: some View {
NavigationView {
VStack{
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
}
.navigationTitle("Profile")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
OptionsView()
} label: {
Label("Settings", systemImage: "gear")
}
}
}
}
}
}
You can use the isActive property of a NavigationLink to programmatically activate the link. The NavigationLink can use EmptyView as it's label so that it's hidden, since you only need it to be activated via the Button.
Then, inside of your Button's action, instead of trying to insert the view (which should always be in the view hierarchy -- not inside an action), you can set the the #State variable to activate.
struct ContentView : View {
#State private var optionsActive = false
var body: some View {
NavigationView {
VStack{
Text("Hello, World!")
NavigationLink(isActive: $optionsActive) {
OptionsView()
} label: {
EmptyView()
}
}
.navigationTitle("Profile")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
optionsActive = true
} label: {
Label("Settings", systemImage: "gear")
}
}
}
}
}
}