SwiftUI - TabView initial tabItem not displaying text - ios

Summary of the problem
I have HomeView which contains the TabView (which is inside a NavigationView, see the code below). If I am to load the HomeView from another view (LoginView) it loads as expected and everything works. If I try to load the HomeView directly like this (code is in my ContentView):
if authService.isLoggedIn {
HomeView()
} else {
LoginView()
}
it again loads the HomeView with the tabs at the bottom but my first tab is missing text and only displays its image. Strangely if I switch to a tab (i.e. click Account) the text on the first tab appears again.
Here is the code for my TabView:
NavigationView {
TabView(selection: $selected) {
PlayView()
.tabItem {
Image(systemName: "play.circle.fill")
.font(.system(size: 24))
Text("Play")
}
.navigationBarHidden(true)
.navigationBarTitle("")
.tag(1)
AccountView()
.tabItem {
Image(systemName: "person.circle.fill")
.font(.system(size: 24))
Text("Account")
}
.tag(3)
NotificationsView()
.tabItem {
Image(systemName: "bell.fill")
.font(.system(size: 24))
Text("Notifications")
}
.tag(4)
}
.accentColor(Color(K.Colors.Secondary))
.navigationBarTitle("")
.navigationBarHidden(true)
}
Expected Result
Actual Result
Note: Notice how the icon is displayed at lower level than other icons, so the text is actually not displaying at all
What I Tried So Far
I did try only a few things, because the TabView is not very well documented on Apple's official documentation.
I tried moving the NavigationView both down and up the view hierarchy
Setting another tab for initial selection
Switching places of Image() and Text() inside the .tabItem section
Searching for a similar problem through the internet

I have found a solution! Turns out what was showing at the bottom was that navigation bar title, which I was setting to an empty string. So I changed my navigation bar titles on every View of the TabView elements. The code now looks like this:
NavigationView {
TabView(selection: $selected) {
PlayView()
.tabItem {
Image(systemName: "play.circle.fill")
.font(.system(size: 24))
Text("Play")
}
.navigationBarHidden(true)
.navigationBarTitle("Play")
.tag(1)
AccountView()
.tabItem {
Image(systemName: "person.circle.fill")
.font(.system(size: 24))
Text("Account")
}
.navigationBarHidden(true)
.navigationBarTitle("Account")
.tag(3)
NotificationsView()
.tabItem {
Image(systemName: "bell.fill")
.font(.system(size: 24))
Text("Notifications")
}
.navigationBarHidden(true)
.navigationBarTitle("Notifications")
.tag(4)
}
.accentColor(Color(K.Colors.Secondary))
.navigationBarTitle("")
.navigationBarHidden(true)
}
and It works as expected.

Related

Tab Bar Customisation (tab bar item positioning) with Tabview in SwiftUI

I am trying to figure out how to use the functions from xcode with very limited information from the Apple documentation. How to use certain functions or variables or where to find examples and detailed explanations? The particular case that I am working on now is to offset the tab bar item images on the tab bar. I came across "itemPositioning", "stackedItemPositioning" but I have no clues about how to use them. Should I put it inside the initialiser function? And how to use it?
Also, a quick question. Is there any book or video covering like somethings that teach me how to explore unknown things on SwiftUI independently?
truct MainContentView: View {
#State private var navSelection = 0
init(){
UITabBar.appearance().backgroundColor = UIColor.clear
//stackedItemPositioning = UIOffset(horizontal: 0, vertical: -10)
}
var body: some View {
VStack(alignment: .center){
NavigationView{
TabView(selection: self.$navSelection){
Text("hey")
.tabItem{
Image(systemName: "heart.fill")
.offset(y:-15)
}
.tag(0)
Text("hey")
.tabItem{
Image(systemName: "magnifyingglass")
}
.tag(1)
Text("hey")
.tabItem{
Image(systemName: "capsule.portrait.fill")
}
.tag(2)
Text("hey")
.tabItem{
Image(systemName: "wand.and.stars")
}
.tag(3)
}
}
}
}
}

SwiftUI - Views inside TabView jump after a second if TabView is nested in NavigationLink

