I have a question about "Back" of the screen transition of SwiftUI - swiftui

"A> B> A> B> A ..."
When repeating the operation of switching from screen A to screen B and returning to screen A again, we believe that if the following code is used, the load will continue to accumulate because the old screen information is retained.
However, the return code included in "NavigationLink" does not reload the previous screen, so the following code is currently used:
Question:
What if I want to always reload the screen when recalling the old screen?
struct ContentView: View {
#State private var Flag : Bool = false
var body: some View {
NavigationView {
VStack{
Spacer()
Button(action: {self.Flag.toggle()}){
Text("OpenA")
NavigationLink(destination: A(),isActive: $Flag) {
EmptyView()
}
}
Spacer()
}.navigationBarTitle("").navigationBarHidden(true)
}.navigationBarBackButtonHidden(true).navigationViewStyle(StackNavigationViewStyle())
}
}
struct A: View {
#State private var Flag : Bool = false
var body: some View {
VStack{
Group{
Spacer()
Button(action: {self.Flag.toggle()}){
Text("A > B")
}
NavigationLink(destination: B(),isActive: $Flag) {
EmptyView()
}
Spacer()
}.navigationBarTitle("").navigationBarHidden(true).navigationBarBackButtonHidden(true)
}.navigationBarBackButtonHidden(true).navigationViewStyle(StackNavigationViewStyle())
}
}
struct B: View {
#State private var Flag : Bool = false
var body: some View {
VStack{
Group{
Spacer()
Button(action: {self.Flag.toggle()}){
Text("B > A")
}
NavigationLink(destination: A(),isActive: $Flag) {
EmptyView()
}
Spacer()
}.navigationBarTitle("").navigationBarHidden(true).navigationBarBackButtonHidden(true)
}.navigationBarBackButtonHidden(true).navigationViewStyle(StackNavigationViewStyle())
}
}

