How to deselect a list button within a NavigationLink - ios

I have a list with one of the list items in a NavigationLink because it needs to move to a detail view once tapped. When I come back from that detail view the list button is still in a selected state. Prior to SwiftUI, I'd just tell the .isSelected to equal false but I can't figure out how to do that in SwiftUI?
List {
NavigationLink(destination: SettingsStartdayView()){
HStack {
Text("Start Day Notification")
Spacer()
Text(startDayNotificationSetting)
.font(.subheadline)
.foregroundColor(Color.gray)
.multilineTextAlignment(.trailing)
}
}
}
Starting List View
After Detail View, Back
This View is being loaded into the main app's view through:
NavigationView{
TabView(selection: $isSelectedTab) {
SettingsView()
.tabItem{
}.tag(1)
Here's a public project that has a complete example of what I'm dealing with: https://gitlab.com/jammyman34/test-sounds-project
Go to the Settings tab and then click the top list item to go to the detail page. Notice how the list you clicked stays selected when you go back. It doesn't clear unless you change to the other tab.

The issue here is the Text which is above the List in SettingsView - a bug reported here.
Instead, you can use a native navigation title and attach it to the TabView.
struct SettingsHomeView: View {
#State var startDayNotificationSetting: String = "8:30AM"
#State var appVersion: String = "0.01"
var body: some View {
// no `Text` above `List`
List {
NavigationLink(destination: SettingsStartdayView()){
HStack {
Text("Start Day Notification")
Spacer()
Text(startDayNotificationSetting)
.font(.subheadline)
.foregroundColor(Color.gray)
.multilineTextAlignment(.trailing)
//Image(systemName: "chevron.right")
}
}
}
}
}
struct ContentView: View {
#State private var isSelectedTab = 1 // select the first tab
var body: some View {
NavigationView{
TabView(selection: $isSelectedTab) {
// ...
}
// control displaying the title depending on the `isSelectedTab`
.navigationTitle("Settings")
.navigationBarHidden(isSelectedTab == 1)
}
}
}

Related

SwiftUI: TabView and NavigationView play nicely on iPhone but not on iPad?

My app has a home screen showing a list of options and a few entirely different pages. Each leads to a dispatcher view with a tag indicating which page to show. I also want to be able to swipe between the pages. So I have this code (hugely simplified from the actual app, of course, but behaving in the same way):
struct Tab: Hashable {
let name: String
let id: Int
}
struct ContentView: View {
let tabs: [Tab] = [Tab(name: "first", id: 1), Tab(name: "second", id: 2), Tab(name: "third", id: 3)]
var body: some View {
NavigationView {
Form {
List (tabs, id: \.self) { tab in
NavigationLink(destination: Dispatcher(which: tab.id)) {
VStack (alignment: .leading) {
Text(tab.name).font(.headline).bold()
}
}
}
}.navigationTitle("test")
Text("hello")
}
}
}
struct Dispatcher: View {
var which: Int
#State private var tab = 2
var body: some View {
TabView(selection: $tab) {
Text("first page").tag(1)
Text("second page").tag(2)
Text("third page").tag(3)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.onAppear(perform: { tab = which })
}
}
On an iPhone, it works fine. But on an iPad, in either orientation, tapping any option in the home page takes me to the same sub-page, the one set by the initial value of the #State variable tab. The assignment in .onAppear is being executed, but the new value of tab is being ignored.
Any ideas gratefully received.
Jeremy
why not just do this:
struct Dispatcher: View {
#State var which: Int
var body: some View {
TabView(selection: $which) {
Text("first page").tag(1)
Text("second page").tag(2)
Text("third page").tag(3)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
Now that workingdog has answered my question, can I perhaps expand it?
I implement his (or her, of course) solution on the iPad. I click on a selection in the NavigationView list (say, "second"). The clicked line highlights and the second page appears.
I then swipe to show another page (say "third"). That's fine.
But "second" remains highlighted in the NavigationView list, so I can't click on it to return to the second page.
Can I tell the NavigationView list to highlight the entry corresponding to the actual shown page? Or to highlight nothing?

How to replace the current view in SwiftUI?

I am developing an app with SwiftUI.
I have a NavigationView and I have buttons on the navigation bar. I want to replace the current view (which is a result of a TabView selection) with another one.
Basically, when the user clicks "Edit" button, I want to replace the view with another view to make the edition and when the user is done, the previous view is restored by clicking on a "Done" button.
I could just use a variable to dynamically choose which view is displayed on the current tab view, but I feel like this isn't the "right way to do" in SwiftUI. And this way I could not apply any transition visual effect.
Some code samples to explain what I am looking for.
private extension ContentView {
#ViewBuilder
var navigationBarLeadingItems: some View {
if tabSelection == 3 {
Button(action: {
print("Edit pressed")
// Here I want to replace the tabSelection 3 view by another view temporarly and update the navigation bar items
}) {
Text("Edit")
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
ContactPage()
.tabItem {
Text("1")
}
.tag(1)
Text("Chats")
.tabItem() {
Text("2")
}
.tag(2)
SettingsView()
.tabItem {
Text("3")
}
.tag(3)
}.navigationBarItems(leading: navigationBarLeadingItems)
}
}
}
Thank you
EDIT
I have a working version where I simply update a toggle variable in my button action that makes my view display one or another thing, it is working but I cannot apply any animation effect on it, and it doesn't look "right" in SwiftUI, I guess there is something better that I do not know.
If you just want to add animations you can try:
struct ContentView: View {
...
#State var showEditView = false
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
...
view3
.tabItem {
Text("3")
}
.tag(3)
}
.navigationBarItems(leading: navigationBarLeadingItems)
}
}
}
private extension ContentView {
var view3: some View {
VStack {
if showEditView {
FormView()
.background(Color.red)
.transition(.slide)
} else {
Text("View 3")
.background(Color.blue)
.transition(.slide)
}
}
}
}
struct FormView: View {
var body: some View {
Form {
Text("test")
}
}
}
A possible alternative is to use a ViewRouter: How To Navigate Between Views In SwiftUI By Using An #EnvironmentObject.

NavigationView containing VStack embedded with HStack and List causes error when swiping backwards

I currently have a navigationView embedded with a navigationLink that leads to a VStack that contains an HStack and a List. For some reason, when I drag back to the previous navigation in the stack and leave it halfway, the app glitches and needs to be restarted. This only occurs if I include the navigationbar title.
Here is the code for the view that is embedded in the navigationView. Removing the HStack causes the code bug to stop. The navigation bar items section is not large enough for my desired behavior.
Desired Behavior: Static HStack with a list below it and navigation bar title.
Any help would be appreciated. Thanks.
struct Sell: View {
var body: some View {
NavigationView {
List(self.stores) { store in
NavigationLink(destination: StoreHome2(store: store)) {
VStack(alignment: .center) {
Text(store.storeName)
}
}
}.navigationBarTitle(Text("Sell").foregroundColor(Color.black))
}.onAppear(perform: getStores)
}
}
import SwiftUI
struct StoreHome2: View {
var categoryItem: [String: [Item]]
var store: Store
#State var isPresentingFirst = false
var body: some View {
VStack {
HStack {
Button(action: {
self.isPresentingFirst = true
}) {
Image(systemName: "plus")
}.sheet(isPresented: $isPresentingFirst) {
EmptyView()
}
}.padding()
List {
ForEach(categoryItem.keys.sorted(), id: \.self) { key in
Text("Hello World")
}.listRowInsets(EdgeInsets())
}
}.navigationBarTitle(Text("Hello World"))
}
}

Default text for back button in NavigationView in SwiftUI

I use a NavigationLink to navigate from "View1" to "View2", on the second view, the back button gets the title of the previous view
But, if the title of the previous view is very long, then the back button gets the text "Back"
How could I change that "Back" text?
I wanna make my app available in multiple languages, but it seems that "Back" does not change when phone's language changes
struct ContentView: View {
var body: some View {
return NavigationView {
VStack {
Text("View1")
NavigationLink(destination: Text("View2").navigationBarTitle("Title View2", displayMode: .inline)) {
Text("NavigationLink")
}
}.navigationBarTitle("Title View1")
}
}
}
PS: I'd like to keep this functionality as it it, I just want to change the language used for back button
here is a workaround ....
struct ContentView: View {
#State private var isActive: Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView(), isActive: $isActive) {
Text("Title View2")
}
}.navigationBarTitle(! isActive ? "Title View2" : "Your desired back Title", displayMode: .inline)
}
}
}
struct DetailView: View {
var body: some View {
Text("View2")
}
}
I found a solution which can work also very good.
View1 set a toolbar item with .principal and add your Text or what you want.
for example :
ToolbarItem(placement: .principal) {
HStack{
Text("View1")
}.font(.subheadline)
}
and set also your title in View1:
.navigationTitle("Back")
And do nothing in your view2. it will automatically add your view1 title to your view2 default back button text
I've managed to localize back buttons by providing translations for the Back key in the Localizable.strings file.
I am using SwiftUI though.
You can create a custom back button in your navigation link by hiding native navigationBackButton. In the custom back button, you can add your translated custom back button title.
struct ContentView: View {
var body: some View {
return NavigationView {
VStack {
Text("View1")
NavigationLink("NavigationLink", destination: NextView())
}.navigationBarTitle("Title View1")
}
}
}
struct NextView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var backButton : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("backImage") // BackButton Image
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
Text("Go Back") //translated Back button title
}
}
}
var body: some View {
VStack {
Text("View2")
}
.navigationBarTitle("Title View2",displayMode: .inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)
}
}
Output:-
Create your own button, then assign it using .navigationBarItems(). I found the following format most nearly approximated the default back button.
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var backButton : some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack(spacing: 0) {
Image(systemName: "chevron.left")
.font(.title2)
Text("Cancel")
}
}
}
Make sure you use .navigationBarBackButtonHidden(true) to hide the default button and replace it with your own!
List(series, id:\.self, selection: $selection) { series in
Text(series.SeriesLabel)
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)
Add localization to your project. If language was set with user device settings(or simulator), after you add localization to your project it will work. Project's supported language must match with selected one on device.

