SwiftUI TabView not updating View - ios

My problem is that TabView is not updating.
I want to log-in and then re-render another view (Profile Screen) instead of (Login Screen) in the same TabItem.
The TabView goes to ProfileScreen only after i kill the app and reopen it.
Here's the TabView code:
import SwiftUI
struct ContentView: View {
#State var userToken: String = UserDefaults.standard.string(forKey: "UserToken") ?? ""
var body: some View {
TabView{
HomeScreen(text: .constant("")).tabItem({ Image(systemName: "house") }).tag(0)
Text("Cart").tabItem({ Image(systemName: "cart") }).tag(1)
if userToken.isEmpty {
LoginScreen().tabItem({ Image(systemName: "person") }).tag(2)
}
else {
ProfileScreen().tabItem({ Image(systemName: "person") }).tag(2)
}
}
}
}
Things I have tried:
Passing different #State values to both screens
Rendering Profile Screen in LoginScreen but it renders on top of the TabView

If you're using the latest SwiftUI, consider using #AppStorage instead of #State:
#AppStorage("UserToken") var userToken: String = ""

If you are modifying the value of userToken in any other view, for example may be LoginView, you need a mechanism to inform the content view about that update.
You can consider using LocalNotifications here for sending the update to ContentView so that the view reloads on status update.

Related

How to dismiss a presenting view to the root view of tab view in SwiftUI?

I'm using TabView on my home page. Let's just say I have 4 tabs.
On second tab, i can go to another view using NavigationLink and I go to another 2 views using NavigationLink. Then on the latest view, there is a button to present a view and i use .fullScreenCover (since I want to present it full screen).
In the presenting view, I add an X mark on the left side of the navigationBarItems to dismiss. I use #Environment(\.presentationMode) var presentationMode and presentationMode.wrappedValue.dismiss() to dismiss. But it only dismiss the presenting view to the previous view, while actually I want to dismiss it to the root of my view which is the 2nd tab of my TabView.
Is there a way to do this? Because I have looked up to some articles and nothing relevant especially in TabView context.
I also have a question tho:
Is it a right approach to use .fullScreenCover? Or is there another possible solution for example presenting a modal with full screen style (if there's any cause i'm not sure either).
Any suggestions will be very appreciated, thankyou in advance.
The presentationMode is one-level effect value, ie changing it you close one currently presented screen.
Thus to close many presented screens you have to implement this programmatically, like in demo below.
The possible approach is to use custom EnvironmentKey to pass it down view hierarchy w/o tight coupling of every level view (like with binding) and inject/call only at that level where needed.
Demo tested with Xcode 12.4 / iOS 14.4
struct ContentView: View {
var body: some View {
TabView {
Text("Tab1")
.tabItem { Image(systemName: "1.square") }
Tab2RootView()
.tabItem { Image(systemName: "2.square") }
}
}
}
struct Tab2RootView: View {
#State var toRoot = false
var body: some View {
NavigationView {
Tab2NoteView(level: 0)
.id(toRoot) // << reset to root !!
}
.environment(\.rewind, $toRoot) // << inject here !!
}
}
struct Tab2NoteView: View {
#Environment(\.rewind) var rewind
let level: Int
#State private var showFullScreen = false
var body: some View {
VStack {
Text(level == 0 ? "ROOT" : "Level \(level)")
NavigationLink("Go Next", destination: Tab2NoteView(level: level + 1))
Divider()
Button("Full Screen") { showFullScreen.toggle() }
.fullScreenCover(isPresented: $showFullScreen,
onDismiss: { rewind.wrappedValue.toggle() }) {
Tab2FullScreenView()
}
}
}
}
struct RewindKey: EnvironmentKey {
static let defaultValue: Binding<Bool> = .constant(false)
}
extension EnvironmentValues {
var rewind: Binding<Bool> {
get { self[RewindKey.self] }
set { self[RewindKey.self] = newValue }
}
}
struct Tab2FullScreenView: View {
#Environment(\.presentationMode) var mode
var body: some View {
Button("Close") { mode.wrappedValue.dismiss() }
}
}
You have 2 options:
With .fullScreenCover you will have a binding that results in it being presented you can pass this binding through to the content and when the user taps on x set to to false
You can use the #Environment(\.presentationMode) var presentationMode then call presentationMode.wrappedValue.dismiss() in your button body.
Edit:
If you want to unwind all the way you should make the TabView be binding based. I like to use SceneStorage for this take a look at this post then you can access this SceneStorage value anywhere in your app to respond to it but also to update and change the navigation (this also has the benefit of providing you proper state restoration!)
If you make your TabView in this way:
struct ContentView: View {
#SceneStorage("selectedTab") var selectedTab: Tab = .car
var body: some View {
TabView(selection: $selectedTab) {
CarTrips()
.tabItem {
Image(systemName: "car")
Text("Car Trips")
}.tag(Tab.car)
TramTrips()
.tabItem {
Image(systemName: "tram.fill")
Text("Tram Trips")
}.tag(Tab.tram)
AirplaneTrips()
.tabItem {
Image(systemName: "airplane")
Text("Airplane Trips")
}.tag(Tab.airplaine)
}
}
}
enum Tab: String {
case car
case tram
case airplaine
}
Then deep within your app in the place you want to change the navigation you can create a button view.
struct ViewCarButton: View {
#SceneStorage("selectedTab") var selectedTab: Tab = .car
var body: some View {
Button("A Button") {
selectedTab = .car
}
}
}
This will forced the selected tab to be the car tab.
if instead of this you do not want to change tab but rather change what the navigation view is navigated to you can use the same concept for that, NavigationLink that's a binding if this binding is created using a #SceneStorage then in your ViewCarButton you can make changes to it that will change the navigation state.

