【问题标题】:Don't Display Date if Same as Previous Entry如果与上一个条目相同,则不显示日期
【发布时间】:2021-05-06 20:04:24
【问题描述】:

我有一个条目列表,每个条目都附有日期。我只想在日期发生变化时才显示日期。我首先在 iOS 14.4 中开发了这个软件,导致视图不可变错误。这是因为我正在存储和更改输入日期的副本。

现在在 iOS 14.5 版本中,我看不到不可变错误。但是我的软件还是不行。如果您运行代码并在控制台中查看,您会注意到 Xcode 两次检查了我的六个条目:第一次始终为真(显示日期),第二次始终为假(不显示日期)。为什么?

在我的实际代码中,我在此示例代码中使用 Date 类型的日期而不是字符串。在我的实际代码中,操作挂起,因为它通过我的函数 checkDate 无休止地循环(比条目数多很多倍)。 Date 类型的日期是否包括导致比较失败的时间?

如果日期与上一个条目相同,是否有更好的方法来防止显示日期?

struct KitchenItem:  Codable, Identifiable {
    var id = UUID()
    var item: String
    var itemDate: String
    var itemCost: Double
}

class Pantry: ObservableObject {
    
    @Published var oldDate: String = ""
    @Published var kitchenItem: [KitchenItem]
    
    init() {
        self.kitchenItem = []
        
        let item0 = KitchenItem(item: "String Beans", itemDate: "1/13/2021", itemCost: 4.85)
        self.kitchenItem.append(item0)
        
        let item1 = KitchenItem(item: "Tomatoes", itemDate: "1/22/2021", itemCost: 5.39)
        self.kitchenItem.append(item1)
        
        let item2 = KitchenItem(item: "Bread", itemDate: "1/22/2021", itemCost: 4.35)
        self.kitchenItem.append(item2)
        
        let item3 = KitchenItem(item: "Corn", itemDate: "3/18/2021", itemCost: 2.75)
        self.kitchenItem.append(item3)
        
        let item4 = KitchenItem(item: "Peas", itemDate: "3/18/2021", itemCost: 7.65)
        self.kitchenItem.append(item4)
        
        let item5 = KitchenItem(item: "Ice Cream", itemDate: "4/12/2021", itemCost: 7.95)
        self.kitchenItem.append(item5)
    }
}


struct ContentView: View {
    
    @ObservedObject var pantry: Pantry = Pantry()
    
    var body: some View {
        
        LazyVStack (alignment: .leading) {
            Text("Grandma's Food Pantry")
                .font(.title)
                .fontWeight(.bold)
                .padding(.top, 36)
                .padding(.leading, 36)
                .padding(.bottom, 30)
            
            ForEach(0..<pantry.kitchenItem.count, id: \.self) { item in
                
                VStack (alignment: .leading) {
                    showRow(item: item)
                }
            }
        }
    }
}


struct showRow: View {
    
    @ObservedObject var pantry: Pantry = Pantry()
    
    var item: Int
    
    var body: some View {
        
        // don't show the date if is the same as the previous entry
        let newDate = pantry.kitchenItem[item].itemDate
        if checkDate(newDate: newDate) == true {
            Text("\n\(newDate)")
                .font(.title2)
                .padding(.leading, 10)
        }
        
        HStack {
            Text("\(pantry.kitchenItem[item].item)")
                .padding(.leading, 50)
                .frame(width: 150, alignment: .leading)
            
            Text("\(pantry.kitchenItem[item].itemCost, specifier: "$%.2f")")
        }
    }
    
    func checkDate(newDate: String) -> (Bool) {
        print("    ")
        print("new date = \(newDate)")
       
        if newDate == pantry.oldDate {
            print("false: don't show the date")
            return false
        } else {
            pantry.oldDate = newDate
            print("old date = \(pantry.oldDate)")
            print("true: show the date")
            return true
        }
    }
}

实际代码:

struct ListView: View {
    
    @EnvironmentObject var categories: Categories
    @EnvironmentObject var userData: UserData
    @Environment(\.managedObjectContext) var viewContext
    
    var money: String = ""
    var xchRate: Double = 0.0
    var cat: Int = 0
    var mny: String = ""
    
    @FetchRequest(
        entity: CurrTrans.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \CurrTrans.entryDT, ascending: true)]
    ) var currTrans: FetchedResults<CurrTrans>
    
    var body: some View {
        
        GeometryReader { g in
            ScrollView {
                LazyVStack (alignment: .leading) {
                    
                    TitleView()
                    
                    ForEach(currTrans, id: \.self) { item in
                            showRow(item: item, priorDate: priorDate(forItemIndex: item), g: g)
                    }
                    .onDelete(perform: deleteItem)
                }
                .font(.body)
            }
        }
    }
    
    private func priorDate(forItemIndex item: Int) -> Date? {
           guard item > 0 else { return nil }
           return currTrans[item - 1].entryDT
       }
}
struct showRow: View {
    
    @EnvironmentObject var userData: UserData
    
    var item: CurrTrans
    var priorDate: Date?
    var g: GeometryProxy
    
    var payType = ["Cash/Debit", "Credit"]
    
