swift - 表达式类型不明确,在 `ForEach` 中自定义类数组上没有更多上下文

标签 swift xcode swiftui

我知道有很多关于Type of expression is ambiguous without more context 编译时错误的问题,但我读了很多,似乎不明白为什么会出现我的错误。

首先,我有

protocol ExpensePeriod: AnyObject, Identifiable, Hashable {
    associatedtype Period: ExpensePeriod
    
    var type: Calendar.Component { get set }
    var interval: DateInterval { get set }
    var start: Date { get }
    var end: Date { get }
    var expenses: FetchRequest<Expense> { get }
    
    static func array(from startDate: Date, to endDate: Date) -> [Period]
    
    init(from date: Date)
}

广告然后:

extension ExpensePeriod {
    
    var start: Date { interval.start }
    var end: Date { interval.end }
    var expenses: FetchRequest<Expense> {
        FetchRequest<Expense>(
            entity: Expense.entity(),
            sortDescriptors: [NSSortDescriptor(key: "datetime", ascending: false)],
            predicate: NSPredicate(
                format: "datetime > %@ AND datetime < %@",
                argumentArray: [start, end]
            )
        )
    }
    
    static func array(of timeComponent: Calendar.Component, from startDate: Date, to endDate: Date) -> [Self] {
        var currentDate = startDate
        var array = [Self(from: currentDate)]
        while !Calendar.current.dateInterval(of: timeComponent, for: currentDate)!.contains(endDate) {
            currentDate = Calendar.current.date(byAdding: timeComponent, value: 1, to: currentDate)!
            array.append(Self(from: currentDate))
        }
        return array
    }
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.interval == rhs.interval
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(interval)
    }
}

然后:

final class ExpenseYear: ExpensePeriod {
    
    typealias Period = ExpenseYear
    
    var type: Calendar.Component
    var interval: DateInterval
    
    var year: Int { Calendar.current.component(.year, from: interval.start) }
    var expenseMonths: [ExpenseMonth] {
        return ExpenseMonth.array(from: start, to: end)
    }
    
    static func array(from startDate: Date, to endDate: Date) -> [ExpenseYear] {
        array(of: .year, from: startDate, to: endDate)
    }
    
    init(from date: Date) {
        self.type = .year
        self.interval = Calendar.current.dateInterval(of: type, for: date)!
    }
}

现在是 SwiftUI 主视图:

struct ListView: View {
    
    @Environment(\.managedObjectContext) private var managedObjectContext
    @FetchRequest(
        entity: Expense.entity(),
        sortDescriptors: [NSSortDescriptor(key: "datetime", ascending: false)]
    ) var expenses: FetchedResults<Expense>
    
    @State private var showingNewExpenseSheet = false
    @State private var showingPreferencesSheet = false
    
    private var dateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "YYYY MMM"
        return formatter
    }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(ExpenseYear.array(from: expenses.last!, to: expenses.first!)) { expenseYear in
                    ForEach(expenseYear.expenseMonths) { expenseMonth in
                        MonthlyListView(expenseMonth)
                    }
                    Text("\(0)")
                }.onDelete(perform: deleteExpenseItem)
            }
            .navigationBarTitle("Expenses")
            .navigationBarHidden(true)
        }
    }
    
    func deleteExpenseItem(at offsets: IndexSet) {
        for index in offsets {
            let expense = expenses[index]
            managedObjectContext.delete(expense)
        }
        do {
            try managedObjectContext.save()
        } catch {
            print("Wasn't able to save after delete due to \(error)")
        }
    }
}

struct MonthlyListView: View {
    @Environment(\.managedObjectContext) private var managedObjectContext
    var expenseFetchRequest: FetchRequest<Expense>
    var expenses: FetchedResults<Expense> {
        expenseFetchRequest.wrappedValue
    }
    let expenseMonth: ExpenseMonth
    
    init(_ month: ExpenseMonth) {
        self.expenseMonth = month
        self.expenseFetchRequest = month.expenses
    }
    
    var body: some View {
        Section(header: Text("\(expenseMonth.month)")) {
            ForEach(expenses) { expense in
                ExpenseRowItemView(expense)
            }
        }
    }
}

ExpenseRowItemView 仅显示各种日期时间/备注项目。

Expense 实体如下所示: enter image description here