the return code included in "NavigationLink"
NavigationLink is struct, so it nothing like returning value exists, except the NavigationLink itself
On the other site
NavigationLink(destination: A(),isActive: $Flag) {
EmptyView()
}
one of its constructor's parameter is binding to Bool value
#State private var Flag : Bool = false
If user click on navigation link, this value change to true, once the linked View is dismissed by user, it changed to false again. That means, ContentView is informed about and the ContentView has to recalculate its body property (in other words, if there is something different in its body, the value will reflect that.
I don't see nothing is changed there! What do you expect?

Related

How hide navigation bar always back from any view directly using NavigationView?

I am using xcode-14.2 & minimum target version 14. I have three views ContentView, Welcome & `FundTransfer. Here is my case.
ContentView - Load first view & navigationBarHidden is working. When Welcome page button click it goes to Welcome page
Welcome view - When Fund Transfer button is clicked, it goes to FundTransfer view
FundTransfer - when Log out button is clicked, it goes to ContentView
It goeslike: ContentView-> FundTransfer-> ContentView
Problem: When it goes from FundTransfer view to ContentView it shows navigationBar. That means when back from FundTransfer view to ContentView shows navigationBar which was hidden at the first.
How do I hide navigation bar always back from any view directly to ContentView?
Here is my code:
ContentView:
struct ContentView: View {
#State private var showWelcome = false
#State var isNavigationBarHidden: Bool = true
var body: some View {
NavigationView {
VStack {
ScrollView {
VStack(alignment: .customCenter,spacing: 0){
VStack {
SubmitButton(action: {
self.showWelcome = true
}) {
Text("Welcome page")
}
}
NavigationLink(destination: Welcome(), isActive: $showWelcome) { EmptyView() }
}
}
}
.navigationBarTitle("") //this must be empty
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}
Welcome View:
struct Welcome: View {
#State private var showFundTransfer = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
ScrollView {
VStack(alignment: .customCenter,spacing: 0){
VStack {
SubmitButton(action: {
showFundTransfer = true
}) {
Text("Fund Transfer")
}
}
NavigationLink(destination: FundTransfer(), isActive: $showFundTransfer) { EmptyView() }
}
}
.navigationBarHidden(true)
}
}
}
FundTransfer View:
struct FundTransfer: View {
#State var isNavigationBarHidden: Bool = true
#State private var logon = false
var body: some View {
VStack {
ScrollView {
VStack(alignment: .customCenter,spacing: 0){
SubmitButton(action: {
self.logon = true
}) {
Text("Log out")
}
}
}
NavigationLink(destination: ApplicationSwitcher(), isActive: $logon) { EmptyView() }.opacity(0)
}
.navigationBarHidden(true)
}
}
Please help me..
Add .navigationBarHidden(true) in NavigationLink also for eg:
NavigationLink(destination: ApplicationSwitcher()
.navigationBarHidden(true), isActive: $logon) { EmptyView() }.opacity(0)
In ContentView add "navigationBarHidden(true)" after the closure of NavigationView instead of VStack as mentioned below:
NavigationView {
...
}.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)

NavigationLink in a Section doesn't behave like in a normalView with a simultaneous action

I have created a simple View with a NavigationLink in a Section an when the user presses on it, the value of the variable should change and should navigate the next View simultaneously. But it doesn't work like it should. If I press the "Text", the Value changes, but no navigation. If I press the "empty Space" it navigates to the next View, but the value doesn't change.
If I out the NavigationLink in a "normal" View, it does work like it should.
Is there a way to get this working without SubViews?
#State private var newValue = -1
var body: some View {
NavigationView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("\(newValue)")
List {
Section ("Navigationlink") {
NavigationLink(destination: EmptyView()) {
Text("to Emptyview")
}.simultaneousGesture(TapGesture().onEnded{
newValue = 100
})
}
}
}
}
}
}
struct EmptyView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
}
}
You need a #State for Navigation to take place, this is needed as a source of truth needs to change(including navigation) in SwiftUI for any View change to happen , you change the #State for newValue so it changes, but you need to do same for NavigationView, also try NavigationStack in place of NavigationView in future , try below code , good luck
struct ContentView: View {
#State private var newValue = -1
#State private var changeView = false
var body: some View {
NavigationView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("\(newValue)")
List {
Section ("Navigationlink") {
NavigationLink(destination: EmptyView(), isActive: $changeView) {
Text("to Emptyview")
}.simultaneousGesture(TapGesture().onEnded{
newValue = 100
changeView = true
})
}
}
}
}
}
}

SwiftUI NavigationLink with constant binding for isActive

I don't understand why SwiftUI NavigationLink's isActive behaves as if it has it's own state. Even though I pass a constant to it, the back button overrides the value of the binding once pressed.
Code:
import Foundation
import SwiftUI
struct NavigationLinkPlayground: View {
#State
var active = true
var body: some View {
NavigationView {
VStack {
Text("Navigation Link playground")
Button(action: { active.toggle() }) {
Text("Toggle")
}
Spacer()
.frame(height: 40)
FixedNavigator(active: active)
}
}
}
}
fileprivate struct FixedNavigator: View {
var active: Bool = true
var body: some View {
return VStack {
Text("Fixed navigator is active: \(active)" as String)
NavigationLink(
destination: SecondScreen(),
// this is technically a constant!
isActive: Binding(
get: { active },
set: { newActive in print("User is setting to \(newActive), but we don't let them!") }
),
label: { Text("Go to second screen") }
)
}
}
}
fileprivate struct SecondScreen: View {
var body: some View {
Text("Nothing to see here")
}
}
This is a minimum reproducible example, my actual intention is to handle the back button press manually. So when the set inside the Binding is called, I want to be able to decide when to actually proceed. (So like based on some validation or something.)
And I don't understand what is going in and why the back button is able to override a constant binding.
Your use of isActive is wrong. isActive takes a binding boolean and whenever you set that binding boolean to true, the navigation link gets activated and you are navigated to the destination.
isActive does not control whether the navigation link is clickable/disbaled or not.
Here's an example of correct use of isActive. You can manually trigger the navigation to your second view by setting activateNavigationLink to true.
EDIT 1:
In this new sample code, you can disable and enable the back button at will as well:
struct ContentView: View {
#State var activateNavigationLink = false
var body: some View {
NavigationView {
VStack {
// This isn't visible and should take 0 space from the screen!
// Because its `label` is an `EmptyView`
// It'll get programmatically triggered when you set `activateNavigationLink` to `true`.
NavigationLink(
destination: SecondScreen(),
isActive: $activateNavigationLink,
label: EmptyView.init
)
Text("Fixed navigator is active: \(activateNavigationLink)" as String)
Button("Go to second screen") {
activateNavigationLink = true
}
}
}
}
}
fileprivate struct SecondScreen: View {
#State var backButtonActivated = false
var body: some View {
VStack {
Text("Nothing to see here")
Button("Back button is visible: \(backButtonActivated)" as String) {
backButtonActivated.toggle()
}
}
.navigationBarBackButtonHidden(!backButtonActivated)
}
}

How can I use multiple fullScreenCover in IOS14

I want to present the two destinations view in full screen mode from a single view.
Below is a sample of my code. Seem that the function only works for single presentation, if I have a second fullScreenCover defined, the first fullScreenCover didn't work properly.Is that any workaround at this moment?
import SwiftUI
struct TesFullScreen: View {
init(game : Int){
print(game)
}
var body: some View {
Text("Full Screen")
}
}
ContentView
import SwiftUI
struct ContentView: View {
#State var showFullScreen1 : Bool = false
#State var showFullScreen2 : Bool = false
var body: some View {
NavigationView {
VStack {
Spacer()
Button(action: { self.showFullScreen1 = true }) {
Text("Show Full Screen 1")
}
Button(action: { self.showFullScreen2 = true }) {
Text("Show Full Screen 2")
}
Spacer()
}
.navigationBarTitle("TextBugs", displayMode: .inline)
}
.fullScreenCover(isPresented: self.$showFullScreen1){
TesFullScreen(game: 1)
}
.fullScreenCover(isPresented: self.$showFullScreen2){
TesFullScreen(game: 2)
}
}
}
Not always the accepted answer works (for example if you have a ScrollView with subviews (cells in former days) which holds the buttons, that set the navigational flags).
But I found out, that you also can add the fullScreen-modifier onto an EmptyView. This code worked for me:
// IMPORTANT: Has to be within a container (e.g. VStack, HStack, ZStack, ...)
if myNavigation.flag1 || myNavigation.flag2 {
EmptyView().fullScreenCover(isPresented: $myNavigation.flag1)
{ MailComposer() }
EmptyView().fullScreenCover(isPresented: $myNavigation.flag2)
{ RatingStore() }
}
Usually some same modifier added one after another is ignored. So the simplest fix is to attach them to different views, like
struct FullSContentView: View {
#State var showFullScreen1 : Bool = false
#State var showFullScreen2 : Bool = false
var body: some View {
NavigationView {
VStack {
Spacer()
Button(action: { self.showFullScreen1 = true }) {
Text("Show Full Screen 1")
}
.fullScreenCover(isPresented: self.$showFullScreen1){
Text("TesFullScreen(game: 1)")
}
Button(action: { self.showFullScreen2 = true }) {
Text("Show Full Screen 2")
}
.fullScreenCover(isPresented: self.$showFullScreen2){
Text("TesFullScreen(game: 2)")
}
Spacer()
}
.navigationBarTitle("TextBugs", displayMode: .inline)
}
}
}
Alternate is to have one .fullScreenCover(item:... modifier and show inside different views depending on input item.
The only thing that worked for me was the answer in this link:
https://forums.swift.org/t/multiple-sheet-view-modifiers-on-the-same-view/35267
Using the EmptyView method or other solutions always broke a transition animation on one of the two presentations. Either transitioning to or from that view and depending on what order I chose them.
Using the approach by Lantua in the link which is using the item argument instead of isPresented worked in all cases:
enum SheetChoice: Hashable, Identifiable {
case a, b
var id: SheetChoice { self }
}
struct ContentView: View {
#State var sheetState: SheetChoice?
var body: some View {
VStack {
...
}
.sheet(item: $sheetState) { item in
if item == .a {
Text("A")
} else {
Text("B")
}
}
}
}
The sheetState needs to be optional for it to work.

SwiftUI Navigation Controller stuttering with two Navigationlinks per List row

I am trying to create two NavigationLinks in a repeating List. Each has a separate destination. The code all works fine until I imbed the call to the root view in a List/ForEach loop. At which point the navigation becomes very strange.
Try to click on either link and then click the back indicator at the top. It will go to one NavigationLink, and then the other. Sometimes in a different order, and sometimes it will auto-return from one of the links, and othertimes it won't open the second detail view until you return from the first detail view. It does this both in Preview, as well as if you build and run the application.
I have distilled down the code to the most basic below. If you comment the 2 lines as indicated in ContentView, you will then see correct behavior.
I am running Catalina 10.15.5, xCode 11.6, with the application target of IOS 13.6.
How can I modify the code, so that it will work with the List/ForEach loop?
import SwiftUI
struct DetailView1: View {
var body: some View {
HStack {
Text("Here is Detail View 1." )}
.foregroundColor(.green)
}
}
struct DetailView2: View {
var body: some View {
HStack {
Text( "Here is Detail View 2.") }
.foregroundColor(.red)
}
}
struct RootView: View {
var body: some View {
HStack {
VStack {
NavigationLink(destination: DetailView1())
{ VStack { Image(systemName: "ant.circle").resizable()
.frame(width:75, height:75)
.scaledToFit()
}.buttonStyle(PlainButtonStyle())
Text("Tap for Detail 1.")
.foregroundColor(.green)
}
}
NavigationLink(destination: DetailView2())
{ Text("Tap for Detail 2.")
.foregroundColor(.red)
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
// Comment the following line for correct behavior
List { ForEach(0..<3) {_ in
RootView()
// Comment the following line for correct behavior
} }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
.navigationBarTitle("Strange Behavior")
}
}
}
In your case both navigation links are activated at once user tap a row, to avoid this below is possible approach
Tested with Xcode 12 / iOS 14
The idea is to have one link which is activated programmatically and destination is selected dynamically depending on which button is clicked
struct RootView: View {
#State private var isFirst = false
#State private var isActive = false
var body: some View {
HStack {
VStack {
Button(action: {
self.isFirst = true
self.isActive = true
})
{ VStack { Image(systemName: "ant.circle").resizable()
.frame(width:75, height:75)
.scaledToFit()
}
Text("Tap for Detail 1.")
.foregroundColor(.green)
}
}
Button(action: {
self.isFirst = false
self.isActive = true
})
{ Text("Tap for Detail 2.")
.foregroundColor(.red)
}
.background(
NavigationLink(destination: self.destination(), isActive: $isActive) { EmptyView() }
)
}
.buttonStyle(PlainButtonStyle())
}
#ViewBuilder
private func destination() -> some View {
if isFirst {
DetailView1()
} else {
DetailView2()
}
}
}