ios RealmSwift,如何在加载时对属性列表进行排序,并在重新排序时保持更新?

hc8w905p  于 2023-08-08  发布在  iOS
关注(0)|答案(2)|浏览(90)

我在Realm中使用了以下对象模式(我使用Typescript符号描述它,因为我对它很熟悉):

Publisher {
    name: String (primary key),
    position: Int,
    games: RealmSwift.List<Game>,
}

Game {
    name: String (primary key),
    description: String,
    cover: String,
    position: Int,
}

字符串
因此,每个发行商都有自己的游戏列表,并且onAppear位于ContentView的最外层视图。swift我从Realm加载所有发行商,并按position keyPath对其进行排序。但是,我也希望games列表按position对每个发布者进行排序,我不确定您是否可以直接这样做。下面是我在onAppear方法下编写的代码:

ContentView.swift相关部分:

import SwiftUI
import RealmSwift

struct ContentView: View {        
    @State var publishers: Results<Publisher>? = nil
    private var realm: Realm
    
    init() {
        self.realm = try! Realm()
    }
    
    var body: some View {
        
        NavigationView {
            ScrollView {
                VStack(alignment: .leading, spacing: 10) {
                    
                    if publishers != nil {
                        ForEach(publishers!, id: \.name) { publisher in
                            PublisherName(publisher: publisher.name)
                            
                            List {
                                ForEach(publisher.games, id: \.name) { game in
                                    HStack {
                                        Text(String(game.position))
                                            .font(.system(size:20))
                                            .fontWeight(.bold)
                                        GameRow(game: game)
                                    }
                                }.onMove(perform: {
                                    startIndex, destination in
                                    do {
                                        try self.realm.write {
                                            publisher.games.move(fromOffsets: startIndex, toOffset: destination-1)
                                        }
                                    }catch {
                                        print("Error: \(error)")
                                    }
                                })                                    
                            }
                        }
                    }
                    
                }
            }
            .onAppear(perform: {
                
                let realm = try! Realm()

                self.publishers
                    = realm.objects(Publisher.self)
                    .reduce(RealmSwift.List<Publisher>(), { publishersList, publisher -> RealmSwift.List<Publisher> in

                        let p = Publisher()
                        p.name = publisher.name
                        p.position = publisher.position

                        p.games = publisher.games.sorted(byKeyPath: "position")
                            .reduce(RealmSwift.List<Game>(), { gamesList, game -> RealmSwift.List<Game> in

                            gamesList.append(game)
                            return gamesList

                        })
                        publishersList.append(p)
                        return publishersList

                    })
                    .sorted(byKeyPath: "position")
                
            })
        }
    }
}


当我运行上面的代码时,我在@main得到以下错误,显然是由onAppear块中的代码引起的(因为如果我删除它,一切都正常):

"This method may only be called on RLMArray instances retrieved from an RLMRealm"


此外,我想当在编辑模式下,在交换两个游戏的位置,变化应该写在境界数据库,并立即在我的应用程序更新,因为我想“重新加载”的变化。实际上,它已经在Realm上写入了更改,但除非我重新加载视图,否则更改不会发生,例如切换编辑模式。

yc0p9oo0

yc0p9oo01#

这是绝对可行的。假设您有两个这样的模型,您应该能够在更新模型对象的位置属性时添加、删除和移动行。我经常这样。
让我们假设您的模型对象如下所示:

class Publisher: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var name = ""
    @Persisted var position: Int = 0
    @Persisted var games: RealmSwift.List<Game>

    convenience init(name: String, position: Int, games: [Game]) {
        self.init()
        self.name = name
        self.position = position
        self.games.append(objectsIn: games)
    }
}

class Game: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var name = ""
    @Persisted var position: Int = 0

    convenience init(name: String, position: Int) {
        self.init()
        self.name = name
        self.position = position
    }
}

字符串
现在,让我们创建一个定义position属性的协议。

protocol Positionable {
    var position: Int { get set }
}


现在让我们让我们的模型符合它:

extension Game: Positionable {}
extension Publisher: Positionable {}


这个Results扩展将允许我们“移动”按position排序的对象。这意味着它将更新所有需要更新的元素的position属性。请记住,这要求您的结果集已经按位置排序。

extension Results where Element: Object & Positionable {
    func repositionElement(atOffset source: Int, toOffset destination: Int) {
        if source < destination {
            let nextElements = self[source+1..<destination]
            var target = self[source]
            for var element in nextElements {
                element.position -= 1
            }
            target.position = destination - 1

        } else {
            let prevElements = self[destination..<source]
            var target = self[source]
            for var element in prevElements {
                element.position += 1
            }
            target.position = destination
        }
    }
}


现在,我们需要对List执行类似的操作。这个方法稍微简单一点,因为List提供了一个move(fromOffsets: toOffsets:)方法。但是,您还记得,您还需要更新所有受影响元素的position属性。

