Hi, I am trying to create somethings like an infinite scroll view with SwiftData but I am not able to figure out how to append elements to the @Query result. Let's say I have 1000 entries in my DB but I only want to load 40 at a time. If the user scrolls to the last element of the result I want to load the next batch of 40 entries.
That is the code:
import SwiftData
import OSLog
private let logger = Logger(subsystem: "SetsTab", category: "SetsTab")
struct SetsTab: View {
@Environment(\.modelContext) private var modelContext
@Binding var searchText: String
@Query private var sets: [SetModel]
@State private var fetchLimit = 40
@State private var offset = 0
init(searchText: Binding<String>) {
_searchText = searchText
if searchText.wrappedValue.isEmpty {
var fetchDesc = FetchDescriptor<SetModel>()
fetchDesc.fetchLimit = fetchLimit
fetchDesc.fetchOffset = offset
_sets = Query(fetchDesc)
} else {
let term = searchText.wrappedValue
let setCodePred = #Predicate<SetModel> {
$0.name.contains(term)
}
var fetchDesc = FetchDescriptor<SetModel>(predicate: setCodePred)
fetchDesc.fetchLimit = fetchLimit
fetchDesc.fetchOffset = offset
_sets = Query(fetchDesc)
}
}
var body: some View {
ScrollView{
LazyVStack{
ForEach(sets){actSet in
VStack{
Text("Set: \(actSet.name)")
}
.onAppear {
self.loadMoreContentIfNeeded(currentItem: actSet)
}
}
}}
.onAppear {
for i in 1...1000 {
let set = SetModel(name: "Set \(i)")
modelContext.insert(set)
}
try! modelContext.save()
}
}
func loadMoreContentIfNeeded(currentItem item: SetModel?) {
if sets.endIndex < self.fetchLimit
{
return
}
guard let item = item else {
loadMoreContent()
return
}
let thresholdIndex = sets.index(sets.endIndex, offsetBy: -5)
if sets.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
loadMoreContent()
}
}
private func loadMoreContent() {
self.offset += self.fetchLimit
loadMoreSets()
}
private func loadMoreSets(){
logger.info("Loading more Sets with offset \(self.offset)")
let term = searchText
let setCodePred = #Predicate<SetModel> {
$0.name.contains(term)
}
do{
var fetchDesc = FetchDescriptor<SetModel>(predicate: setCodePred)
fetchDesc.fetchLimit = self.fetchLimit
fetchDesc.fetchOffset = self.offset
var newBatchOfSets = try modelContext.fetch(fetchDesc)
logger.info("new Batch of Sets count: \(newBatchOfSets.count)")
// sets.append(contentsOf: newBatchOfSets)
}
catch{
logger.error("\(error.localizedDescription)")
}
}
}
The logic for registering when to load the new batch works so loadMoreSets() is called correctly but to actually fetch the new batch and appending to the already fetched data is missing.
I commented this part: sets.append(contentsOf: newBatchOfSets)
because it is giving me the error:
Cannot use mutating member on immutable value: 'sets' is a get-only property
so basically telling me that the @Query var sets can only be set inside the initializer.
So how can we realise something like an infinite scroll view with SwiftData? I feel that this is an valid and for me mandatory use case when working with thousands of db entries in order to be performant.
Has anybody realised this in a different way?
Best regards,
Sven
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
Hi,
I am trying my first SwiftData migration, but my custom migration stage never gets called.
Since I am not sure if this is a bug with the current beta of if I am "holding it wrong" I was wondering, if anybody got migration working (their MigrationStage.custom called)? Would be great, if you could just let me know, if you got it working or running into the same issue! :-)
Thank you!
Cheers, Michael
Why is the order of my array random when i use the @Model macro.
class TestModel {
var name: String?
var array: [TestModel2]
init(name: String = "") {
self.name = name
array = []
}
}
class TestModel2 {
var name: String?
init(name: String = "") {
self.name = name
}
}
This works fine, and all the items in array are in the order I add them.
But if I declare both of them as @Model, like this:
@Model
class TestModel {
var name: String?
var array: [TestModel2]
init(name: String = "") {
self.name = name
array = []
}
}
@Model
class TestModel2 {
var name: String?
init(name: String = "") {
self.name = name
}
}
The array items are always in a random order. When I reload the view where they are displayed or when I add items to the array the order is randomised.
Is this a beta bug? Or is this intended?
I'm working on an app where I want to add a large amount of data to SwiftData, but roll back if any errors occur during this process. SwiftData has a method called transaction (linked below), but it does not have any documentation and I can't find any info about it online. What does it actually do?
https://developer.apple.com/documentation/swiftdata/modelcontext/transaction(block:)
I have a model like this:
@Model
final class TreeNodeModel{
var createdAt: Date
var title: String
var content: String
//Relations
@Relationship(deleteRule:.cascade) var children: [TreeNodeModel]?
init(title:String,content:String = "",) {
self.title = title
self.content = content
}
}
When I try to insert models like below (works well):
let parent = TreeNodeModel(title: "parent")
let child = TreeNodeModel(title: "child")
parent.children!.append(child)
modelContext.insert(parent)
But if try to insert models like below (wierd things happened): not only parent has one children [child], child also has one children [parent]
let parent = TreeNodeModel(title: "parent")
modelContext.insert(parent)
let child = TreeNodeModel(title: "child")
parent.children!.append(child)
This is quit wierd, anyone has met this kind of issue?
I was testing out iOS 17 Beta 5 with Advanced Data Protection turned on. I wanted to downgrade to iOS 16 for improved stability; however, once I downgraded all of my iCloud data was gone (~100GB) and about 10+ years of digital accumulation poof.
Yes, I'm on the correct iCloud account. Yes, I'm logged in. Yes, I've checked iCloud.com as well as my device. Yes, I went back to iOS 17 Beta 5 to see if my backup would show up but there is no data and no backup anywhere else. I was discussing with apple tech support for over an hour and they were unable to assist.
Just checking if anyone else might have suggestions on other things to try.
.modelContainer(for: MyMode.self, isUndoEnabled: true)
This may work for single model containers, but I have a number of models. I don't see how to enable undo for multiple model containers.
In Xcode 15 beta 6, the swift compiler crashes with a simple SwiftData model. The model compiles OK, but any attempt to instantiate the entity in code, including a unit test, will crash the compiler. A stack track is provided with a note to submit a bug report. Some SwiftData classes work without error, but this example will crash. Cannot find any workaround and am forced to remain on beta 5 for now. Anyone else experiencing this? Works without error in beta 5.
Compiler crashes in a basic unit test on this line, or any other attempt to instantiate the model.
let note = Note(content: "Went for a bike ride this morning!")
public final class Note: Identifiable {
@Attribute(.unique) public private(set) var id: UUID?
public private(set) var createdDate: Date
public private(set) var updatedDate: Date
/// Optional title for this note
public var title: String? {
didSet { updatedDate = Date() }
}
/// Note content
public var content: String? {
didSet { updatedDate = Date() }
}
public init(title: String? = nil, content: String) {
self.id = UUID()
self.createdDate = Date()
self.updatedDate = Date()
self.title = title
self.content = content
}
}
If I comment let _ = entries everything goes well, but if not, accessing(not editing) the collection I don't know why forces the view update (dependency change in items) and with the view update the computed property checkingDate is triggered firing the view update... and so on. Cycle
Why accessing entries forces view update ? If I change let _ = entries for let _ = name everything goes well so I conclude that is a problem with collection...
Models:
@Model
final class Item {
var name: String
@Relationship(deleteRule:.cascade) var entries: [Entry]
init(name: String) {
self.name = name
entries = []
}
}
extension Item {
@Transient var checkingDate: Date {
let _ = entries // < -- !!!
//...
return Date()
}
}
@Model final class Entry {
var value: Int
var date: Date
init(value: Int, date: Date) {
self.value = value
self.date = date
}
}
View:
struct ContentView: View {
@Query(sort: \Item.name) private var items: [Item]
@Environment(\.modelContext) private var context
var body: some View {
let _ = Self._printChanges()
List{
ForEach(items){item in
Text("Name:\(item.name), checking date: \(item.checkingDate, format: Date.FormatStyle(date: .numeric, time: .standard))")
}
}
}
}
@Model final class Entry {
var value: Int
var date: Date
var item: Item?
init(value: Int, date: Date) {
self.value = value
self.date = date
}
}
@Model
final class Item {
var name: String
@Relationship(deleteRule:.cascade, inverse: \Entry.item) var entries: [Entry]
init(name: String) {
self.name = name
entries = []
}
}
extension Item {
func checkingDate(in context: ModelContext)->Date{
try! context.fetch(FetchDescriptor<Entry>(predicate:#Predicate<Entry>{$0.item == self})).last!.date
}
}
same if self == self
Why I'm fetching and not directly accessing entries: https://developer.apple.com/forums/thread/735735
The Sendable documentation says we can mark reference types as Sendable if they "internally manage access to their state."
Adding Sendable conformance to my SwiftData classes silences warnings such as the following: "Non-sendable type '[Item]' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary"
@Model final class Item: Sendable {
var sampleProperty = ""
}
My understanding is that the compiler would complain if adding explicit Sendable conformance to a swift data model was breaking concurrency rules, but I wanted to check with the community to see what everyone thinks.
Best,
Taylor
Running on Version 15.0 beta 6 (15A5219j)
Crash on launch (replicatable with a new template iOS project with SwiftData in addition to my existing project). Error on launch looks like this:
dyld[8046]: Symbol not found: _$s9SwiftData15PersistentModelP14schemaMetadataSaySS_s10AnyKeyPathCypSgAGtGyFZTq
Referenced from: <CFCE9A78-4C88-3DCA-AA36-643C911902C4> /private/var/containers/Bundle/Application/8F5A5864-78B5-42DD-9481-9AABC79AFCF4/TestThisNew.app/TestThisNew
Expected in: <8B52C2B3-931A-3736-B357-ECF87A41F3EB> /System/Library/Frameworks/SwiftData.framework/SwiftData
Message from debugger: killed
Hi guys,
First of all, I'm sorry if this is the wrong place to post this. I'm in the last steps of my task manager app: getting the tasks to sync between devices. However, I get the error "This NSPersistentStoreCoordinator has no persistent stores (unknown). It cannot perform a save operation." What does this error exactly mean? My container is initialised so it should have a persistent store, right? I've also enabled all the proper capabilities I'm pretty sure (eg, I've enabled CloudKit, created a container, enabled background fetch and remote notifications.) Here is the code for my data controller:
import CoreData
import Foundation
class DataController: ObservableObject {
let container = NSPersistentCloudKitContainer(name: "TaskDataModel")
init() {
guard let description = container.persistentStoreDescriptions.first else {
fatalError("Container descriptions not loaded")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
container.loadPersistentStores { description, error in
if let error = error {
print("Core Data failed to load: \(error.localizedDescription)")
}
}
}
}
Here is TaskManMain:
@main
struct TaskManApp: App {
@StateObject private var dataController = DataController()
var body: some Scene {
WindowGroup {
MainView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
}
Here is the full repo if y'all are interested:
https://github.com/aabagdi/TaskMan
Thanks for any help!
Anyone know the correct usage for CKQueryOperation.recordMatchedBlock and .queryResultBlock?
Xcode is telling me to use them as the old .recordFetchedBlock and .queryCompletionBlock are deprecated, but there isn't any documentation to say how they should be used. ChatGPT, Bard etc aren't able to provide a correct answer either:
specifically, I need to convert the following to use those methods:
operation.recordFetchedBlock = { record in
// Convert CKRecord to Core Data object and save to Core Data
self.saveToCoreData(record: record)
}
operation.queryCompletionBlock = { (cursor, error) in
// Handle potential errors
if let error = error {
print("Error syncing first of month records: \(error)")
}
if let cursor = cursor {
// There are more records to fetch for this category, continue fetching
self.continueSyncing(cursor: cursor, completion: completion)
} else {
// Done syncing this category, move to the next sync task
completion()
}
}
Hi, i'm trying out SwiftData in Xcode 15.0 beta 5.
I’m building a POC of a Note app using SwiftData for the data persistence. I started by creating a new model SimpleNote, where i could successfully implement a CRUD of a note.
Now i want to add a new feature introducing the TODO list, however when i tried add the new relationship between the new model TodoItem to SimpleNote the app crashes with the following error:
NotesPOC crashed due to fatalError in ModelContainer.swift at line 167. failed to find a currently active container for SimpleNote
Both models works fine independently, only crashes when the Relationship is set. I tried to clean simulator data, remove DerivedData, clean the Build Folder but nothing works.
The code:
SimpleNote.swift:
final class SimpleNote: Identifiable {
let uuid = UUID()
var title: String = ""
var content: String = ""
var createdAt: Date = Date()
var modifiedAt: Date = Date()
var theme: String = ""
@Relationship(.cascade) var todoList: [TodoItem] = []
init(title: String, content: String, createdAt: Date, modifiedAt: Date, todoList: [TodoItem]) {
self.title = title
self.content = content
self.createdAt = createdAt
self.modifiedAt = modifiedAt
self.theme = Theme.random().rawValue
self.todoList = todoList
}
}
TodoItem.swift:
import Foundation
import SwiftData
@Model
class TodoItem: Identifiable {
let uuid = UUID()
var isCompleted: Bool = false
var content: String = ""
init(isCompleted: Bool, content: String) {
self.isCompleted = isCompleted
self.content = content
}
}
NoteApp.swift:
import SwiftData
@main
struct StickyNotesPOCApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black)
}
}
.modelContainer(for: [SimpleNote.self, TodoItem.self])
}
}
Has anyone faced this issue? am i missing something?
In Core Data, I can merge two predicates (perform an OR operation) using the following code:
let compound3 = NSCompoundPredicate(type: .or, subpredicates: [compound1, compound2])
How can I achieve a similar operation in SwiftData with new Predicate?
Hello,
I'm trying to use a #Predicate filtering by an Enum property. But in runtime I getSwiftData.SwiftDataError._Error.unsupportedPredicate
Somehow I couldn't find any recent posts related to this issue. Has anyone encountered this or been able to solve/workaround it? Will it be ever fixed?
What is the point in supporting stored enums if I cannot use them to run a query? I'm struggling with this since beta 1, it really seems that SwiftData is not a priority for Apple right now. I'm really trying to stick with it but I'm running into issues that are indefinitely delaying all my development every day.
@Model final class ModelA {
var name: String
enum ModelAType: Int, Codable { case type1 = 1, type2 }
var type: ModelAType
// ...
}
// ...
let predicateConstant = ModelA.ModelAType.type1
#Predicate<ModelA> { $0.type == predicateConstant }
Hi There,
i'm not sure if anything changed in Beta 7 or it's me being an ***** but I can't seem to update my relationship property, anyone else experienced this?
@Model final class Goal {
// More properties here
@Relationship(deleteRule: .cascade, inverse: \Progress.goal) var progress: [Progress]?
init(progress: [Progress]? = []) {
self.progress = progress
}
func updateProgress(with value: Double) {
// I've also tried this with having the modelContext in the initialiser
let context = ModelContext(DataStore.container)
let newProgress = Progress(date: Date.now, value: value)
newProgress.goal = self
context.insert(newProgress)
self.progress?.append(newProgress)
// Other code
}
}
@Model final class Progress {
var progressDate: Date?
var value: Double?
var goal: Goal?
init(date: Date = Date.now, value: Double = 0.0, goal: Goal? = nil) {
self.progressDate = date
self.value = value
self.goal = goal
}
}
Everytime I call the updateProgress method I get a fatal error (e.g. goal.updateProgress(with: 33.2)
The Error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-one relationship: property = "goal"; desired type = NSManagedObject; given type = NSManagedObject; value = <NSManagedObject: 0x2823e0c80> (entity: Goal; id: 0x8617307020b13c03 <x-coredata://7AE46844-B3C6-46A3-B350-FC65B7CCDF8F/Goal/p17>; data: { // Properties here })
I've tried many different ways but without any luck…
In case this is a bug: FB13034172
I have created an actor for the ModelContainer, in order to perform a data load when starting the app in the background. For this I have conformed to the ModelActor protocol and created the necessary elements, even preparing for test data.
Then I create a function of type async throws to perform the database loading processes and everything works fine, in that the data is loaded and when loaded it is displayed reactively.
actor Container: ModelActor {
nonisolated let modelContainer: ModelContainer
nonisolated let modelExecutor: ModelExecutor
static let modelContainer: ModelContainer = {
do {
return try ModelContainer(for: Empleados.self)
} catch {
fatalError()
}
}()
let context: ModelContext
init(container: ModelContainer = Container.modelContainer) {
self.modelContainer = container
let context = ModelContext(modelContainer)
self.modelExecutor = DefaultSerialModelExecutor(modelContext: context)
self.context = context
Task {
do {
try await loadData()
} catch {
print("Error en la carga \(error)")
}
}
}
}
The problem is that, in spite of doing the load inside a Task and that there is no problem, when starting the app it stops responding the UI while loading to the user interactions. Which gives me to understand that actually the task that should be in a background thread is running somehow over the MainActor.
As I have my own API that will provide the information to my app and refresh it at each startup or even send them in Batch when the internet connection is lost and comes back, I don't want the user to be continuously noticing that the app stops because it is performing a heavy process that is not really running in the background.
Tested and compiled on Xcode 15 beta 7.
I made a Feedback for this: FB13038621.
Thanks
Julio César
I have in my model a date created attribute and a date last updated attribute. When adding a new item, I set both to the current timestamp. However, on an update I would like to be able to have the autosave option auto-update the last updated date automatically. Turning off autosave and doing it manually is challenging to get right.
Does anyone know of a solution to this such as extending ModelContext? It seems like last updated date would be a common model requirement.