How to reset the toolbar back to its original position - swiftui

I'm trying to setup a toolbar in watchos which displays a button when I scroll the view down. Everything works, the button scrolls down and I can navigate to another page. When returning however, I would like the scrollview to be in the same position as when the app loads, so without the button being visible.
The code I have now is:
struct ContentView: View {
#State private var selectedPage: String? = nil
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(0..<100) {
Text("Row \($0)")
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Settings") {
selectedPage = "Settings"
}
}
}
.background(
NavigationLink(destination: SettingsView(), tag: "Settings",selection: $selectedPage) {}
.hidden()
)
.navigationTitle {
Text("Navigation")
}
}
}
}
I Have tried using a scrollViewReader, but think I'm looking into the wrong directing as it allows the scrollView to go to a certain position, but the toolbar seems to be no part of it and stays in view. When reading the scrollviews offset position (not in code but check gif), the offset is 0 when the button is not visible (initial state). When scrolling down the offset goes up, but when scrolling up until the button becomes visible the offset of the scrollview is 0 as well.
struct ContentView: View {
#State private var selectedPage: String? = nil
var body: some View {
ScrollView {
ScrollViewReader { reader in
VStack(alignment: .leading) {
ForEach(0..<100) { i in
Text("Row \(i)")
.id(i)
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Settings") {
selectedPage = "Settings"
}
}
}
.background(
NavigationLink(destination: SettingsView(), tag: "Settings",selection: $selectedPage) {}
.hidden()
)
.onAppear {
withAnimation {
reader.scrollTo(0, anchor: .top)
}
}
.navigationTitle {
Text("Navigation")
}
}
}
}
}
}
When choosing 1 as value to scroll to the button is pushed back to the top and in the list id 1 is selected.
.onAppear {
withAnimation {
reader.scrollTo(1, anchor: .top)
}
}
So it's working with id 1 and higher, but when using id 0 the view is not reset.
So how to reset the view to the initial state with the button being hidden on top ?

Solved it simply by adding a #State property which is true when loading the view and which changes to false if navigated away from the view and then conditionally show the item within the .toolbar view modifier.
#State var loadedMainView = true
var body: some view {
ScrollView {
// .... code
}
.toolbar {
ToolbarItem {
if loadedMainView {
Button("My Button") {
}
}
}
.onAppear {
loadedMainView = true
}
.onDissappear {
loadedMainView.toggle()
}

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)

SwiftUI toolbar .bottomBar button doesn't trigger when user configures accessibility1 size or larger

How can I accommodate user accessibility sizes with a bottomBar button?
With accessibility1 or larger user-configured in iOS, a bottomBar button fails to trigger. Smaller font sizes work. Configuring the toolbar placement to .navigation works.
Details: Navigate from ContentView > FirstView > SecondView, then back to the FirstView via the "Goodbye" bottomBar button displays the FirstView. Then FirstView's "Goodbye" button does not trigger. There is a user-workaround: in this situation, scroll the FirstView down to hide the Goodbye button, release, it returns, then press and it works. Code is below.
struct ContentView: View {
#State var showFirstView: Bool = false
var body: some View {
NavigationStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Button("First View") { showFirstView = true }
}
.sheet(isPresented: $showFirstView) {
FirstView()
}
}
struct FirstView: View {
#Environment(\.dismiss) var dismiss
#State var showSecondView: Bool = false
var body: some View {
NavigationStack {
VStack {
Text("First View")
Button("Second View") { showSecondView = true }
}
.toolbar {
// On return from SecondView with accessibility1
// or large text size configured, the button below
// does not trigger (even with a programmatically limited
// font size via .dynamicTypeSize).
// However, scroll the view down slightly to hide the button,
// let it return on screen, then the Goodbye button works.
ToolbarItem(placement: .bottomBar) {
Button("Goodbye") { dismiss() }
}
}
}
.sheet(isPresented: $showSecondView) {
SecondView()
}
}
struct SecondView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationStack {
VStack { Text("Second View") }
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button("Goodbye") { dismiss() }
}
}
}
}
The technique #Asperi shared in the post, StackOverflow Toolbar disappears when going back, resolves my problem. See Update (1), Update (2), and Update (3) below.
struct FirstView: View {
#Environment(\.dismiss) var dismiss
#State var showSecondView: Bool = false
#State private var refresh = UUID() // <- Update (1)
var body: some View {
NavigationStack {
VStack {
Text("First View")
Button("Second View") { showSecondView = true }
}
.toolbar {
// On return from SecondView with accessibility1
// or larger text size configured, the button below
// does not trigger (even with a programmatically limited
// font size via .dynamicTypeSize).
// However, scroll the view down slightly to hide the button,
// let it return on screen, then the Goodbye button works.
// Or, change placement to .navigation and it works as expected.
ToolbarItem(placement: .bottomBar) {
Button("Goodbye") { dismiss() }
}
}.id(refresh) // <- Update (2)
}
.sheet(isPresented: $showSecondView) {
SecondView()
.onDisappear { refresh = UUID() } // <- Update (3)
}
}
}