extension List where Element: Object & Positionable {
    func repositionElement(atOffset source: Int, toOffset destination: Int) {
        move(fromOffsets: [source], toOffset: destination)
        enumerated()
            .forEach {
                var element = $0.element
                element.position = $0.offset
            }
    }
}


让我们添加一个delete方法来更新position属性。现在我们可以在ListResults上使用此方法,因为它们都符合RealmCollection。同样,此方法假定集合已按position排序。

extension RealmCollection where Element: Object & Positionable {
    func deleteElement(atOffset offset: Int) {
        guard let element = objects(at: [offset]).first else { return }
        realm?.delete(element)
        for i in offset..<count {
            if var element = objects(at: [i]).first {
                element.position -= 1
            }
        }
    }
}


至于UI,请确保对Results<Publisher>属性使用@ObservedResults属性 Package ,对Publisher属性使用@ObservedRealmObject属性 Package 。
此外,请记住,这些属性 Package 器将冻结所有对象,因此您需要调用thaw()以便在任何领域写入之前解冻这些对象。
下面是一个UI示例:

struct ContentView: View {

    @ObservedResults(
        Publisher.self,
        configuration: .defaultConfiguration,
        sortDescriptor: .init(keyPath: \Publisher.position)) var publishers

    let names = ["Bob","Tom","Tim","Sally","Joe","Tammy","Tina","Jeff","Meghan","Don","Kirk","Justin","Jason"]

    var body: some View {

        NavigationView {
            List {
                ForEach(publishers) { publisher in
                    HStack {
                        Text("\(publisher.position):")
                            .font(.system(size: 20))
                            .bold()
                        Text(publisher.name)
                            .font(.system(size: 20))
                    }
                    GameList(publisher: publisher)
                }
                .onMove(perform: movePublisher(fromOffsets:toOffset:))
                .onDelete (perform: deletePublisher(atOffsets:))
            }.toolbar {
                ToolbarItemGroup.init(placement: .navigationBarTrailing) {
                    EditButton()
                    Button("Add") {
                        addPublisher()
                    }
                    .disabled(publishers.count == names.count)
                }
            }
        }
    }

    func addPublisher() {
        let realm = try! Realm()
        let pos = Publisher.nextPosition(in: realm)

        guard let name = names.first(
            where: { publishers.filter("name == %@", $0).count == 0 }) else {
            return print("No more names")
        }

        let ordinalFormatter = NumberFormatter()
        ordinalFormatter.numberStyle = .ordinal

        let pub = Publisher(
            name: name,
            position: pos,
            games: (0..<3)
                .map {
                .init(
                    name: "\(name).game.\(ordinalFormatter.string(from: NSNumber(value: $0 + 1)) ?? "")",
                    position: $0)
            }
        )

        try! realm.write {
            realm.add(pub)
        }
    }

    func movePublisher(fromOffsets offsets: IndexSet, toOffset destination: Int) {
        guard let offset = offsets.first else { return }

        let realm = try! Realm()
        try! realm.write {
            publishers.thaw()?.repositionElement(
                atOffset: offset,
                toOffset: destination
            )
        }
    }

    func deletePublisher(atOffsets offsets: IndexSet) {
        guard let offset = offsets.first else { return }
        let realm = try! Realm()
        try! realm.write {
            guard let publishers = self.publishers.thaw() else { return }
            realm.delete(publishers[offset].games)
            publishers.deleteElement(atOffset: offset)
        }
    }

}

struct GameList: View {

    @ObservedRealmObject var publisher: Publisher

    var body: some View {
        ForEach(publisher.games) { game in
            HStack {
                Text(String(game.position) + ":")
                Text(String(game.name))
            }
            .padding(.leading)
        }.onMove(perform: moveGame(fromOffsets:toOffset:))
        .onDelete(perform: deleteGame(atOffsets:))
    }

    func moveGame(fromOffsets offsets: IndexSet, toOffset destination: Int) {
        guard let offset = offsets.first else { return }

        let realm = try! Realm()
        try! realm.write {
            publisher.thaw()?.games
                .repositionElement(atOffset: offset, toOffset: destination)
        }
    }

    func deleteGame(atOffsets offsets: IndexSet) {
        guard let offset = offsets.first else { return }
        let realm = try! Realm()
        try! realm.write {
            publisher.thaw()?.games
                .deleteElement(atOffset: offset)
        }
    }
}

dldeef67

dldeef672#

如果您已经在使用列表,则可以简单地使用list.move

class ItemGroup: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var items = RealmSwift.List<Item>()
}
@ObservedRealmObject var itemGroup: ItemGroup
List {
    ForEach(itemGroup.items) { item in
        ItemRow(item: item)
    }
    .onDelete(perform: $itemGroup.items.remove)
    .onMove(perform: $itemGroup.items.move)
}

List存储为对象id数组,因此您可以依赖对象id在数组中的位置。$将操作 Package 在写事务中。
如果您想使用自定义字段来对数据进行排序,here是一些模板代码。如果您使用的是Flexible Sync,则需要处理在2个不同设备上使用相同顺序/位置创建项目的情况。

相关问题