Лист отображается несколько раз в SwiftUI

Краткое описание: в detailView у меня есть список связанной сущности. Для каждого элемента есть кнопка, чтобы открыть лист редактирования для этого элемента.

      List {
  if (book.booksBorrowers != nil) {
    ForEach (Array(book.booksBorrowers! as! Set<Borrowers>), id: \.self) { borrower in
      HStack {
        Text(borrower.firstName ?? "unbekannter Vorname")
        Text(borrower.lastName ?? "unbekannter Nachname")
        Text(String(format: "%.0f", borrower.age))
        Spacer()
        Button {
          showingBorrowerEditScreen.toggle()
        } label: {
          Image(systemName: "pencil")
            .frame(width: 20.0, height: 20.0)
        }.multilineTextAlignment(.center).buttonStyle(.borderless)
          .sheet(isPresented: $showingBorrowerEditScreen) {
          EditBorrowerView(
            aBorrower: borrower,
            firstName: borrower.firstName!,
            lastName: borrower.lastName!,
            age: Int(borrower.age)
            
          ).environment(\.managedObjectContext, self.viewContext)
        }
      }
    }.onDelete(perform: deleteBorrower)
  }
}.listStyle(.inset(alternatesRowBackgrounds: true))

При нажатии одной из кнопок редактирования в списке появляется лист с формой редактирования, предварительно заполненной значениями выбранного элемента списка.

      struct EditBorrowerView: View {
  @Environment(\.managedObjectContext) var moc
  @Environment(\.dismiss) var dismiss
  
  @State private var firstName = ""
  @State private var lastName = ""
  @State private var age = 0.0
  
  @StateObject var aBorrower: Borrowers

  init(aBorrower: Borrowers, firstName: String, lastName: String, age: Int) {
    self._aBorrower = StateObject(wrappedValue: aBorrower)
    self._firstName = State(initialValue: aBorrower.firstName ?? "")
    self._lastName = State(initialValue: aBorrower.lastName ?? "")
    self._age = State(initialValue: Double(aBorrower.age))
  }
  let formatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.numberStyle = .decimal
    formatter.minimumFractionDigits = 0
    formatter.maximumFractionDigits = 0
    return formatter
  }()

  var body: some View {
    VStack {
      Text("Ausleiher bearbeiten").font(.title)
      Form {
        VStack {
          TextField("Vorname", text: $firstName)
          TextField("Nachname", text: $lastName)
          HStack {
            Slider(value: $age, in: 0...99, step: 1)
            TextField("Alter", value: $age, formatter: formatter)
          }
        }
      }
      HStack {
        Button("Save") {
          // save the edited book
          aBorrower.firstName = firstName
          aBorrower.lastName = lastName
          aBorrower.age = Double(age)
          
          try? moc.save()
          dismiss()
        }
        Button("Cancel") {
          dismiss()
        }
      }
    }.padding(10)
  }
}

Но теперь при нажатии кнопки редактирования строки

  • а) в отображаемой форме отображается только содержимое первого элемента списка
  • б) нажав кнопку «Отмена» на листе, лист появится для каждого элемента в списке перед исчезновением, содержа соответствующие значения.

Небольшая отладка с print() показывает, что при нажатии кнопки редактирования сначала устанавливаются правильные значения (например, я нажимаю на третий элемент, значения третьего элемента передаются), но, кроме того, все элементы списка также передаются на лист .


Первое изображение как пример верхнего вопроса

Второе изображение как пример верхнего вопроса

1 ответ

Если у тебя есть модификатор в представлении, которое повторяется в цикле, но ваша логическая переменная состояния находится вне цикла, тогда эта одна переменная состояния используется для нескольких копий листа. Иногда это может проявляться в виде листа с деталями, отличными от строки, на которую вы нажали; в других случаях вы можете увидеть несколько листов. Похоже, побочные эффекты, которые вы видите, связаны с этим.

Есть несколько способов обойти это.

Опция 1 - sheet(item:)

Замените свой с переменная состояния, которая является необязательным объектом и по умолчанию равна нулю:

      @State private var borrowerToEdit: Borrower? = nil

В действии кнопки установите это для заемщика текущей строки в цикле:

      Button {
  borrowerToEdit = borrower
} label: {
  // etc

И, наконец, используйте форма модификатора листа вне петля. Обратите внимание, что эта форма принимает блок со ссылкой на соответствующий объект-заемщик.

      ForEach(...) { borrower in
  // etc.
}
.sheet(item: $borrowerToEdit) { borrower in 
  EditBorrowerView(
    aBorrower: borrower,
    firstName: borrower.firstName!,
    lastName: borrower.lastName!,
    age: Int(borrower.age)            
  )
}

Вариант 2 - отдельные подпредставления

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

      List {
  if (book.booksBorrowers != nil) {
    ForEach (Array(book.booksBorrowers! as! Set<Borrowers>), id: \.self) { borrower in
      BorrowerListRow(borrower: borrower)
      .onDelete(perform: deleteBorrower)
    }
  }
}

struct BorrowerListRow: View {
  @ObservedObject var borrower: Borrower
  @State private var showingBorrowerEditScreen = false

  var body: some View {
    HStack {
      Text(borrower.firstName ?? "unbekannter Vorname")
      Text(borrower.lastName ?? "unbekannter Nachname")
      Text(String(format: "%.0f", borrower.age))
      Spacer()
      Button {
        showingBorrowerEditScreen.toggle()
      } label: {
        Image(systemName: "pencil")
          .frame(width: 20.0, height: 20.0)
      }.multilineTextAlignment(.center).buttonStyle(.borderless)
        .sheet(isPresented: $showingBorrowerEditScreen) {
          EditBorrowerView(
            aBorrower: borrower,
            firstName: borrower.firstName!,
            lastName: borrower.lastName!,
            age: Int(borrower.age)
          ).environment(\.managedObjectContext, self.viewContext)
        }
    }
  }
}

Таким образом, каждая строка имеет свое собственное логическое значение «должен ли я показывать модальное», поэтому не может быть путаницы в отношении того, какая строка «владеет» активным листом, и одновременно может отображаться только одна строка.


Какой вариант вы выберете, частично зависит от личных предпочтений. Я склоняюсь к варианту 1 в своем собственном коде, так как мне кажется, что модальное окно «принадлежит» списку, а не строке, и это подтверждает идею о том, что вы можете редактировать только одного заемщика за раз. Но любой подход должен устранить проблемы, с которыми вы сталкиваетесь в настоящее время.

Другие вопросы по тегам