SwiftUI call function on tab view navigation - swiftui

I want to execute functions when the user switches tabs in a TabView. Strangely, I can't seem to do this with .simultaneousGesture. How can I execute functions on the event of the user switching tabs in a tab view?
Sample Code:
struct ContentView: View {
#State var selectedTab: Int = 0
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
Text("View One")
.tabItem {
Text("View One")
}
.simultaneousGesture(TapGesture().onEnded {
print("This isn't printing")
})
Text("View Two")
.tabItem {
Text("View Two")
}
.simultaneousGesture(TapGesture().onEnded {
print("This isn't printing")
})
Text("View Three")
.tabItem {
Text("View Three")
}
.simultaneousGesture(TapGesture().onEnded {
print("This isn't printing")
})
}
}
}
}
EDIT: Updated Code
struct ContentView: View {
#State var selectedTab: Int = 0
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
Text("View One")
.tabItem {
Text("View One")
}
Text("View Two")
.tabItem {
Text("View Two")
}
Text("View Three")
.tabItem {
Text("View Three")
}
}
}
.onReceive(Just(selectedTab)) { thing in
print("Tapped!!")
print("\(thing)")
}
}
}

Add a .tag modifier to each tab and use a custom Binding that does the work in the setter.
struct ContentView: View {
#State var selectedTab: Int = 0
var body: some View {
NavigationView {
TabView(selection: Binding(
get: { selectedTab },
set: {
selectedTab = $0
print("switched to tab \(selectedTab)")
}
)) {
Text("View One")
.tabItem { Text("1") }
.tag(1)
Text("View Two")
.tabItem { Text("2") }
.tag(2)
Text("View Three")
.tabItem { Text("3") }
.tag(3)
}
}
}
}

Related

SwiftUI - WatchOS - NavigationView - Layout issues

I am writing a simple watchOS app using SwiftUI but I am having issues to proper layout it.
This is the code:
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
HeaderView()
NavigationView {
ScrollView {
NavigationLink(destination: View1()) {
Text("View 1")
}
NavigationLink(destination: View2()) {
Text("View 2")
}
NavigationLink(destination: View2()) {
Text("View 3")
}
NavigationLink(destination: View2()) {
Text("View 4")
}
}
}
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct HeaderView: View {
var body: some View {
VStack() {
Text("Header")
}
.frame(maxWidth: .infinity)
.background(Color.red)
}
}
struct View2: View {
var body: some View {
VStack {
Text("View 2 Content")
}
.navigationTitle("View 2")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("+") {
}
}
}
}
}
Issues:
I cannot rid of this unused space
No matter what the "+" button shows up below the toolbar. I would like to have it in the shown position

NavigationView scrolling broken when using TabView

