Sheet 在 SwiftUI 中多次显示

Sheet displayed several times in SwiftUI

简短描述:在 detailView 中,我有一个相关实体的列表。对于每个项目,都有一个按钮可以打开该项目的编辑 sheet。

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))

单击列表中的一个编辑按钮后,会出现一个带有编辑表单的 sheet,其中预先填充了所选列表项的值。

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() 进行的小调试显示,在单击编辑按钮时,首先设置了正确的值(例如,我单击第三项,第三项的值被传递),但是另外,所有列表项都被传递也到 sheet。


First image as example of upper question

Second image as example of upper question

如果您在循环中重复的视图中有一个 .sheet(isPresented:) 修饰符,但您的布尔状态变量在循环之外,那么该状态变量将用于 [=43 的多个副本=].有时,这可能表现为 sheet 显示的详细信息与您单击的行不同;其他时候您可能会看到多个 sheet。听起来您看到的副作用与此有关。

有几种方法可以解决这个问题。

选项 1 - sheet(item:)

将您的 showingBorrowerEditScreen 替换为 borrowerToEdit 状态变量,它是一个可选对象,默认为 nil:

@State private var borrowerToEdit: Borrower? = nil

在您的按钮操作中,将其设置为循环中当前行的借款人:

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

最后,在 ForEach 循环 之外使用 sheet 修饰符 item: 形式。请注意,此表单采用一个块,其中包含对相关借款人对象的引用。

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

选项 2 - 单独的子视图

如果您想坚持使用布尔值来表示是否应该使用模态 sheet,则需要通过提取行详细信息将该布尔值隔离到它自己的子视图中。例如:

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

这样一来,每一行都有自己的“我应该显示模式”布尔值,因此不会混淆哪一行“拥有”活动 sheet,并且只有一个能够一次显示。


您选择哪个选项部分取决于个人喜好。在我自己的代码中,我倾向于支持选项 1,因为它感觉模式更多地由列表而不是行“拥有”,并且它强化了一次只能编辑一个借款人的想法。但是这两种方法都应该可以消除您当前遇到的问题。