Sorting a SwiftData array

I have the following query:

  @Query(sort: \Account.orderIndex) var accounts: [Account]

The user is able to move accounts in the display (using the Edit button), so after the move, I update the moved account’s orderIndex according to its new position. I then want to re-display the list showing the new order, by sorting the array.

Currently, I do this with

try? modelContext.save()

which does the job, but I can’t help but feel that there is a better way.

Is there?

If you are manually choosing the order in which the the accounts items are displayed then what you are doing to record that order and save it makes sense.

This is one way and it works, but it implies re-writing all of your list items every time you reorder them. Not necessarily bad but something to keep in mind.

Alternatively, you can also store an array of ids that persists the order of elements. It requires more code but it works.

See the post on Person vs PersonOrder here to get an idea:

I have personally tried both ways and each had their own quirks and advantages so in the end it really depends on what other requirements your project needs to satisfy

Thanks to you and Chris. The list will most likely consist of fewer than ten items and reordering will rarely occur, so I guess I’ll stick with this method.

1 Like

I revisited the project and I think the @Query may not be the best way to do it because the reordering with a list can be glitchy:

You use .onMove on the list, which moves the item in the View, triggers your function to change each item’s sortIndex, then those changes trigger the @Query which redraws the view again in a sometimes glitchy way…

So I prefer fetching the data in the correct order on app init using sortIndex, put it in an array, and then just manipulate an array somewhere (in the view in a @State, in a view model as @Published…). Upon reordering update the sortIndex but you don’t actually need the Query. The array order is persisted across sessions via sortIndex.

And forget about Person & PersonOrder in my previous post, it’s glitchier and brings useless complexity

Can you tell me what you mean by “glitchy”? I haven’t seen any problem with the method I described.

The easiest way is to sort the array manually through setting up your own sort routines. Just retrieve the data in the default order and then sort at your leisure. That overcomes the the need for CoreData (SwiftData) (sqlite in the background) to set up multiple indexes in order to satisfy your needs. sqlite is fast but if you have a look at the table and index structure for even the simplest SwiftData database model it’s somewhat mind boggling.

As an example for these two relationships:

@Model
class Restaurant {
    var name: String = ""
    var priceRating: Int = 3
    var qualityRating: Int = 3
    var speedRating: Int = 3
    @Relationship(deleteRule: .cascade, inverse: \Dish.restaurant) var dishes: [Dish]?

    var unwrappedDishes: [Dish] {
        dishes ?? []
    }

    var totalRating: Double {
        Double(priceRating + qualityRating + speedRating) / 3
    }

    init(name: String, priceRating: Int, qualityRating: Int, speedRating: Int, dishes: [Dish]) {
        self.name = name
        self.priceRating = priceRating
        self.qualityRating = qualityRating
        self.speedRating = speedRating
        self.dishes = []
    }
}

… and

@Model
class Dish {
    var name: String = ""
    var review: String = ""
    var restaurant: Restaurant?

    init(name: String, review: String, restaurant: Restaurant? = nil) {
        self.name = name
        self.review = review
        self.restaurant = restaurant
    }
}

This is the schema in sqlite:

