带节列表:列表中的项目发生更改时的奇怪行为



我有一个简单的演示应用程序,我在其中展示了包含两个部分的列表中的项目。第一部分显示最喜欢的项目,第二部分包含其余项目(不是最喜欢的(。

如果我改变isFav状态,就会出现奇怪的行为。

在iPhone操作系统上:

  • 当我选择一个项目时,DetailView将出现
  • 如果我更改isFav状态(切换(,DetailView将消失
  • 视频

在iPadOS:上

  • 当我选择一个项目时,DetailView将出现
  • 如果我更改isFav状态(切换(,则DetailView而不是正在消失,但在侧边栏中,选择[消失]
  • 视频
//
//  ContentView.swift
//  Shared
//
//  Created by Christian on 06.06.21.
//
import SwiftUI
//MARK: - Data Model
struct Item: Identifiable, Equatable, Hashable {
var id = UUID().uuidString
var isFav = false
var text: String
}
struct ItemScoped: Identifiable, Equatable, Hashable {
var id: String {
return item.id
}
var item: Item
var index: Int
}
//MARK: Store
class ItemStore: ObservableObject {
@Published var items = [Item(text: "Item 1"),
Item(text: "Item 2"),
Item(isFav: true, text: "Item 3"),
Item(text: "Item 4")]

func scopedItems(isFav: Bool) -> [ItemScoped] {
let sItems: [ItemScoped]  = items.compactMap {
guard let idx = items.firstIndex(of: $0) else { return nil }
//find(items, $0)
return ItemScoped(item: $0, index: idx)
}
return sItems.filter { $0.item.isFav == isFav }
}
}
//MARK: - Views
struct ContentView: View {

// usally this is @EnvironmetObject, due to simplicity I put it here
@StateObject var store: ItemStore = ItemStore()

var body: some View {
NavigationView {
List {
Section(header: Text("Favorites")) {
ForEach(store.scopedItems(isFav: true)) { scopedItems in
NavigationLink(
destination: DetailView(item: $store.items[scopedItems.index]),
label: {
RowView(item: $store.items[scopedItems.index])
})
}
}
Section(header: Text("Other")) {
ForEach(store.scopedItems(isFav: false)) { scopedItems in
NavigationLink(
destination: DetailView(item: $store.items[scopedItems.index]),
label: {
RowView(item: $store.items[scopedItems.index])
})
}
}
}
.navigationTitle("Items")
}
}
}
// MARK: Row View
/// RowView for item, tapping the text toggle the `isFav` state
struct RowView: View {

@Binding var item: Item

var body: some View {
Label(
title: { Text(item.text) },
icon: { item.isFav ? Image(systemName: "star.fill") : Image(systemName: "star")}
)
}
}

// MARK: Detail View
/// DetailView to change item `text` and toggle `isFav` state
struct DetailView: View {

@Binding var item: Item

var body: some View {
VStack {
Spacer()
.frame(height: 20.0)
TextField("Title", text: $item.text)
.background(Color.gray.opacity(0.2))
.padding(10)
Toggle("is Fav", isOn: $item.isFav.animation())
.padding()
Spacer()
}
.padding()
}
}
// MARK: - Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}


我找到了一个使用NavigationLink@SceneStorage属性包装器的tag属性的解决方案。

  1. 创建@SceneStorage(每个场景的持久状态(

    @State private var sceneItemID: String?  
    

    @SceneStorage private var sceneItemID: String?  
    
  2. 为每个NavigationLink添加一个具有项目唯一id的标签

    NavigationLink(destination:  DetailView(item: $item),
    tag: item.id,
    selection: $sceneItemID,
    label: {
    RowView(item: $item)
    })
    

    每次使用导航链接时,sceneItemID都会更新为tag(在这种情况下为item.id(。

  3. DetailView中,更新.onAppear()修饰符中的sceneItemID
    由于isFav状态变化期间的行为,这是必要的。

现在它只在iPad上工作,边栏无法正确显示所选内容。在macOS和iPhone上,这是有效的。


//
//  ContentView.swift
//  Shared
//
//  Created by Christian on 06.06.21.
//
import SwiftUI
//MARK: - Data Model
struct Item: Identifiable, Equatable, Hashable {
var id = UUID().uuidString
var isFav = false
var text: String
}
struct ItemScoped: Identifiable, Equatable, Hashable {
var id: String {
return item.id
}
var item: Item
var index: Int
}
//MARK: Store
class ItemStore: ObservableObject {
@Published var items = [Item(id: "uuid01", text: "Item 1"),
Item(id: "uuid02", text: "Item 2"),
Item(id: "uuid03", isFav: true, text: "Item 3"),
Item(id: "uuid04", text: "Item 4")]

/// scope item to sections and keep knowledge of origin index
func scopedItems(isFav: Bool) -> [ItemScoped] {
let sItems: [ItemScoped]  = items.compactMap {
guard let idx = items.firstIndex(of: $0) else { return nil }
//find(items, $0)
return ItemScoped(item: $0, index: idx)
}
return sItems.filter { $0.item.isFav == isFav }
}
}

//MARK: - Views
struct ContentView: View {

// usally this is @EnvironmetObject, due to simplicity I put it here
@StateObject var store: ItemStore = ItemStore()

@SceneStorage("SceneItemSelectionID") private var sceneItemID: String?

var body: some View {
NavigationView {

List {
Section(header: Text("Favorites")) {
ForEach(store.scopedItems(isFav: true)) { scopedItems in
NavigationLink(
destination: DetailView(item: $store.items[scopedItems.index]),
//MARK: !! IMPORTANT: use unique indetifier as tag
tag: store.items[scopedItems.index].id,
selection: $sceneItemID,
label: {
RowView(item: $store.items[scopedItems.index])
})
}
}
Section(header: Text("Others")) {
ForEach(store.scopedItems(isFav: false)) { scopedItems in
NavigationLink(
destination: DetailView(item: $store.items[scopedItems.index]),
//MARK: !! IMPORTANT: use unique indetifier as tag
tag: store.items[scopedItems.index].id,
selection: $sceneItemID,
label: {
RowView(item: $store.items[scopedItems.index])
})

}
}
}
.listStyle(SidebarListStyle())
.navigationTitle("Items")
}
}
}
// MARK: Row View
/// RowView for item, tapping the text toggle the `isFav` state
struct RowView: View {

@Binding var item: Item

var body: some View {
Label(
title: { Text(item.text) },
icon: { item.isFav ? Image(systemName: "star.fill") : Image(systemName: "star")}
)
}
}

// MARK: Detail View
/// DetailView to change item `text` and toggle `isFav` state
struct DetailView: View {

@Binding var item: Item

@SceneStorage("SceneItemSelectionID") private var sceneItemID: String?

var body: some View {
VStack {
Spacer()
.frame(height: 20.0)
TextField("Title", text: $item.text)
.background(Color.gray.opacity(0.2))
.padding(10)
Toggle("is Fav:", isOn: $item.isFav.animation())
.padding()
Spacer()
}
.padding()
.onAppear() {
//MARK: !! IMPORTANT set scene selction id again
sceneItemID = item.id
}
}
}
// MARK: - Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

相关内容

  • 没有找到相关文章

最新更新