【问题标题】:List with Sections: Odd Behavior when item in list changes带部分的列表:列表中的项目更改时的奇怪行为
【发布时间】:2021-06-07 08:27:02
【问题描述】:

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

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

在 iPhoneOS 上:

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

在 iPadOS 上:

  • 当我选择一个项目时,DetailView 将出现。
  • 如果我更改 isFav 状态(切换)DetailView不会消失但在侧边栏中,选择 [消失]
  • Video
//
//  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()
   }
}





【问题讨论】:

  • 这种行为并不奇怪,它实际上是预期的行为,因为当isFav 状态发生变化时,List 正在更新,并且最喜欢的项目正在从列表中删除。跨度>
  • @cedricbahirwe 它没有被删除它只显示在另一个部分。另一个示例是,如果列表的排序基于上次输入日期,那么每次用户在“DetailView”中编辑项目时,详细视图都会消失。这是无法预料的,​​因为在 iPadOS 中它的行为不同。

标签: swiftui swiftui-list swiftui-navigationlink swiftui-navigationview


【解决方案1】:

我找到了使用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()
   }
}



【讨论】:

猜你喜欢
  • 1970-01-01
  • 2011-11-22
  • 2020-01-08
  • 2021-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-06
  • 1970-01-01
相关资源
最近更新 更多