Add NavigationLink to Custom designed button in Swift UI - ios

I am trying to add NavigationLink inside NavigationView in which there is a custom designed button.
I want to navigate on that button tap but NavigationLink code gives compilation error. it requires localisedTitle which I don't want to display and also tried to give Custom button in place of label but not working. Any help would be appreciated
Here is my code! I am using Xcode 13.3.1
#State private var isShowingSignupView = false
var body: some View {
NavigationView {
VStack {
Spacer()
VStack(alignment: .center, spacing: 12) {
AppTextField(Constants.Email, text: $email)
AppTextField(Constants.Password, text: $password, isSecure: $isSecure, leftImage: "lock",rightImage: Image(systemName: "eye.fill"), rightSelectedImage: Image(systemName: "eye.slash.fill"))
AppButtonText(title: Constants.ForgotPassword) {
debugPrint("Forgot Password tapped")
}
.frame(maxWidth: .infinity, maxHeight: 20, alignment: .trailing)
AppButton(title: Constants.Login) {
}
}
Spacer()
NavigationLink($isShowingSignupView, destination: Signup) {
AppButtonText(title: Constants.SignUp) {
isShowingSignupView = true
}
}
}
.padding()
}
.navigationTitle("Login")
}
**ERROR:-
Cannot convert value of type 'Signup.Type' to expected argument type '() -> Destination'
Generic parameter 'Destination' could not be inferred**
**I have also tried after replacing this **
NavigationLink(destination: Signup()) {
AppButtonText(title: Constants.SignUp) {
isShowingSignupView = true
}
}
Which just removed error but does not navigate on new screen

Hey there!
I got the issue and sollution to it.
Actually above code was almost correct but the problem was in Custom Button view action which was not getting triggered due to simultaneousGesture added in it for functionality
struct AppButtonText: View {
var title: String
#State var action: () -> ()
#State private var isPressed = false
var body: some View {
Text(title)
.foregroundColor(isPressed ? Color.red.opacity(0.5) : Color.red)
.background(Color.clear)
.padding(.vertical, 0)
.font(.body)
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged({ _ in
isPressed = true
})
.onEnded({ _ in
isPressed = false
action() `THIS LINE WAS NOT ADDED BEFORE`
})
)
}
}
And this below line was having no issue
NavigationLink($isShowingSignupView, destination: Signup) {
AppButtonText(title: Constants.SignUp) {
isShowingSignupView = true
}
}
THANKS ALL FOR YOUR VALUABLE REPLIES

You need to use NavigationLink(destination: { Signup() })
Also since you are using custom Button instead of NavigationLink it's better use isActive property:
NavigationLink("Signup", isActive: $isShowingSignupView) {
Signup()
}
AppButtonText(title: Constants.SignUp) {
isShowingSignupView = true
}

You can just do it like this. Note that you need to initialize when using a view struct, like Signup().
#State private var isShowingSignupView = false
struct Signup: View {
var body: some View {
Text("Your view contents here")
}
}
var signup: some View {
Text("Your view contents here")
}
var body: some View {
NavigationView {
VStack {
// Using a view
NavigationLink(isActive: $isShowingSignupView) {
signup
} label: {
AppButtonText(title: Constants.SignUp) {
isShowingSignupView = true
}
}
// Using a view struct
NavigationLink(isActive: $isShowingSignupView) {
Signup()
} label: {
AppButtonText(title: Constants.SignUp) {
isShowingSignupView = true
}
}
}
}
}

Related

Why does withAnimation not cause an animation?

