How to customise navigation title in SwftUI - swift

I have a very simple NavigationStack that I would like to customise the title, but I can't seem to find the right modifiers to achieve this.
NavigationStack {
List {
NavigationLink {
Text("My Child View")
} label: {
Label("Child View")
}
}.navigationTitle("Parent View")
}
I would like to change how the font looks for the .navigationTitle and be able to add a button to the right. When I add a .font modifier to .navigationTitle it adds it to the list items, not the title. And .navigationTitle only appears to accept a string.
I am looking to achieve the below (my button will be a + rather than a chevron).
From what I can tell, I can't use navigationTitle to achieve this. However, if I don't use that, then the back button has the wrong title (it uses the text Back instead of Parent View).

You can achieve both using the .toolbar modifier:
public var body: some View {
NavigationStack {
List {
NavigationLink {
Text("My Child View")
} label: {
Label("Child View", systemImage: "questionmark")
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Text("Parent View")
.font(.system(size: 22, weight: .bold))
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
} label: {
Image(systemName: "plus")
}
}
}
}
}

Related

How to create a custom toolbar?

I want to have a sticky toolbar that has multiple different filtering options above a List in SwiftUI. Is there a way to just place a full custom view inside the .toolbar property of a List?
With the below code, things look very unexpected
var body: some View {
NavigationView {
List() {
Text("List item")
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
VStack {
Toggle(isOn: $viewModel.isPound) {
Text("test")
}
.toggleStyle(.switch)
Text("A slider to control data")
Text("Another filtering option")
Text("Some random piece of information")
}
}
}
}
}
The NavigationBar is a fixed height for iOS, and is really designed for buttons. You can see the problem (and the space you have to work with) if you add a background colour to it.
struct ContentView: View {
#State private var isPound = false
var body: some View {
NavigationView {
List() {
Text("List item")
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
VStack {
Toggle(isOn: $isPound) {
Text("test")
}
.toggleStyle(.switch)
Text("A slider to control data")
Text("Another filtering option")
Text("Some random piece of information")
}
.border(.red)
}
}
.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(.red, for: .navigationBar)
}
}
}

How to navigate between NavigationLink while leave a part of main window stay the same in SwiftUI

I would like to navigate between different NavigationLinks in NavigationView while some part of the main window stay the same. For example, I want to make a music app and I want to let the play controller always on top, while I can display different navigation contents (songs page, artists page...) using the rest of the window.
Like what's showed in the picture below, I want to keep the red part always there while the blue part changes.
Navigation Example Picture
My code would be like below, but it won't work correctly. The AlwaysStayView() disappears when I click any NavigationLink on sidebar. So, how can I correct it or is there any solution (prefer in SwiftUI, but framework like UIKit would also be OK). I would appreciate it.
NavigationView {
List {
NavigationLink { DiscoverView() }
label: { Label("Discover", systemImage: "magnifyingglass") }
NavigationLink { SongsView() }
label: { Label("Songs", systemImage: "music.note") }
NavigationLink { ArtistsView() }
label: { Label("Artists", systemImage: "music.mic") }
}
}
.listStyle(SidebarListStyle())
VStack {
AlwaysStayView()
SongsView()
}
}
In this case the default details view and navigated details view should be the same, but updated content can be injected in it in navigation link.
Here is a demo. Tested with Xcode 13.3 / iPadOS 15.4
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink { DetailsView { DiscoverView() } }
label: { Label("Discover", systemImage: "magnifyingglass") }
NavigationLink { DetailsView { SongsView() } }
label: { Label("Songs", systemImage: "music.note") }
NavigationLink { DetailsView { ArtistsView() } }
label: { Label("Artists", systemImage: "music.mic") }
}
.navigationBarHidden(true)
.listStyle(SidebarListStyle())
DetailsView { SongsView() } // << here default !!
}
}
}
struct DetailsView<V: View>: View {
#ViewBuilder var content: () -> V // << injected via builder !!
var body: some View {
VStack(spacing: 0) {
AlwaysStayView()
content() // << changed part here !!
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.navigationBarHidden(true)
}
}
The NavigationLink from sidebar always exchanges the whole right screen area. So you would have to put your AlwaysStayView inside the navigation links – in each. Either on top level or inside the respective detail views. Here is one example:
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Discover", color: .blue) }
label: { Label("Discover", systemImage: "magnifyingglass") }
NavigationLink {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Songs", color: .teal) }
label: { Label("Songs", systemImage: "music.note") }
NavigationLink {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Artists", color: .mint) }
label: { Label("Artists", systemImage: "music.mic") }
}
.listStyle(.sidebar)
// Standard view if no item is lelected
VStack {
DetailView(title: "Always stay", color: .red).frame(height: 100)
DetailView(title: "Songs", color: .teal)
}
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("Toolbar")
}
}
}
}
struct DetailView: View {
let title: String
let color: Color
var body: some View {
Text(title).font(.title)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(color)
}
}

