在我的 macOS 应用程序项目中,我有一个 NavigationLinks 的 SwiftUI ListView ,它使用来自项目数组的 foreach 循环构建:
struct MenuView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
List(selection: $settings.selectedWeek) {
ForEach(settings.weeks) { week in
NavigationLink(
destination: WeekView(week: week)
.environmentObject(settings)
tag: week,
selection: $settings.selectedWeek)
{
Image(systemName: "circle")
Text("\(week.name)")
}
}
.onDelete { set in
settings.weeks.remove(atOffsets: set)
}
.onMove { set, i in
settings.weeks.move(fromOffsets: set, toOffset: i)
}
}
.navigationTitle("Weekplans")
.listStyle(SidebarListStyle())
}
}
此 View 为整个 NavigationView 创建侧边栏菜单。
在此 ListView 中,我想将选择机制与来自 NavigationLink 的标记一起使用。 Week 是自定义模型类:
struct Week: Identifiable, Hashable, Equatable {
var id = UUID()
var days: [Day] = []
var name: String
}
UserSettings 看起来像这样:
class UserSettings: ObservableObject {
@Published var weeks: [Week] = [
Week(name: "test week 1"),
Week(name: "foobar"),
Week(name: "hello world")
]
@Published var selectedWeek: Week? = UserDefaults.standard.object(forKey: "week.selected") as? Week {
didSet {
var a = oldValue
var b = selectedWeek
UserDefaults.standard.set(selectedWeek, forKey: "week.selected")
}
}
}
我的目标是直接将列表选择的值存储在 UserDefaults 中。 didSet 属性被执行,但变量始终为 nil。由于某种原因,所选列表值无法存储在已发布/可绑定(bind)变量中。 为什么 $settings.selectedWeek 总是 nil?
最佳答案
一些建议:
- SwiftUI(特别是在 macOS 上)对于某些
List
行为不可靠/不可预测。其中之一是selection
—— 有许多东西要么完全不起作用,要么充其量只是略有损坏,但可以与等效的 iOS 代码一起正常工作。好消息是NavigationLink
和isActive
就像列表中的选择一样工作——我将在我的示例中使用它。 @Published
didSet
可能 在某些情况下工作,但这是您不应该依赖的另一件事。属性包装器方面使它的行为不同于一个可能的行为(搜索 SO 以查找“@Published didSet”以查看处理它的合理数量的问题)。好消息是您可以使用 Combine 重新创建行为并以更安全/更可靠的方式进行。
代码中的一个逻辑错误:
- 您正在使用某个 UUID 在您的用户默认值中存储一个
Week
。但是,您在每次启动时动态地重新生成weeks
数组,以保证它们的 UUID 不同。如果您想在发布到发布期间维护它们,则需要将您的一周与您的选择一起存储。
这是一个工作示例,我将在下面指出一些事情:
import SwiftUI
import Combine
struct ContentView : View {
var body: some View {
NavigationView {
MenuView().environmentObject(UserSettings())
}
}
}
class UserSettings: ObservableObject {
@Published var weeks: [Week] = []
@Published var selectedWeek: UUID? = nil
private var cancellable : AnyCancellable?
private var initialItems = [
Week(name: "test week 1"),
Week(name: "foobar"),
Week(name: "hello world")
]
init() {
let decoder = PropertyListDecoder()
if let data = UserDefaults.standard.data(forKey: "weeks") {
weeks = (try? decoder.decode([Week].self, from: data)) ?? initialItems
} else {
weeks = initialItems
}
if let prevValue = UserDefaults.standard.string(forKey: "week.selected.id") {
selectedWeek = UUID(uuidString: prevValue)
print("Set selection to: \(prevValue)")
}
cancellable = $selectedWeek.sink {
if let id = $0?.uuidString {
UserDefaults.standard.set(id, forKey: "week.selected.id")
let encoder = PropertyListEncoder()
if let encoded = try? encoder.encode(self.weeks) {
UserDefaults.standard.set(encoded, forKey: "weeks")
}
}
}
}
func selectionBindingForId(id: UUID) -> Binding<Bool> {
Binding<Bool> { () -> Bool in
self.selectedWeek == id
} set: { (newValue) in
if newValue {
self.selectedWeek = id
}
}
}
}
//Unknown what you have in here
struct Day : Equatable, Hashable, Codable {
}
struct Week: Identifiable, Hashable, Equatable, Codable {
var id = UUID()
var days: [Day] = []
var name: String
}
struct WeekView : View {
var week : Week
var body: some View {
Text("Week: \(week.name)")
}
}
struct MenuView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
List {
ForEach(settings.weeks) { week in
NavigationLink(
destination: WeekView(week: week)
.environmentObject(settings),
isActive: settings.selectionBindingForId(id: week.id)
)
{
Image(systemName: "circle")
Text("\(week.name)")
}
}
.onDelete { set in
settings.weeks.remove(atOffsets: set)
}
.onMove { set, i in
settings.weeks.move(fromOffsets: set, toOffset: i)
}
}
.navigationTitle("Weekplans")
.listStyle(SidebarListStyle())
}
}
- 在
UserSettings.init
中,如果之前保存过周数(保证相同的 ID),则会加载周数 - 在
$selectedWeek
上使用 Combine 而不是didSet
。我只存储 ID,因为存储整个Week
结构似乎没有意义,但您可以更改它 - 我为
NavigationLink
的isActive
属性创建了一个动态绑定(bind)——如果存储的selectedWeek
与NavigationLink
的周 ID。 - 除此之外,它与您的代码基本相同。我没有在
List
上使用selection
,只是在NavigationLink
上使用 - 如果您执行了
onMove
或onDelete
,我没有实现再次存储Week
,因此您必须实现它。
isActive
关于SwiftUI 列表选择始终为零,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66128198/