I try to switch from one screen to another by pressing a button (see full code below). The switch from the first view to the second view (and vice versa) works, but no animation is taking place.
Why does this behavior happen?
Full code:
struct ContentView: View {
#AppStorage("firstViewActive") var isFirstViewActive: Bool = true
var body: some View {
if isFirstViewActive {
FirstView()
} else {
SecondView()
}
}
}
struct FirstView: View {
#AppStorage("firstViewActive") var isFirstViewActive: Bool = true
var body: some View {
ZStack {
Color(.red).ignoresSafeArea(.all, edges: .all)
VStack {
Spacer()
Text("This is the first view")
Spacer()
Button {
withAnimation {
isFirstViewActive = false
}
} label: {
Text("Go to second view")
}
Spacer()
}
}
}
}
struct SecondView: View {
#AppStorage("firstViewActive") var isFirstViewActive: Bool = false
var body: some View {
ZStack {
Color(.blue).ignoresSafeArea(.all, edges: .all)
VStack {
Spacer()
Text("This is the second view")
Spacer()
Button {
withAnimation {
isFirstViewActive = true
}
} label: {
Text("Go to first view")
}
Spacer()
}
}
}
}
The problem is with
#AppStorage("firstViewActive") var isFirstViewActive: Bool = true
If you change that to
#State var isFirstViewActive: Bool = true
and use #Binding in subviews, you will get the default animations.
In iOS 16, there seems to be a problem with #AppStorage vars and animation. But you can refer to this workaround

Conditional component in SwiftUI

I'm giving my first steps with SwiftUI and I'm having problems with a component shown depending on a condition.
I'm trying to show a fullscreen popup (full screen with semi transparent black background and the popup in the middle with white background). To achieve this I've made this component:
struct CustomUiPopup: View {
var body: some View {
ZStack {
}
.overlay(CustomUiPopupOverlay, alignment: .top)
.zIndex(1)
}
private var CustomUiPopupOverlay: some View {
ZStack {
Spacer()
ZStack {
Text("POPUP")
.padding()
}
.zIndex(1)
.frame(width: UIScreen.main.bounds.size.width - 66)
.background(Color.white)
.cornerRadius(8)
Spacer()
}
.frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
.background(Color.black.opacity(0.6))
}
}
If I set this in my main view, the popup is shown correctly over the button:
struct MainView: View {
var body: some View {
CustomUiPopup()
Button("Click to show popup") {
print("click on button")
}
}
}
If I set this, my popup is not shown (correct because hasToShowPopup is false), but if I click on the button it fails, the popup is not shown and the button can not be clicked again (?!), it seems like the view was freezed.
struct MainView: View {
#State private var hasToShowPopup = false
var body: some View {
if hasToShowPopup {
CustomUiPopup()
}
Button("Click to show popup") {
hasToShowPopup = true
}
}
}
I've even tried to initializate hasToShowPopup to true but the popup keeps failing, it's not shown in the first place:
struct MainView: View {
#State private var hasToShowPopup = true
var body: some View {
if hasToShowPopup {
CustomUiPopup()
}
Button("Click to show popup") {
hasToShowPopup = true
}
}
}
So my conclusion is that, I don't know why, but if I put my CustomUiPopup inside an "if" something is not rendered correctly.
What is wrong with my code?
Anyway, if this is not the correct approach to show a popup, I'll be glad to have any advice.
Following Ptit Xav suggestion I've tried this with the same results (my CustomUiPopup doesn't show):
struct MainView: View {
#State private var hasToShowPopup = false
var body: some View {
VStack {
if hasToShowPopup {
CustomUiPopup()
}
Button("Click to show popup") {
hasToShowPopup = true
}
}
}
}
This works fine with me:
struct CustomUiPopup: View {
var body: some View {
ZStack {
Spacer()
Text("POPUP")
.padding()
.zIndex(1)
.frame(width: UIScreen.main.bounds.size.width - 66)
.background(Color.white)
.cornerRadius(8)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
Color.black.opacity(0.6)
.ignoresSafeArea()
)
}
}
struct ContentView: View {
#State private var hasToShowPopup = false
var body: some View {
ZStack {
Button("Click to show popup") {
hasToShowPopup = true
}
if hasToShowPopup {
CustomUiPopup()
}
}
}
}

SwiftUI NavigationLink for iOS 14.5 not working

