Using Navigationlink in Menu (Swiftui)

Using NavigationLink in Menu (SwiftUI)

NavigationLink should be inside NavigationView hierarchy. The Menu is outside navigation view, so put buttons inside menu which activate navigation link placed inside navigation view, eg. hidden in background.

Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1)

demo

struct DemoNavigateFromMenu: View {
@State private var navigateTo = ""
@State private var isActive = false
var body: some View {
NavigationView {
Menu {
Button("item1") {
self.navigateTo = "test1"
self.isActive = true
}
Button("item2") {
self.navigateTo = "test2"
self.isActive = true
}
} label: {
Text("open menu")
}
.background(
NavigationLink(destination: Text(self.navigateTo), isActive: $isActive) {
EmptyView()
})
}
}
}

backup

I use navigationlink and I also want to use side menu

You need to use the NavigationView only in ContentView(root view).

struct SwiftUIView_map: View {
@State private var isShowing = false
var body: some View {
// NavigationView{
ZStack{
if isShowing{
SideMenuView(isShowing: $isShowing)
}
HomeView()
.cornerRadius(isShowing ? 20 : 10 )
.offset(x: isShowing ? 300 : 0,y:isShowing ? 44 :0)
.scaleEffect(isShowing ? 0.8 : 1)
.navigationBarItems(trailing: Button(action: {
withAnimation(.spring()){
isShowing.toggle()
}
}, label: {
Image(systemName: "list.bullet")
.foregroundColor(.black)
}))
.navigationTitle("home")
}
// }
}
}

Sample Image

Sample Image

Menu on NavigationLink is not tapable

I've found a "solution". A little bit hacky, but it works so far. I just wrapped the menu in a button:

Button(action: {/*do something*/}, label: {
Menu {
Button("Option 1", action: {print("1") })
Button("Option 2", action: {print("2") })
} label: {Text("Menu") }
})

Using same View for different data via NavigationLink - SwiftUI

A way to have only one view that can reload is to have a dynamic way to define its contents. One might use an enum to save the state of the survey :

class ListModel: ObservableObject {
// The enum is the list of tests
enum Choosing {
case male
case female
case color
// Define each test title
var title: String {
switch self {
case .male:
return "Male Names"
case .female:
return "Female Names"
case .color:
return "Color Names"
}
}
// define each test possible values
var items: [String] {
switch self {
case .male:
return ["Todd", "Liam", "Noah", "Oliver", "James", "William"]
case .female:
return ["Jessica", "Monica", "Stephanie"]
case .color:
return ["Pink", "Blue", "Yellow", "Green"]
}
}
// choosing next test
var next: Choosing? {
switch self {
case .male:
return .female
case .female:
return .color
case .color:
return nil
}
}
}

@Published var choosedItems: [Choosing:[String]] = [.male:[], .female:[], .color:[]]
}


struct StartView: View {
@StateObject var listModel = ListModel()

var body: some View {
NavigationView{
NavigationLink(destination: {
// Just give model and first test
OwnListView(listModel: listModel,
choosing: .male)
}, label: {
Text("Start")
.bold()
})
}
}
}

The common view :

struct OwnListView: View {
//ListModel
@ObservedObject var listModel: ListModel

var choosing: ListModel.Choosing
// Use enum var to get title and items
var title: String {
choosing.title
}
var items: [String] {
choosing.items
}

var body: some View {
VStack{
Text(title)
.font(.largeTitle)
.bold()

ForEach(choosing.items, id: \.self){ item in
// Use the current test result
let alreadyInList: Bool = listModel.choosedItems[choosing]?.contains(where: { $0 == item }) ?? false

Button(action: {
if alreadyInList {
listModel.choosedItems[choosing]?.removeAll(where: { $0 == item })
} else {
listModel.choosedItems[choosing]?.append(item)
}
}, label: {
//Can be an own View, but for simplicity
ZStack{
Rectangle()
.fill(alreadyInList ? .black : .purple)
.frame(width: 250, height: 50)

Text(item)
.bold()
.foregroundColor(.white)
}
})
}

Spacer()

// if still somthing test next
if let next = choosing.next {
NavigationLink(destination: {
OwnListView(listModel: listModel,
choosing: next)
}, label: {
Text("Continue")
})
} else {
// Here you can have a button to navigation link to go to end of survey
Text("Finish")
}

Spacer()

}.navigationBarTitleDisplayMode(.inline)
}
}

Note: The enum and title and values could comes from external json file to make it more generic. Here was just a way to do it.
To complete survey, just complete the enum definitions.