My setup
Entry point (ContentView):
struct ContentView: View {
var body: some View {
NavigationView {
FirstView()
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
}
}
}
FirstView:
struct FirstView : View {
var body : some View {
NavigationLink(destination:
SecondView()
.navigationBarTitle("")
.navigationBarHidden(true)) {
Text("Fourth View")
}
}
}
SecondView:
struct SecondView : View {
var body : some View {
TabView {
FirstTabView()
.tabItem {
Image(systemName: "play.circle.fill")
Text("First Tab")
}
.navigationBarTitle("First Tab")
.navigationBarHidden(true)
SecondTabView()
.tabItem {
Image(systemName: "bell.fill")
Text("Second Tab")
}
.navigationBarTitle("Second Tab")
.navigationBarHidden(true)
}
}
}
FirstTabView
struct FirstTabView : View {
var body : some View {
VStack {
HStack(spacing: 10) {
Spacer()
Text("FIRST")
Spacer()
Text("SECOND")
Spacer()
}
.padding(.all)
Spacer()
}
.border(Color.red, width: 5)
}
}
And SecondTabView is very similar to the first one so I won't include it.
The issue
The issue I am having right now is that the content of my TabViews jump up ignoring safe areas (For clarification see the attached screenshots). The view actually displays perfectly fine and as expected for a brief moment and then immediately jumps up breaking everything.
I have found that this is only happening if the TabView is loaded via NavigationLink as I do right now. If I directly load the SecondView() inside my NavigationView on ContentView (or in other words its the first view that I load) then the issue is gone and everything works as expected.
I am sorry that I wasn't able to include GIF of the problem - the View starts off as the expected result and then after a seconds jumps up and becomes what you see on the actual result.
Expected Result
Actual Result
NOTE: On iPhones with notch my text is not even visible. Also I've put thick border in order to make my point more clear.
EDIT
I just found out that locking the device and then unlocking it brings the screen back to normal.

How to permanently hide NavigationView nav bar in SwiftUI

I believe the recommended way to hide the navigation bar in SwiftUI is as follows (placed on the child of the NavigationView) however when a button is tapped on the view, the view updates itself & the nav bar reappears even though I'm using a constant value of true to hide the bar
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
How do I get the NavigationView's bar to stay hidden on this view?
EDIT
Here is the code for NavigationView, when a gesture/tap happens in a nested subview inside props.selectedView the bar appears again
NavigationView {
GeometryReader { geometry in
VStack(spacing: 0) {
ZStack {
VStack {
props.selectedView
Spacer()
.frame(height: searchHeaderHeight)
}
Search()
}
TabBar()
.frame(width: geometry.size.width, height: 60)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color(.systemGroupedBackground))
}
.frame(width: geometry.size.width)
.edgesIgnoringSafeArea(.bottom)
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
}.navigationViewStyle(StackNavigationViewStyle())

How to hide the TabBar when navigate with NavigationLink in SwiftUI?

I have a TabView with 2 tabs in it, each tab containing a NavigationView. I need to hide the TabBar when navigating to another view. One solution would be to place the TabView inside of one NavigationView, but I have to set different properties for each NavigationView.
TabView(selection: $selectedTab, content: {
NavigationView {
VStack {
NavigationLink(destination: Text("SecondView Tab1")) {
Text("Click")
}
}
}.tabItem {
Text("ONE")
}.tag(0)
NavigationView {
VStack {
NavigationLink(destination: Text("SecondView Tab2")) {
Text("Click")
}
}
}.tabItem {
Text("TWO")
}.tag(1)
})
P.S. I am using Xcode 11 Beta 5
A little late but it will work, put your NavigationView before the TabView and the tab buttons are going to be hidden when you use a navigation link in your tabbed views.
NavigationView{
TabView{
...
}
}
I have same problem for this;
And I did the following actions to solve this problem:
Use NavigationView Contain a TabView And Hidden the NavigationBar
Make a Custom NavigaitonView like this
In next view Still hidden NavigationBar
// root tab
NavigationView {
TabView {
// some
}
.navigationBarTitle(xxx, displayMode: .inline)
.navigationBarHidden(true)
}
// custom navigation view
#available(iOS 13.0.0, *)
struct MyNavigationView: View {
var body: some View {
HStack {
Spacer()
Text(some)
Spacer()
}
.frame(height: 44)
}
}
// this view
VStack {
MyNavigationView()
Image(some)
.resizable()
.frame(width: 100, height: 100, alignment: .top)
.padding(.top, 30)
Spacer()
HStack {
ClockView()
Spacer()
NavigationLink(
destination: DynamicList(),
label: {
Image(some)
}).navigationBarHidden(true)
}
.padding(EdgeInsets(top: 0, leading: 15, bottom: 0, trailing: 15))
Spacer()
}
// next view
var body: some View {
VStack {
List {
MyNavigationView()
ForEach(date, id: \.self) { model in
Text(model)
}
}
.navigationBarHidden(true)
.navigationBarTitle(some, displayMode: .inline)
}
}
You can't hide the tab bar as far as I know if you navigation view its listed as a child, your tab bar contains your navigation view.

