I have a TabView with custom icons - like this:
PastView( settings ).tabItem
{
Image("past")
Text("Past")
}.tag(1)
which works great - but just the text changing color is very subtle - it's not obvious which tab is "current". I want to make it much more obvious by doing something to the image. I've tried all of thse
Image("past").shadow( radius:5 )
Image("past").border( Color.red, width:8 )
Image("past").background( Color.green )
but none of them have any effect at all - and I don't really understand why
try adding ".accentColor(.red)" to the TabView.
TabItem image in SwiftUI has very limited customization at present. Even in UIKit you would be making those changes to the ImageView container not the Image itself.
Design selected and unselected versions of your icons, and then use them as below. You may also want to create different versions of the icons in the Asset Catalog for dark mode.
#State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
ViewA()
.tabItem {
selectedTab == 0 ? Image("past-selected") : Image("past-unselected")
}.tag(0)
ViewB()
.tabItem {
selectedTab == 1 ? // etc ...
}.tag(1)
}
}
Related
Is there anyway to keep the tab bar showing while presenting a modal / sheet view?
Here is a minimal failing example.
import SwiftUI
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
Button("Press to dismiss") {
dismiss()
}
.padding()
}
}
struct Tab1: View {
#State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}
struct MainView: View {
var body: some View {
TabView {
Tab1()
.tabItem {
Label("Tab 1", systemImage: "heart")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
Thanks for answering my question in the comments.
Unfortunately the standard means of presenting views in SwiftUI is that they are truly modal – they capture the whole interaction context for the current scene, and you can’t interact with anything else until the modal is dismissed.
This is also the case for iPadOS. Even though a modal presented with .sheet on an iPad allows much more of the underlying view to be visible, you can’t interact with it until the sheet disappears. You can interact with different parts of the app by running two scenes side-by-side in split screen mode, but each half is a separate scene and any presented sheets are modal for that scene.
If you want one tab to optionally present a view over its usual content but still allow access to the tab view and its other tabs, that’s not a modal context and SwiftUI’s built-in sheet won’t work. You will have to implement something yourself - but I think that’s doable.
Rather than using .sheet, you could optionally add an overlay to your Tab1 view, using the same boolean state variable showingSheet. In this approach, the default dismiss environment variable won’t be available, so passing in the state variable as a binding value would be an alternative:
var body: some View
<main display>
.overlay(showingSheet ? Sheet1(presented: $showingSheet) : EmptyView())
You might also find that a ZStack works better than .overlay depending on what the contents of the tab view actually are.
You’ll definitely have a lot more structural work to do to make this work, but I hope you can see that it’s possible.
I have a vanilla TabView, an #ObservedObject, and a background task running which periodically updates the #Published field of the #ObservedObject.
I've noticed that when the background task runs more frequently, and generates more changes to the #ObservedObject, the navigation tabs on the TabView become less responsive - they 'miss' taps. If I tap two or three times, I can normally change the view. The more frequent the background updates, the more often taps are 'missed'.
So I'm wondering if the TabView somehow fails to notice taps while it is redrawing, or something like that?
Any insights?
import Foundation
import SwiftUI
struct ContentView2: View {
enum Tabs {
case main
case audio
case preferences
}
#State var tab = Tabs.preferences
#ObservedObject var appState = Globals.appState
var body: some View {
TabView(selection: $tab) {
MainView()
.tabItem {
Image(systemName: "video.fill")
Text("Main")
}.tag(Tabs.main)
AudioControlView()
.tabItem {
Image(systemName: "speaker.fill")
Text("Audio")
}.tag(Tabs.audio)
PreferenceView()
.tabItem {
Image(systemName: "gear")
Text("Prefs")
}.tag(Tabs.preferences)
}
}
}
Found the problem...
The TabView doesn't need #ObservedObject var appState = Globals.appState because the TabView itself doesn't depend on the #Published fields.
Instead, the individual tabs (MainView() etc. ) need to observe.
So what was happening was that the TabView was being unnecessarily recomputed and redrawn for every change. Removing this line fixed everything.
I'm trying to create a TabView Slider in SwiftUI with 3 modals that will onboard a user. The default PageTabViewStyle() is a little basic and I'm wanting it to animate with the default slide speed etc.
I've tried appending animation along with the transition from what I've seen online including StackOverflow but it doesn't work.
Here's what I currently have:
ZStack {
Color.black
TabView(selection: $currentTab) {
ForEach(OnboardingData.list) { viewData in
OnboardingModal(data: viewData)
.tag(viewData.id)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.animation(.easeInOut)
.transition(.slide)
}
I have set up a TabView in my application, so that I can swipe horizontally between multiple pages, but I also have an unwanted vertical scroll that may appear, with a bounce effect so. How can I disable this vertical scroll?
My code:
struct ContentView: View {
#State private var currentTabIndex: Double = 0
var body: some View {
VStack {
TabView(selection: $currentTabIndex) {
Text("Text n°1")
.tag(0)
Text("Text n°2")
.tag(1)
}
.border(Color.black)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
}
I had this same problem. It's not an exact solution, but you can turn off bouncing on scrollviews (which is used within a TabView). And as long as the items within the TabView are not larger than the TabView frame, it should act as if you disabled vertical scrolling.
I would call it either .onAppear or in your init function:
.onAppear(perform: {
UIScrollView.appearance().bounces = false
})
Note: this disables the bouncing on ALL scrollviews across your app... So you may want to re-enable it .onDisappear.
Still an issue with Xcode 12.4.
I managed to workaround that by wrapping the TabView within a ScrollView and using the alwaysBounceVertical property set to false, as follow:
ScrollView(.horizontal) {
TabView {
///your content goes here
}
.tabViewStyle(PageTabViewStyle())
}
.onAppear(perform: {
UIScrollView.appearance().alwaysBounceVertical = false
})
.onDisappear(perform: {
UIScrollView.appearance().alwaysBounceVertical = true
})
I actually came across this because I saw this effect in a tutorial but couldn’t replicate it on iOS 15.2. However, I managed to replicate it on iOS 14.4 on another simulator side by side. So I guess this behaviour is disabled or fundamentally changed in the newer iOS.
Demonstration
Here I have use SwiftUI 2.0 and manage TabBar badge count. Reference of
https://medium.com/flawless-app-stories/swiftui-tutorial-showing-badge-on-tab-bar-item-d71e4075b67a
In Xcode 12.1 , the badge gets pushed up when the keyboard appears
How to mange this badge count When keyboard appear ?
Try adding .ignoresSafeArea(.keyboard) to your GeometryReader and/or the badge’s containing ZStack.
Your view is resizing to avoid the keyboard, which is the new default in iOS 14. Use the new .ignoresSafeArea(.keyboard) modifier to disable that behavior.
As of SwiftUI 3, you can use the .badge modifier to add a badge to your tab item. This requires iOS 15 or later.
Examples:
struct Tabs_Previews: PreviewProvider {
static var previews: some View {
TabView {
Text("String tab")
.tabItem {
Text("String")
Image(systemName: "text.quote")
}
.badge("hi")
.tag("string")
Text("Int tab")
.tabItem {
Text("Int")
Image(systemName: "number.circle")
}
.badge(123)
.tag("string")
}
}
}
Result:
You can use an Int, a String, a Substring, a LocalizedStringKey, or a Text. In my testing, the badge ignores any styling applied to the Text.