    var body: some View {
        
        // don't show the date if is the same as the previous entry
        let gotDate = item.entryDT ?? Date()
        let newDate = gotDate.getFormattedDate()
            Text("\(newDate)")
                .opacity(gotDate == priorDate ? 0 : 1)
                .font(.title2)
                .padding(.leading, 10)
     
          displays entry parameters in HStack...

【问题讨论】:

  • 不相关,但请注意ContentViewshowRow 中的pantry 对象是两个不同的实例。您应该只创建一次 ObservableObject 并将其传递到层次结构中。
  • 感谢您指出我的班级 Pantry 有不止一个实例。看起来我的示例代码已经消失了!我是一名长期从事 C 和汇编工作的固件工程师,我们不必担心类和实例。我的实际代码确实通过层次结构向下传递,并且条目存储在 coredata 中。这是获取:@FetchRequest(实体:CurrTrans.entity(),sortDescriptors:[NSSortDescriptor(keypath:\CurrTrans.entryDT,升序:true)] var currTrans:FetchedResults

标签: swiftui ios14


【解决方案1】:

你不能在你的 body 方法中改变你的数据,因为这在 SwiftUI 眼中是可憎的。

body 方法内修改oldDate 是错误的。如果您在渲染视图时修改它正在观察的数据,SwiftUI 会感到困惑。此外,SwiftUI 不保证它呈现 LazyVStack(或任何其他容器)的子级的顺序。

如果日期与上一个条目相同,是否有更好的方法来防止显示日期?

是的。将当前条目和之前条目的日期传递到条目视图。

这是您的数据模型和存储,没有繁琐:

struct KitchenItem:  Codable, Identifiable {
    var id = UUID()
    var item: String
    var itemDate: String
    var itemCost: Double
}

class Pantry: ObservableObject {
    @Published var kitchenItems: [KitchenItem] = [
        .init(item: "String Beans", itemDate: "1/13/2021", itemCost: 4.85),
        .init(item: "Tomatoes", itemDate: "1/22/2021", itemCost: 5.39),
        .init(item: "Bread", itemDate: "1/22/2021", itemCost: 4.35),
        .init(item: "Corn", itemDate: "3/18/2021", itemCost: 2.75),
        .init(item: "Peas", itemDate: "3/18/2021", itemCost: 7.65),
        .init(item: "Ice Cream", itemDate: "4/12/2021", itemCost: 7.95),
    ]
}

对于每个KitchenItem,如果有前项,您还需要提取前项的日期。我们将使用一个辅助方法priorDate(forItemIndex:) 来做到这一点。此外,如果您要在视图中创建商店,您需要使用StateObject,而不是ObservedObject。因此:

struct ContentView: View {

    @StateObject var pantry: Pantry = Pantry()

    var body: some View {
        ScrollView(.vertical) {
            LazyVStack (alignment: .leading) {
                Text("Grandma's Food Pantry")
                    .font(.title)
                    .fontWeight(.bold)
                    .padding(.top, 36)
                    .padding(.leading, 36)
                    .padding(.bottom, 30)

                ForEach(0 ..< pantry.kitchenItems.count) { i in
                    if i > 0 {
                        Divider()
                    }
                    KitchenItemRow(item: pantry.kitchenItems[i], priorDate: priorDate(forItemIndex: i))
                }
            }
        }
    }

    private func priorDate(forItemIndex i: Int) -> String? {
        guard i > 0 else { return nil }
        return pantry.kitchenItems[i - 1].itemDate
    }
}

这里是KitchenItemRow。如果日期与前一个项目的日期相同,您可以看到它使日期 Text 透明。我将其保留在原位,但使其透明,因此行布局相同:

struct KitchenItemRow: View {
    var item: KitchenItem
    var priorDate: String?

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text(item.item)
                Spacer()
                Text("\(item.itemCost, specifier: "$%.2f")")
            }
            Text(item.itemDate)
                .opacity(item.itemDate == priorDate ? 0 : 1)
        }
        .padding([.leading, .trailing], 10)
    }
}

这里是TitleView,为了卫生,从ContentView中提取:

struct TitleView: View {
    var body: some View {
        Text("Grandma's Food Pantry")
            .font(.title)
            .fontWeight(.bold)
            .padding(.top, 36)
            .padding(.leading, 36)
            .padding(.bottom, 30)

    }
}

结果:

更新

由于您的“真实代码”使用 onDelete,因此为每个项目提供 ForEach 的 ID 而不是使用索引非常重要。

注意onDelete only works inside List, not inside LazyVStack

所以我们需要将每个项目映射到它的索引,这样我们才能找到之前的项目。这是我的ContentView 的修订版,它使用了onDelete

struct ContentView: View {

    @StateObject var pantry: Pantry = Pantry()

    var body: some View {
        let indexForItem: [UUID: Int] = .init(
            uniqueKeysWithValues: pantry.kitchenItems.indices.map {
                (pantry.kitchenItems[$0].id, $0) })

        List {
            TitleView()

            ForEach(pantry.kitchenItems, id: \.id) { item in
                let i = indexForItem[item.id]!
                KitchenItemRow(item: item, priorDate: priorDate(forItemIndex: i))
            }
            .onDelete(perform: deleteItems(at:))
        }
    }

    private func priorDate(forItemIndex i: Int) -> String? {
        guard i > 0 else { return nil }
        return pantry.kitchenItems[i - 1].itemDate
    }

    private func deleteItems(at offsets: IndexSet) {
        pantry.kitchenItems.remove(atOffsets: offsets)
    }
}

在我的测试中,这有效并且允许滑动删除。我相信您可以将其调整为您的“真实”代码。

【讨论】:

  • 嗨 Rob:我在上面包含了我的实际代码。我不确定如何使用返回类型 Date 来实现 priorDate 私有函数?我需要通过我的 coredata 日期类型来支持
猜你喜欢
  • 2022-01-04
  • 1970-01-01
  • 2021-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多