Как сделать список с одним выделением с SwiftUI 5 - PullRequest
1 голос
/ 29 октября 2019

Я создаю один список выбора для использования в разных местах моего приложения.


Вопросы:

Есть ли простое решение, которое я не знаю?

Если нет, как я могу закончить свое текущее решение?


Моя цель:

  1. Список, в котором всегда выбран только один элемент или выбран один или ни один элемент(в зависимости от конфигурации)
  2. Прозрачный фон
  3. При выборе элемента - выполнить действие, заданное в качестве параметра с помощью метода init (). (Для этого действия требуется информация о выбранном элементе.)
  4. Изменить данные списка программно и сбросить выбор до первого элемента

Мое текущее решение выглядит следующим образом: Вид списка с выбранным вторым элементом

Я не могу использовать Пикер, потому что внешнее действие (цель № 3) отнимает много времени. Поэтому я думаю, что это не будет работать гладко. Скорее всего, есть решение моей проблемы в SwiftUI, но я либо пропустил его, потому что я новичок в swift, либо, как я понимаю, еще не все отлично работает в SwiftUI, например: прозрачный фон для List (вот почему мне нужно было очиститьbackground в init ()).

Поэтому я сам начал осуществлять выделение и остановился на этом: мое текущее решение не обновляет представление при нажатии на элемент (кнопку). (Только выходя и возвращаясь к просмотру обновлений страницы). И все же можно выбрать несколько элементов.

import SwiftUI

struct ModuleList: View {
    var modules: [Module] = []
    @Binding var selectionKeeper: Int
    var Action: () -> Void


    init(list: [Module], selection: Binding<Int>, action: @escaping () -> Void) {
        UITableView.appearance().backgroundColor = .clear
        self.modules = list
        self._selectionKeeper = selection
        self.Action = action
    }

    var body: some View {
        List(){
            ForEach(0..<modules.count) { i in
                ModuleCell(module: self.modules[i], action: { self.changeSelection(index: i) })
                }
        }.background(Constants.colorTransparent)
    }

    func changeSelection(index: Int){
        modules[selectionKeeper].isSelected =  false
        modules[index].isSelected = true
        selectionKeeper = index
        self.Action()
    }
}

struct ModuleCell: View {
    var module: Module
    var Action: () -> Void

    init(module: Module, action: @escaping () -> Void) {
        UITableViewCell.appearance().backgroundColor = .clear
        self.module = module
        self.Action = action
    }

    var body: some View {
        Button(module.name, action: {
            self.Action()
        })
            .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
            .modifier(Constants.CellSelection(isSelected: module.isSelected))
    }
}

class Module: Identifiable {
    var id = UUID()
    var name: String = ""
    var isSelected: Bool = false
    var address: Int

    init(name: String, address: Int){
        self.name = name
        self.address = address
    }
}

let testLines = [
    Module(name: "Line1", address: 1),
    Module(name: "Line2", address: 3),
    Module(name: "Line3", address: 5),
    Module(name: "Line4", address: 6),
    Module(name: "Line5", address: 7),
    Module(name: "Line6", address: 8),
    Module(name: "Line7", address: 12),
    Module(name: "Line8", address: 14),
    Module(name: "Line9", address: 11),
    Module(name: "Line10", address: 9),
    Module(name: "Line11", address: 22)
]

Проверка некоторых идей:

Попытка добавить массив @State (isSelected: Bool) в ModuleList и связать его с параметром Module isSelectedэто МОЖЕТ обновить представление ... Но не удалось затем заполнить этот массив в init (), потому что параметр массива @State останется пустым после .append () ... Возможно, добавление функции setList решило бы эту проблему, и моя цель Nr. 4. Но я не был уверен, что это действительно обновит мой взгляд.

struct ModuleList: View {
     var modules: [Module] = []
     @State var selections: [Bool] = []


     init(list: [String]) {
         UITableView.appearance().backgroundColor = .clear
         selections = [Bool] (repeating: false, count: list.count) // stays empty
         let test = [Bool] (repeating: false, count: list.count) // testing: works as it should
         selections = test
         for i in 0..<test.count { // for i in 0..<selections.count {
             selections.append(false)
             modules.append(Module(name: list[i], isSelected: $selections[i])) // Error selections is empty
         }
     }

     var body: some View {
         List{
             ForEach(0..<modules.count) { i in
                 ModuleCell(module: self.modules[i], action: { self.changeSelection(index: i) })
                 }
         }.background(Constants.colorTransparent)
     }
     func changeSelection(index: Int){
         modules[index].isSelected = true
     }
 }

 struct ModuleCell: View {
     var module: Module
     var Method: () -> Void

     init(module: Module, action: @escaping () -> Void) {
         UITableViewCell.appearance().backgroundColor = .clear
         self.module = module
         self.Method = action
     }

     var body: some View {
         Button(module.name, action: {
             self.Method()
         })
             .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
             .modifier(Constants.CellSelection(isSelected: module.isSelected))
     }
 }

 struct Module: Identifiable {
     var id = UUID()
     var name: String = ""
     @Binding var isSelected: Bool

     init(name: String, isSelected: Binding<Bool>){
         self.name = name
         self._isSelected = isSelected
     }
 }

 let testLines = ["Line1","Line2","Line3","Line4"
 ]

Ответы [ 2 ]

0 голосов
/ 30 октября 2019

Самый простой способ добиться этого - иметь @State в представлении, содержащем список с выделением, и передать его как @Binding для ячеек:

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
            }
    }
}
0 голосов
/ 30 октября 2019

Выбор

В SwiftUI в настоящее время нет встроенного способа выбрать одну строку списка и соответственно изменить ее внешний вид. Но вы на самом деле очень близки к вашему ответу. Фактически, ваш выбор уже работает, но никак не используется.

Для иллюстрации добавьте следующую строку сразу после ModuleCell(...) в вашем ForEach:

.background(i == self.selectionKeeper ? Color.red : nil)

Другими словами: «Если моя текущая строка (i) совпадает со значением, хранящимся в selectionKeeper, закрасьте ячейку красным, в противном случае используйте цвет по умолчанию». Вы увидите, что, когда вы нажимаете на разные строки, красный цвет следует за вашими нажатиями, показывая, что базовый выбор фактически меняется.

Отмена выбора

Если вы хотите включить отмену выбора, вы можетепередайте Binding<Int?> в качестве вашего выбора и установите его на nil при нажатии текущей выбранной строки:

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<Int?>, action: @escaping () -> Void) {

    ...

    func changeSelection(index: Int){
        if selectionKeeper != index {
            selectionKeeper = index
        } else {
            selectionKeeper = nil
        }
        self.Action()
    }
}

Дедупликация состояния и разделение проблем

На более структурномУровень, вы действительно хотите единый источник правды при использовании декларативной инфраструктуры пользовательского интерфейса, как SwiftUI, и четко отделить ваше представление от вашей модели. В настоящее время у вас есть дублированное состояние - selectionKeeper в ModuleList и isSelected в Module оба отслеживают, выбран ли данный модуль.

Кроме того, isSelected действительно должно быть свойствомваш взгляд (ModuleCell), а не вашей модели (Module), потому что он связан с тем, как выглядит ваш взгляд, а не с внутренними данными каждого модуля.

Таким образом, ваш ModuleCell долженвыглядит примерно так:

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 ------------------------------^
    }
}

А ваш ForEach будет выглядеть

ForEach(0..<modules.count) { i in
    ModuleCell(module: self.modules[i],
               isSelected: i == self.selectionKeeper,
               action: { self.changeSelection(index: i) })
}
...