SwiftUI 列表选择始终为零

标签 swift xcode macos swiftui

在我的 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?

最佳答案

一些建议:

  1. SwiftUI(特别是在 macOS 上)对于某些 List 行为不可靠/不可预测。其中之一是 selection —— 有许多东西要么完全不起作用,要么充其量只是略有损坏,但可以与等效的 iOS 代码一起正常工作。好消息是 NavigationLinkisActive 就像列表中的选择一样工作——我将在我的示例中使用它。
  2. @Published didSet 可能 在某些情况下工作,但这是您不应该依赖的另一件事。属性包装器方面使它的行为不同于一个可能的行为(搜索 SO 以查找“@Published didSet”以查看处理它的合理数量的问题)。好消息是您可以使用 Combine 重新创建行为并以更安全/更可靠的方式进行。

代码中的一个逻辑错误:

  1. 您正在使用某个 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())
    }
}
  1. UserSettings.init 中,如果之前保存过周数(保证相同的 ID),则会加载周数
  2. $selectedWeek 上使用 Combine 而不是 didSet。我只存储 ID,因为存储整个 Week 结构似乎没有意义,但您可以更改它
  3. 我为 NavigationLinkisActive 属性创建了一个动态绑定(bind)——如果存储的 selectedWeekNavigationLink 的周 ID。
  4. 除此之外,它与您的代码基本相同。我没有在 List 上使用 selection,只是在 NavigationLink
  5. 上使用 isActive
  6. 如果您执行了 onMoveonDelete,我没有实现再次存储 Week,因此您必须实现它。

关于SwiftUI 列表选择始终为零,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66128198/

相关文章:

swift - 在 Swift/SpriteKit 中生成 Sprite 时出错

swift - NSCollectionView 在同一点呈现所有 NSCollectionViewItems

c++ - Qt 在寻找匹配项时出现意外的 EOF

objective-c - 有一种简单的方法可以在 objective-c 中应用最短路径算法吗?

macos - 无法将 USB 设备 SEGGER J-Link [0100] 连接到虚拟机 WINDOWS 7

带有加号和减号按钮的 MacOS 样式列表

ios - reloadData()后无法重绘UICollectionViewCell

ios - Storyboard未在 Xcode 11 中删除

swift - Xcode 项目中的 Cocoapods SwiftWebSocket - 找不到库

macos - 在 OS X 上安装 libval​​a >= 0.12