Add accessory view below navigation bar title in SwiftUI - ios

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

Related

SwiftUI - Empty bottom bar after tapping NavigationLink

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

SwiftUI: Malfunctioning NavigationBar and TabBar in iOS 15

In SwiftUI, I would like to use a background color for my view while also setting navigationViewStyle to .stack, to force a single-column stack navigation Plus-sized devices.
var body: some View {
TabView {
NavigationView {
ScrollView {
ForEach(0..<100) { _ in
Text("Hello, world!")
.padding()
}
.frame(maxWidth: .infinity)
}
.navigationTitle("Demo")
// .background(Color.yellow) // I can do this …
}
// .navigationViewStyle(.stack) // … or this, but not both!
.tabItem {
Label("Demo", systemImage: "swift")
}
}
}
However, when I do both, the navigation bar won't collapse when I scroll down. Also both the navigation bar and tab bar appear without background.
When I only set the background, but leave out the line that sets the navigationViewStyle, everything looks fine in portrait mode, or smaller devices. But on a Plus-size device in landscape, it looks like this:
So I guess I really can't do this without setting the navigationViewStyle.
What can I do to fix this? Is this a bug that should be fixed by Apple? All help is greatly appreciated.
Use the .navigationViewStyle view modifier on the ScrollView
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
ScrollView {
ForEach(0..<100) { _ in
Text("Hello, world!")
.padding()
}
.frame(maxWidth: .infinity)
}
.navigationTitle("Demo")
.background(Color.yellow)
.navigationViewStyle(.stack)
}
.tabItem {
Label("Demo", systemImage: "swift")
}
}
}
}
Update
I guess, it is a bug. It does not work.
1
If all of your ScrollViews have the same background, use this once on a view.
struct ScrollViewBackground: ViewModifier {
let color: Color
func body(content: Content) -> some View {
content
.ignoresSafeArea(edges: .horizontal)
.onAppear {
UIScrollView.appearance().backgroundColor = UIColor(color)
}
}
}
extension View {
func setBackgroundColor(color: Color) -> some View {
return self.modifier(ScrollViewBackground(color: color))
}
}
2
Use introspect to access the underlaying UIScrollView and change its background. You need to also use .ignoresSafeArea(edges: .horizontal) on the ScrollView.
ScrollView {
Text("Item 2")
}
.introspectScrollView { scrollView in
scrollView.backgroundColor = UIColor(color.yellow)
}

SwiftUI - How to add button as a navigation title?

Is it possible to make the navbar title clickable like a button?
Tried .navigationBarTitle(Button(....)), but this won’t work bc button doesn't conform to string protocol...
Here is a possible solution
struct ContentView: View {
var body: some View {
NavigationView {
GeometryReader { geo in
List {
Text("Have a nice day!")
Text("Today is sunny")
}.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(leading: HStack{
Spacer().frame(width: geo.size.width * 0.5)
VStack{
Text("Title")
Button(action: {
print("I'm feeling lucky ;)")
})
{
Text("Button")
}
}
Spacer().frame(width: geo.size.width * 0.5)
})
}
}
}
}
You can not create a button on NavigationTitle in SwiftUI Navigation View
Solution: You have to create custom Navigation Bar & Other Views

SwiftUI Bring Down Navigation Items

In my SwiftUI app, I would like to bring the navigation bar items down like in Apple's own UIKit apps.
Seen below is a screenshot from the Health app. Notice how the profile picture is in line with the 'Summary' text. This is what I am looking to achieve.
I have tried using .padding(.top, 90) but this has not worked as it does not bring down the virtual box that allows the button to be clicked. Using padding means that you have to tap the button above the image/text.
Thank you.
Unfortunately I didn't find any solution for changing navigation bar height in iOS 13 with SwiftUI, and had the same issues earlier. Solution below will fit you, if your navigation bar is always only black and you're ok with gap on the top:
struct NavBarCustomItems: View {
init() {
setNavigationBarToBlackOnly()
}
func setNavigationBarToBlackOnly() {
let blackAppearance = UINavigationBarAppearance()
blackAppearance.configureWithOpaqueBackground()
blackAppearance.backgroundColor = .black
blackAppearance.shadowColor = .clear // to avoid border line
UINavigationBar.appearance().standardAppearance = blackAppearance
UINavigationBar.appearance().scrollEdgeAppearance = blackAppearance
}
var body: some View {
NavigationView {
VStack {
NavigationBarMimicry()
// here is your content
HStack {
Text("Favorites")
Spacer()
Button(action: {}) { Text("Edit") }
}
.padding()
Spacer()
VStack {
Text("Main screen")
}
// you need spacer(s) to be sure, that NavigationBarMimicry is always on the top
Spacer()
}
}
}
}
// MARK: here is what you need in navigation bar
struct NavigationBarMimicry: View {
var body: some View {
HStack {
Text("Summary")
.bold()
.font(.system(size: 40))
.foregroundColor(.white)
.padding(.horizontal)
Spacer()
Rectangle()
.foregroundColor(.white)
.frame(width: 40)
.padding(.horizontal)
}
.background(Color.black)
.frame(height: 40)
.navigationBarTitle("", displayMode: .inline)
// you can add it to hide navigation bar, navigation will work via NavigationLink
// .navigationBarHidden(true)
}
}
struct NavBarCustomItems_Previews: PreviewProvider {
static var previews: some View {
NavBarCustomItems().environment(\.colorScheme, .dark)
}
}
the result should be like this:
P.S. maybe the other ways are:
Put views in this order: VStack { NavigationBarMimicry(); NavigationView {...}};
uncomment line of code: .navigationBarHidden(true);

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

Resources