SwiftUI - How To Set Child Screen Title

I have a SwiftUI Home screen:
import SwiftUI
struct HomeView: View {
#State private var navigateToSettingsView : Bool = false
var body: some View {
NavigationView {
if navigateToSettingsView {
NavigationLink(destination: UserSettingsView(), isActive: $navigateToSettingsView) {
EmptyView()
}
.navigationTitle("Home") // This overrides the word Back with Home on the child back button
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Text("User Settings").font(.headline)
}
}
}
}
else {
NavigationLink(destination: Text("Hello, I am HomeView child screen")) {
homeScreen
}
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: {
navigateToSettingsView = true
}) {
Image(systemName: "gearshape")
}
}
ToolbarItemGroup(placement: .navigationBarLeading) {
Text("App Name")
}
}
}
}
}
}
extension HomeView {
private var homeScreen: some View {
VStack {
Text("Hello, I am HomeView")
}
.frame(maxWidth: .infinity)
}
}
The UserSettingsView is just basic right now:
import SwiftUI
struct UserSettingsView: View {
var body: some View {
VStack {
Text("I am a UserSettingsView")
}
}
}
What I am struggling with is setting the title of the child screen when the user clicks on the Gear icon. The ToolbarItem seems to be ignored. How do you set the title of the child screen so that it has a title of User Settings?
I believe you can achieve what you want using a cleaner approach.
The example below replaces the Button with another NavigationLink. It maintains the Home text instead of Back, but it also shows a title in the user settings screen. The only catch is that the title is in the center of the top area, not in the leading position.
Note that the #State variable does not exist anymore.
struct Example: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Hello, I am HomeView child screen")) {
Text("I'm a home screen")
}
.navigationTitle("Home")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
NavigationLink(destination: UserSettingsView().navigationTitle("User settings")) {
Image(systemName: "gearshape")
}
}
ToolbarItemGroup(placement: .navigationBarLeading) {
Text("App Name")
}
}
}
}
}

SWIFTUI Button or NavigationLink?

I have a button called "save" that saves the user inputs.
But, I want to make it like, if the user tap on Button "Save", then the screen automatically goes back to the previous view. Can I do that by just adding a code to an action in Button? or do I have to use NavigationLink instead of Button?
Button(action: {
let title = shortcutTitle
currentShortcutTitle = title
UserDefaults.standard.set(title, forKey: "title")
}, label: {
Text("Save")
.padding()
.frame(width: 120, height: 80)
.border(Color.black)
}) //: Button - save
If you're just trying to go back to the previous view and already inside a NavigationView stack, you can use #Environment(\.presentationMode):
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Screen2()) {
Text("Go to screen 2")
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct Screen2 : View {
#Environment(\.presentationMode) var presentationMode //<-- Here
var body: some View {
Button("Dismiss") {
presentationMode.wrappedValue.dismiss() //<-- Here
}
}
}

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()
}
}
}