With the below code I get a very weird scrolling behaviour in my TabViews, LoginView is called on app launch:
struct LoginView: View {
#State private var presentContent = false
var body: some View {
return NavigationView {
ZStack{
NavigationLink(
destination: ContentView(),
isActive: $presentContent,
label: {
EmptyView()
})
Button("TEst") {
self.presentContent.toggle()
}
}
}
}
}
struct ContentView: View {
var body: some View {
TabView{
Group{
List{
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}
.navigationTitle("Transactions")
.tabItem {
Image(systemName: "list.dash")
Text("Transactions")
}
Group{
List{
Text("Item 11")
Text("Item 12")
Text("Item 13")
}
}
.navigationTitle("Summary")
.tabItem {
Image(systemName: "list.dash")
Text("Summary")
}
}
}
}
Any ideas what that might cause?
Here the issue in verbatim: after tapping the button in the LoginView to jump over to the ContentView, I see the first tab. Now I scroll the list up and it goes beyond the screen border which is not correct.
UPDATE: Adding the app launch code below to emphasize the point "LoginView is called on app launch:":
struct TabTestApp: App {
var body: some Scene {
WindowGroup {
LoginView()
}
}
}
Use a NavigationView inside the TabView instead of Group. The login view you have isn't presented or even used. This will fix the scrolling issue.
Edit: Adding a login add additional elements which are unclear. You'd need a data model to handle the login details yourself, but a simple approach could be something like this.
class LoginModel: ObservableObject {
#Published var loggedin: Bool = false
}
struct Login: View {
#ObservedObject var model: LoginModel
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
List {
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
dismiss()
print("if successful login handle here and dismiss")
}, label: {
Label("Close", systemImage: "xmark")
.labelStyle(.iconOnly)
})
}
}
}
}
}
struct ContentView: View {
#StateObject var loginmodel = LoginModel()
#State var sheet: Bool = false
var body: some View {
TabView {
NavigationView {
VStack {
if !loginmodel.loggedin {
Button(action: {
sheet.toggle()
}, label: {
Label("Login", systemImage: "person.circle.fill")
})
} else {
List {
NavigationLink("Item 1", destination: Text("Item 1"))
NavigationLink("Item 2", destination: Text("Item 2"))
NavigationLink(destination: Text("Item 3")) {
Text("Item 3")
}
}
}
}
.navigationTitle("Transactions")
}
.sheet(isPresented: $sheet) {
Login(model: loginmodel)
}
.tabItem {
Label("Transactions", systemImage: "list.dash")
}
NavigationView {
List {
Text("Item 11")
Text("Item 12")
Text("Item 13")
}
.navigationTitle("Summary")
}
.tabItem {
Label("Summary", systemImage: "chart.line.uptrend.xyaxis")
}
}
}
}
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
List {
// Either or navigation link if needed
NavigationLink("Item 1", destination: Text("Item 1"))
NavigationLink("Item 2", destination: Text("Item 2"))
NavigationLink(destination: Text("Item 3")) {
Text("Item 3")
}
}
.navigationTitle("Transactions")
}
.tabItem {
Label("Transactions", systemImage: "list.dash")
}
NavigationView {
List {
Text("Item 11")
Text("Item 12")
Text("Item 13")
}
.navigationTitle("Summary")
}
.tabItem {
Label("Summary", systemImage: "chart.line.uptrend.xyaxis")
}
}
}
}
Ok, I have changed the whole code to
//
// ContentView.swift
// TabTest
//
// Created by Max on 2022-05-04.
//
import SwiftUI
struct LoginView: View {
#Environment(\.presentationMode) var presentationMode
#State private var presentContent = false
var body: some View {
return NavigationView {
ZStack{
Button("Login") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
}
struct ContentView: View {
#State private var isPresented = false
var body: some View {
TabView{
NavigationView{
List{
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.navigationTitle("Transactions")
.navigationBarBackButtonHidden(true)
}
.tabItem {
Image(systemName: "list.dash")
Text("Transactions")
}
NavigationView{
List{
Text("Item 11")
Text("Item 12")
Text("Item 13")
}
.navigationTitle("Summary")
.navigationBarBackButtonHidden(true)
}
.tabItem {
Image(systemName: "list.dash")
Text("Summary")
}
}
.onAppear{
self.isPresented = true
}
.fullScreenCover(isPresented: $isPresented, content: LoginView.init)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and now it actually seems to work. Never have thought of fullScreenCover - thanks! :)

How to hide TabView navigating from tabItem in childview in SwiftUI?

I am using TabView in swiftui. I want navigate child view from tabview tabItem. When I navigate from taview to childview, it shows tabview in bottom. Here is the image.. that click login goes to Myview page. But in Myview, Tabview is not hiding
Here is my code..
Tabview
struct WelcomeView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Image("home_icon")
Text("Home")
}
.tag(0)
NotificationView()
.tabItem {
Image("notification_icon")
Text("Notification")
}.tag(1)
AccountView()
.tabItem {
Image("account_icon")
Text("Account")
}.tag(2)
SettingView()
.tabItem {
Image("settings_icon")
Text("Setting")
}.tag(3)
}
}
}
In SettingView tabItem:
struct SettingView: View {
#State private var isActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
VStack {
Button(action: {
isActive = true
}) {
Text("Login")
}
NavigationLink("", destination: MyView(), isActive: $isActive)
}
}
}
}
}
MyView:
struct MyView: View {
var body: some View {
ZStack{
Text("Hello My View")
}.navigationBarHidden(true)
.navigationBarTitleDisplayMode(.inline)
}
}
When I click login Button in SettingView tabItem, It goes to MyView page. But in MyView page tabview is not hide.
How to hide tabview from MyView page?
To hide the tab we can add a Bool that will take care of showing the view or not. Then by using #Binding we can pass it to the other child views, whatever changes you make down the chain will affect all the views.
struct WelcomeView: View {
#State var isTabViewShown = true
var body: some View {
TabView {
if isTabViewShown {
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(0)
NotificationView()
.tabItem {
Image(systemName: "envelope.open.fill")
Text("Notification")
}.tag(1)
AccountView()
.tabItem {
Image(systemName: "person.crop.circle")
Text("Account")
}.tag(2)
SettingView(isTabViewShown: $isTabViewShown)
.tabItem {
Image(systemName: "gearshape")
Text("Setting")
}.tag(3)
}
}
}
}
struct MyView: View {
#Binding var isTabViewShown: Bool
var body: some View {
ZStack{
Text("Hello My View")
}
.navigationBarHidden(false)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
isTabViewShown = false
}
}
}
struct SettingView: View {
#State private var isActive = false
#Binding var isTabViewShown: Bool
var body: some View {
NavigationView {
VStack(alignment: .leading) {
VStack {
Button(action: {
isActive = true
}) {
Text("Login")
}
NavigationLink("", destination: MyView(isTabViewShown: $isTabViewShown), isActive: $isActive)
//Here is a button if you want to show it again
Button(action: {
isTabViewShown.toggle()
//You can also use this is you don't want to use a toggle
// isTabViewShown = true
}) {
Text("Show again")
}
}
}
}
}
}
struct HomeView: View {
var body: some View {
Text("Home View")
}
}
struct NotificationView: View {
var body: some View {
Text("Notification View")
}
}
struct AccountView: View {
var body: some View {
Text("Account View")
}
}

