Add Padding to TabView Page Indicator - swift

I have a view that is a TabView with the style PageTabViewStyle(indexDisplayMode: .always which looks great for my use case however the page indicator is bumping right up to the safe area near the bottom of the screen and it looks bad. I'd like to move the page indicator up to some n value. here's a sample of my view to reproducing it. If this view were built on any device without a Home Button, it will ride on top of the home indicator line.
var body: some View {
ZStack {
TabView(selection: $homeVM.selectedPageIndex) {
// Any number of views here.
}
.frame(width: UIScreen.main.bounds.width)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .never))
}.edgesIgnoringSafeArea(.all)
}
I attempted to add the padding to the ZStack, which does work but then my TabView is cut off on the bottom, which means my cells disappear prematurely.
Here's an image for what I'm trying to fix. Notice the page indicator sits on the home bar indicator. I need the indicators pushed up, without pushing up the background ScrollView
Update #1
This view is being presented by a base view that I use to handle my navigation stack. The view is as follows. The important thing to note here is the .ignoresSafeArea() that I have on this view. I did that because it's a containing view for my eventual TabView to be presented from. Interestingly if I remove this modifier the indicators move up to a more manageable position, but then my form becomes clipped at the top and bottom of the device when scrolling, and that's not ideal.
struct BaseLaunchView: View {
#StateObject var baseNavVM = BaseLaunchViewModel()
#State var shouldLogin = false
#State var shouldRegister = false
var body: some View {
VStack {
switch baseNavVM.loggedIn {
case true:
HomeView()
default:
NavigationView {
VStack{
Spacer()
VStack(spacing: 30) {
VStack {
Text("Stello")
.font(Fonts.title)
Text("Life Groups")
.font(Fonts.body)
}
StelloDivider()
Text("Connect with like minded people, to fellowship and find your home.")
.font(Fonts.subheading)
.multilineTextAlignment(.center)
}
Spacer()
NavigationLink(destination: RegisterOptionsView(isLoggingIn: true), isActive: $shouldLogin) {
Button(action: {
shouldLogin.toggle()
}, label: {
Text("Login")
.font(Fonts.button)
}).buttonStyle(StelloFillButtonStyle())
}
NavigationLink(destination: RegisterOptionsView(isLoggingIn: false), isActive: $shouldRegister) {
Button(action: {
shouldRegister.toggle()
}, label: {
Text("Register")
.font(Fonts.button)
}).buttonStyle(StelloHollowButtonStyle())
}
}
}.accentColor(.black)
}
}
.padding()
.environmentObject(baseNavVM)
.ignoresSafeArea()
}
}

Related

when I open the app on ipad simulator the design is broken

When I run the app on the iPad, the design does not appear on the screen. When you click on Home in the top left navigation bar, the design comes up, but it is half loaded. When I delete the NavigationView, the normal design appears but is not clickable.
struct MainView: View {
#EnvironmentObject var store: BlogPostsStore
#Environment(\.colorScheme) var colorScheme
var featuredPosts: [BlogPost] {
return store.blogPosts.filter {$0.featured == true}
}
var body: some View {
NavigationView {
ScrollView {
// featured article
if featuredPosts.count > 0 {
VStack {
HStack {
Text("Featured posts")
.font(.title.bold())
Spacer()
}
LazyVStack {
ForEach(featuredPosts) {post in
NavigationLink(destination: BlogPostView(blogPost: post)) {
BlogPostCardMain(blogPost: post)
}
}
}
}
.padding(.horizontal, 15)
.padding(.vertical, 30)
}
// latest articles
VStack {
HStack {
Text("Latest posts")
.font(.title.bold())
Spacer()
}
.padding(.horizontal, 15)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 15) {
if store.blogPosts.count >= 3 {
ForEach(store.blogPosts[0...2]) {post in
NavigationLink(destination: BlogPostView(blogPost: post)) {
BlogPostCardMain(blogPost: post)
}
}
} else {
ForEach(store.blogPosts[0..<store.blogPosts.count]) {post in
NavigationLink(destination: BlogPostView(blogPost: post)) {
BlogPostCardMain(blogPost: post)
}
}
}
}
.padding(.leading, 15)
.padding(.trailing, 30)
}
.frame(height: 420)
Spacer()
}
.padding(.bottom, 40)
}
.navigationBarTitle("Home")
.navigationBarItems(
trailing: Button(action: {store.refreshView()}) { Image(systemName: "arrow.clockwise.circle.fill")
.resizable()
.frame(width: 30, height: 30)
})
}
}
}
enter image description here
enter image description here
This is down to how NavigationView works on iPads (and also larger iPhones in landscape).
The first view given to NavigationView acts as the collapsible left hand navigation, which is a fixed width. Any NavigationLink destinations in that view will open in the main, “detail” view that takes up the full screen.
You can specify a second view underneath the first one to provide a ‘default’ view to display in the main screen:
NavigationView {
// the sidebar view
ScrollView {
// etc.
}
// the default view
Text("Default view")
}
You could also add a third view, which will automatically give your iPad a three-column view similar to that used by Mail, etc. if you wanted to.
Another option is to force the NavigationView to work exactly the same way as it does for an iPhone in portrait mode, by adding a .navigationViewStyle argument:
NavigationView {
// contents as before
}
.navigationViewStyle(.stack)
While that will give you an iPhone-like experience on the iPad, it doesn’t really take full use of the larger screen space without careful design work. For that reason, it’s usually a good idea to invest some time in coming up with an app design that is tailored to the default iPad style of navigation view.

