Closed7

SwiftUIとRealm

yuto05490yuto05490

概要

リストデータを管理するアプリのSample

ItemsView アイテム一覧View
追加・削除・並び替えができる

ItemDetailsView アイテムをタップしたときに表示される詳細View
アイテム名の変更とお気に入り登録ができる

yuto05490yuto05490

モデル定義

PersistedというPropertyWrapperが用意されている
https://docs.mongodb.com/realm-sdks/swift/latest/Structs/Persisted.html
primaryKey: trueとするとプライマリーキーになる

/// An individual item. Part of a `Group`.
final class Item: Object, ObjectKeyIdentifiable {
    /// The unique ID of the Item. `primaryKey: true` declares the
    /// _id member as the primary key to the realm.
    @Persisted(primaryKey: true) var _id: ObjectId
    /// The name of the Item, By default, a random name is generated.
    @Persisted var name = "\(randomAdjectives.randomElement()!) \(randomNouns.randomElement()!)"
    /// A flag indicating whether the user "favorited" the item.
    @Persisted var isFavorite = false
    /// The backlink to the `Group` this item is a part of.
    @Persisted(originProperty: "items") var group: LinkingObjects<Group>
}
/// Represents a collection of items.
final class Group: Object, ObjectKeyIdentifiable {
    /// The unique ID of the Group. `primaryKey: true` declares the
    /// _id member as the primary key to the realm.
    @Persisted(primaryKey: true) var _id: ObjectId
    /// The collection of Items in this group.
    @Persisted var items = RealmSwift.List<Item>()
}
yuto05490yuto05490

Sampleアプリはこんな構成

@main
struct ContentView: SwiftUI.App {
    var body: some Scene {
        WindowGroup {
            LocalOnlyContentView()
        }
    }
}

struct LocalOnlyContentView: View {
    // Implicitly use the default realm's objects(Group.self)
    @ObservedResults(Group.self) var groups
    
    var body: some View {
        if let group = groups.first {
            // Pass the Group objects to a view further
            // down the hierarchy
            ItemsView(group: group)
        } else {
            // For this small app, we only want one group in the realm.
            // You can expand this app to support multiple groups.
            // For now, if there is no group, add one here.
            ProgressView().onAppear {
                $groups.append(Group())
            }
        }
    }
}

@ObservedResultsを利用するとView表示時にDefaultのRealmを利用してロードしてくれる
また監視対象になるため変更があった場合にViewが更新される
https://github.com/realm/realm-cocoa/pull/7045

yuto05490yuto05490

@ObservedResults(MyObject.self, keyPaths: ["myList.property"])
こうすることで通知対象を絞ることも可能

yuto05490yuto05490
struct ItemsView: View {
    /// The group is a container for a list of items. Using a group instead of all items
    /// directly allows us to maintain a list order that can be updated in the UI.
    @ObservedRealmObject var group: Group
    /// The button to be displayed on the top left.
    var leadingBarButton: AnyView?
    var body: some View {
        NavigationView {
            VStack {
                // The list shows the items in the realm.
                List {
                    ForEach(group.items) { item in
                        ItemRow(item: item)
                    }.onDelete(perform: $group.items.remove)
                    .onMove(perform: $group.items.move)
                }.listStyle(GroupedListStyle())
                    .navigationBarTitle("Items", displayMode: .large)
                    .navigationBarBackButtonHidden(true)
                    .navigationBarItems(
                        leading: self.leadingBarButton,
                        // Edit button on the right to enable rearranging items
                        trailing: EditButton())
                // Action bar at bottom contains Add button.
                HStack {
                    Spacer()
                    Button(action: {
                        // The bound collection automatically
                        // handles write transactions, so we can
                        // append directly to it.
                        $group.items.append(Item())
                    }) { Image(systemName: "plus") }
                }.padding()
            }
        }
    }
}

ItemViewは@ObservedRealmObjectとしてgroupを受け取る
そうすると書き込みに応じてトランザクションを自動的に開いてくれる。便利

yuto05490yuto05490

ItemRowとItemDetailsViewは@ObservedRealmObjectとしてItemを受け取っている

/// Represents an Item in a list.
struct ItemRow: View {
    @ObservedRealmObject var item: Item
    var body: some View {
        // You can click an item in the list to navigate to an edit details screen.
        NavigationLink(destination: ItemDetailsView(item: item)) {
            Text(item.name)
            if item.isFavorite {
                // If the user "favorited" the item, display a heart icon
                Image(systemName: "heart.fill")
            }
        }
    }
}
/// Represents a screen where you can edit the item's name.
struct ItemDetailsView: View {
    @ObservedRealmObject var item: Item
    var body: some View {
        VStack(alignment: .leading) {
            Text("Enter a new name:")
            // Accept a new name
            TextField("New name", text: $item.name)
                .navigationBarTitle(item.name)
                .navigationBarItems(trailing: Toggle(isOn: $item.isFavorite) {
                    Image(systemName: item.isFavorite ? "heart.fill" : "heart")
                })
        }.padding()
    }
}
このスクラップは2022/02/20にクローズされました