Type of expression is ambiguous without more context 似乎发生在 ForEach(ExpenseYear.array(from: expenses.last!, to: expenses.first!)) 。我正在使用 Xcode 12 测试版。感谢您的帮助。

(如果你想知道为什么我要经历所有这些麻烦只是为了呈现一个费用列表:我曾经有一个函数遍历每个费用的 datetime 并构建一个嵌套结构SwiftUI 渲染(使用 Section 等)需要花费数年和数月的时间。但我认为这不是一种可扩展/高性能的方法,因为每次渲染 View 时都会调用此函数,从而触及 Core Data 中的每个条目,所以我想我会让每个月列表处理自己的 FetchRequest 和自己的日期边界,这也会使动态 View (例如“选择一个月以查看本月之前的交易列表”)更容易。请告诉我是否还有更好的方法。)

最佳答案

看来你不符合ExpensePeriodExpenseYear你不见了startend变量(很可能是错误的来源,但很难说)

确认后,如果错误仍然存​​在,我将在循环中替换 MonthlyListView查看 Text并且我会不断更换东西,直到找到错误的根源。

此错误通常在您缺少关键字或格式化循环时发生。大多数时候,这只是意味着编译器无法解释您编写的内容。

我会解决这个问题,但上面的代码缺少一些东西,无法通过复制和粘贴来运行它。

编辑:

所以你的问题出在forEach因为如果你仔细观察,你的代码看起来像这样 ForEach(ExpenseYear.array(from: expenses.last!, to: expenses.first!))然而,费用定义如下var expenses: FetchedResults<Expense>该数组中的每一项都是 Expense 类型在你的ExpenseYear排列你的标题看起来像这样 tatic func array(from startDate: Date, to endDate: Date) -> [ExpenseYear]其中第一个和第二个参数的类型是 Date但是您正在向他们传递一个 Expense 类型的项目. expenses.last!返回 Expense对象,那不是 Date !所以要解决这个问题,你首先必须做这样的事情 expenses.last!.datetime!

所以把你的代码改成这样

ForEach(ExpenseYear.array(from: expenses.last!.datetime!, to: expenses.first!.datetime!), id: \.id) { expense in

应该可以解决你的问题。请记住以下几点

  1. 更改此代码以反射(reflect)您应用程序中的所有位置,我只在 1 个实例中更改了它,因为我已注释掉您的其余代码。

  2. 强制解包总是一个坏主意,所以我建议您正确处理日期,但要小心先解包。

此外,我知道您评论说我不需要实现 startendExpenseYear但不幸的是,如果不实现它们我就无法编译,所以我不得不这样做。

或者,您可以更改 .array接收 Expense 的协议(protocol)而不是 Date然后处理如何从对象 Expense 返回数组所以你的协议(protocol)看起来像这样

static func array(from startExpense: Expense, to endExpense: Expense) -> [Period]

实现可以是这样的

static func array(from startExpense: Expense, to endExpense: Expense) -> [ExpenseYear] {
        guard let startDate = startExpense.datetime, let endDate = endExpense.datetime else {
            return []
        }
        
        return array(of: .year, from: startDate, to: endDate)
    }

你已经注意防范 nil 日期,除了实现之外你不需要改变任何东西(老实说我更喜欢这种方法)

我知道要实现第二种方法,您必须更改设置协议(protocol)和其他一些东西的方式,所以您可以做的是传递一个可选的 Date。给你的array ,像这样的东西 static func array(from startExpense: Date?, to endExpense: Date?) -> [Period]

然后守卫打开它们,否则返回空数组。但是你仍然有解包的问题.last.first您的费用 ForEach循环。

祝大家好运!

关于swift - 表达式类型不明确,在 `ForEach` 中自定义类数组上没有更多上下文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62907643/

相关文章:

ios - 如何从一个 xib 文件中注册多个 TableViewCells?

swift - TabBar 后面的 TableView 内容

ios - UIPrintPageRenderer 如何在 US-Letter 和 DIN-A4 格式之间切换

xcode - Swift NSLocale 模拟器 iOS 8

ios - 返回时swiftUI bottomBar工具栏消失

ios - Swift 提取正则表达式匹配

ios - Sprite Kit 的 'didBeginContact' 函数不是很准确。我应该怎么办?

ios - 在启动屏幕中将按钮设为圆形?

SwiftUI 从 String(Base64) 解码图像

ios - SwiftUI 无法获取 Firebase Firestore 实例