I had the following code in Xcode 12.4 that worked perfectly
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: rows, spacing: 0) {
HStack {
if (type == "Quiz") {
NavigationLink(destination: Quiz(id: quiz.id)) {
VStack(alignment: .leading) {
Text("Quiz")
.font(.headline)
.foregroundColor(.white)
.padding(.top, 8)
.padding(.leading)
}
.background(Color.green)
.cornerRadius(12)
.shadow(color: .green, radius: 3, x: 0.0, y: 0.0)
}
} else {
NavigationLink(destination: Survey(id: survey.id)) {
VStack(alignment: .leading) {
Text("Survey")
.font(.headline)
.foregroundColor(.white)
.padding(.top, 8)
.padding(.leading)
}
.background(Color.green)
.cornerRadius(12)
.shadow(color: .green, radius: 3, x: 0.0, y: 0.0)
}
} // End If
if (type == "Quiz") {
NavigationLink(destination: QuizResults(id: quiz.id)) {
VStack(alignment: .leading) {
Text("Quiz Results")
.font(.headline)
.foregroundColor(.white)
.padding(.top, 8)
.padding(.leading)
}
.background(Color.blue)
.cornerRadius(12)
.shadow(color: .blue, radius: 3, x: 0.0, y: 0.0)
}
} else {
NavigationLink(destination: SurveyResults(id: survey.id)) {
VStack(alignment: .leading) {
Text("Survey Results")
.font(.headline)
.foregroundColor(.white)
.padding(.top, 8)
.padding(.leading)
}
.background(Color.blue)
.cornerRadius(12)
.shadow(color: .blue, radius: 3, x: 0.0, y: 0.0)
}
}
}
.padding([.leading, .trailing], 25)
}
.frame(height: 100)
I just updated Xcode to 12.5 and the above does not work any more.
It was working fine in 12.4!?
Now when I click the 'Quiz' element, it starts the transition to the Quiz View which is displays it but immediately closes the view and I'm back in the Detail View!?
Can someone see what I am doing wrong, and why now based on the update to 12.5 this stopped working?
UPDATE
I refined the code to the minimal possible reproducible form. What seems to be happening is that I have two or more NavigationLinks sets.
the first is the set to navigate the user to either the Quiz or Survey which the if statement addresses the user to the correct view to fill in.
Where the issue is in 12.5 is that the second set where the user can click to go see the overall results of the Quiz or Survey does not work when it's directly after the first navigation.
Like I said before hand it worked perfectly in 12.4 but seems like 12.5 does not agree with it. Can someone offer a better way for the user to click an element to either go fill in a quiz or survey or go see the results of a quiz or survey?
I got exactly the same problem, everything works fine with Xcode 12.4.
https://developer.apple.com/forums/thread/677333
I try to following this thread, it might work but on some case, I still have this bug.
NavigationLink(destination: EmptyView()) {
EmptyView()
}
Apparently, you can put this 3 lines of code close to your NavigationLink...
If someone got a better answer I will really appreciate it !
What a horrible bug! From my testing and some googling it happens when there are exactly 2 navigation links in a view. The code in the question has 4 but because of the if else statements there are effectively only 2 at a time.
I often don't know how many nav links I will have as it depends on what data the user has added/how many search hits there are etc. To be safe I've made a tripleEmptyNavigationLink modifier which I've stuck at the end of all my views. It's solving the popping behaviour but I'm still getting the 'Unable to present' warnings. Would love to know if anyone has anything better than this!
import SwiftUI
struct TripleEmptyNavigationLink: View {
var body: some View {
VStack {
NavigationLink(destination: EmptyView()) {EmptyView()}
NavigationLink(destination: EmptyView()) {EmptyView()}
NavigationLink(destination: EmptyView()) {EmptyView()}
}
}
}
struct TripleEmptyNavigationLinkBackground: ViewModifier {
func body(content: Content) -> some View {
content
.background(TripleEmptyNavigationLink())
}
}
extension View {
func tripleEmptyNavigationLink()-> some View {
self.modifier(TripleEmptyNavigationLinkBackground())
}
}
usage:
MyView()
.tripleEmptyNavigationLink()
In Xcode13 beta still has this issue.
So far solution:
1、Wrap NavigationLink with List or Form:
List {
NavigationLink(destination: Text("1")) {
Text("1")
}
NavigationLink(destination: Text("2")) {
Text("2")
}
NavigationLink(destination: Text("3")) {
Text("3")
}
}
2、Or Use one NavigationLink, and create destination view from func:
struct TaskIndexPage: View {
func buildView() -> some View {
// return you destination
switch self.option {
case 1:
return Text("\(option)")
default:
return Text("\(option)")
}
}
#State private var showDetail: Bool = false
#State private var option: Int = 0
var body: some View {
VStack {
Button {
showDetail = true
option = 1
} label: { Text("button 1") }
Button {
showDetail = true
option = 2
} label: { Text("button 2") }
Button {
showDetail = true
option = 3
} label: { Text("button 3") }
}
// handle navigating
NavigationLink(destination: self.buildView(), isActive: $showDetail) {}.opacity(0)
}
}
Adding a NavigationLink with an empty view didn't work for me. I solved my issue removing all NavigationLinks from the ForEach and using a single one to control the navigation to the detail view, a tap gesture and 2 state variables to keep track on what is being tapped on.
The example broken code and fix can be found at Paul Hudson's site.
https://www.hackingwithswift.com/forums/swiftui/unable-to-present-please-file-a-bug/7901/8237
Below is the complete working version
import SwiftUI
struct NavigationViewOptions {
enum OptionType { case main, optional }
typealias Option = (id: UUID, value: String, type: Self.OptionType)
static var options: [Option] = [
(UUID(), "Option 1", .main),
(UUID(), "Option 2", .optional),
(UUID(), "Option 3", .main),
(UUID(), "Option 4", .main),
(UUID(), "Option 5", .optional),
]
static func buildView(for option: Option) -> some View {
switch option.type {
case .main:
return Text("Main Option selected\n\(option.value)").font(.title).fontWeight(.bold)
case .optional:
return Text("Optional Option selected\n\(option.value)").font(.title3).italic().fontWeight(.medium)
}
}
}
struct NavigationViewWorking: View {
// State variables to leep track of what option has been tapped on and when to navigate to new view
#State private var selectedOption: NavigationViewOptions.Option = (id:UUID(),"",.main)
#State private var showDetail: Bool = false
var body: some View {
NavigationView {
ScrollView{
VStack (alignment:.leading) {
Text("NAVIGATION FIX FOR:\nUnable to present. Please file a bug.")
.padding(.bottom, 40)
ForEach(NavigationViewOptions.options, id: \.id) { option in
Text(option.value)
.font(.title)
.padding(.vertical, 10)
.foregroundColor(.accentColor) // same color as navigationLink
// handle tap on option
.onTapGesture {
selectedOption = option
showDetail = true
}
}
Spacer()
NavigationLink("", destination: NavigationViewOptions.buildView(for: selectedOption), isActive: $showDetail)
.opacity(0)
}
.navigationTitle("Options")
}
// INITIAL DETAIL VIEW
Text("Select option from the left")
}
}
}
For me the correct answer didn't work.
It showed Unable to present-message and then required view was pushed and poped out back quickly.
While playing around I found a working solution. I keep NotificationLink's without label set as a plain List items.
NavigationView {
ZStack {
List {
NavigationLink(isActive: $isFirstViewPresented,
destination: firstView,
label: EmptyView.init)
NavigationLink(isActive: $isSecondViewPresented,
destination: secondView,
label: EmptyView.init)
}
.listStyle(.plain)
//...
Button("Show first view") { isFirstViewPresented.toggle() }
Button("Show second view") { isSecondViewPresented.toggle() }
}
}
Don't forget to wrap active-properties with #State.
It also has some benefits as for me (all the navigation links are placed at the top of the view-getter and I don't need to look for it through all the code.
I could never find a reliable solution to this horrible bug. So I decided to create a custom NavigationLink, https://gist.github.com/Arutyun2312/a0dab7eecaa84bde99c435fecae76274. This works way better than expected, because all swiftui related functions continue working as usual. Seems like the bug is specifically with NavigationLink.
struct NavigationLink: View {
fileprivate init<T: View>(body: T) {
self.body = .init(body)
}
let body: AnyView
}
private struct NavigationLinkImpl<Destination: View, Label: View>: View {
let destination: () -> Destination?
#State var isActive = false
#ViewBuilder let label: () -> Label
var body: some View {
NavigationLinkImpl1(destination: destination, isActive: $isActive, label: label)
}
}
private struct NavigationLinkImpl1<Destination: View, Label: View>: View {
let destination: () -> Destination
#Binding var isActive: Bool
#ViewBuilder let label: () -> Label
#State var model = Model()
var body: some View {
Button(action: action, label: label)
.introspectNavigationController(customize: handle)
.id(isActive)
}
func handle(nav: UINavigationController) {
if isActive {
if model.destination == nil {
let dest = UIHostingController<Destination>(rootView: destination())
nav.pushViewController(dest, animated: true)
model.destination = dest
}
} else {
if let dest = model.destination {
if let i = nav.viewControllers.lastIndex(of: dest) {
nav.setViewControllers(.init(nav.viewControllers.prefix(i + 1)), animated: true)
}
model.destination = nil
}
}
if isActive != model.contains(nav: nav) { // detect pop
isActive = model.contains(nav: nav)
}
}
final class Model {
var destination: UIHostingController<Destination>?
func contains(nav: UINavigationController) -> Bool { destination.map { nav.viewControllers.contains($0) } ?? false }
}
func action() { isActive = true }
}
extension NavigationLink {
init<Destination: View, Label: View>(destination: #autoclosure #escaping () -> Destination, #ViewBuilder label: #escaping () -> Label) {
self.init(body: NavigationLinkImpl(destination: destination, label: label))
}
init<Destination: View, Label: View>(destination: #autoclosure #escaping () -> Destination, isActive: Binding<Bool>, #ViewBuilder label: #escaping () -> Label) {
self.init(body: NavigationLinkImpl1(destination: destination, isActive: isActive, label: label))
}
init<Destination: View>(_ text: String, destination: #autoclosure #escaping () -> Destination, isActive: Binding<Bool>) {
self.init(destination: destination(), isActive: isActive) { Text(text) }
}
init<Destination: View>(_ text: String, destination: #autoclosure #escaping () -> Destination) {
self.init(destination: destination()) { Text(text) }
}
}
Put this in a file, and your existing NavigationLinks will work just fine. Tested in ios 14 and 15
Like anybody else on iOS 14.5.1 my application is hit by this awful bug. I have more than 3 NavigationLinks in the page, and I was not lucky to modify the numbers of the NavigationLinks (by adding a dummy NavigationLink) to get the correct behaviour.
A workaround that is Okay for me is to add a NavigationLink conditionally into the view.
Instead of:
var body: some View {
NavigationLink(destination: AnotherView(), isActive: $someCondition) { EmptyView() }
}
I have this:
var body: some View {
if someCondition {
NavigationLink(destination: AnotherView(), isActive: $someCondition) { EmptyView() }
}
}
The behaviour is not exactly the same, as you lose some navigation animation candy, but at least you have a working application again with relatively easy to understand fix.
You can also short-circuit it to 14.5 only, and normal behaviour elsewhere:
/// Assumes this gets fixed by Apple until 14.6 is out
var onIOS14_5: Bool {
let systemVersion = UIDevice.current.systemVersion
return systemVersion.starts(with: "14.5")
}
var body: some View {
if !onIOS14_5 || someCondition {
NavigationLink(destination: AnotherView(), isActive: $someCondition) { EmptyView() }
}
}
Perhaps this helps someone and lets all hope Apple will fix this embarrasing bug. Now I want my half day back.
In my case, the NavigationLink didn't work because of an .onTapGesture I added to dismiss the keyboard.
I got exactly the same problem.
my code:
class NavigationManager: ObservableObject {
static let shared: NavigationManager = {
return NavigationManager()
}()
#Published var showingMain: Bool
#Published var showingSub: Bool
#Published var content: AnyView
init() {
showingMain = false
showingSub = false
content = AnyView(EmptyView())
}
func forward<T:View>(content: #escaping () -> T ) {
showView()
self.content = AnyView(content())
}
private func showView() {
if !showingMain,!showingSub {
showingMain = true
} else if showingMain,!showingSub {
showingSub = true
} else if !showingMain,showingSub {
showingMain = true
}
}
}
struct NavigationLinkGroup: View {
#EnvironmentObject var navigationManager: NavigationManager
var body: some View {
Group {
NavigationLink(destination: navigationManager.content, isActive: $navigationManager.showingMain) {EmptyView()}
NavigationLink(destination: navigationManager.content, isActive: $navigationManager.showingSub) {EmptyView()}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLinkGroup()
}
}
}
https://github.com/Ftrybe/CustomBackButtonOfSwiftUIApp/tree/master/CustomBackButtonOfSwiftUI
It seems that if there's more than one NavigationLink in NavigationView, this bug will be filed.
Here's my solution.
import SwiftUI
enum MyLink {
case myView1
case myView2
}
struct MyView1: View {
var body: some View {
Text("MyView1")
}
}
struct MyView2: View {
var body: some View {
Text("MyView2")
}
}
struct ExampleView: View {
#State var currentLink: MyLink = .myView1
#State var isLinkViewShow: Bool = false
func getLinkView(_ myLink: MyLink) -> some View {
if myLink == .myView1 {
return AnyView(MyView1())
} else {
return AnyView(MyView2())
}
}
var body: some View {
NavigationView {
VStack {
NavigationLink("",
destination: getLinkView(currentLink),
isActive: $isLinkViewShow)
// Press to navigate to MyView1
Button(action: {
currentLink = .myView1
isLinkViewShow = true
}) {
Text("To MyView1")
}
// Press to navigate to MyView2
Button(action: {
currentLink = .myView2
isLinkViewShow = true
}) {
Text("To MyView2")
}
}
}
}
}
Adding a delay gets auto-navigation working again.
NavigationLink(destination: PopupView(),isActive: $showView){}
&
.onAppear {
if (test()){
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {showView = true}
}
}

Can I use a button inside a SwiftUI sheet view that appears on top of my main SwiftUI view to change a subview inside the main view?

I'm trying to use a pop up menu (that appears when the user triggers it) to make the users of my app able to change the subview that is shown inside my main view between a Subview1 and a Subview2.
I'm trying to do that using global Bool variables that are changed when a button in the view inside the sheet is pressed. Based on those values, the main view should return a different subview
The problem is that when I try to select one option from the view that appears inside the sheet, the action of the Button is performed and the sheet is dismissed but the subview displayed by the main view remains unchanged
Is there a way to change the subview or reload the main view?
The code I'm using for the main view is:
struct ContentView: View {
#State var showMenu = false
var body: some View {
if(subview1Selected){
return AnyView(SubView1())
} else if (subview2Selected){
return AnyView(SubView2())
}
else {
return AnyView(
Button(action: {
showMenu = true
})
{
Text("Button")
}
.sheet(isPresented: $showMenu, content: {
MenuView()
})
)
}
}
the code I'm using for the pop-up sheet that is used like a menu is:
struct MenuView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
List{
Button(action: {
subview1Selected = true
subview2Selected = false
self.presentationMode.wrappedValue.dismiss()
})
{
Text("Subview1")
}
Button(action: {
subview2Selected = true
subview1Selected = false
self.presentationMode.wrappedValue.dismiss()
})
{
Text("Subview2")
}
}
}
}
The subviews are:
struct SubView1: View {
#State var showMenu = false
var body: some View {
Button(action: {
showMenu = true
})
{
Text("SubView1")
}
.sheet(isPresented: $showMenu, content: {
MenuView()
})
}
}
and:
struct SubView2: View {
#State var showMenu = false
var body: some View {
Button(action: {
showMenu = true
})
{
Text("SubView2")
}
.sheet(isPresented: $showMenu, content: {
MenuView()
})
}
}
I think this is what you are trying to do. You can pass the #State showMenu variable as a #Binding variable into the menu. You can use a Bool if you only have 2 views, but it's more practical to use a custom enum, which I added for you. Also, the menu button should probably be separate from the subviews.
struct ContentView: View {
enum SubViewOption {
case subview1
case subview2
}
#State var showMenu = false
#State var subviewSelected: SubViewOption?
var body: some View {
ZStack() {
switch subviewSelected {
case .subview1:
SubView1()
case .subview2:
SubView2()
default:
Text("Select a view to begin.")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay(
Button(action: {
showMenu.toggle()
}, label: {
Text("Menu")
.padding()
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.background(Color.gray.cornerRadius(30))
.padding()
})
, alignment: .bottom
)
.sheet(isPresented: $showMenu, content: {
MenuView(subviewSelected: $subviewSelected)
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MenuView: View {
#Environment(\.presentationMode) var presentationMode
#Binding var subviewSelected: ContentView.SubViewOption?
var body: some View {
List {
Button(action: {
subviewSelected = .subview1
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Subview1")
})
Button(action: {
subviewSelected = .subview2
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Subview2")
})
}
}
}
struct SubView1: View {
var body: some View {
ZStack {
Color.red
.edgesIgnoringSafeArea(.all)
Text("THIS IS SUBVIEW 1")
.foregroundColor(.white)
}
}
}
struct SubView2: View {
var body: some View {
ZStack {
Color.blue
.edgesIgnoringSafeArea(.all)
Text("THIS IS SUBVIEW 2")
.foregroundColor(.white)
}
}
}

Show different views from NavigationBarItem menu in SwiftUI

I am trying to show a different view based on the option chosen in the NavigationBar menu. I am getting stuck on the best way to do this.
First, based on my current approach (I think it is not right!), I get a message in Xcode debugger when I press the menu item:
SideMenu[16587:1131441] [UILog] Called -[UIContextMenuInteraction
updateVisibleMenuWithBlock:] while no context menu is visible. This
won't do anything.
How do I fix this?
Second, When I select an option from the menu, how do I reset the bool so that it does not get executed unless it is chosen from the menu again. Trying to reset as self.showNewView = false within the if condition gives a compiler error
Here is a full executable sample code I am trying to work with. Appreciate any help in resolving this. Thank you!
struct ContentView: View {
#State var showNewView = false
#State var showAddView = false
#State var showEditView = false
#State var showDeleteView = false
var body: some View {
NavigationView {
GeometryReader { g in
VStack {
if self.showAddView {
AddView()
}
if self.showNewView {
NewView()
}
if self.showEditView {
EditView()
}
if self.showDeleteView {
DeleteView()
}
}.frame(width: g.size.width, height: g.size.height)
}
.navigationTitle("Title")
.navigationBarItems(leading: {
Menu {
Button(action: {showNewView.toggle()}) {
Label("New", systemImage: "pencil")
}
Button(action: {showEditView.toggle()}) {
Label("Edit", systemImage: "square.and.pencil")
}
} label: {
Image(systemName: "ellipsis.circle")
}
}(), trailing: {
Menu {
Button(action: {showAddView.toggle()}) {
Label("Add", systemImage: "plus")
}
Button(action: {showDeleteView.toggle()}) {
Label("Delete", systemImage: "trash")
}
} label: {
Image(systemName: "plus")
}
}())
}
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct NewView: View {
var body: some View {
GeometryReader { g in
Text("This is New View")
}
.background(Color.red)
}
}
struct EditView: View {
var body: some View {
GeometryReader { g in
Text("This is Edit View")
}
.background(Color.green)
}
}
struct AddView: View {
var body: some View {
GeometryReader { g in
Text("This is Add View")
}
.background(Color.orange)
}
}
struct DeleteView: View {
var body: some View {
GeometryReader { g in
Text("This is Delete View")
}
.background(Color.purple)
}
}
Here is what I get when I select each of the menu items. I would like to be able to show only one menu item. Essentially dismiss the other one when a new menu item is selected
A possible solution is to use a dedicated enum for your current view (instead of four #State properties):
enum CurrentView {
case new, add, edit, delete
}
#State var currentView: CurrentView?
Note that you can also extract parts of code to computed properties.
Here is a full code:
enum CurrentView {
case new, add, edit, delete
}
struct ContentView: View {
#State var currentView: CurrentView?
var body: some View {
NavigationView {
GeometryReader { g in
content
.frame(width: g.size.width, height: g.size.height)
}
.navigationTitle("Title")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(leading: leadingBarItems, trailing: trailingBarItems)
}
.navigationViewStyle(StackNavigationViewStyle())
}
#ViewBuilder
var content: some View {
if let currentView = currentView {
switch currentView {
case .add:
AddView()
case .new:
NewView()
case .edit:
EditView()
case .delete:
DeleteView()
}
}
}
var leadingBarItems: some View {
Menu {
Button(action: { currentView = .new }) {
Label("New", systemImage: "pencil")
}
Button(action: { currentView = .edit }) {
Label("Edit", systemImage: "square.and.pencil")
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
var trailingBarItems: some View {
Menu {
Button(action: { currentView = .add }) {
Label("Add", systemImage: "plus")
}
Button(action: { currentView = .delete }) {
Label("Delete", systemImage: "trash")
}
} label: {
Image(systemName: "plus")
}
}
}

Resources