Color in ZStack not behaving properly

so I'd like to lay a Color at the top of a ZStack over another view. The reason I don't want to use overlay is the Color is going to have a tap gesture attached to it. A minimal reproducible example is below. Essentially, I want the Color.secondary to be confined to the same area as the HStack (without explicitly setting frame size. Here's the code:
struct ContentView: View {
var body: some View {
ZStack {
HStack {
Spacer()
Button(action: {
print("tapped button")
}, label: {
Text("button")
})
}.background(Color.red)
Color.secondary
.onTapGesture {
print("clicked color")
}
}
}
}
So I'd like the view to just be a white screen, with an HStack that looks slightly darker red.
Below is a picture of my UI. The view is greyed out during onboarding, and the user will essentially just tap the grey area to go to the next step in the onboarding. If I attach a tap gesture to the Color view and then just hide the color view according to state changes, this isn't a problem. After the onboarding is completed, the greyed area won't be there and the buttons underneath need to be interactable. When using overlays, AFTER onboarding, I don't want tapping anywhere on the view to change the app state.
https://i.stack.imgur.com/lphHg.png
Given your further description, I believe you have the onTapGesture(perform:) in the wrong place, outside of the overlay rather than inside.
In the code below, the onTapGesture(perform:) can only be tapped on the gray overlay when it is showing. However when it is attached after the overlay, it can be tapped on when not tapping on something like the button.
Working code:
struct ContentView: View {
#AppStorage("is_onboarding") var isOnboarding = true
var body: some View {
HStack {
Spacer()
Button {
print("tapped button")
} label: {
Text("button")
}
}
.background(Color.red)
.overlay {
if isOnboarding {
Color.secondary
.onTapGesture {
print("clicked color")
isOnboarding = false
}
}
}
}
}
If on iOS 14+ not iOS 15+, use the following overlay instead:
.overlay(
isOnboarding ?
Color.secondary
.onTapGesture {
print("clicked color")
isOnboarding = false
}
: nil
)

How can I change my SwiftUI background without losing the navigation bar UI?

I'm trying to change my View background colour to a specific color, however, whenever I add it using the basic Zstack way, it loses the navigation bar UI. (See pictures)
EDITED CODE
This method is not working for me:
var body: some View {
ZStack{
Color("Background")
.edgesIgnoringSafeArea(.vertical)
VStack {
ScrollView {
ZStack {
VStack {
HStack{
VStack (alignment: .leading) {
Text("")
}
}
}
}
}
Text("")
}
}
}
Current UI with simple ZStack:
Desired UI:
How do I change my background color in SwiftUI without losing the navigation bar UI?
At this period of SwiftUI evolution it is possible only as workaround via UIKit
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1):
var body: some View {
NavigationView {
VStack {
ScrollView {
VStack {
ForEach(0..<50) {
Text("Item \($0)")
}
}
}
Text("Test").navigationTitle("Test")
.background(UINavigationConfiguration { nc in
nc.topViewController?.view.backgroundColor = .yellow
})
}
}
}
Note: the UINavigationConfiguration is taken from next my answer https://stackoverflow.com/a/65404368/12299030