SwiftUI navigation link back button working in preview but not in simulator or device

Im trying to use a simple navigation view, with a navigation link to a different view. When running the app and the navigation link is pressed it takes me to the new view.
However when I'm on a simulator or device the back button dose not work, whereas on the preview it works fine.
Any ideas what the problem may be?
import SwiftUI
struct HomeView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Detail View")) {
Text("Hello World")
}
}
.navigationBarTitle("SwiftUI")
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
I think the problem may be caused by the fact that the HomeView is part of a tabView. The following code is from AppView.swift which is what is run when the app is run (You can see the code for that at the very bottom).
I think this is the problem because when the code bellow is commented out the app works fine.
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.onTapGesture {
self.selectedTab = 2
}
.tag(2)
#main
struct Skate_AppApp: App {
var body: some Scene {
WindowGroup {
HomeView()
}
}
}
From your code I can tell that the problem is in onTapGesture, and I presume from self.selectedTab = 2 that you want to get which tab the user has selected.
Let's refactor your code a little bit with the same concept.
Solution: Delete onTapGesture and add onAppear.
TabView {
HomeView().tabItem {
Image(systemName: "house.fill")
Text("Home")
}.onAppear{
self.selectedTab = 2
}.tag(2)
AnotherView().tabItem {
Image(systemName: "car.fill")
Text("Login View")
}.onAppear{
self.selectedTab = 3
}.tag(3)
}
By this whenever a view appears, it means that the user has selected it, onAppear will be called, and your variable selectedTab will be changed. I hope this answer your question.

SwiftUI - Why is TabView and its contents sometimes not refreshing when I change tabs on macOS?

