TabView, tabItem: running code on selection or adding an onTapGesture
Asked Answered
G

2

1

I would like to run some code when one of my tabs in tabview is selected.

Hypothetical: I want to create an app with the intentions to: A) Use a tabview and B) seriously confuse the user. To achieve this, I will create an app with tabs "one", "two", and "three", and views "one", "two", and "three". Then, when the second tab is selected, I will change view "two" to say "one" instead. Diabolical.

A common sense solution to this goes something like this:

import SwiftUI

struct ContentView: View {
    @State private var two : String = "Two"
    var body: some View {
        TabView() {
          Text("One")
            .tabItem {
              Text("One")
            }
          Text("Two")
            .tabItem {
              Text(two)
            }
              .onTapGesture {
                print("Tapped!!")
                self.two = "One"
              }
          Text("Three")
            .tabItem {
              Text("Three")
            }
        }
    }
}

Unfortunately, this works exactly like a normal app and fails to confuse the user because two is not updated (and there is no "Tapped!" in the console).

How can I run code when a tabItem is selected or tapped? This could be updating variables, running an animation, or anything else.

Goglet answered 22/9, 2020 at 23:4 Comment(0)
K
3

Instead of using onTapGesture on tabView we can write an extension to Binding and it will detect the new tab selection value even if we tap the tab bar within the same tab it will detect the changes. Here I am provided the binding extension.

extension Binding {
func onUpdate(_ closure: @escaping () -> Void) -> Binding<Value> {
    Binding(get: {
        wrappedValue
    }, set: { newValue in
        wrappedValue = newValue
        closure()
    })
}}

I used this in my tabView. I attached my code below.

TabView(selection: $tabSelection.onUpdate {
        setNewValue(value: tabSelection)
    }) {
      ContentView()
        .tabItem {
                Label {
                    Text("Home")
                } icon: {
                    Image("HomePage_icon")
                        .renderingMode(.template)
                }
            }
            .tag(TabViews.homepage)
}

SetNewValue function, this function acts like onTapGesture

func setNewValue(value: TabViews){
     self.tabSelection = value
     /* inside this function we can write the code, we like to write it in onTapGesture */
 }
Karyosome answered 1/12, 2022 at 14:23 Comment(0)
I
12

Here is a solution - you can observe tab selection change and react correspondingly.

Tested with Xcode 12 / iOS 14

import Combine   // << needed for Just publisher below

struct ContentView: View {
    @State private var two : String = "Two"
    @State private var selection: Int = 1

    var body: some View {
        TabView(selection: $selection) {
          Text("One")
            .tabItem {
              Text("One")
            }.tag(1)
          Text("Two")
            .tabItem {
              Text(two)
            }.tag(2)
          Text("Three")
            .tabItem {
              Text("Three")
            }.tag(3)
        }
  //      .onChange(selection) {          // << if SwiftUI 2.0 min spec
        .onReceive(Just(selection)) {
                 print("Tapped!!")
            if $0 == 2 {
                 self.two = "One"
            }
        }
    }
}
Isham answered 23/9, 2020 at 3:50 Comment(3)
How to count with .onTapGesture for a tabItemEwer
Thanks! I'm getting multiple print statements with the following: LazyHStack { TabView(selection: $annotationIndex) { ForEach(filteredListings.indices, id: \.self) { index in Text("FOO") } } .onReceive(Just(annotationIndex)) { index in print("select \(index)") } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) }Donofrio
This seems like the right approach, but I can't get it to work for me. "Tapped" is printed once when the view loads but not again upon changing tabsExedra
K
3

Instead of using onTapGesture on tabView we can write an extension to Binding and it will detect the new tab selection value even if we tap the tab bar within the same tab it will detect the changes. Here I am provided the binding extension.

extension Binding {
func onUpdate(_ closure: @escaping () -> Void) -> Binding<Value> {
    Binding(get: {
        wrappedValue
    }, set: { newValue in
        wrappedValue = newValue
        closure()
    })
}}

I used this in my tabView. I attached my code below.

TabView(selection: $tabSelection.onUpdate {
        setNewValue(value: tabSelection)
    }) {
      ContentView()
        .tabItem {
                Label {
                    Text("Home")
                } icon: {
                    Image("HomePage_icon")
                        .renderingMode(.template)
                }
            }
            .tag(TabViews.homepage)
}

SetNewValue function, this function acts like onTapGesture

func setNewValue(value: TabViews){
     self.tabSelection = value
     /* inside this function we can write the code, we like to write it in onTapGesture */
 }
Karyosome answered 1/12, 2022 at 14:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.