How to put a logo in NavigationView in SwiftUI?

I am trying to use a logo image instead of a NavigationView title at the top section of the app. Couldn't find any documentation of using images inside a NavigationView.
iOS 14+
Starting from iOS 14 you can create a ToolbarItem with the principal placement:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Test")
.toolbar {
ToolbarItem(placement: .principal) {
Image(systemName: "ellipsis.circle")
}
}
}
}
}
See the ToolbarItemPlacement documentation for more placements.
NavigationView.navigationBarTitle() can only take a Text() argument right now. You could instead use .navigationBarItems() to set an Image as either the trailing or leading argument, but this is the SwiftUI equivalent of UINavigationItem.leftBarButtonItem[s] and UINavigationItem.rightBarButtonItem[s], which means that you're restricted to navigation bar button dimensions. But if you're ok with that, you may want to set a blank title so that you can specify a standard-height navigation bar.
Hard-Coded Positioning
If you can stand to live with yourself, you can fake a centered nav bar item by hard-coding padding around the image, like
.padding(.trailing, 125),
(Note that I deliberately positioned it off-center so that you can see that it's hard-coded.)
Slightly Less Hard-Coded Positioning
Even better would be to wrap the whole thing in a GeometryReader { geometry in ... } block to use the screen dimensions to calculate precise positioning, if you know the exact width of the image you're using:
GeometryReader { geometry in
NavigationView {
...
}
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(trailing:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(.trailing, (geometry.size.width / 2.0) + -30), // image width = 60
destination: ProfileHost()
)
)
If you don't want to hack it, here's what you can do:
Standard nav bar height, left button item
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(leading:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(),
destination: ProfileHost()
)
)
Standard nav bar height, right button item
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(trailing:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(),
destination: ProfileHost()
)
)
Expanded nav bar height, no title, left button item
.navigationBarItems(leading:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(),
destination: ProfileHost()
)
)
Use this:
NavigationView {
Text("Hello, SwiftUI!")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
Image(systemName: "sun.min.fill")
Text("Title").font(.headline)
}
}
}
}
Credit: https://sarunw.com/posts/custom-navigation-bar-title-view-in-swiftui/
With SwiftUIX, you can use navigationBarTitleView(View):
NavigationView() {
NavigationLink(destination:YourView().navigationBarTitleView(Image(systemName: "message.fill")))
}
I don't want to claim 100% accuracy whether title image positioned at center but visually it looks center to me. Do your judgment and adjust padding :)
Here is code:
.navigationBarTitle(
Text("")
, displayMode: .inline)
.navigationBarItems(leading:
HStack {
Button(action: {
}) {
Image(systemName: "arrow.left")
}.foregroundColor(Color.oceanWhite)
Image("oceanview-logo")
.resizable()
.foregroundColor(.white)
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 40, alignment: .center)
.padding(UIScreen.main.bounds.size.width/4+30)
}
,trailing:
HStack {
Button(action: {
}) {
Image(systemName: "magnifyingglass")
}.foregroundColor(Color.oceanWhite)
}
)
To extend on NRitH's answer, putting your logo in a different component (to borrow a React way of putting it) may help anyone looking to understand the concepts.
The actual Image can be wrapped in any container view such as a VStack, etc. An example of setting up a struct as a component to be used in our navigation items could be something like the following:
struct NavLogo: View {
var body: some View {
VStack {
Image("app-logo")
.resizable()
.aspectRatio(2, contentMode: .fit)
.imageScale(.large)
}
.frame(width: 200)
.background(Color.clear)
}
}
When the aspect ratio is set, only the width needs to be set on the frame on the container view. We could also set a property in the NavLogo to set width and/or height from property dependency injection. Regardless, our navigationBarItems becomes very straight forward and more readable 🙂
NavigationView {
Text("Home View")
.navigationBarItems(
leading: NavLogo()
trailing: ProfileButton()
)
}
On iOS 13, a little hacky way to achieve this:
private var logo: some View {
Image("logo-image")
}
var body: some View {
GeometryReader { g in
content()
.navigationBarTitle("")
.navigationBarItems(leading:
ZStack(alignment: .leading) {
logo.frame(width: g.size.width).padding(.trailing, 8)
HStack {
leadingItems().padding(.leading, 10)
Spacer()
trailingItems().padding(.trailing, 10)
}
.frame(width: g.size.width)
}
)
}
}
Try the following.
struct ContainerView: View {
var body: some View {
VStack {
Image(systemName: "person.crop.square")
ContentView()
}
}
}
It worked for me.
Make sure you change ContentView to ContainerView inside SceneDelegate.swift before running on simulator or device.

Resources