I'm trying to make a tabbed application on macOS with SwiftUI, and I have an odd issue with TabView.
When I have two tabs with TextFields each and save their text states to their respective private variables, something odd happens: When I switch from tab A to tab B after entering text into tab A's TextField, the tab indicator shows that I am still on tab A, but the content shows tab B's content. When I click on the button for tab B once again, it will still show tab B's content. Furthermore, when I press the button for tab A afterward, it will show the content of tab A, but the indicator for the tab still shows that I am on tab B.
What might I possibly be doing wrong?
Here is an example that illustrates my issue:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView
{
TabAView()
.tabItem({Text("Tab A")})
TabBView()
.tabItem({Text("Tab B")})
}
}
}
struct TabAView: View {
#State private var text = ""
var body: some View {
VStack{
Text("Tab A")
TextField("Enter", text: $text)
}
}
}
struct TabBView : View {
#State private var text = ""
var body: some View {
VStack {
Text("Tab B")
TextField("Enter", text: $text)
}
}
}
Here's a screen capture of the issue occurring:
This is definitely a bug in SwiftUI's implementation of TabView. But you can easily work around the problem by binding to TabView selection and setting the current tab manually like so:
struct ContentView: View {
#State private var currentTab = 0
var body: some View {
TabView(selection: $currentTab)
{
TabAView()
.tabItem({Text("Tab A")})
.tag(0)
.onAppear() {
self.currentTab = 0
}
TabBView()
.tabItem({Text("Tab B")})
.tag(1)
.onAppear() {
self.currentTab = 1
}
}
}
}
This bug only seems to manifest itself when the user changes tabs while a TextField has focus.
If you make the above changes to your code, it will work as expected.

How to navigate from one view to another in Swift UI on a click of button

I want to push from one view to another in SwiftUI on a click of button. Likewise we navigate from one controller to another controller in storyboard.
But in SwiftUI how to achieve this.
You can solve it by using NavigationLink
What is NavigationLink ?
NavigationLink: Creates a button on the right hand side of each cell and triggers presentation to the detail view
Apple Link:
https://developer.apple.com/documentation/swiftui/navigationlink
Sample Projects
Apple
https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation
Github
https://github.com/appbrewery/H4X0R-News-iOS13-SwiftUI-Completed
How to Implement:
import SwiftUI
struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
}
*Note: "LandmarkDetail" is your destination view.
You can also download the sample project from apple (link mentioned above) to understand it clearly
There are two ways to achieve that.
The first one is with "isActive" binding.
struct ViewA: View {
#State private var isActive = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: NextView(),
isActive: $isActive) {
Button(action: {
self.isActive = true
}) {
Text("Push Next View")
}
}
}
}
}
}
You place the view which you want to push in the NavigationLink's destination.
The second way is again with NavigationLink, but using "tag" and "selection". There the navigation is activated when the selection matches the tag.

SwiftUI bug when using a ScrollView

So I want to use this thing to make my view scroll up when editing a TextField (it's a TabbedView App btw).
However the animation didn't work unless I embedded it into a scroll view. This somehow broke my App.
So I get a Thread 1: EXC_BAD_ACCESS (code=1, address=0x8) in my AppDelegate now each time when I try to switch to another tab from the start of my App. I had a similar issue earlier where it I couldn't add my environmentObject() to my TabbedView directly but had to add it to the ContentView in the SceneDelegate. So this time my App works if I remove all my #State var for some reason.. can you see anything that is wrong with that?
struct Home: View {
//#State var isSheetPresented = false //if this one isn't commented out it breaks my tabbed-view app
#State var comment: String = "" //this one doesn't break everything?!?
#EnvironmentObject var someStore: SomeStore //works fine as well
var body: some View {
NavigationView {
ScrollView{ //This is THE ScrollView without everything else works (besides the animation)
VStack(spacing: 20) {
Text("Hi!")
}
}
}
}
}
//--------------------
struct ContentView : View {
#State private var selection = 0
var body: some View {
TabbedView(selection: $selection){
Home()
.tabItem{
Image(systemName: "house")
Text("Home")
}
.tag(0)
Text("Tab # 2")
.tabItem{
Image(systemName: "gear")
Text("#2")
}
.tag(1)
}
}
}
So yea, if you experienced sth. similar or have any idea what is going on please let me know....

Resources