SwiftUI: How to align a NavigationBarItem with a large title navigation title? - ios

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"))
})
)

Related

Add accessory view below navigation bar title in SwiftUI

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("....")

Issues with a custom tab view combined with ScrollView

The look of my tab view is great. Everything in fact looks good. But with the current code. The ScrollView ends behind the tab bar instead of above. I'm guessing it has to do with it being a ZStack (it - meaning the content).
I tried adding padding on each view inside the switch (padding bottom same height as tab bar but not working).
The two results I've gotten is either it won't scroll enough to see all content but tab bar looks good.
OR
Scrolling works great but the tab bar has some overlaying issues visually with showing whitespace.
I just don't know how to move around the code any more to get a better result. :(
//Landing-page..
VStack {
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
switch tabController.selectedIndex {
case 0:
HomeView()
.padding(.bottom, 50)
case 1:
Text("Test..")
case 2:
Text("Test...")
case 3:
Text("Test....")
default:
VStack {
Text("Default View")
}
}
CustomTabView()
}
}
.background(Color.gray.ignoresSafeArea()).edgesIgnoringSafeArea(.bottom)
// TabView..
HStack {
// tabControllers.icons.count = 4 tabs at the moment
ForEach(0..<tabController.icons.count, id: \.self) { tab in
Button(action: {
// animation when changing tab comes here eventually
tabController.selectedIndex = tab
}, label: {
Spacer()
Image(systemName: tabController.icons[tab])
.frame(width: 50, height: 50)
.font(.system(
size: 22,
weight: tabController.selectedIndex == tab ? .semibold : .regular,
design: .default))
.foregroundColor(tabController.selectedIndex == tab ? Color.themeAccent : Color.themeAccent.opacity(0.3))
.background(tabController.selectedIndex == tab ? Color.primaryPurple : nil)
.cornerRadius(tabController.selectedIndex == tab ? 30 : 0)
Spacer()
})
}
}
.padding()
.background(Color.blue)
.clipShape(CShape())
.shadow(color: Color.black.opacity(0.3), radius: 20, x: 0.0, y: 0.0)
The ScrollView starts at the top in the HomeView.
Tab view showing correctly, but not scrollview
ScrollView showing correctly, but not tab view
I think you can get rid of the ZStack, put the switch/case in the VStack and the CustomTabView() below that. The ScrollView will push the CustomTabView to the bottom and not overlap it. If not all of the views in the switch are ScrollViews, then you can add a Spacer() above the CustomTabView, which will push the views apart to the top and bottom.

Add button to navigationBarTitle Swift ui

Im working in swift ui. I want to put a button on the side of the NavigationBar title.
I want to be able to click the user image and navigate to another view
Can this be done?
The buttons are placed in navigation bar using .navigationBarItems(). Any view can be used inside a Button, so a button almost like the one in your image can be declared like this:
var body: some View {
NavigationView {
// the rest of your UI components
.navigationBarTitle("Browse")
.navigationBarItems(trailing: Button(action: {}) {
VStack {
Spacer()
Image("name")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
})
}
}
Please note that it has a slightly different alignment (is going to get drawn a bit higher than your example).
This should solve the problem with the offset, but it is very hacky. And maybe there is a better answer to your problem than this. But if you are ok with this answer please give LuLuGaGa an upvote as I have copied a lot from him. And I did not come up with that answer myself, but I can not remember where I found the original answer.
NavigationView {
// the rest of your UI components
.navigationBarTitle("") // To hide the real navigationBarTitle
.navigationBarItems(leading:
Text("Browse").font(.largeTitle).bold().padding(.top, 10), // To add a fake navigationBarTitle
trailing: Button(action: {}) {
VStack {
Spacer()
Image("swiftui")
.resizable()
.frame(width: 45, height: 45)
.clipShape(Circle())
}
} .buttonStyle(PlainButtonStyle()) // You should also add that to your code otherwise the picture will turn blue
)
}
var body: some View {
NavigationView {
Text("SwiftUI")
.navigationBarTitle("Welcome")
.navigationBarItems(trailing:
Button("Bar button") {
print("Bar button!")
}
)
}
Try this
NavigationView{
.navigationBarTitle("Browse")
.navigationBarItems(trailing:
Button(action: { // Move to next View }){
Image()
}
)
}

Big unwanted space in subview navigation bar

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.

Prevent SwiftUI System Image from dynamically changing size for navigation bar item

I would like to add a plus button to the navigation bar items using the system plus image in SwiftUI. However, I am unable to prevent the system image from scaling dynamically when the accessibility font changes.
How can I stop it resizing so it acts like a standard UINavigationBarButtonItem system plus button?
The accessibility feature of holding on to a navigation bar button for large font types also doesn't work like it does with UIKit.
Really frustrating that a potentially simple thing can't be done with SwiftUI and that accessibility hasn't been thought about. The SwiftUI tutorials profile bar button also doesn't work for large font sizes. (PS SwiftUI is the future)
Here was my attempt:
struct ContentView : View {
var body: some View {
NavigationView {
List {
Text("Stop + Bar Button resizing")
.lineLimit(nil)
}
.navigationBarTitle(Text("Plus"))
.navigationBarItems(trailing:
PlusNavigationButton()
)
}
}
}
struct PlusNavigationButton: View {
var body: some View {
PresentationButton(
Image(systemName: "plus")
.resizable()
.frame(width: 44, height: 44),
destination: NewView())
}
}
You should build what you exactly want. So if you want to use 16x16 image with some extra hitTest area, you can build like this:
var body: some View {
PresentationButton(
HStack() {
Spacer()
Image(systemName: "plus")
.resizable()
.frame(width: 16, height: 16)
Spacer()
}.frame(width: 44, height: 44),
destination: NewView()
)
}
or if you like some space around your image and let it fill the rest, you can:
var body: some View {
PresentationButton(
Image(systemName: "plus")
.resizable()
.padding(14)
.frame(width: 44, height: 44),
destination: NewView()
)
}

Resources