SwiftUI - Multiple Buttons in a List row
You need to use BorderlessButtonStyle() or PlainButtonStyle().
List([1, 2, 3], id: \.self) { row in
HStack {
Button(action: { print("Button at \(row)") }) {
Text("Row: \(row) Name: A")
}
.buttonStyle(BorderlessButtonStyle())
Button(action: { print("Button at \(row)") }) {
Text("Row: \(row) Name: B")
}
.buttonStyle(PlainButtonStyle())
}
}
SwiftUI - Two buttons in a List
Set the button style to something different from the default, e.g., BorderlessButtonStyle()
struct Test: View {
var body: some View {
NavigationView {
List {
ForEach([
"Line 1",
"Line 2",
], id: \.self) {
item in
HStack {
Text("\(item)")
Spacer()
Button(action: { print("\(item) 1")}) {
Text("Button 1")
}
Button(action: { print("\(item) 2")}) {
Text("Button 2")
}
}
}
.onDelete { _ in }
.buttonStyle(BorderlessButtonStyle())
}
.navigationBarItems(trailing: EditButton())
}
.accentColor(.red)
}
}
How do I keep multiple button actions separate in SwiftUI ForEach content?
This is default behaviour of List, it identifies Button in row and makes entire row active, use instead .onTapGesture
as below
List {
ForEach(tasks, id: \.self) { task in
HStack {
Image(systemName: task.isComplete ? "square.fill" : "square")
.padding()
.onTapGesture {
task.isComplete.toggle()
try? self.moc.save()
print("Done button tapped")
}
Text(task.name ?? "Unknown Task")
Spacer()
Image("timer")
.onTapGesture {
print("timer button tapped")
}
}
}
.onDelete(perform: deleteTask)
}
Two Buttons inside HStack taking action of each other
Need to use onTapGesture
instead of action
like this way.
Button(action: {}) {
Text("watch")
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.red)
.onTapGesture {
print("watch")
}
SwiftUI Multiple Buttons with Popovers in HStack Behavior
Normally you'd use popover(item:content:)
, but you'll get an error... even the example in the documentation crashes.
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'UIPopoverPresentationController (<UIPopoverPresentationController: 0x14a109890>) should have a non-nil sourceView or barButtonItem set before the presentation occurs.'
What I came up with instead is to use a singular @State presentingItem: Item?
in ContentView
. This ensures that all the popovers are tied to the same State
, so you have full control over which ones are presented and which ones aren't.
But, .popover(isPresented:content:)
's isPresented
argument expects a Bool
. If this is true it presents, if not, it will dismiss. To convert presentingItem
into a Bool
, just use a custom Binding
.
Binding(
get: { presentingItem == item }, /// present popover when `presentingItem` is equal to this view's `item`
set: { _ in presentingItem = nil } /// remove the current `presentingItem` which will dismiss the popover
)
Then, set presentingItem
inside each button's action. This is the part where things get slightly hacky - I've added a 0.5
second delay to ensure the current displaying popover is dismissed first. Otherwise, it won't present.
if presentingItem == nil { /// no popover currently presented
presentingItem = item /// dismiss that immediately, then present this popover
} else { /// another popover is currently presented...
presentingItem = nil /// dismiss it first
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
presentingItem = item /// present this popover after a delay
}
}
Full code:
/// make equatable, for the `popover` presentation logic
struct Item: Equatable {
let id = UUID()
var name: String
}
struct ContentView: View {
@State var presentingItem: Item? /// the current presenting popover
let items = [
Item(name: "item1"),
Item(name: "item2"),
Item(name: "item3")
]
var body: some View {
HStack {
MyGreatItemView(presentingItem: $presentingItem, item: items[0])
MyGreatItemView(presentingItem: $presentingItem, item: items[1])
MyGreatItemView(presentingItem: $presentingItem, item: items[2])
}
.padding(300)
}
}
struct MyGreatItemView: View {
@Binding var presentingItem: Item?
let item: Item /// this view's item
var body: some View {
Button(action: {
if presentingItem == nil { /// no popover currently presented
presentingItem = item /// dismiss that immediately, then present this popover
} else { /// another popover is currently presented...
presentingItem = nil /// dismiss it first
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if presentingItem == nil { /// extra check to ensure no popover currently presented
presentingItem = item /// present this popover after a delay
}
}
}
}) {
Text(item.name)
}
/// `get`: present popover when `presentingItem` is equal to this view's `item`
/// `set`: remove the current `presentingItem` which will dismiss the popover
.popover(isPresented: Binding(get: { presentingItem == item }, set: { _ in presentingItem = nil }) ) {
PopoverView(item: item)
}
}
}
struct PopoverView: View {
let item: Item /// no need for @State here
var body: some View {
print("new PopoverView")
return Text("View for \(item.name)")
}
}
Result:
Buttons in SwiftUI List ForEach view trigger even when not tapped ?
Whenever you have multiple buttons in a list row, you need to manually set the button style to .borderless
or .plain
. This is because buttons “adapt” to their context.
According to the documentation:
If you create a button inside a container, like a List, the style resolves to the recommended style for buttons inside that container for that specific platform.
So when your button is in a List, its tap target extends to fill the row and you get a highlight animation. SwiftUI isn’t smart enough to stop this side effect when you have more than 2 buttons, so you need to set buttonStyle
manually.
CellTestView()
.buttonStyle(.borderless)
Result:
Related Topics
How to Capture Notifications in a Wkwebview
Swift 3 (Spritekit): Reseting the Gamescene After the Game Ends
Swift Protocol and Return Types on Global Functions
Is There a Difference Between "Is" and Iskindofclass()
How to Make a Swift Enum with Uicolor Value
Initializer for Conditional Binding Must Have Optional Type, Not 'String'
How to Hide Labels in iOS-Charts
How to Open Settings App Programmatically
How to Link a .Sks File to a .Swift File in Spritekit
Firebase Storage Downloadurl()' Is Deprecated: Use 'Storagereference.Downloadurlwithcompletion()
Swiftui - Two Buttons in a List
Keyboard Overlaying Action Sheet in iOS 13.1 on Cncontactviewcontroller
Accessing Mkmapview Elements as Uiviewrepresentable in the Main (Contentview) Swiftui View
What Is the Practical Use of Nested Functions in Swift
Pointers, Pointer Arithmetic, and Raw Data in Swift
Swift Combine Sink Stops Receiving Values After First Error