SwiftUI TextField takes max width

I have a navigation list with each list item being in this format:
HStack {
TextField("Insert something here.",text: self.$userData.pages[i].title)
.border(Color.blue)
Spacer()
}
This results in the following view:
The touchable area is highlighted by the blue border and it takes the whole width of the row
The problem with this is that despite the list item being a navigation link, the user clicking anywhere along the item will result in them editing the text content. What I would prefer is a TextField that has the same width as a Text:
The blue border wraps the text instead of taking the max width
So if the user clicks outside the TextField, the navigation works, but if they click on the text, it will let them edit the text. (The above view is with Text field).
Apologies if I've asked an unclear or bad question. I'm new to Stack Overflow and SwiftUI.
Edit:
I've tried using the fixedSize modifier, and the TextField correctly wraps my Text, but now the Navigation Link doesn't work (i.e. clicking on it just doesn't navigate). This is my full code:
NavigationLink(destination: PageView(page: self.userData.pages[i])) {
HStack {
Button(action: {}){
TextField(" ", text: self.$userData.pages[i].title)
.fixedSize()
}
.buttonStyle(MyButtonStyle())
.border(Color.blue)
Spacer()
}
}
No need to apologize, your question is clear.
You can do this by using fixedSize()
so your code should be like this
HStack {
TextField("Insert something here.",text: self.$userData.pages[i].title)
.border(Color.blue)
.fixedSize()
Spacer()
}
You can further specify how would you like the stretch to be, either vertical or horizontal or even both by passing parameters like so
.fixedSize(horizontal: true, vertical: false)
UPDATED ANSWER TO MATCH YOUR NEW REQUIREMENTS
import SwiftUI
struct StackOverflow5: View {
#State var text: String = ""
#State var selection: Int? = nil
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: Page2(), tag: 1, selection:self.$selection) {
Color.clear
.onTapGesture {
self.selection = 1
}
}
TextField("Text", text: self.$text)
.fixedSize()
}
}
}
}
struct StackOverflow5_Previews: PreviewProvider {
static var previews: some View {
StackOverflow5()
}
}
struct Page2: View {
var body: some View {
Text("Page2")
}
}
We used a ZStack here to separate between our TextField and our NavigationLink so they can be interacted with separately.
Note the use of Color.clear before our TextField and this is on purpose so that our TextField has interaction priority. Also we used Color.clear because it will stretch as a background and it's clear so it's not visible.
Obviously I hard coded 1 here but this can be from List or a ForEach
Additionally, if you don't want to use selection and tag you can do something like this
...
#State var isActive: Bool = false
...
NavigationLink(destination: Page2(), isActive: self.$isActive) {
Color.clear
.onTapGesture {
self.isActive.toggle()
}
}
....

SwiftUI Text disappearing behind Navigation Bar

I have an Issue where my text disappears behind the navigation bar. The navigation bar is made visible once the user clicks on Settings (or any other menu button). It shows up and the content is visible but then when done loading the new view the text disappears behind the bar. Any solutions?
I change the status of the navigation bar being visible with .onAppear and .onDisappear of views that are root level.
Code something like this:
struct ContentView: View {
#State public var navBarHidden = true
var body: some View {
NavigationView{
VStack{
ZStack(alignment: .center){
WhiteImage().onAppear{self.navBarHidden = true} //Here only seen as white background
BottomButtons().onDisappear{self.navBarHidden = false}
ProfileInvoke().navigationBarTitle("").navigationBarHidden(self.navBarHidden)
}
}
}
}
}
//The buttons are done with such a construct
struct MenuButton: View {
var buttonText: String
var buttonCallView: AnyView
var body: some View {
NavigationLink(destination: self.buttonCallView) {
Text(self.buttonText)
}.padding()
}
}
//Population of a button
MenuButton(buttonText: "My Favourites", buttonCallView: AnyView(MyFavouritesView().navigationBarTitle(Text("My Favourites"), displayMode: .inline)))
// The settings view where the title disappears
struct SettingsView: View {
var body: some View {
HStack(alignment: .top){
VStack(alignment: .leading){
Text("General").bold()
Divider()
Spacer()
}.padding()
Spacer()
}
}
}
I have the feeling that it has something to do with the .onAppear and .onDisappear where I set the status of the navigation bar being hidden or not. Ain't sure tho.