Простой SwiftUI CRUD с использованием структур, а не классов? - PullRequest
2 голосов
/ 03 августа 2020

У меня сложная структура данных, в которой используются типы значений (структуры и перечисления), и я столкнулся с серьезными проблемами, связанными с работой c CRUD. В частности:

  1. Как лучше всего «повторно привязать» значение в ForEach для редактирования дочерним представлением
  2. Как удалить / удалить значение

Rebinding

Если у меня есть массив элементов как @State или @Binding, почему нет простого способа привязать каждый элемент к представлению? Например:

import SwiftUI

struct Item: Identifiable {
  var id = UUID()
  var name: String
}

struct ContentView: View {
  @State var items: [Item]
  var body: some View {
    VStack {
      ForEach(items, id: \.id) { item in
        TextField("name", text: $item) // ? Cannot find '$item' in scope 
      }
    }
  }
}

Обходной путь

Я смог обойти это, представив вспомогательную функцию для поиска правильного индекса для элемента в al oop:

struct ContentView: View {
  @State var items: [Item]

  func index(of item: Item) -> Int {
    items.firstIndex { $0.id == item.id } ?? -1
  }

  var body: some View {
    VStack {
      ForEach(items, id: \.id) { item in
        TextField("name", text: $items[index(of: item)].name)
      }
    }
  }
}

Однако это кажется неуклюжим и, возможно, опасным.

Удаление

Гораздо более серьезная проблема: как вы должны правильно удалить элемент? Это звучит как базовый c вопрос, но учтите следующее:

struct ContentView: View {
  @State var items: [Item]

  func index(of item: Item) -> Int {
    items.firstIndex { $0.id == item.id } ?? -1
  }

  var body: some View {
    VStack {
      ForEach(items, id: \.id) { item in
        TextField("name", text: $items[index(of: item)].name)
        Button( action: {
          items.remove(at: index(of: item))
        }) {
          Text("Delete")
        }
      }
    }
  }
}

Нажатие кнопки «Удалить» на первых нескольких элементах работает должным образом, но попытка Удалить последний элемент приводит к Fatal error: Index out of range ...

Мой конкретный вариант использования не соответствует списку, поэтому я не могу использовать там помощник по удалению.

Типы ссылок

Я знаю что ссылочные типы значительно упрощают эту задачу, особенно если они могут соответствовать @ObservableObject. Однако у меня есть массивный, вложенный, уже существующий тип значения, который нелегко преобразовать в классы.

Любая помощь будет очень признательна!

Обновление: Предлагаемые решения

  • Удаление элементов списка из списка SwiftUI : принятый ответ предлагает сложную настраиваемую оболочку привязки. Swift - мощный инструмент, поэтому многие проблемы можно решить с помощью продуманных обходных путей, но я не думаю, что для получения списка редактируемых элементов требуется сложное обходное решение.
  • Пометить представления как «удаленные» с помощью State или частную переменную, а затем условно скройте их, чтобы избежать ошибок, выходящих за границы. Это может сработать, но похоже на взлом, и это должно выполняться фреймворком.

1 Ответ

1 голос
/ 03 августа 2020

Я подтверждаю, что более подходящим подходом для CRUD является использование модели представления на основе классов ObservableObject. И ответ, предоставленный @NewDev в комментариях, является хорошей демонстрацией этого подхода.

Однако, если у вас уже есть массивный, вложенный, уже существующий тип значения, который нелегко преобразовать в классы. , это может быть решено с помощью @State/@Binding, но вы должны подумать о что / когда / и как обновлять каждое представление и в каждом порядке - это источник всего такого индекса из границы при удалении проблем (и некоторых других).

Вот демонстрация подхода к тому, как разорвать эту зависимость обновления, чтобы избежать cra sh и по-прежнему использовать типы значений.

Протестировано на основе вашего кода с Xcode 11.4 / iOS 13.4 (SwiftUI 1.0 +)

struct ContentView: View {
  @State var items: [Item] = [Item(name: "Name1"), Item(name: "Name2"), Item(name: "Name3")]

  func index(of item: Item) -> Int {
    items.firstIndex { $0.id == item.id } ?? -1
  }

  var body: some View {
    VStack {
      ForEach(items, id: \.id) { item in
        // separate dependent views as much as possible to make them as 
        // smaller/lighter as possible
        ItemRowView(items: self.$items, index: self.index(of: item))
      }
    }
  }
}

struct ItemRowView: View {
    @Binding var items: [Item]
    let index: Int

    @State private var destroyed = false   // internal state to validate self

    var body: some View {
        // proxy binding to have possibility for validation
        let binding = Binding(
            get: { self.destroyed ? "" : self.items[self.index].name },
            set: { self.items[self.index].name = $0 }
        )

        return HStack {
            if !destroyed { // safety check against extra update
                TextField("name", text: binding)
                Button( action: {
                  self.destroyed = true
                  self.$items.wrappedValue.remove(at: self.index)
                }) {
                  Text("Delete")
                }
            }
        }
    }
}

Да, это непростое решение, но иногда возникают ситуации, когда оно нам нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...