Update TabView and move to Home View after login

I have the fllowing TabViews in the ContentView. For the onAppear, it works fine. However, I am trying also to reload the tabs and move to the home after the user login from LoginView.
Following is the code of the ContentView
// ContentView.swift
// Matjri
//
//
import SwiftUI
struct ContentView: View {
#ObservedObject var user = User()
var body: some View {
HStack{
if (user.tokenIsActive) {
TabView {
HomeView()
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}.tag(0)
UserPostsView()
.tabItem {
VStack {
Image(systemName: "person.fill")
Text("Me")
}
}.tag(1)
NewPostView()
.tabItem {
VStack {
Image(systemName: "plus")
Text("Add")
}
}.tag(2)
SearchView()
.tabItem {
VStack{
Image(systemName: "magnifyingglass")
Text("Search")
}
}.tag(3)
}
} else {
TabView {
HomeView()
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}.tag(0)
LoginView()
.tabItem {
VStack {
Image(systemName: "person.fill")
Text("Me")
}
}.tag(1)
SearchView()
.tabItem{
VStack{
Image(systemName: "magnifyingglass")
Text("Search")
}
}.tag(2)
}
}
}
.onAppear() {
self.checkLoginValidity()
}
}
func checkLoginValidity() {
let userLogged = UserDefaults.standard.object(forKey: "userIsLogged") as? Bool ?? false
if (userLogged) {
let existinLogin = UserDefaults.standard.object(forKey: "loginExpiry") as! Date
if (existinLogin > Date().addingTimeInterval(86400 * 2)){
self.user.tokenIsActive = true
} else {
self.user.tokenIsActive = false
}
} else {
self.user.tokenIsActive = false
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
When the user login from the LoginView, I set the tokenIsActive to true
struct LoginView: View {
#ObservedObject var user = User()
........
//Login successful
self.user.tokenIsActive = true
TabView is not updating. How can I achieve this and I move the user to the homeview after login.
It is used different User() instances in ContentView and LoginView. Here is a solution:
struct LoginView: View {
#ObservedObject var user: User // << only declare
and here
LoginView(user: self.user) // << inject own user instance
.tabItem {
VStack {
Image(systemName: "person.fill")
Text("Me")
}
}.tag(1)

How to perform a modal transition like the center button in the Instagram app with SwiftUI TabView?

import SwiftUI
struct ContentView: View {
#State var showModal = false
var body: some View {
TabView {
Text("Home View")
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}
Text("Dummy View")
.onAppear {
self.showModal = true
}
.sheet(isPresented: self.$showModal) {
Text("Camera View")
}
.tabItem {
VStack {
Image(systemName: "camera")
Text("Camera")
}
}
Text("Setting View")
.tabItem {
VStack {
Image(systemName: "person")
Text("Setting")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
When I tap the center camera button with above code,
"Dummy View" has been shown.
onAppear is called and self.showModal is set to true.
But, modal transition is not performed, and can not show the "Camera View".
How to perform a modal transition when tapped tab button with SwiftUI TabView?
Thank you krjw!
Finally, I have solved the problem with the following code:
import SwiftUI
struct ContentView: View {
#State var showModal = false
var body: some View {
TabView {
Text("Home View")
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}
Text("Dummy View")
.onAppear {
DispatchQueue.main.async {
self.showModal = true
}
}
.sheet(isPresented: self.$showModal) {
Text("Camera View")
}
.tabItem {
VStack {
Image(systemName: "camera")
Text("Camera")
}
}
Text("Setting View")
.tabItem {
VStack {
Image(systemName: "person")
Text("Setting")
}
}
}
}
}
I had trouble with that and I am not sure why, but .sheet always worked best for me when I put it on the very top of my view hierarchy. Additionally I wrapped the call in .onAppear to run explicitly on the main queue (UI) which got it working:
struct ContentView: View {
#State var showModal = false
var body: some View {
TabView {
Text("Home View")
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}
Text("Dummy View")
.onAppear {
print("Hallo")
DispatchQueue.main.async {
self.showModal = true
}
}
.tabItem {
VStack {
Image(systemName: "camera")
Text("Camera")
}
}
Text("Setting View")
.tabItem {
VStack {
Image(systemName: "person")
Text("Setting")
}
}
}
.sheet(isPresented: self.$showModal) {
Text("Camera View")
}
}
}
I hope this helps!