How to hide empty space caused by a Navigation View in SwiftUI?

I have a problem. I have empty space on the top of my views, and I think that the problem is the Navigation View. View Image
I figured it out to make it work and hide that empty space with this line of code, but if I'm using this approach, my toolbar items dissapears too, and I do not want this.
.navigationBarHidden(true)
I'll share my code below. Thanks !
TabView{
NavigationView{
VStack {
MeniuriView()
NavigationLink(isActive: $optionsActive) {
WaitingOrderView()
.environmentObject(syncViewModel)
} label: {
EmptyView()
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ToolbarButtons(numberOfProducts: menus.count) {
optionsActive = true
}
}
ToolbarItem(placement: .navigationBarLeading) {
Text(Texts.mainViewText1)
.font(.system(size: 24))
.fontWeight(.bold)
.padding()
}
}
}
.tabItem {
Text(Texts.mainViewText2)
Image(systemName: "fork.knife")
}
}
struct MeniuriView: View {
#EnvironmentObject var syncViewModel : SyncViewModel
var body: some View {
List {
ForEach(syncViewModel.menuType) { type in
SectionView(menuType: type)
}
}
.listStyle(PlainListStyle())
}
}
The space is reserved for the (large) NavigationTitle. You can use
.navigationBarTitleDisplayMode(.inline)
on the NavigationView to make it small. And if its empty, it won't show.

Why doesn't the button's image background show up in toolbar using SwiftUI?

In Apple's calendar app, they provide a toolbar item that toggles its style based on some state. It essentially acts as a Toggle. I'm trying to re-create this same thing in SwiftUI and make it work well in both light and dark mode. I was able to make a view that works as intended, until I put it into the toolbar and it no longer shows the selected state. Here is my attempt:
struct ToggleButtonView: View {
#State private var isOn = false
var body: some View {
Button(action: {
isOn.toggle()
}, label: {
if isOn {
Image(systemName: "list.bullet.below.rectangle")
.accentColor(Color(.systemBackground))
.background(RoundedRectangle(cornerRadius: 5.0)
.fill(Color.accentColor)
.frame(width: 26, height: 26))
} else {
Image(systemName: "list.bullet.below.rectangle")
}
})
.accentColor(.red)
}
}
And here is how I am actually placing the button into the toolbar:
struct TestView: View {
var body: some View {
NavigationView {
ScrollView {
ForEach(0..<5) { number in
Text("Number \(number)")
}
}
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
ToggleButtonView()
Button(action: {}, label: {
Image(systemName: "plus")
})
}
}
.navigationTitle("Plz halp")
}
.accentColor(.red)
}
}
Here are screenshots from the calendar app. Notice the toolbar item to the left of the search icon.
you could try this:
.toolbar {
// placement as you see fit
ToolbarItem(placement: .navigationBarTrailing) {
ToggleButtonView()
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}, label: {
Image(systemName: "plus")
})
}
}
This looks like an issue with how SwiftUI handles ToolbarItems before iOS 15. According to Asperi's answer to a similar question, "...all standards types (button, image, text, etc) are intercepted by ToolbarItem and converted into an appropriate internal representation."
Toggle buttons in SwiftUI iOS 15
Interestingly enough, iOS 15 now provides a standard solution to the use-case above using the .button toggle style, as shown in the following code:
struct ContentView: View {
#State private var isOn = false
var body: some View {
Toggle(isOn: $isOn) {
Image(systemName: "list.bullet.below.rectangle")
}
}
}

macOS toolbar items with image and text in SwiftUI

I'd like to achieve something similar in SwiftUI to what is described in Apple's Human Interface Guidelines about toolbars.
I tried using .toolbar { } but items are too small and NavigationLink doesn't change the selected View. I tried setting ExpandedWindowToolbarStyle() on WindowGroup.
Code:
NavigationView { }
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.automatic) {
HStack {
Text("")
NavigationLink(
destination: getDestination(forOption: Option.home)) {
VStack {
Image(systemName: Option.home.iconName)
Text("test")
}
.frame(height: 50)
}
}
}
}
current state:
You can use a customizable toolbar and labels.
.toolbar(id: "Main") {
ToolbarItem(id: "Sidebar") {
Button(action: {}) {
Label("Sidebar", systemImage: "sidebar.right")
}
}
}
Also it might be possible to use TitleAndIconLabelStyle with MacOS 11.3. I haven't tried it yet.