Asked 1 month ago by AuroraProbe382
How can I implement a selectable view modifier in SwiftUI for list cells?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by AuroraProbe382
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm trying to create a clean, reusable view modifier to apply a selectable behavior to my list cells.
I initially implemented it by overlaying a color based on a condition, like this:
SWIFTstruct ExpenseSelectableCell: ViewModifier { @State var isSelected: Bool func body(content: Content) -> some View { content .overlay { Color.green.opacity(isSelected ? 0.2 : 0) } } } extension View { func selectableCell(isSelected: Bool) -> some View { self.modifier(ExpenseSelectableCell(isSelected: isSelected)) } }
However, the selected behavior does not seem to update as expected. Here’s how I’m using the modifier within my list view:
SWIFTstruct AllExpensesView: View { @Environment(\.expenseVM) var expensesVM @State private var selectedItem: Expense? @State var shouldPresentAddExpense = false var body: some View { List { ForEach(expensesVM.expenses, id: \.self) { expense in ExpenseCellView(expense: expense) .selectableCell(isSelected: selectedItem == expense) .listRowSeparator(.hidden) .onTapGesture { selectedItem = expense } }.onDelete { indexSet in Task { try await expensesVM.delete(removeAt: indexSet) } } } } }
I’ve tried using @State and even Bindings to update the selection, but the view doesn’t seem to reload. Why is the selected state not updating, and is there a cleaner way to achieve this selectable behavior?
Comments 0
•Answers 3
•Views 69
Answer by TychoAI Agent • 1 month ago
Below is an explanation of what's happening and a suggested solution.
Your view modifier uses an internal @State property:
SWIFTstruct ExpenseSelectableCell: ViewModifier { @State var isSelected: Bool // ... }
When you pass a new value from the parent view (via the modifier call), the modifier isn’t "listening" for changes because its own @State variable is initialized once and then managed internally. In other words, the boolean you pass in never updates the internal state once it’s set.
Since you want the modifier to react to external changes (i.e. when selectedItem changes), remove the @State and simply use a constant property. This way, each time the parent view updates the state, the modifier is recreated with the new value.
SWIFTstruct ExpenseSelectableCell: ViewModifier { let isSelected: Bool func body(content: Content) -> some View { content .overlay { Color.green.opacity(isSelected ? 0.2 : 0) } } } extension View { func selectableCell(isSelected: Bool) -> some View { self.modifier(ExpenseSelectableCell(isSelected: isSelected)) } }
And your parent view remains the same:
SWIFTstruct AllExpensesView: View { @Environment(\.expenseVM) var expensesVM @State private var selectedItem: Expense? @State var shouldPresentAddExpense = false var body: some View { List { ForEach(expensesVM.expenses, id: \.self) { expense in ExpenseCellView(expense: expense) .selectableCell(isSelected: selectedItem == expense) .listRowSeparator(.hidden) .onTapGesture { selectedItem = expense } } .onDelete { indexSet in Task { try await expensesVM.delete(removeAt: indexSet) } } } } }
By removing the internal state from your view modifier and using a constant property instead, the modifier will always reflect the current selection state from its parent. This approach simplifies your code while achieving the desired behavior.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by OrbitalEngineer430 • 1 month ago
So the problem that i was facing is passing the bool to a viewModifier
. I needed to pass the selectedItem
as binding in order to work properly
SWIFT// MARK: - Selectable Cell ViewModifier struct ExpenseSelectableCell: ViewModifier { @Binding var selectedItem: Expense? var expense: Expense func body(content: Content) -> some View { content .overlay { Color.black.opacity(isSelected() ? 0.2 : 0) } } func isSelected() -> Bool { guard let selectedItem else { return false } return selectedItem.id == expense.id } } extension ExpenseCellView { func selectableCell(selectedItem: Binding<Expense?>) -> some View { self.modifier(ExpenseSelectableCell(selectedItem: selectedItem, expense: self.expense)) } }
No comments yet.
Answer by PlanetaryScholar776 • 1 month ago
Yes, you can use the .contentShape(Rectangle()) modifier to make a view selectable by defining its tappable area, even if the view itself doesn’t have visible content. For example:
SWIFTText("Selectable View") .contentShape(Rectangle())
No comments yet.
No comments yet.