Solución:
La forma más fácil de lograr esto sería tener @State en la Vista que contiene la lista con la selección y pasarla como @Binding a las celdas:
struct SelectionView: View
let fruit = ["apples", "pears", "bananas", "pineapples"]
@State var selectedFruit: String? = nil
var body: some View
List
ForEach(fruit, id: .self) item in
SelectionCell(fruit: item, selectedFruit: self.$selectedFruit)
struct SelectionCell: View
let fruit: String
@Binding var selectedFruit: String?
var body: some View
HStack
Text(fruit)
Spacer()
if fruit == selectedFruit
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.onTapGesture
self.selectedFruit = self.fruit
Selección
SwiftUI actualmente no tiene una forma incorporada para seleccionar una fila de una lista y cambiar su apariencia en consecuencia. Pero en realidad estás muy cerca de tu respuesta. De hecho, su selección ya está funcionando, pero simplemente no se está utilizando de ninguna manera.
Para ilustrar, agregue la siguiente línea justo después ModuleCell(...)
en tus ForEach
:
.background(i == self.selectionKeeper ? Color.red : nil)
En otras palabras, “Si mi fila actual (i
) coincide con el valor almacenado en selectionKeeper
, colorea la celda de rojo, de lo contrario, usa el color predeterminado. “Verás que a medida que tocas en diferentes filas, el color rojo sigue a tus toques, lo que muestra que la selección subyacente está cambiando de hecho.
Deselección
Si desea habilitar la deselección, puede pasar un Binding
como su selección, y configúrelo en nil
cuando se toca la fila actualmente seleccionada:
struct ModuleList: View {
var modules: [Module] = []
// this is new ------------------v
@Binding var selectionKeeper: Int?
var Action: () -> Void
// this is new ----------------------------v
init(list: [Module], selection: Binding, action: @escaping () -> Void)
...
func changeSelection(index: Int)
if selectionKeeper != index
selectionKeeper = index
else
selectionKeeper = nil
self.Action()
Estado de deduplicación y separación de preocupaciones
En un nivel más estructural, realmente desea una única fuente de verdad cuando usa un marco de interfaz de usuario declarativo como SwiftUI, y para separar claramente su vista de su modelo. En la actualidad, tiene un estado duplicado: selectionKeeper
en ModuleList
y isSelected
en Module
ambos realizan un seguimiento de si se selecciona un módulo determinado.
Además, isSelected debería ser realmente una propiedad de su vista (ModuleCell
), no de tu modelo (Module
), porque tiene que ver con cómo aparece su vista, no con los datos intrínsecos de cada módulo.
Por lo tanto, tu ModuleCell
debería verse algo como esto:
struct ModuleCell: View
var module: Module
var isSelected: Bool // Added this
var Action: () -> Void
// Added this -------v
init(module: Module, isSelected: Bool, action: @escaping () -> Void)
UITableViewCell.appearance().backgroundColor = .clear
self.module = module
self.isSelected = isSelected // Added this
self.Action = action
var body: some View
Button(module.name, action:
self.Action()
)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.modifier(Constants.CellSelection(isSelected: isSelected))
// Changed this ------------------------------^
Y tu ForEach
se vería como
ForEach(0..
Aquí hay un enfoque más genérico, aún puede extender la respuesta según sus necesidades;
TLDR
https://gist.github.com/EnesKaraosman/d778cdabc98ca269b3d162896bea8aac
Detalle
struct SingleSelectionList: View
var items: [Item]
@Binding var selectedItem: Item?
var rowContent: (Item) -> Content
var body: some View
List(items) item in
rowContent(item)
.modifier(CheckmarkModifier(checked: item.id == self.selectedItem?.id))
.contentShape(Rectangle())
.onTapGesture
self.selectedItem = item
struct CheckmarkModifier: ViewModifier
var checked: Bool = false
func body(content: Content) -> some View
Group
if checked
ZStack(alignment: .trailing)
content
Image(systemName: "checkmark")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.green)
.shadow(radius: 1)
else
content
Y para demostrar;
struct PlaygroundView: View
struct Koko: Identifiable
let id = UUID().uuidString
var name: String
var mock = Array(0...10).map Koko(name: "Item - ($0)")
@State var selectedItem: Koko?
var body: some View
VStack
Text("Selected Item: (selectedItem?.name ?? "Select one")")
Divider()
SingleSelectionList(items: mock, selectedItem: $selectedItem) (item) in
HStack
Text(item.name)
Spacer()
Resultado final
Sección de Reseñas y Valoraciones
Recuerda dar visibilidad a este escrito si lograste el éxito.