I'm making a MacOS app and a list of Mission with its own toolbar, and when clicking on a Mission, some informations about it are displayed with also its own toolbar.
The thing is that sometimes the border is here, sometimes not depending on the size of my Mission list -> here with border and here with no border.
Here is a sample of my code :
Entry point
ZStack {
NavigationLink(
isActive: Binding(
get: { selectedItem == nil },
set: { _ in }),
destination: { NoSelectionView() },
label: { EmptyView() })
List {
ForEach(missionsClients) { missionClient in
NavigationLink(), tag: missionClient.id, selection: $selectedItem, label: { MissionRow() })
}
}
}
.navigationTitle("Missions")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: { createNewMission = true },
label: { Image(systemName: "plus") })
}
}
NoSelectionView
struct NoSelectionView: View {
var body: some View {
Text("No Selection")
.foregroundColor(.gray)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.background()
.ignoresSafeArea()
.toolbar {
ToolbarItem(placement: .destructiveAction) {
Button(action: { }, label: { Image(systemName: "trash") }).disabled(true)
}
ToolbarItem(placement: .primaryAction) {
Button(action: { }, label: { Image(systemName: "square.and.pencil") }).disabled(true)
}
}
}
}
MissionRow
struct MissionRow: View {
var missionClient: MissionClient
var body: some View {
VStack(alignment: .leading) {
if let name = missionClient.mission.name.presence {
Text(name)
} else {
NoNameText()
}
if let clientName = missionClient.client.name.presence {
Text(clientName).foregroundStyle(.secondary)
}
}
}
}
Related
Is there a way to enable Gradient Buttons [ + | – ] with SwiftUI? Can't find any useful information about this topic.
https://developer.apple.com/design/human-interface-guidelines/components/menus-and-actions/buttons#gradient-buttons/
Update
I made a few cosmetic changes to #workingdogsupportUkraine answer. Now Gradient Buttons looks similar as in the Settings app on macOS 13. Please feel free to suggest any enhancements
import SwiftUI
struct TestView: View {
#State private var selection: Int?
struct GradientButton: View {
var glyph: String
var body: some View {
ZStack {
Image(systemName: glyph)
.fontWeight(.medium)
Color.clear
.frame(width: 24, height: 24)
}
}
}
var body: some View {
Form {
Section {
List(selection: $selection) {
ForEach(0 ..< 5) { Text("Item \($0)") }
}
.padding(.bottom, 24)
.overlay(alignment: .bottom, content: {
VStack(alignment: .leading, spacing: 0) {
Divider()
HStack(spacing: 0) {
Button(action: {}) {
GradientButton(glyph: "plus")
}
Divider().frame(height: 16)
Button(action: {}) {
GradientButton(glyph: "minus")
}
.disabled(selection == nil ? true : false)
}
.buttonStyle(.borderless)
}
.background(Rectangle().opacity(0.04))
})
}
}
.formStyle(.grouped)
}
}
you could try something simple like this:
struct ContentView: View {
var body: some View {
List {
ForEach(0 ..< 5) { Text("item \($0)") }
HStack {
Button(action: {print("plus")}) {
Image(systemName: "plus").imageScale(.small)
}
Divider()
Button(action: {print("minus")}) {
Image(systemName: "minus").imageScale(.small)
}
}.buttonStyle(.borderless)
.frame(width: 66, height: 33)
}
}
}
Is it possible to have an expanding menu that does not expand the HStack it is in?
I want a "menu bar" on top of my app and on the right is an expanding menu with all my objects in. But when I expand the menu all of the HStack expands which looks bad.
I tried putting a .fixedsize() on the HStack but it didn't help.
Do I have to move the menu or is it possible?
This is my code:
HStack{
Text("Input searchfunction here")
.foregroundColor(Color.white)
.padding()
Spacer()
Button(action: {
createViewIsActive = true
}, label: {
Text("Lägg till tecken.")
.foregroundColor(Color.white)
.onAppear {
if signs.count != 0 {
activeSign = signs[0]
}
}
Image(systemName: "plus")
.foregroundColor(Color.white)
})
.padding()
Button(action: {
deleteItems()
}, label: {
Text("Delete")
})
Spacer()
DisclosureGroup("Tecken", isExpanded: $isExpanded) {
ScrollView{
VStack{
ForEach(signs, id: \.self) { sign in
HStack{
Text(sign.name!)
.font(.title3)
.padding(.all)
.onTapGesture {
self.isExpanded.toggle()
self.activeSign = sign
}
}
}
}
}
}
.accentColor(.white) // Arrow color
.font(.title3)
.foregroundColor(.white) // Text color
.padding(.all)
.cornerRadius(8)
.frame(minWidth: 10, maxWidth: 300)
}
.background(Color.gray)
.padding()
I have tried to implement a search bar within my app (using swiftUI), which shows up when I build the app. However, it won't allow me to type into the actual text field. My code is below. I have been searching for solutions to this problem for awhile and can't seem to see any problems in my code. Is there something wrong with my TextField?
Code for search bar -
import SwiftUI
struct SearchBar: View {
#Binding var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search...", text: $text)
// .foregroundColor(Color("Teal"))
// .background(Color("Grey"))
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(Color(UIColor.systemGray3))
.frame(minWidth:0, maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets.init(top: 0, leading: 30, bottom: 0, trailing: 20))
// Search icon
if isEditing {
Button(action: {
self.text = ""
}, label: {
Image(systemName: "multiply.circle")
.foregroundColor(Color(UIColor.systemGray3))
.padding(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 30))
// Delete button
})
}
}
).onTapGesture {
self.isEditing = true
}
if isEditing{
Button(action: {
self.isEditing = false
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
// Hide delete button when search bar is not selected
}){
}
}
}
}
}
Code implementing search bar into my view -
import SwiftUI
struct BusinessList: View {
#EnvironmentObject var modelData: ModelData
#State private var showFavoritesOnly = false
#State var searchText = ""
var filteredBusinesses: [Business] {
modelData.businesses.filter { business in
(!showFavoritesOnly || business.isFavorite)
}
}
var body: some View {
NavigationView {
ScrollView {
LazyVStack {
VStack {
SearchBar(text: $searchText)
.padding(.bottom)
.padding(.top)
.padding(.top)
.padding(.top)
.padding(.top)
// Search bar won't show text when 'typed into'
ScrollView(.horizontal, showsIndicators: false) {
HStack {
Button(action: {
showFavoritesOnly = true
}) {
Text("Favourites")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(Color.white)
.frame(width: 100.0, height: 30)
.background(Color("Teal"))
// How the button looks
.cornerRadius(25)
.zIndex(1)
.textCase(.uppercase)
.padding(EdgeInsets.init(top: 0, leading: 20, bottom: 0, trailing: 0))
}
Button(action: {
}) {
Text("Hospitality")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(Color.white)
.frame(width: 100.0, height: 30)
.background(Color("Teal"))
// How the button looks
.cornerRadius(25)
.zIndex(1)
.textCase(.uppercase)
}
Button(action: {
}) {
Text("Retail")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(Color.white)
.frame(width: 100.0, height: 30)
.background(Color("Teal"))
// How the button looks
.cornerRadius(25)
.zIndex(1)
.textCase(.uppercase)
}
Button(action: {
}) {
Text("Lifestyle")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(Color.white)
.frame(width: 110.0, height: 30)
.background(Color("Teal"))
// How the button looks
.cornerRadius(25)
.zIndex(1)
.textCase(.uppercase)
}
}
.padding(.bottom)
}
}
.background(Color(UIColor.white))
.shadow(radius: 6)
.padding(.bottom)
ForEach(filteredBusinesses) { business in
NavigationLink(destination: BusinessDetail(business: business)) {
BusinessRow(business: business)
}
}
}
}
.navigationBarHidden(true)
.ignoresSafeArea()
}
}
}
struct BusinessList_Previews: PreviewProvider {
static var previews: some View {
BusinessList()
}
}
your code works for me with minor changes to the colors, nothing major. Maybe the colors did not show your typing, but the text was there.
This is the test I did:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct SearchBar: View {
#Binding var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search...", text: $text)
// .foregroundColor(Color("Teal"))
// .background(Color("Grey"))
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth:0, maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets.init(top: 0, leading: 30, bottom: 0, trailing: 20))
// Search icon
if isEditing {
Button(action: {
self.text = ""
}, label: {
Image(systemName: "multiply.circle")
.foregroundColor(.gray)
.padding(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 30))
// Delete button
})
}
}
).onTapGesture {
self.isEditing = true
}
if isEditing{
Button(action: {
self.isEditing = false
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
// Hide delete button when search bar is not selected
}){
}
}
}
}
}
struct ContentView: View {
#State var searchText = ""
var body: some View {
SearchBar(text: $searchText)
.frame(width: 222, height: 55).padding(.horizontal, 20)
.border(.red)
}
}
EDIT:
Your new code works for me, when you remove
.shadow(radius: 6)
and
.ignoresSafeArea()
Following this question, how to add a toolbar divider for a three column view in swiftUI life cycle
, I have a slightly different issue. I am trying to achive the same thing but the second and third columns are contained in a view which in turn is added inside a NavigationView next to first column which is the Sidebar.
Code example
import SwiftUI
#main
struct ThreeColumnsAppApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.frame(minWidth: 900, maxWidth: .infinity, minHeight: 600, maxHeight: .infinity)
}
.windowStyle(DefaultWindowStyle())
.windowToolbarStyle(UnifiedWindowToolbarStyle(showsTitle: true))
}
}
struct ContentView: View {
var body: some View {
NavigationView {
Sidebar()
.toolbar { Button(action: {}, label: { Image(systemName: "sidebar.left") }) }
MainContentView()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct Sidebar: View {
var body: some View {
List {
Text("Menu 1")
Text("Menu 2")
Text("Menu 3")
Text("Menu 4")
}
.frame(minWidth: 250)
.listStyle(SidebarListStyle())
}
}
struct MainContentView: View {
var body: some View {
NavigationView {
ListItemView()
DetailView()
}
.navigationTitle("Items List")
.navigationSubtitle("5 items found")
}
}
struct ListItemView: View {
var body: some View {
List {
Text("List item 1")
Text("List item 2")
Text("List item 3")
Text("List item 4")
Text("List item 5")
}
.frame(minWidth: 250)
.listStyle(InsetListStyle())
.toolbar {
Button(action: {}, label: { Image(systemName: "arrow.up.arrow.down.square") })
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail of list 1")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.toolbar {
Button(action: {}, label: { Image(systemName: "plus") })
Button(action: {}, label: { Image(systemName: "minus") })
Button(action: {}, label: { Image(systemName: "pencil") })
}
}
}
Output
As you can see bellow, the arrow icon, which should be on the second column's toolbar, is pushed to the right together with the detail view's toolbar icons. It seems NavigationView sees ListItemView and DetailView toolbars as a single one.
Desired output
Question
So, my question is how to have the toolbar icons aligned with their view?
In order to do this properly, you need a ToolbarItem on each of the three columns of the view. For example, this:
struct TripleNavView: View {
var body: some View {
NavigationView {
Text("Column 1")
.toolbar {
ToolbarItem {
Button(action: {}, label: {Image(systemName: "sidebar.left")})
}
}
Text("Column 2")
.toolbar {
ToolbarItem {
Button(action: {}, label: {Image(systemName: "plus")})
}
}
Text("Column 3")
.toolbar {
ToolbarItem {
Button(action: {}, label: {Image(systemName: "trash")})
}
}
}
.navigationTitle("Title")
}
}
Produces this:
The important thing is that both columns 2 and 3 need to have a ToolbarItem of some kind. If you only put a button on one of the columns, then SwiftUI will place it all the way at the trailing edge of the toolbar, and ruin the look.
As a result, if you don't want a button on a particular column, substitute a Spacer in place of the button.
Note that leaving it blank or using EmptyView() won't work - SwiftUI will optimize it out, and act as if you didn't include a ToolbarItem in the first place!
I'd like to add a "compose" button onto the .bottomBar of a .toolbar in my NavigationView.
Adding a Spacer() simply almost center aligns the item:
struct HomeView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.navigationTitle("Hello, World!")
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
Spacer()
Button(action: { print("Pressed") }) {
Image(systemName: "plus.circle.fill")
.imageScale(.large)
.font(.title)
}
}
}
}
}
}
}
This produces the following:
Not what I would have expected. What's even stranger is that that's not exactly center aligned, it's off by a few pixels.
So how do I:
Right align?
Center align?
Thanks
I had this same problem, here's what I found (Xcode 12.0 Beta 6)
Right Aligned
One way to do this is to use two .bottomBar items.
.toolbar(content: {
ToolbarItem(placement: .bottomBar) {
Spacer()
}
ToolbarItem(placement: .bottomBar) {
Button(action: {}) {
Text("Add List")
}
}
})
A bit cleaner is to use a ToolbarItemGroup with a Spacer().
.toolbar(content: {
ToolbarItemGroup(placement: .bottomBar) {
Spacer()
Button(action: {}) {
Text("Add List")
}
}
})
Centered
To center an item you can use .status as the placement.
.toolbar(content: {
ToolbarItem(placement: .status) {
Button(action: {}) {
Text("Add List")
}
}
})
Every ToolbarItem have to include single view, so just move Spacer into separated toolbar item
Tested with Xcode 12b3
.toolbar {
ToolbarItem(placement: .bottomBar) {
Spacer()
}
ToolbarItem(placement: .bottomBar) {
Button(action: { print("Pressed") }) {
Image(systemName: "plus.circle.fill")
.imageScale(.large)
.font(.title)
}
}
}
Note: to have it centered remove toolbar item with spacer