Tabview, Tabitem: Running Code on Selection or Adding an Ontapgesture

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

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"
}
}
}
}

SwiftUI TabView: how to detect click on a tab?

As of iOS 14 you can use onChange to execute code when a state variable changes. You can replace your tap gesture with this:

.onChange(of: selectedTab) { newValue in
model.myFunction(item: newValue)
}

If you don't want to be restricted to iOS 14 you can find additional options here: How can I run an action when a state changes?

TabView selection resets to first tab on sheet presentation

Use .tag() like this:

struct ContentView: View {
@State private var selection = 1
var body: some View {
TabView(selection: $selection) {
One()
.tabItem { Label("One", systemImage: "1.circle") }
.tag(1)
Two()
.tabItem { Label("Two", systemImage: "2.circle") }
.tag(2)
Three()
.tabItem { Label("Three", systemImage: "3.circle") }
.tag(3)
}
}
}

SwiftUI TabView: Set selected tabItem from different view but detecting repeated selection

You need to make appState observed and you don't need selection at all (it is just a duplicate).

I've put everything into separated ContentView (to leave scene for scene only)

Tested with Xcode 12 / iOS 14

struct ContentView: View {

@StateObject var appState = AppState()

var body: some View {
// this code is required to detect a repeated selection of
// the same tab to trigger a special action
let index = Binding<Int>(
get: { self.appState.selectedTab },
set: {
if $0 == TestApp.kIndex1 && self.appState.selectedTab == $0 {
print("Trigger special action for index 1")
}
print("Pressed tab: \($0) app.selectedTab: \(appState.selectedTab)")
appState.selectedTab = $0
})

TabView(selection: index) {
First()
.environmentObject(appState)
.tabItem {
Image(systemName: "1.circle")
Text("Home")
}.tag(TestApp.kIndex0)

Text("Second Content View")
.tabItem {
Image(systemName: "2.circle")
Text("Screen Two")
}.tag(TestApp.kIndex1)

Text("Third Content View")
.tabItem {
Image(systemName: "3.circle")
Text("Screen Three")
}.tag(TestApp.kIndex2)
}
}
}

class AppState: ObservableObject {
@Published var selectedTab = TestApp.kIndex0
}

struct First: View {
@EnvironmentObject var appState: AppState

var body: some View {
VStack(alignment: .leading, spacing: 20) {
AButton(tabIndex: TestApp.kIndex0, iconName: "1.circle", text: "This first screen")

AButton(tabIndex: TestApp.kIndex1, iconName: "2.circle", text: "Second screen")

AButton(tabIndex: TestApp.kIndex2, iconName: "3.circle", text: "Third screen")
}
}
}

struct AButton: View {
let tabIndex: Int
let iconName: String
let text: String

@EnvironmentObject var appState: AppState

var body: some View {
Button(action: {
appState.selectedTab = tabIndex
}) {
HStack() {
Image(systemName: iconName)
.imageScale(.large)
.frame(minWidth: 50)
Text(text)
}
}
}
}

How to use different tab items for selected/unselected?

Use selectedTab with tag and change your tab image by using the selectedTab condition.

And for Font you can use UITabBarAppearance().

struct ContentView: View {
@State private var selectedTab = 0

init() {
// Set font here of selected and normal
let appearance = UITabBarAppearance()
appearance.stackedLayoutAppearance.normal.titleTextAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10)]
appearance.stackedLayoutAppearance.selected.titleTextAttributes = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15)]
UITabBar.appearance().standardAppearance = appearance
}

var body: some View {
TabView(selection: $selectedTab) {
Text("Content 1")
.tabItem {
Label("First", systemImage: selectedTab == 0 ? "alarm" : "alarm_unselected") //<-- Here
.accessibilityHint("Something 1")
}.tag(0) //<-- Here
Text("Content 2")
.tabItem {
Label("Second", systemImage: selectedTab == 1 ? "calendar" : "calendar_unselected") //<-- Here
.accessibilityHint("Something 2")
}.tag(1) //<-- Here
}
}
}

SwiftUI: Tabview Repeating itself

Try to wrap them in container (stack or something) explicitly, like

struct BookmarksView: View {
var body: some View {
VStack { // << this !!
Text("Bookmarks View")
.font(.title)
.font(Font.body.bold())

Text("Testing")
.font(.system(size: 15))
}
}
}

SwiftUI Disable specific tabItem selection in a TabView?

You are pretty close to what you want to achieve. You will just need to preserve the previous selected tab index and reset the current selected tab index with that preserved value at the time of the dismissal of the sheet. That means:

.sheet(isPresented: self.$isPresenting, onDismiss: {
// change back to previous tab selection
self.appState.selectedTab = self.appState.previousSelectedTab
}, content: { self.content })

So how do you keep track of the last selected tab index that stays in sync with the selectedTab property of the AppState? There may be more ways to do that with the APIs from Combine framework itself, but the simplest solution that comes to my mind is:

final class AppState: ObservableObject {
// private setter because no other object should be able to modify this
private (set) var previousSelectedTab = -1
@Published var selectedTab: Int = 0 {
didSet {
previousSelectedTab = oldValue
}
}
}

Caveats:

The above solution of may not be the exact thing as disable specific tab item selection but after you dismiss the sheet it will revert back with a soothing animation to the selected tab prior to presenting the sheet. Here is the result.



Related Topics



Leave a reply



Submit