Handle Tab Selection SwiftUI

Following tutorials, I have the following code to show a tab view with 3 tab items all with an icon on them, When pressed they navigate between the three different views. This all works fine, however, I want to be able to handle the selection and only show views 2 or 3 if certain criteria are met.
Is there a way to get the selected value and check it then check my own criteria and then show the view is criteria is met, or show an alert if it is not saying they can't use that view at the moment.
Essentially I want to be able to intercept the selection value before it switches out the view, maybe I need to rewrite all of this but this is the functionality I'm looking for as this is how I had my previous app working using the old framework.
#State private var selection = 1
var body: some View
{
TabbedView(selection: $selection)
{
View1().tabItemLabel(
VStack
{
Image("icon")
Text("")
})
.tag(1)
View2().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(2)
View3().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(3)
}
}
You can do it by changing the value of selection on tap. You can use .onAppear() method for a particular tab to check your condition:
#State private var selection = 1
var conditionSatisfied = false
var body: some View
{
TabbedView(selection: $selection)
{
View1().tabItemLabel(
VStack
{
Image("icon")
Text("")
})
.tag(1)
View2().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(2)
.onAppear() {
if !conditionSatisfied {
self.selection = 1
}
}
View3().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(3)
.onAppear() {
if !conditionSatisfied {
self.selection = 1
}
}
}
}

Resources