How can we dynamically change the items of .toolbar modifier in SwiftUI?
I want to achieve something like the below, but it doesn't compile.
#State private var flag = false
var body: some View {
//
// some view code here
//
.toolbar {
if flag {
ToolbarItemGroup(placement: .principal) {
Text("Something full width")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
} else {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button("Something else") { ... }
}
}
}
}
Put condition inside group, like
.toolbar {
ToolbarItemGroup(placement: .principal) {
if flag {
Button("Something") { ... }
}
}
ToolbarItemGroup(placement: .navigationBarTrailing) {
if !flag {
Button("Something else") { ... }
}
}
}
I could achieve it by combining .principal placement and HStack within it. In case you have any better ideas, that's really welcome and appreciated.
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
if flag {
SearchBar(searchText: $searchText)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Button("Cancel") { flag = false }
} else {
Spacer()
Button {
flag = true
} label: {
Image(systemName: "text.magnifyingglass")
}
Button {
} label: {
Image(systemName: "phone.connection")
}
Button {
} label: {
Image(systemName: "line.3.horizontal")
}
}
}
}
}
When the flag is false
and then true
Related
I want to animate the position of Expand button, but instead it appears right at the final position. There's a strange hack .transition(.scale) that fixes my problem, but I hope to see a better not hacky solution.
struct TextView: View {
#State var isExpanded = false
var body: some View {
VStack(spacing: 10) {
Text("This is the most recent test comment with a picture. Lörem ipsum berölogi kjolprotest. Trist gigade. Sms-livräddare grönt elcertifikat.")
.lineLimit(isExpanded ? nil : 2)
HStack {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text(isExpanded ? "Less" : "Expand")
}
// .transition(.scale)
}
}
.padding(.all)
.background(Color.yellow)
.cornerRadius(13)
.padding(.horizontal)
}
}
One option is to use a ZStack containing Buttons in both states, and use the .opacity modifier to hide or show them…
struct ContentView: View {
#State var isExpanded = false
var body: some View {
VStack(spacing: 10) {
Text("This is the most recent test comment with a picture. Lörem ipsum berölogi kjolprotest. Trist gigade. Sms-livräddare grönt elcertifikat.")
.lineLimit(isExpanded ? nil : 2)
HStack {
ZStack {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Less")
}
.opacity(isExpanded ? 1 : 0)
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Expand")
}
.opacity(isExpanded ? 0 : 1)
}
}
}
.padding(.all)
.background(Color.yellow)
.cornerRadius(13)
.padding(.horizontal)
}
}
Or alternatively, use .matchedGeometryEffect
if isExpanded {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Less")
}
.matchedGeometryEffect(id: "button", in: namespace)
} else {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Spacer()
Text("Expand")
}
.matchedGeometryEffect(id: "button", in: namespace)
}
Obviously there's some duplication so you would pull the Button out into a func or its own View struct.
I am trying to put an option for .onlongpressgesture for this list of groups. However, it doesn't seem to work and I figure, it will work on Button.
I have tried to apply ".onTapGesture" and ".onLongPressGesture", but there is no effect.
Is there a way I can transform the Code below from a NavigationLink to a Button with the same destination when tapped and an additional Menu (called "OptionsmenuView") when long pressed?
The NavigationLink:
VStack (spacing: 20){
ForEach(groups, id:\.self) { Group in
NavigationLink(destination: GroupView()) {
ZStack (alignment: .bottomLeading) {
Image(uiImage: (UIImage(data: Group.groupThumbnail ?? self.image) ?? UIImage(named: "defaultGroupThumbnail"))!)
.resizable(capInsets: EdgeInsets())
.aspectRatio(contentMode: .fill)
.frame(height: 200.0, alignment: .center)
.cornerRadius(22)
VStack (alignment: .leading) {
Text("\(Group.groupTitle ?? "Untitled")")
.font(.title)
.fontWeight(.heavy)
.multilineTextAlignment(.leading)
Text("Consists of 5 Flowers")
}
.padding([.leading, .bottom], 18.0)
.foregroundColor(.primary)
}
.listRowBackground(Color.black)
}
}
}
The OptionMenuView:
struct OptionsMenuView: View {
var body: some View {
Menu {
Button("Cancel", role: .destructive) {
// Do something
}
Button {
// Do something
} label: {
Label("Edit", systemImage: "square.and.pencil")
}
Button {
// Do something
} label: {
Label("Delete", systemImage: "trash")
}
} label: {
Label("Settings", systemImage: "gearshape.fill")
}
}
}
I appreciate any form of advice. Thanks in advance.
Couldn't you just use .contextMenu ?
NavigationView {
List (0..<10) { i in
NavigationLink {
Text("DestionationView")
} label: {
Text("Item \(i)")
}
.contextMenu { // << here
Button("Cancel", role: .destructive) {
// Do something
}
Button {
// Do something
} label: {
Label("Edit", systemImage: "square.and.pencil")
}
Button {
// Do something
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
In Xcode 12 Beta 6, dismissing a sheet doesn't work inside a button's action inside a ToolbarItem.
My sheet view looks like:
NavigationView {
Form {
Section {
TextField("Name", text: $name)
}
}
.navigationTitle("New Thing")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(action: {
self.presentation.wrappedValue.dismiss()
}, label: {
Text("Cancel")
})
}
ToolbarItem(placement: .confirmationAction) {
Button(action: {
do {
// some saving logic
try managedObjectContext.save()
self.presentation.wrappedValue.dismiss()
} catch {
print("didn't save due to \(error.localizedDescription)")
}
}, label: {
Text("Save")
})
}
}
}
EDIT: here's how I constructed the sheet
var body: some View {
List {
ForEach(results) { result in
HStack {
NavigationLink(destination: SingleResultView(result: result)) {
SingleResultRowView(result: result)
}
}
}
.onDelete(perform: deleteResult)
}
.navigationTitle("All Results")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
self.isNewResultSheetPresented.toggle()
}, label: {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
.sheet(isPresented: $isNewResultSheetPresented) {
NewResultView()
// ^ this contains the code above
.environment(\.managedObjectContext, self.managedObjectContext)
}
}
}
}
When the sheet is first presented, immediately a console log appears:
2020-09-13 20:52:02.333679-0700 MyApp[2710:89263]
[Presentation] Attempt to present <_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x1027b7890> on
<_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x10270d620>
(from <_TtGC7SwiftUIP10$194f39bd428DestinationHostingControllerVS_7AnyView_: 0x103605930>)
which is already presenting <_TtGC7SwiftUI22SheetHostingControllerVS_7AnyView_: 0x103606d60>.
I can dismiss the sheet only by swiping down.
For reference, I went back to an older commit where I used NavigationBarItems and it worked perfectly. But from what I understand, this is a situation where I'm supposed to be using ToolbarItem.
Does anybody know why the good old self.presentation.wrappedValue.dismiss() doesn't work here or why is the sheet being presented twice?
Move sheet out of toolbar, as
var body: some View {
List {
ForEach(results) { result in
HStack {
NavigationLink(destination: SingleResultView(result: result)) {
SingleResultRowView(result: result)
}
}
}
.onDelete(perform: deleteResult)
}
.navigationTitle("All Results")
.sheet(isPresented: $isNewResultSheetPresented) { // << here !!
NewResultView()
// ^ this contains the code above
.environment(\.managedObjectContext, self.managedObjectContext)
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
self.isNewResultSheetPresented.toggle()
}, label: {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
})
}
}
}
I'm trying to make a custom menu (dynamic based on the editMode) in the trailing part of the navigation bar. I need 3 buttons in "view" mode and only one in "edit" mode.
The problem is I can't align the buttons to the right as below:
As you can see, the "done" button is way to the left.
I tried adding Spacers() but no luck.
.navigationBarItems(
leading: BackButton(label: "") {
self.presentation.wrappedValue.dismiss()
},
trailing:
HStack {
if self.mode?.wrappedValue == .inactive {
HStack(alignment: .center, spacing: 20) {
Button(action: {
////////
}) {
Image(systemName: "trash")
.imageScale(.large)
}
////////
Button(action: {
}) {
Image(systemName: "square.and.arrow.up")
.imageScale(.large)
}
CustomEditButton() {
////////
}
}
} else {
HStack {
CustomEditButton() {
/////
}
}
}
}
)
Same issue. Spacer()'s inside or outside of conditional statement don't work.
I noticed is that the alignment is fixed/correct when popping back to the view from a NavigationLink screen, though...
In any case, I made it work on my end using opacities depending on the condition driving the changes.
In your case, a workaround may look like this:
.navigationBarItems(
leading: BackButton(label: "") {
self.presentation.wrappedValue.dismiss()
},
trailing:
ZStack {
HStack(alignment: .center, spacing: 20) {
Button(action: {
////////
}) {
Image(systemName: "trash")
.imageScale(.large)
}
Button(action: {
////////
}) {
Image(systemName: "square.and.arrow.up")
.imageScale(.large)
}
CustomEditButton() {
////////
}
}
.opacity((self.mode?.wrappedValue == .inactive) ? 1.0 : 0.0)
HStack {
CustomEditButton() {
/////
}
}
.opacity((self.mode?.wrappedValue == .inactive) ? 0.0 : 1.0)
}
)
I would like to show contextMenu by clicking on the left mouse button?
How to manually show view in SwiftUI?
Image(systemName: "book")
.contextMenu {
Text("something1")
Text("something2")
Text("something3")
}
You can use MenuButton with a menuButtonStyle to create a button which, when clicked, shows a menu. Seems to be Mac only currently.
MenuButton("Menu") {
Button(action: {
print("Clicked an item")
}) {
Text("Menu Item Text")
}
}.menuButtonStyle(BorderlessButtonMenuButtonStyle())
The currently accepted answer uses MenuButton, which is now deprecated. Below is how to use the new Menu option.
This is possible now via Menu (iOS 14+, MacOS 11+)
Menus are covered in this WWDC video at ~11:15.
Playground Example:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
HStack {
// Other views
Text("Example View 1")
// Button, that when tapped shows 3 options
Menu {
Button(action: {
}) {
Label("Add", systemImage: "plus.circle")
}
Button(action: {
}) {
Label("Delete", systemImage: "minus.circle")
}
Button(action: {
}) {
Label("Edit", systemImage: "pencil.circle")
}
} label: {
Image(systemName: "ellipsis.circle")
}
}
.frame(width: 300, height: 300, alignment: .center)
}
}
PlaygroundPage.current.setLiveView(ContentView())
If you wish to support MacOS 10.15 you could do something like:
if #available(iOS 14, macOS 11, *) {
Menu {
Button(action: {
}) {
Label("Add", systemImage: "plus.circle")
}
Button(action: {
}) {
Label("Delete", systemImage: "minus.circle")
}
Button(action: {
}) {
Label("Edit", systemImage: "pencil.circle")
}
} label: {
Image(systemName: "ellipsis.circle")
}
} else if #available(macOS 10.15, *) {
MenuButton(
label: Image(nsImage: NSImage(named: NSImage.actionTemplateName)!),
content: {
Button(action: {}) {
HStack {
Image(nsImage: NSImage(named: NSImage.addTemplateName)!)
Text("Add")
}
}
Button(action: {}) {
HStack {
Image(nsImage: NSImage(named: NSImage.removeTemplateName)!)
Text("Delete")
}
}
Button(action: {}) {
HStack {
Image(nsImage: NSImage(named: NSImage.refreshTemplateName)!)
Text("Edit")
}
}
})
.menuButtonStyle(BorderlessButtonMenuButtonStyle())
} else {
// Custom code here would be needed to support iOS 13 and MacOS 10.14
}