在我的 macOS 应用项目中,我有一个 NavigationLinks 的 SwiftUI 列表视图,其中包含来自一组项目的 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())
}
}
此视图为整个 NavigationView 创建侧边栏菜单。
在此列表视图中,我想将选择机制与来自 NavigationLink 的标记一起使用。 Week 是一个自定义模型类:
struct Week: Identifiable, Hashable, Equatable {
var id = UUID()
var days: [Day] = []
var name: String
}
用户设置如下所示:
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 属性被执行,但变量始终为零。由于某种原因,选定的 List 值无法存储在已发布/可绑定变量中。 为什么 $settings.selectedWeek 总是为零?
答案 0 :(得分:4)
一些建议:
List
行为。其中之一是 selection
- 有许多东西要么完全不起作用,要么最多稍微损坏,但在等效的 iOS 代码中可以正常工作。好消息是 NavigationLink
和 isActive
的作用类似于列表中的选择 -- 我将在我的示例中使用它。@Published
didSet
可能在某些情况下工作,但这是另一件您不应该依赖的事情。属性包装器方面使其行为与可能不同,除了(搜索“@Published didSet”以查看处理它的合理数量的问题)。好消息是,您可以使用Combine 重新创建行为,并以更安全/更可靠的方式进行操作。代码中的逻辑错误:
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
上使用组合而不是 didSet
。我只存储 ID,因为存储整个 Week
结构似乎没有意义,但您可以更改它NavigationLink
的 isActive
属性创建了动态绑定 -- 如果存储的 selectedWeek
与 NavigationLink
的周 ID 相同,则链接处于活动状态.selection
上使用 List
,只是在 isActive
NavigationLink
Week
或 onMove
,我没有再次实现存储 onDelete
,因此您必须实现它。答案 1 :(得分:0)
遇到这样的情况,即多项目选择在 macOS 上不起作用。这是我认为正在发生的事情以及如何解决它并使其正常工作
背景
因此,在 macOS 上,嵌入在列表中的导航链接在详细视图中呈现其目的地(无论如何默认情况下)。例如
struct ContentView: View {
let beatles = ["John", "Paul", "Ringo", "George", "Pete"]
@State var listSelection = Set<String>()
var body: some View {
NavigationView {
List(beatles, id: \.self, selection: $listSelection) { name in
NavigationLink(name) {
Text("Some details about \(name)")
}
}
}
}
}
像这样渲染
问题
当使用 NavigationLinks 时,不可能在侧边栏中选择多个项目(至少从 Xcode 13 beta4 开始)。
...但如果只使用 Text 元素而没有任何 NavigationLink 嵌入,则效果很好。
发生了什么
详细视图一次只能显示一个 NavigationLink 视图,在代码中的某处(可能是 NavigationView)有一段代码通过踩踏多项选择并将其设置为强制执行该合规性零,例如
let selectionBinding = Binding {
backingVal
} set: { newVal in
guard newVal <= 1 else {
backingVal = nil
return
}
backingVal = newVal
}
据我所知,在这些情况下会发生什么并没有定义。对于某些视图,例如 TextField,它与它的原始数据源不同步(对于 more),而对于其他视图,因为在这里它尊重它。
解决方法/修复
要启用多项选择,请使用 Zstack 将 NavigationLinks 隐藏在基于文本的列表后面,并以编程方式驱动 NavigationLinks。例如
struct ContentView: View {
let beatles = ["John", "Paul", "Ringo", "George", "Pete"]
@State var listSelection = Set<String>()
var detailShow: String? {
return listSelection.count == 1 ? listSelection.first
: listSelection.count > 1 ? "tooMany"
: nil
}
var body: some View {
NavigationView {
/// Workaround for inability to select multiple NavigationLinks in a List on macOS
ZStack {
/// Hidden List with programatically driven NavigationLinks
List {
ForEach(beatles, id: \.self) { name in
NavigationLink(
"",
tag: name,
selection: .constant(detailShow)
) {
Text("Some details on \(name)")
}
}
NavigationLink(
"",
tag: "tooMany",
selection: .constant(detailShow)
) {
Text("Too many selected")
}
}
.opacity(0.0)
/// List that is selected from
List(beatles, id: \.self, selection: $listSelection) { name in
Text(name)
}
}
}
}
}
玩得开心。