How to add Context Menu into NavigationLink using SwiftUI?

Try putting your code to execute in a separate function in your view struct and then call it from the context menu:

// put this in your view struct
func action() {
// code to run your menu action
}

And then call it from your context menu

NavigationLink(recent.name, destination: TextEditing(isNew: false, fileName: recent.name)).contextMenu {
Button("Rename", action: action)
}

SwiftUI: Native menu items navigation to view

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:

Sample Image

swiftui open view from menu button

In this case where you want to open a view but can't use a NavigationLink directly you can use it in another place and activate it programmatically from the button via a State property:

@State private var isShowingDetailView = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
NavigationLink(destination: Activities(), isActive: $isShowingDetailView) {
EmptyView()
}
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Button(action: {
isShowingDetailView = true
}) {
Label("Activities", systemImage: "doc")
}
}
}
}
}
}

NavigationLink and Menu inside Horizontal ScrollView

It could be how you are organizing your Views maybe the ZStack you aren't showing is what is conflicting.

In your code the NavigationLink likely doesn't pop up because you have made it an EmptyView

If you make clear distinctions for the tappable areas you should not have an issue.

The first example makes the tappable area for the ellipsis a small square

import SwiftUI

struct NavLinkMenuView: View {
var body: some View {
GeometryReader{geo in
NavigationView{
List{
ForEach(0..<3){ count in
VStack{
Text("category \(count)")
NavLinkRowView(geo: geo)
}
}
}.listStyle(PlainListStyle())
}
}
}
}
struct NavLinkRowView: View {
let geo: GeometryProxy
var body: some View {
ScrollView(.horizontal){
HStack{
ForEach(0..<15){ count in
ZStack{
//The the user to the next page
NavigationLink(destination: Text(count.description), label: {
Text(count.description)
//Make the NavigationLink take all the available space
.frame(maxWidth: .infinity, maxHeight: .infinity)
//Just to make a visible marke of the view
.border(Color.green, width: 2)
})
HStack{
Spacer()
//CategoryItem
VStack(alignment: .trailing, spacing: nil){
Menu(content: {
Text("context menu")
}, label: {

Image(systemName: "ellipsis")
//stretch the tappable area to the ellipsis column
.frame(maxWidth: .infinity, maxHeight: .infinity)

.font(.subheadline)
.foregroundColor(.gray)
.padding(8)
}
)
}
.aspectRatio(1, contentMode: .fit)
//Limit the size of the ellipsis column
.frame(maxWidth: geo.size.width * 0.08)

//Just to make a visible marke of the view
.border(Color.red)
}
}
//Just to make a visible marke of the view
.border(Color.orange)
//Size of the overall view
.frame(width: (geo.size.width - 90), height: 155)
//Just to make a visible marke of the view
.border(Color.blue)
.padding(.leading, 10)
}
}
}
}
}

The second example creates a tappable column for the ellipsis

import SwiftUI

struct NavLinkMenuView: View {
var body: some View {
GeometryReader{geo in
NavigationView{
List{
ForEach(0..<3){ count in
VStack{
Text("category \(count)")
NavLinkRowView(geo: geo)
}
}
}.listStyle(PlainListStyle())
}
}
}
}
struct NavLinkRowView: View {
let geo: GeometryProxy
var body: some View {
ScrollView(.horizontal){
HStack{
ForEach(0..<15){ count in
HStack(spacing: 0){
//The the user to the next page
NavigationLink(destination: Text(count.description), label: {
Text(count.description)
//Make the NavigationLink take all the available space
.frame(maxWidth: .infinity, maxHeight: .infinity)
//Just to make a visible marke of the view
.border(Color.green, width: 2)
})

//CategoryItem
VStack(alignment: .trailing, spacing: nil){
Menu(content: {
Text("context menu")
}, label: {

Image(systemName: "ellipsis")
//stretch the tappable area to the ellipsis column
.frame(maxWidth: .infinity, maxHeight: .infinity)

.font(.subheadline)
.foregroundColor(.gray)
.padding(8)
}
)
}
//Limit the size of the ellipsis column
.frame(maxWidth: geo.size.width * 0.08, maxHeight: .infinity)

//Just to make a visible marke of the view
.border(Color.red)

}
//Just to make a visible marke of the view
.border(Color.orange)
//Size of the overall view
.frame(width: (geo.size.width - 90), height: 155)
//Just to make a visible marke of the view
.border(Color.blue)
.padding(.leading, 10)
}
}
}
}
}


Related Topics



Leave a reply



Submit