Mac Catalyst下ScrollView嵌套VStack分页无限滚动实现求助
问题描述
我正在实现基于Core Data查询请求、包含VStack的ScrollView分页无限滚动功能。当前方案在iOS 17和16.4上可正常运行,但在Mac Catalyst 16上无法生效。注意:我不能使用List组件(已找到List的可行实现方案)。
以下代码基于StackOverflow优质回答编写,依赖名为Item的Core Data实体,该实体包含date、timestamp两个Date类型属性,以及一个Int32类型的value属性。希望有人能帮忙修复使其在Mac Catalyst上正常工作,谢谢!
import SwiftUI import CoreData struct PositionData: Identifiable { let id: Int let center: Anchor<CGPoint> } struct Positions: PreferenceKey { static var defaultValue: [PositionData] = [] static func reduce(value: inout [PositionData], nextValue: () -> [PositionData]) { value.append(contentsOf: nextValue()) } } struct ScrollGeomSectionedFetchQueryView: View { @Environment(\.managedObjectContext) private var viewContext @State var fetchLimit = 10 var body: some View { NavigationView { SectionedFetchQueryScrollVStackGeomView(initialFetchLimit: fetchLimit, fetchLimitBinding: $fetchLimit) .toolbar { ToolbarItem { Button(action: { addItem(viewContext) }) { Label("Add Item", systemImage: "plus") } } ToolbarItem { Button(action: { add50Items(viewContext) } ) { Label("Add 50 Items", systemImage: "plus.square.on.square") } } } } } } struct SectionedFetchQueryScrollVStackGeomView: View { @Environment(\.managedObjectContext) private var viewContext @SectionedFetchRequest private var items: SectionedFetchResults<Date, Item> @Binding var fetchLimitBinding : Int @State var flag = false init(initialFetchLimit: Int, fetchLimitBinding: Binding<Int>) { self._fetchLimitBinding = fetchLimitBinding let request: NSFetchRequest<Item> = Item.fetchRequest() request.sortDescriptors = [ NSSortDescriptor(keyPath: \Item.timestamp, ascending: false) ] request.fetchLimit = initialFetchLimit _items = SectionedFetchRequest<Date, Item>(fetchRequest: request, sectionIdentifier: \.date!) } func getPosition(proxy: GeometryProxy, tag: Int, preferences: [PositionData])->CGPoint { let p = preferences.filter({ (p) -> Bool in p.id == tag }) if p.isEmpty { return .zero } if proxy.size.height - proxy[p[0].center].y > 0 && flag == false { self.flag.toggle() fetchLimitBinding = fetchLimitBinding + 10 DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { self.flag.toggle() } print("fetch") } return .zero } var body: some View { ScrollViewReader { proxy in ScrollView([.vertical]) { HStack(alignment: .top, spacing: 0) { VStack(alignment: .leading, spacing: 0) { EmptyView().id("top") ForEach(items) { section in ForEach(section) { item in NavigationLink(destination: EditItemView(item: item)) { Text("\(item.timestamp!, formatter: timeFormatter) - \(item.value)") } } } Rectangle().tag(items.count).frame(height: 0).anchorPreference(key: Positions.self, value: .center) { (anchor) in [PositionData(id: self.items.count, center: anchor)] }.id(items.count) } } } .backgroundPreferenceValue(Positions.self) { (preferences) in GeometryReader { proxy in Rectangle().frame(width: 0, height: 0).position(self.getPosition(proxy: proxy, tag: self.items.count, preferences: preferences)) } } } } } private func stripTime(_ timestamp: Date?) -> Date { let components = Calendar.current.dateComponents([.year, .month, .day], from: timestamp!) let date = Calendar.current.date(from: components) return date! } private func addItem(_ viewContext: NSManagedObjectContext) { withAnimation { let newItem = Item(context: viewContext) newItem.timestamp = Date() newItem.date = stripTime(newItem.timestamp) newItem.value = Int32(Int.random(in: 1..<1000)) saveContext(viewContext) } } private func add50Items(_ viewContext: NSManagedObjectContext) { withAnimation { for _ in 0..<50 { let newItem = Item(context: viewContext) newItem.timestamp = Date() newItem.date = stripTime(newItem.timestamp) newItem.value = Int32.random(in: 0..<1000) } saveContext(viewContext) } } private func saveContext(_ viewContext: NSManagedObjectContext) { if viewContext.hasChanges { do { try viewContext.save() } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } } } let datetimeFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .short return formatter }() let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .none return formatter }() let timeFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .none formatter.timeStyle = .short return formatter }() struct EditItemView: View { @Environment(\.managedObjectContext) private var viewContext @ObservedObject var item: Item @State private var selectedDate: Date init(item: Item) { self.item = item self._selectedDate = State(initialValue: item.timestamp!) } var body: some View { Form { DatePicker("Date", selection: $selectedDate, displayedComponents: .date) DatePicker("Time", selection: $selectedDate, displayedComponents: .hourAndMinute) LabeledContent("Value") { TextField("Value", value: $item.value, formatter: NumberFormatter()) } } .navigationTitle("Edit") .onDisappear { item.timestamp = selectedDate item.date = stripTime(item.timestamp) try? viewContext.save() } } } struct PersistenceController { static let shared = PersistenceController() let container: NSPersistentContainer init() { container = NSPersistentContainer(name: "TestPagination") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) container.viewContext.automaticallyMergesChangesFromParent = true } } @main struct TestPaginationApp: App { let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { TabView { ScrollGeomSectionedFetchQueryView() .tabItem { Text("ScrollGeom SectionedFetch") } } .environment(\.managedObjectContext, persistenceController.container.viewContext) } } }
内容的提问来源于stack exchange,提问作者John Cashew
相关产品推荐
相关产品推荐

