如何解决带部分的列表:列表中的项目更改时的奇怪行为
我有一个简单的演示应用程序,我在其中展示了包含两个部分的列表中的项目。第一部分显示最喜欢的项目,第二部分包含剩余的项目(不是最喜欢的项目)。
如果我更改 isFav
状态,就会出现奇怪的行为。
在 iPhoneOS 上:
在 iPadOS 上:
//
// 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,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()
}
}
解决方法
我找到了使用 tag
和 NavigationLink
属性包装器的 @SceneStorage
属性的解决方案。
-
创建一个
@SceneStorage
(每个场景的持久状态)@State private var sceneItemID: String?
或
@SceneStorage private var sceneItemID: String?
-
为每个
id
添加一个带有唯一NavigationLink
的标签NavigationLink(destination: DetailView(item: $item),tag: item.id,selection: $sceneItemID,label: { RowView(item: $item) })
每次使用导航链接时,
sceneItemID
都会更新为tag
(在本例中为item.id
)。 -
在
DetailView
中更新sceneItemID
修饰符中的.onAppear()
。
由于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,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,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]),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()
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。