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
How to Run App in Simulator Xcode 6
Switch Statement for Imported Ns_Options (Rawoptionsettype) in Swift
Swift: Call Self Method Inside Init
Avaudioplayer.Play() Works But Avaudioplayernode.Play() Fails
When Two Optionals Are Assigned to an If Let Statement, Which One Gets Unwrapped? Swift Language
Performing a Completion Handler Before App Launches
Prepare for Segue with Array - Xcode 8.0 Swift 3.0
How to Check for Value in Firebase That Is Held Under a Autoid Child
How to Change Values Inside Array Without a Loop Swift
How to Execute Different Implementation of a Method of a Generic Struct Based on the Generic Type
Swift Switch Char{ Case "\U{E2}:
Comparing Objects in an Array Extension Causing Error in Swift
How to Make Struct Lazylist in Swiftui
App Window on Top of All Windows Including Others App Windows
How to Get All Characters of the Font with Ctfontcopycharacterset() in Swift