sqlite> .sch
CREATE TABLE ZDISH ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZRESTAURANT INTEGER, ZNAME VARCHAR, ZREVIEW VARCHAR );
CREATE TABLE ZRESTAURANT ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZPRICERATING INTEGER, ZQUALITYRATING INTEGER, ZSPEEDRATING INTEGER, ZNAME VARCHAR );
CREATE INDEX ZDISH_ZRESTAURANT_INDEX ON ZDISH (ZRESTAURANT);
CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER);
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB);
CREATE TABLE Z_MODELCACHE (Z_CONTENT BLOB);
CREATE TABLE ACHANGE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCHANGETYPE INTEGER, ZENTITY INTEGER, ZENTITYPK INTEGER, ZTRANSACTIONID INTEGER, ZCOLUMNS BLOB );
CREATE TABLE ATRANSACTION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZAUTHORTS INTEGER, ZBUNDLEIDTS INTEGER, ZCONTEXTNAMETS INTEGER, ZPROCESSIDTS INTEGER, ZTIMESTAMP FLOAT, ZAUTHOR VARCHAR, ZBUNDLEID VARCHAR, ZCONTEXTNAME VARCHAR, ZPROCESSID VARCHAR, ZQUERYGEN BLOB );
CREATE TABLE ATRANSACTIONSTRING ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZNAME VARCHAR );
CREATE INDEX ACHANGE_ZTRANSACTIONID_INDEX ON ACHANGE (ZTRANSACTIONID);
CREATE INDEX ATRANSACTION_ZAUTHORTS_INDEX ON ATRANSACTION (ZAUTHORTS);
CREATE INDEX ATRANSACTION_ZBUNDLEIDTS_INDEX ON ATRANSACTION (ZBUNDLEIDTS);
CREATE INDEX ATRANSACTION_ZCONTEXTNAMETS_INDEX ON ATRANSACTION (ZCONTEXTNAMETS);
CREATE INDEX ATRANSACTION_ZPROCESSIDTS_INDEX ON ATRANSACTION (ZPROCESSIDTS);
CREATE INDEX Z_TRANSACTION_TransactionAuthorIndex ON ATRANSACTION (ZAUTHOR COLLATE BINARY ASC);
CREATE INDEX Z_TRANSACTION_TransactionTimestampIndex ON ATRANSACTION (ZTIMESTAMP COLLATE BINARY ASC);
CREATE UNIQUE INDEX Z_TRANSACTIONSTRING_UNIQUE_NAME ON ATRANSACTIONSTRING (ZNAME COLLATE BINARY ASC);
CREATE TABLE ANSCKDATABASEMETADATA ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZDATABASESCOPENUM INTEGER, ZHASSUBSCRIPTIONNUM INTEGER, ZLASTFETCHDATE TIMESTAMP, ZDATABASENAME VARCHAR, ZCURRENTCHANGETOKEN BLOB );
CREATE TABLE ANSCKEVENT ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCLOUDKITEVENTTYPE INTEGER, ZCOUNTAFFECTEDOBJECTS INTEGER, ZCOUNTFINISHEDOBJECTS INTEGER, ZERRORCODE INTEGER, ZSUCCEEDED INTEGER, ZENDEDAT TIMESTAMP, ZSTARTEDAT TIMESTAMP, ZERRORDOMAIN VARCHAR, ZEVENTIDENTIFIER BLOB );
CREATE TABLE ANSCKEXPORTEDOBJECT ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZCHANGETYPENUM INTEGER, ZTYPENUM INTEGER, ZOPERATION INTEGER, ZCKRECORDNAME VARCHAR, ZZONENAME VARCHAR );
CREATE TABLE ANSCKEXPORTMETADATA ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZEXPORTEDAT TIMESTAMP, ZIDENTIFIER VARCHAR, ZHISTORYTOKEN BLOB );
CREATE TABLE ANSCKEXPORTOPERATION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZSTATUSNUM INTEGER, ZEXPORTMETADATA INTEGER, ZIDENTIFIER VARCHAR );
CREATE TABLE ANSCKHISTORYANALYZERSTATE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZENTITYID INTEGER, ZENTITYPK INTEGER, ZFINALCHANGETYPENUM INTEGER, ZFINALTRANSACTIONNUMBER INTEGER, ZORIGINALCHANGETYPENUM INTEGER, ZORIGINALTRANSACTIONNUMBER INTEGER, ZFINALCHANGEAUTHOR VARCHAR );
CREATE TABLE ANSCKIMPORTOPERATION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZIMPORTDATE TIMESTAMP, ZOPERATIONUUID BLOB, ZCHANGETOKENBYTES BLOB );
CREATE TABLE ANSCKIMPORTPENDINGRELATIONSHIP ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZNEEDSDELETE INTEGER, ZOPERATION INTEGER, ZCDENTITYNAME VARCHAR, ZRECORDNAME VARCHAR, ZRECORDZONENAME VARCHAR, ZRECORDZONEOWNERNAME VARCHAR, ZRELATEDENTITYNAME VARCHAR, ZRELATEDRECORDNAME VARCHAR, ZRELATEDRECORDZONENAME VARCHAR, ZRELATEDRECORDZONEOWNERNAME VARCHAR, ZRELATIONSHIPNAME VARCHAR );
CREATE TABLE ANSCKMETADATAENTRY ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZBOOLVALUENUM INTEGER, ZINTEGERVALUE INTEGER, ZDATEVALUE TIMESTAMP, ZKEY VARCHAR, ZSTRINGVALUE VARCHAR, ZTRANSFORMEDVALUE BLOB );
CREATE TABLE ANSCKMIRROREDRELATIONSHIP ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZISPENDING INTEGER, ZISUPLOADED INTEGER, ZNEEDSDELETE INTEGER, ZRECORDZONE INTEGER, ZCDENTITYNAME VARCHAR, ZCKRECORDID VARCHAR, ZRECORDNAME VARCHAR, ZRELATEDENTITYNAME VARCHAR, ZRELATEDRECORDNAME VARCHAR, ZRELATIONSHIPNAME VARCHAR, ZCKRECORDSYSTEMFIELDS BLOB );
CREATE TABLE ANSCKRECORDMETADATA ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZENTITYID INTEGER, ZENTITYPK INTEGER, ZLASTEXPORTEDTRANSACTIONNUMBER INTEGER, ZNEEDSCLOUDDELETE INTEGER, ZNEEDSLOCALDELETE INTEGER, ZNEEDSUPLOAD INTEGER, ZPENDINGEXPORTCHANGETYPENUMBER INTEGER, ZPENDINGEXPORTTRANSACTIONNUMBER INTEGER, ZRECORDZONE INTEGER, ZCKRECORDNAME VARCHAR, ZCKRECORDSYSTEMFIELDS BLOB, ZCKSHARE BLOB, ZENCODEDRECORD BLOB );
CREATE TABLE ANSCKRECORDZONEMETADATA ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZHASRECORDZONENUM INTEGER, ZHASSUBSCRIPTIONNUM INTEGER, ZNEEDSIMPORT INTEGER, ZNEEDSNEWSHAREINVITATION INTEGER, ZNEEDSRECOVERYFROMIDENTITYLOSS INTEGER, ZNEEDSRECOVERYFROMUSERPURGE INTEGER, ZNEEDSRECOVERYFROMZONEDELETE INTEGER, ZNEEDSSHAREDELETE INTEGER, ZNEEDSSHAREUPDATE INTEGER, ZSUPPORTSATOMICCHANGES INTEGER, ZSUPPORTSFETCHCHANGES INTEGER, ZSUPPORTSRECORDSHARING INTEGER, ZSUPPORTSZONESHARING INTEGER, ZDATABASE INTEGER, ZLASTFETCHDATE TIMESTAMP, ZCKOWNERNAME VARCHAR, ZCKRECORDZONENAME VARCHAR, ZCURRENTCHANGETOKEN BLOB, ZENCODEDSHAREDATA BLOB );
CREATE TABLE ANSCKRECORDZONEMOVERECEIPT ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZNEEDSCLOUDDELETE INTEGER, ZRECORDMETADATA INTEGER, ZMOVEDAT TIMESTAMP, ZOWNERNAME VARCHAR, ZRECORDNAME VARCHAR, ZZONENAME VARCHAR );
CREATE TABLE ANSCKRECORDZONEQUERY ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZRECORDZONE INTEGER, ZLASTFETCHDATE TIMESTAMP, ZMOSTRECENTRECORDMODIFICATIONDATE TIMESTAMP, ZRECORDTYPE VARCHAR, ZPREDICATE BLOB, ZQUERYCURSOR BLOB );
CREATE UNIQUE INDEX Z_NSCKDatabaseMetadata_UNIQUE_databaseScopeNum ON ANSCKDATABASEMETADATA (ZDATABASESCOPENUM COLLATE BINARY ASC);
CREATE UNIQUE INDEX Z_NSCKEvent_UNIQUE_eventIdentifier ON ANSCKEVENT (ZEVENTIDENTIFIER COLLATE BINARY ASC);
CREATE INDEX ANSCKEXPORTEDOBJECT_ZOPERATION_INDEX ON ANSCKEXPORTEDOBJECT (ZOPERATION);
CREATE INDEX ANSCKEXPORTOPERATION_ZEXPORTMETADATA_INDEX ON ANSCKEXPORTOPERATION (ZEXPORTMETADATA);
CREATE UNIQUE INDEX Z_NSCKHistoryAnalyzerState_UNIQUE_entityId_entityPK ON ANSCKHISTORYANALYZERSTATE (ZENTITYID COLLATE BINARY ASC, ZENTITYPK COLLATE BINARY ASC);
CREATE INDEX ANSCKIMPORTPENDINGRELATIONSHIP_ZOPERATION_INDEX ON ANSCKIMPORTPENDINGRELATIONSHIP (ZOPERATION);
CREATE INDEX ANSCKMIRROREDRELATIONSHIP_ZRECORDZONE_INDEX ON ANSCKMIRROREDRELATIONSHIP (ZRECORDZONE);
CREATE UNIQUE INDEX Z_NSCKMirroredRelationship_UNIQUE_ckRecordID_recordZone ON ANSCKMIRROREDRELATIONSHIP (ZCKRECORDID COLLATE BINARY ASC, ZRECORDZONE COLLATE BINARY ASC);
CREATE INDEX ANSCKRECORDMETADATA_ZRECORDZONE_INDEX ON ANSCKRECORDMETADATA (ZRECORDZONE);
CREATE UNIQUE INDEX Z_NSCKRecordMetadata_UNIQUE_entityId_entityPK ON ANSCKRECORDMETADATA (ZENTITYID COLLATE BINARY ASC, ZENTITYPK COLLATE BINARY ASC);
CREATE UNIQUE INDEX Z_NSCKRecordMetadata_UNIQUE_ckRecordName_recordZone ON ANSCKRECORDMETADATA (ZCKRECORDNAME COLLATE BINARY ASC, ZRECORDZONE COLLATE BINARY ASC);
CREATE INDEX ANSCKRECORDZONEMETADATA_ZDATABASE_INDEX ON ANSCKRECORDZONEMETADATA (ZDATABASE);
CREATE UNIQUE INDEX Z_NSCKRecordZoneMetadata_UNIQUE_ckRecordZoneName_ckOwnerName_database ON ANSCKRECORDZONEMETADATA (ZCKRECORDZONENAME COLLATE BINARY ASC, ZCKOWNERNAME COLLATE BINARY ASC, ZDATABASE COLLATE BINARY ASC);
CREATE INDEX ANSCKRECORDZONEMOVERECEIPT_ZRECORDMETADATA_INDEX ON ANSCKRECORDZONEMOVERECEIPT (ZRECORDMETADATA);
CREATE INDEX ANSCKRECORDZONEQUERY_ZRECORDZONE_INDEX ON ANSCKRECORDZONEQUERY (ZRECORDZONE);
CREATE UNIQUE INDEX Z_NSCKRecordZoneQuery_UNIQUE_recordType_recordZone ON ANSCKRECORDZONEQUERY (ZRECORDTYPE COLLATE BINARY ASC, ZRECORDZONE COLLATE BINARY ASC);

Wow, I see what you mean about so much SQLite code. I’ll keep that in mind as I develop the app.

I remember when I was first coding in a high-level language (COBOL) and saw the code that was generated from simple statements. As an old AutoCoder programmer, it took me a while to get used to it.

Those were the days.

Thanks.

1 Like