How to create a custom toolbar? - swift

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

Related

Change the color of navigationTitle inside NavigationStack

I would like to change the color of navigationTitle inside NavigationStack.
struct InterestView: View {
var body: some View {
NavigationStack {
VStack {
}
.navigationTitle("interest".uppercased())
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
print("Image tapped!")
} label: {
Text("add".uppercased())
.foregroundColor(Color.primaryColor)
.font(.headline)
}
}
}
}
}
}
I could not find any modifier to change it.
In SwiftUI, you cannot change title color with a simple modifier. You need to change UINavigation Appearance but this will effect globally.
Alternative solution:
You are already using toolbar, so adding title to toolbar is easy as follow. I hope this solves your problem.
struct InterestView: View {
var body: some View {
NavigationStack {
VStack {
}
.toolbar {
ToolbarItemGroup(placement: .principal) {
Text("interest".uppercased())
.foregroundColor(.red)
}
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
print("Image tapped!")
} label: {
Text("add".uppercased())
.foregroundColor(Color.red)
.font(.headline)
}
}
}
}
}
}

Multiple NavigationViews in SwiftUI - How to get rid of multiple toolbar items (i.e the back-button on the left corner)?

I am making an app where the first view the users see is a home screen with buttons that takes them to a second view. One of the second views present the user with a list of items. When the user clicks on one of these items the user comes to a detailed view of the item. When the user comes to the detailed view he is unfortunately presented with two toolbar buttons in the corner as can be seen here:
.
I know that one of the solutions is to only have one navigationview and that solves my problem. But I need to have toolbar items in my listview to be able to add more items, sort the list and have the list searchable which I'm not able to do without navigationView. I Have tried using scrollView and NavigationStack but it comes out blank.
Does anyone have an idea how to work with mulitple views, not getting double up "back buttons" on the toolbar and still have other toolbar items?
View one: (Home Screen):
NavigationView {
ZStack {
VStack {
Text(title)
.font(.custom("Glacial Indifference", size: 34, relativeTo: .headline).weight(.bold))
.multilineTextAlignment(.leading)
.foregroundColor(.white)
.tracking(10)
.padding(8)
.background(
Rectangle()
.fill(.gray)
.frame(width: 1000, height: 150)
.ignoresSafeArea()
.opacity(0.5))
Spacer()
}
VStack {
NavigationLink {
MapView()
} label: {
Buttons(str: "Cheese Map")
}
.padding(.bottom, 200)
}
VStack {
NavigationLink {
TabView()
} label: {
Buttons(str: "Cheese List")
}
.padding(.bottom, 400)
}
Second View (list):
NavigationView {
List {
ForEach(items, id: \.id) { item in
NavigationLink {
ItemView(item: item)
} label: {
ListItem(item: item)
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
showingAddItem = true
} label: {
Image(systemName: "plus")
Text("Add Item")
.font(.footnote)
.italic()
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Sort") {
Picker("Filter Options", selection: $selectedSort) {
ForEach(sortOptions, id: \.self) {
value in
Text(value)
.tag(value)
}
}
}
.onChange(of: selectedSort) { _ in
let sortBy = sorts[sortOptions.firstIndex(of: selectedSort)!]
items.sortDescriptors = sortBy.descriptors
}
}
}
.sheet(isPresented: $showingAddItems) {
AddItemsView(items: Items())
}
.navigationTitle("Item List")
.searchable(text: $searchText)
}
}
}
DetailView:
ScrollView {
ZStack {
VStack {
//More code...
Both .toolbar and .searchable find the nearest enclosing NavigationView automatically. You do not need a NavigationView in your list view.
Here's a self-contained demo. It looks like this:
Here's the code:
import SwiftUI
import PlaygroundSupport
struct HomeScreen: View {
var body: some View {
NavigationView {
List {
NavigationLink("Cheese Map") { Text("Map") }
NavigationLink("Cheese List") { ListView() }
}
.navigationTitle("Home Screen")
}
.navigationViewStyle(.stack)
}
}
struct ListView: View {
#State var items = ["Cheddar", "Swiss", "Edam"]
#State var search: String = ""
var filteredItems: [String] {
return items.filter {
search.isEmpty
|| $0.localizedCaseInsensitiveContains(search)
}
}
var body: some View {
List(filteredItems, id: \.self) {
Text($0)
}
.searchable(text: $search)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
items.append("Gouda")
}
} label: {
Label("Add Item", systemImage: "plus")
}
.disabled(items.contains("Gouda"))
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Sort") {
Button("Ascending") {
withAnimation {
items.sort()
}
}
Button("Descending") {
withAnimation {
items.sort()
items.reverse()
}
}
}
}
}
.navigationTitle("Cheese List")
}
}
PlaygroundPage.current.setLiveView(HomeScreen())

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.

Moving between view controllers using toolbar

This is a beginner question
I want to move to the next view controller (OptionsView) when the button in the toolbar tapped, how can I do it?
var body: some View {
NavigationView {
VStack{
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
}
.navigationTitle("Profile")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
OptionsView()
} label: {
Label("Settings", systemImage: "gear")
}
}
}
}
}
}
You can use the isActive property of a NavigationLink to programmatically activate the link. The NavigationLink can use EmptyView as it's label so that it's hidden, since you only need it to be activated via the Button.
Then, inside of your Button's action, instead of trying to insert the view (which should always be in the view hierarchy -- not inside an action), you can set the the #State variable to activate.
struct ContentView : View {
#State private var optionsActive = false
var body: some View {
NavigationView {
VStack{
Text("Hello, World!")
NavigationLink(isActive: $optionsActive) {
OptionsView()
} label: {
EmptyView()
}
}
.navigationTitle("Profile")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
optionsActive = true
} label: {
Label("Settings", systemImage: "gear")
}
}
}
}
}
}

SwiftUI TabBar: Action for tapping TabItem of currently selected Tab to reset view

The app I am working on is based around a TabBar, and when I am on a tab I want to be able to click the tabItem again to reset the view, similar to how Twitter does it in their tabBar.
I do not know how to recognize that action though. Adding a button to the TabItem is not working, addidng a tapGesture modifier isn't either, and I can't think of anything else I could try.
struct ContentView: View {
var body: some View {
TabView() {
Text("Tab 1")
.tabItem {
Image(systemName: "star")
.onTapGesture {
print("Hello!")
}
Text("One")
}
.tag(0)
Text("Tab 2")
.tabItem {
Button(action: {
print("Hello!")
}, label: {
Image(systemName: "star.fill")
})
}
.tag(1)
}
}
}
It should't automatically reset when opening the tab again, which I have seen discussed elsewhere, but when tapping the tabItem again.
What other things am I possibly missing here?
Here is possible solution - inject proxy binding around TabView selection state and handle repeated tab tapped before bound value set, like below.
Tested with Xcode 12.1 / iOS 14.1
struct ContentView: View {
#State private var selection = 0
var handler: Binding<Int> { Binding(
get: { self.selection },
set: {
if $0 == self.selection {
print("Reset here!!")
}
self.selection = $0
}
)}
var body: some View {
TabView(selection: handler) {
Text("Tab 1")
.tabItem {
Image(systemName: "star")
Text("One")
}
.tag(0)
Text("Tab 2")
.tabItem {
Image(systemName: "star.fill")
}
.tag(1)
}
}
}