There is a huge space in my subview navigation bar.
I was assuming by adding trailing buttons, it would align everything nicely to the right of the back button.
This is my main view:
Now this is my subview:
Look at the huge gap at the top. I want the plus button to be to the right of the back button. Do I need to just create a custom back button for this or what?
Here is my code for the subview:
var body: some View {
NavigationView {
List {
Text("hello world")
Text("hello world")
Text("hello world")
}
.navigationBarTitle(todoList.title!)
.navigationBarItems(trailing:
HStack {
Button(action: {
self.add = true
}, label: {
Image(systemName: "plus")
})
}
)
}
}
I also want to remove the text from the back button so it's just an image.
To summarize:
I want the plus button at the top to the right of the back button
I want to remove the back button text, which reads ColorTodo in this example
Is there a SwiftUI native way of doing this or do I need a custom back button and to disable the default one?
The reason for the extra space is that you are wrapping a NavigationView inside a NavigationView; remove the one inside your subview, and the plus button will be at the right height.
As for removing the text, yes, you would need to hide the default back button and replace it with your own. Subview might look something like
struct SubView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
List {
Text("hello world")
Text("hello world")
Text("hello world")
}
.navigationBarTitle(todoList.title!, displayMode: .inline) // 1
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton, trailing: addButton)
}
var backButton: some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
HStack {
Image(systemName: "chevron.left")
Text("Back") // 2
}
})
}
var addButton: some View {
Button(action: {
self.add = true
}, label: {
ZStack(alignment: .trailing) {
Rectangle() // 3
.fill(Color.red.opacity(0.0001)) // 4
.frame(width: 40, height: 40)
Image(systemName: "plus")
}
})
}
}
Notes:
Although displayMode: .inline is not necessary, the default large title style looks a bit strange animating in and out.
You can remove this if you want (but see below)
This rectangle is here to increase tap target size, as the default button will only be the size of the plus icon, which is probably too small.
The rectangle can't be completely transparent, or it will not register taps.
Related
I have a NavigationView with a toolbar that contains a ToolBarItem with a .bottomBar placement and a search field. This NavigationView contains a ScrollView with content that exceeds the screen's vertical size, which means that the bottom bar has a background, as seen below:
When the user taps the "Root View" text element they navigate to a new view, in this case, just another text displaying "Detail View". The problem, however, is that the bottom toolbar's background remains in the screen instead of vanishing as expected. See the screenshot below:
This behavior is not seen if I remove the search bar or shrink the ScrollView's height to fit the vertical dimension of the device. I tried googling this issue to see if it was a known bug with a workaround but maybe I'm not searching the right keywords. How can I fix this issue?
Please see the bare minimum to replicate the issue below:
struct BugView: View {
#State var searchPattern: String = ""
var body: some View {
NavigationView {
ScrollView {
VStack {
NavigationLink(destination: Text("Detail View")) {
Text("Root View").foregroundColor(Color.blue)
}
Spacer()
Text("Root View Bottom").foregroundColor(Color.blue)
}.frame(maxWidth: .infinity, minHeight: 1000)
}
.searchable(text: self.$searchPattern, prompt: "Search Here")
.toolbar(content: {
ToolbarItem(placement: .bottomBar) {
Text("Toolbar text")
}
})
}
}
}
Setup:
XCode Version: 13.4.1
Simulator: iPhone 13
You can use the .toolbar(.hidden, for: .bottomBar) for the destination view as shown in the code below:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
Text("Destination View")
} label: {
// hiding the toolbar for the destination view
Text("Root View").toolbar(.hidden, for: .bottomBar)
}
}.toolbar {
ToolbarItem(placement: .bottomBar) {
Text("Toolbar Text")
.background {
Color.gray
}
}
}
}
}
I’m trying to add an accessory view embedded in a navigation bar below the title, which can be seen in the default iOS calendar app (the “s m t w t f s” row) or the GitHub mobile app:
And I’d like it to work along with the large title style navigation bar like the GH mobile.
LazyVStack’s pinnedView with a section header almost work, but I can’t get the background color to make it seemless with the navigation bar, even with the ultraThinMaterial. It also leaves the divider line between the pinned view and the bar.
Is there a way to achieve this layout?
Solutions in SwiftUI, SwiftUI+Introspect, and UIKit are all welcome!
Have you tried setting a .safeAreaInset view? This will have the stickiness you're looking for, and items in the "main" part of the view will take its height into account when rendering, so won't get obscured.
Here's a quick example I knocked up:
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0 ..< 30) { item in
Text("Hello, world!")
}
}
.navigationTitle("Accessory View")
.safeAreaInset(edge: .top) {
AccessoryView()
}
}
}
}
struct AccessoryView: View {
var body: some View {
HStack {
Button("Button") { }
Button("Button") { }
Button("Button") { }
Spacer()
}
.padding()
.background(Color(uiColor: .systemGroupedBackground))
.buttonStyle(.bordered)
.controlSize(.mini)
}
}
You have to give the view a background otherwise it'll be transparent – but that background will (as long as it's a colour or a material) automatically extend into the navigation bar itself. Here's a GIF of the above code in action, where I've set the background to match the grouped list's background:
It's not perfect, especially as it looks distinct from the nav bar on scroll, but it might be useable for you?
Another idea is to replace the navigation bar with a custom one like this:
{
...
}
.safeAreaInset(edge: .top) {
VStack(alignment: .leading, spacing: 8) {
HStack() {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Image(systemName: "chevron.backward")
}
Spacer()
Text(navigationTitle).font(.title2).bold()
.multilineTextAlignment(.center)
.foregroundColor(.accentColor)
.frame(maxWidth: .infinity)
Spacer()
}
HStack {
Button("Button") { }
Button("Button") { }
Button("Button") { }
Spacer()
}
}
.padding()
.background(
.bar
)
}
You will also have to set:
.navigationBarBackButtonHidden(true)
and do not set a navigation title:
// .navigationTitle("....")
No matter what I've tried I cannot get this navigation bar item to align with the large title on this View? I have tried padding or placing it in a VStack with a Spacer() but neither push it down. How can I properly align it?
var body: some View {
NavigationView {
//omitted
.navigationTitle("Exertion")
}
.navigationBarItems(
trailing:
Button(action: {
self.isShowingExertionCalendarSheet.toggle()
}) {
Image(systemName: "calendar")
.font(.system(size: 30, weight: .bold))
}
)
}
}
You can't. Because the Navigation Title is dynamic as it can change from small to big on scroll, at least you say otherwise, the UI does not allow you to set up the navigation items aligned vertically in other position other than the expected. That is right below the safe area. Is like giving a leading navigation item hides the back button. This is how Cocoa Touch either via SwiftUI or UIKIt works.
Well I think that you can do something like this:
.navigationBarItems(
leading: Text("Exertion")
.fontWeight(.bold)
.font(.largeTitle),
trailing: Button(action: {
... your code here ...
}, label: {
Image(systemName: "calendar")
.foregroundColor(Color("Some Blue"))
})
)
In this sample app, I have a title in the top left of the screen, and a button in the bottom right. I'm using stacks and spacers to align them.
Currently, when you press the button, it animates up/left a little. But I want the button to animate to the exact center of the screen (or safe area), regardless of device or button size. The code is shown below, along with images of the start and end of the animation I want.
struct ContentView: View {
#State var buttonIsMoved = false
var body: some View {
VStack {
HStack {
Text("Title")
.font(.largeTitle)
Spacer()
}
Spacer()
HStack {
Spacer()
// This is the button I want to animate to the center
Button(action: {
self.buttonIsMoved.toggle()
}) {
Text("This is a button")
.foregroundColor(.black)
.padding(16)
.background(Color.green)
}
// Currently I'm just using fixed offset values,
// but I want it to move to the actual center of the screen
.offset(buttonIsMoved ? CGSize(width: -50, height: -200) : .zero)
.animation(.easeInOut)
}
}
.padding(32)
}
}
Start of animation
End of animation
If I use .offset(), I don't know how to calculate the distance between the button's center and the center of the screen. I've also tried to use .position() but it's based on the parent view, which in this case is an HStack below the title, so it wouldn't be centered within the whole screen. I've also heard of GeometryReader, but I can't figure out how to use it for this purpose.
Here is possible solution - no hardcoding, based on SwiftUI native layout engine.
Tested with Xcode 11.4 / iOS 13.4
struct DemoAnimateLayout: View {
#State var buttonIsMoved = false
var body: some View {
ZStack {
VStack {
HStack {
Text("Title")
.font(.largeTitle)
Spacer()
}
Spacer()
}
VStack {
if !buttonIsMoved { // << here !!
Spacer()
}
HStack {
if !buttonIsMoved { // << here !!
Spacer()
}
// This is the button I want to animate to the center
Button(action: {
self.buttonIsMoved.toggle()
}) {
Text("This is a button")
.foregroundColor(.black)
.padding(16)
.background(Color.green)
}
}
}
.animation(.easeInOut) // << animate container layout !!
}.padding(32)
}
}
I have a NavigationView with a NavigationButton inside of it, but I cannot get the NavigationButton to be at the top of the screen and still be able to be pressed, even though the navigation bar is hidden.
This code:
struct ContentView : View {
var body: some View {
NavigationView {
VStack {
NavigationButton(destination: Text("Button Clicked")) {
Text("Hello World")
.background(Color.yellow)
}
Spacer()
}
}
.navigationBarHidden(true)
}
}
Looks like , but I want it to look like .
I've tried adding a negative padding to the top of the VStack (with .padding([.top], -95), and it visually works, but then I can't interact with the button by tapping it (I think it is behind the hidden navigation bar). I've tried setting the VStack's zIndex to 10000 to solve that, but it still didn't work. Is there a way for me to move the button up to the top while still making sure that the button recognizes when it is being tapped?
Add a navigationBarTitle before hiding your navigation bar:
struct ContentView : View {
var body: some View {
NavigationView {
VStack {
NavigationButton(destination: Text("Button Clicked")) {
Text("Hello World")
.background(Color.yellow)
}
Spacer()
}
.navigationBarTitle(Text("Title")) // Add this line
.navigationBarHidden(true)
}
}
Add this modifier to your NavigationView edgesIgnoringSafeArea(.top).