如何在 SwiftUI 列表中显示 Realm 结果?
How to display Realm Results in SwiftUI List?
我已经能够将数据保存在 Realm 数据库中,但无法在 SwiftUI 中显示结果 List
。
我知道我有数据并且在控制台中打印结果没有问题。
有没有办法将 Realm Result
转换成可以在 SwiftUI List
上显示的格式?
import SwiftUI
import RealmSwift
import Combine
class Dog: Object {
@objc dynamic var name = ""
@objc dynamic var age = 0
override static func primaryKey() -> String? {
return "name"
}
}
class SaveDog {
func saveDog(name: String, age: String) {
let dog = Dog()
dog.age = Int(age)!
dog.name = name
// Get the default Realm
let realm = try! Realm()
print(Realm.Configuration.defaultConfiguration.fileURL!)
// Persist your data easily
try! realm.write {
realm.add(dog)
}
print(dog)
}
}
class RealmResults: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
func getRealmResults() -> String{
let realm = try! Realm()
var results = realm.objects(Dog.self) { didSet
{didChange.send(())}}
print(results)
return results.first!.name
}
}
struct dogRow: View {
var dog = Dog()
var body: some View {
HStack {
Text(dog.name)
Text("\(dog.age)")
}
}
}
struct ContentView : View {
@State var dogName: String = ""
@State var dogAge: String = ""
let saveDog = SaveDog()
@ObjectBinding var savedResults = RealmResults()
let realm = try! Realm()
let dogs = Dog()
var body: some View {
VStack {
Text("Hello World")
TextField($dogName)
TextField($dogAge)
Button(action: {
self.saveDog.saveDog(name: self.dogName,
age:self.dogAge)
// self.savedResults.getRealmResults()
}) {
Text("Save")
}
//insert list here to show realm data
List(0 ..< 5) {
item in
Text(self.savedResults.getRealmResults())
} //Displays the same thing 5 times
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
有些代码可能没有意义,因为我尝试了几种方法来查看是否有任何效果。
例如,这一行将在列表视图中显示结果。
return results.first!.name
如果我只是 return 结果,则列表文本视图中不会显示任何内容。
正如我在下面评论的那样,我将在有时间时尝试使用 ForEach 方法。看起来很有希望。
您在 List
或 ForEach
中传递的数据必须符合 Identifiable
协议。
要么在你的 Realm 模型中采用它,要么使用 .identified(by:)
方法。
即便如此,如果数据发生变化,View
也不会重新加载。
您可以包装 Results
并使其成为 BindableObject
,这样视图可以检测到更改并自行重新加载:
class BindableResults<Element>: ObservableObject where Element: RealmSwift.RealmCollectionValue {
var results: Results<Element>
private var token: NotificationToken!
init(results: Results<Element>) {
self.results = results
lateInit()
}
func lateInit() {
token = results.observe { [weak self] _ in
self?.objectWillChange.send()
}
}
deinit {
token.invalidate()
}
}
并像这样使用它:
struct ContentView : View {
@ObservedObject var dogs = BindableResults(results: try! Realm().objects(Dog.self))
var body: some View {
List(dogs.results.identified(by: \.name)) { dog in
DogRow(dog: dog)
}
}
}
我已经创建了一个通用解决方案来显示和 add/delete 任何 Results<T>
。默认情况下,Results<T>
为“实时”。当 @Published
属性 WILL 更新时,SwiftUI 将更改发送到视图。当收到RealmCollectionChange<Results<T>>
通知时,Results<T>
已经更新;因此,由于索引超出范围,删除时将出现 fatalError
。相反,我使用“实时”Results<T>
来跟踪更改,并使用“冻结”Results<T>
与视图一起使用。可以在此处找到完整的工作示例,包括如何将通用 View
与 RealmViewModel<T>
(如下所示)一起使用:SwiftUI+Realm。 enum Status
用于在适用时显示 ProgressView
、“未找到记录”等,如项目中所示。另请注意,在需要计数或单个对象时使用“frozen”Results<T>
。删除时,onDelete
的 IndexSet
将从“冻结”Results<T>
return 移动到一个位置,因此它会检查该对象是否仍然存在于“活动”Results<T>
.
class RealmViewModel<T: RealmSwift.Object>: ObservableObject, Verbose where T: Identifiable {
typealias Element = T
enum Status {
// Display ProgressView
case fetching
// Display "No records found."
case empty
// Display results
case results
// Display error
case error(Swift.Error)
enum _Error: String, Swift.Error {
case fetchNotCalled = "System Error."
}
}
init() {
fetch()
}
deinit {
token?.invalidate()
}
@Published private(set) var status: Status = .error(Status._Error.fetchNotCalled)
// Frozen results: Used for View
@Published private(set) var results: Results<Element>?
// Live results: Used for NotificationToken
private var __results: Results<Element>?
private var token: NotificationToken?
private func notification(_ change: RealmCollectionChange<Results<Element>>) {
switch change {
case .error(let error):
verbose(error)
self.__results = nil
self.results = nil
self.token = nil
self.status = .error(error)
case .initial(let results):
verbose("count:", results.count)
//self.results = results.freeze()
//self.status = results.count == 0 ? .empty : .results
case .update(let results, let deletes, let inserts, let updates):
verbose("results:", results.count, "deletes:", deletes, "inserts:", inserts, "updates:", updates)
self.results = results.freeze()
self.status = results.count == 0 ? .empty : .results
}
}
var count: Int { results?.count ?? 0 }
subscript(_ i: Int) -> Element? { results?[i] }
func fetch() {
status = .fetching
//Realm.asyncOpen(callback: asyncOpen(_:_:))
do {
let realm = try Realm()
let results = realm.objects(Element.self).sorted(byKeyPath: "id")
self.__results = results
self.results = results.freeze()
self.token = self.__results?.observe(notification)
status = results.count == 0 ? .empty : .results
} catch {
verbose(error)
self.__results = nil
self.results = nil
self.token = nil
status = .error(error)
}
}
func insert(_ data: Element) throws {
let realm = try Realm()
try realm.write({
realm.add(data)
})
}
func delete(at offsets: IndexSet) throws {
let realm = try Realm()
try realm.write({
offsets.forEach { (i) in
guard let id = results?[i].id else { return }
guard let data = __results?.first(where: { [=10=].id == id }) else { return }
realm.delete(data)
}
})
}
}
这是另一个使用新的 Realm frozen() 集合的选项。虽然这是早期 UI 会在 'assets' 添加到数据库时自动更新。在此示例中,它们是从 NSOperation 线程添加的,该线程应该是后台线程。
在此示例中,侧边栏根据数据库中的不同值列出了不同的 属性 组 - 请注意,您可能希望以更稳健的方式实现它 - 但作为快速 POC,它工作正常。见下图。
struct CategoryBrowserView: View {
@ObservedObject var assets: RealmSwift.List<Asset> = FileController.shared.assets
@ObservedObject var model = ModelController.shared
@State private var searchTerm: String = ""
@State var isEventsShowing: Bool = false
@State var isProjectsShowing: Bool = false
@State var isLocationsShowing: Bool = false
var projects: Results<Asset> {
return assets.sorted(byKeyPath: "project").distinct(by: ["project"])
}
var events: Results<Asset> {
return assets.sorted(byKeyPath: "event").distinct(by: ["event"])
}
var locations: Results<Asset> {
return assets.sorted(byKeyPath: "location").distinct(by: ["location"])
}
@State var status: Bool = false
var body: some View {
VStack(alignment: .leading) {
ScrollView {
VStack(alignment: .leading) {
// Projects
DisclosureGroup(isExpanded: $isProjectsShowing) {
VStack(alignment:.trailing, spacing: 4) {
ForEach(filteredProjectsCollection().freeze()) { asset in
HStack {
Text(asset.project)
Spacer()
Image(systemName: self.model.selectedProjects.contains(asset.project) ? "checkmark.square" : "square")
.resizable()
.frame(width: 17, height: 17)
.onTapGesture { self.model.addProject(project: asset.project) }
}
}
}.frame(maxWidth:.infinity)
.padding(.leading, 20)
} label: {
HStack(alignment:.center) {
Image(systemName: "person.2")
Text("Projects").font(.system(.title3))
Spacer()
}.padding([.top, .bottom], 8).foregroundColor(.secondary)
}
// Events
DisclosureGroup(isExpanded: $isEventsShowing) {
VStack(alignment:.trailing, spacing: 4) {
ForEach(filteredEventsCollection().freeze()) { asset in
HStack {
Text(asset.event)
Spacer()
Image(systemName: self.model.selectedEvents.contains(asset.event) ? "checkmark.square" : "square")
.resizable()
.frame(width: 17, height: 17)
.onTapGesture { self.model.addEvent(event: asset.event) }
}
}
}.frame(maxWidth:.infinity)
.padding(.leading, 20)
} label: {
HStack(alignment:.center) {
Image(systemName: "calendar")
Text("Events").font(.system(.title3))
Spacer()
}.padding([.top, .bottom], 8).foregroundColor(.secondary)
}
// Locations
DisclosureGroup(isExpanded: $isLocationsShowing) {
VStack(alignment:.trailing, spacing: 4) {
ForEach(filteredLocationCollection().freeze()) { asset in
HStack {
Text(asset.location)
Spacer()
Image(systemName: self.model.selectedLocations.contains(asset.location) ? "checkmark.square" : "square")
.resizable()
.frame(width: 17, height: 17)
.onTapGesture { self.model.addLocation(location: asset.location) }
}
}
}.frame(maxWidth:.infinity)
.padding(.leading, 20)
} label: {
HStack(alignment:.center) {
Image(systemName: "flag")
Text("Locations").font(.system(.title3))
Spacer()
}.padding([.top, .bottom], 8).foregroundColor(.secondary)
}
}.padding(.all, 10)
.background(Color(NSColor.controlBackgroundColor))
}
SearchBar(text: self.$searchTerm)
.frame(height: 30, alignment: .leading)
}
}
func filteredProjectsCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.projects)
} else {
return AnyRealmCollection(self.projects.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func filteredEventsCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.events)
} else {
return AnyRealmCollection(self.events.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func filteredLocationCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.locations)
} else {
return AnyRealmCollection(self.locations.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func filteredCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.assets)
} else {
return AnyRealmCollection(self.assets.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func delete(at offsets: IndexSet) {
if let realm = assets.realm {
try! realm.write {
realm.delete(assets[offsets.first!])
}
} else {
assets.remove(at: offsets.first!)
}
}
}
struct CategoryBrowserView_Previews: PreviewProvider {
static var previews: some View {
CategoryBrowserView()
}
}
struct CheckboxToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
return HStack {
configuration.label
Spacer()
Image(systemName: configuration.isOn ? "checkmark.square" : "square")
.resizable()
.frame(width: 22, height: 22)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
这是最直接的方法:
struct ContentView: View {
@State private var dog: Results<Dog> = try! Realm(configuration: Realm.Configuration(schemaVersion: 1)).objects(Dog.self)
var body: some View {
ForEach(dog, id: \.name) { i in
Text(String((i.name)!))
}
}
}
...就是这样,而且有效!
我已经能够将数据保存在 Realm 数据库中,但无法在 SwiftUI 中显示结果 List
。
我知道我有数据并且在控制台中打印结果没有问题。
有没有办法将 Realm Result
转换成可以在 SwiftUI List
上显示的格式?
import SwiftUI
import RealmSwift
import Combine
class Dog: Object {
@objc dynamic var name = ""
@objc dynamic var age = 0
override static func primaryKey() -> String? {
return "name"
}
}
class SaveDog {
func saveDog(name: String, age: String) {
let dog = Dog()
dog.age = Int(age)!
dog.name = name
// Get the default Realm
let realm = try! Realm()
print(Realm.Configuration.defaultConfiguration.fileURL!)
// Persist your data easily
try! realm.write {
realm.add(dog)
}
print(dog)
}
}
class RealmResults: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
func getRealmResults() -> String{
let realm = try! Realm()
var results = realm.objects(Dog.self) { didSet
{didChange.send(())}}
print(results)
return results.first!.name
}
}
struct dogRow: View {
var dog = Dog()
var body: some View {
HStack {
Text(dog.name)
Text("\(dog.age)")
}
}
}
struct ContentView : View {
@State var dogName: String = ""
@State var dogAge: String = ""
let saveDog = SaveDog()
@ObjectBinding var savedResults = RealmResults()
let realm = try! Realm()
let dogs = Dog()
var body: some View {
VStack {
Text("Hello World")
TextField($dogName)
TextField($dogAge)
Button(action: {
self.saveDog.saveDog(name: self.dogName,
age:self.dogAge)
// self.savedResults.getRealmResults()
}) {
Text("Save")
}
//insert list here to show realm data
List(0 ..< 5) {
item in
Text(self.savedResults.getRealmResults())
} //Displays the same thing 5 times
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
有些代码可能没有意义,因为我尝试了几种方法来查看是否有任何效果。
例如,这一行将在列表视图中显示结果。
return results.first!.name
如果我只是 return 结果,则列表文本视图中不会显示任何内容。
正如我在下面评论的那样,我将在有时间时尝试使用 ForEach 方法。看起来很有希望。
您在 List
或 ForEach
中传递的数据必须符合 Identifiable
协议。
要么在你的 Realm 模型中采用它,要么使用 .identified(by:)
方法。
即便如此,如果数据发生变化,View
也不会重新加载。
您可以包装 Results
并使其成为 BindableObject
,这样视图可以检测到更改并自行重新加载:
class BindableResults<Element>: ObservableObject where Element: RealmSwift.RealmCollectionValue {
var results: Results<Element>
private var token: NotificationToken!
init(results: Results<Element>) {
self.results = results
lateInit()
}
func lateInit() {
token = results.observe { [weak self] _ in
self?.objectWillChange.send()
}
}
deinit {
token.invalidate()
}
}
并像这样使用它:
struct ContentView : View {
@ObservedObject var dogs = BindableResults(results: try! Realm().objects(Dog.self))
var body: some View {
List(dogs.results.identified(by: \.name)) { dog in
DogRow(dog: dog)
}
}
}
我已经创建了一个通用解决方案来显示和 add/delete 任何 Results<T>
。默认情况下,Results<T>
为“实时”。当 @Published
属性 WILL 更新时,SwiftUI 将更改发送到视图。当收到RealmCollectionChange<Results<T>>
通知时,Results<T>
已经更新;因此,由于索引超出范围,删除时将出现 fatalError
。相反,我使用“实时”Results<T>
来跟踪更改,并使用“冻结”Results<T>
与视图一起使用。可以在此处找到完整的工作示例,包括如何将通用 View
与 RealmViewModel<T>
(如下所示)一起使用:SwiftUI+Realm。 enum Status
用于在适用时显示 ProgressView
、“未找到记录”等,如项目中所示。另请注意,在需要计数或单个对象时使用“frozen”Results<T>
。删除时,onDelete
的 IndexSet
将从“冻结”Results<T>
return 移动到一个位置,因此它会检查该对象是否仍然存在于“活动”Results<T>
.
class RealmViewModel<T: RealmSwift.Object>: ObservableObject, Verbose where T: Identifiable {
typealias Element = T
enum Status {
// Display ProgressView
case fetching
// Display "No records found."
case empty
// Display results
case results
// Display error
case error(Swift.Error)
enum _Error: String, Swift.Error {
case fetchNotCalled = "System Error."
}
}
init() {
fetch()
}
deinit {
token?.invalidate()
}
@Published private(set) var status: Status = .error(Status._Error.fetchNotCalled)
// Frozen results: Used for View
@Published private(set) var results: Results<Element>?
// Live results: Used for NotificationToken
private var __results: Results<Element>?
private var token: NotificationToken?
private func notification(_ change: RealmCollectionChange<Results<Element>>) {
switch change {
case .error(let error):
verbose(error)
self.__results = nil
self.results = nil
self.token = nil
self.status = .error(error)
case .initial(let results):
verbose("count:", results.count)
//self.results = results.freeze()
//self.status = results.count == 0 ? .empty : .results
case .update(let results, let deletes, let inserts, let updates):
verbose("results:", results.count, "deletes:", deletes, "inserts:", inserts, "updates:", updates)
self.results = results.freeze()
self.status = results.count == 0 ? .empty : .results
}
}
var count: Int { results?.count ?? 0 }
subscript(_ i: Int) -> Element? { results?[i] }
func fetch() {
status = .fetching
//Realm.asyncOpen(callback: asyncOpen(_:_:))
do {
let realm = try Realm()
let results = realm.objects(Element.self).sorted(byKeyPath: "id")
self.__results = results
self.results = results.freeze()
self.token = self.__results?.observe(notification)
status = results.count == 0 ? .empty : .results
} catch {
verbose(error)
self.__results = nil
self.results = nil
self.token = nil
status = .error(error)
}
}
func insert(_ data: Element) throws {
let realm = try Realm()
try realm.write({
realm.add(data)
})
}
func delete(at offsets: IndexSet) throws {
let realm = try Realm()
try realm.write({
offsets.forEach { (i) in
guard let id = results?[i].id else { return }
guard let data = __results?.first(where: { [=10=].id == id }) else { return }
realm.delete(data)
}
})
}
}
这是另一个使用新的 Realm frozen() 集合的选项。虽然这是早期 UI 会在 'assets' 添加到数据库时自动更新。在此示例中,它们是从 NSOperation 线程添加的,该线程应该是后台线程。
在此示例中,侧边栏根据数据库中的不同值列出了不同的 属性 组 - 请注意,您可能希望以更稳健的方式实现它 - 但作为快速 POC,它工作正常。见下图。
struct CategoryBrowserView: View {
@ObservedObject var assets: RealmSwift.List<Asset> = FileController.shared.assets
@ObservedObject var model = ModelController.shared
@State private var searchTerm: String = ""
@State var isEventsShowing: Bool = false
@State var isProjectsShowing: Bool = false
@State var isLocationsShowing: Bool = false
var projects: Results<Asset> {
return assets.sorted(byKeyPath: "project").distinct(by: ["project"])
}
var events: Results<Asset> {
return assets.sorted(byKeyPath: "event").distinct(by: ["event"])
}
var locations: Results<Asset> {
return assets.sorted(byKeyPath: "location").distinct(by: ["location"])
}
@State var status: Bool = false
var body: some View {
VStack(alignment: .leading) {
ScrollView {
VStack(alignment: .leading) {
// Projects
DisclosureGroup(isExpanded: $isProjectsShowing) {
VStack(alignment:.trailing, spacing: 4) {
ForEach(filteredProjectsCollection().freeze()) { asset in
HStack {
Text(asset.project)
Spacer()
Image(systemName: self.model.selectedProjects.contains(asset.project) ? "checkmark.square" : "square")
.resizable()
.frame(width: 17, height: 17)
.onTapGesture { self.model.addProject(project: asset.project) }
}
}
}.frame(maxWidth:.infinity)
.padding(.leading, 20)
} label: {
HStack(alignment:.center) {
Image(systemName: "person.2")
Text("Projects").font(.system(.title3))
Spacer()
}.padding([.top, .bottom], 8).foregroundColor(.secondary)
}
// Events
DisclosureGroup(isExpanded: $isEventsShowing) {
VStack(alignment:.trailing, spacing: 4) {
ForEach(filteredEventsCollection().freeze()) { asset in
HStack {
Text(asset.event)
Spacer()
Image(systemName: self.model.selectedEvents.contains(asset.event) ? "checkmark.square" : "square")
.resizable()
.frame(width: 17, height: 17)
.onTapGesture { self.model.addEvent(event: asset.event) }
}
}
}.frame(maxWidth:.infinity)
.padding(.leading, 20)
} label: {
HStack(alignment:.center) {
Image(systemName: "calendar")
Text("Events").font(.system(.title3))
Spacer()
}.padding([.top, .bottom], 8).foregroundColor(.secondary)
}
// Locations
DisclosureGroup(isExpanded: $isLocationsShowing) {
VStack(alignment:.trailing, spacing: 4) {
ForEach(filteredLocationCollection().freeze()) { asset in
HStack {
Text(asset.location)
Spacer()
Image(systemName: self.model.selectedLocations.contains(asset.location) ? "checkmark.square" : "square")
.resizable()
.frame(width: 17, height: 17)
.onTapGesture { self.model.addLocation(location: asset.location) }
}
}
}.frame(maxWidth:.infinity)
.padding(.leading, 20)
} label: {
HStack(alignment:.center) {
Image(systemName: "flag")
Text("Locations").font(.system(.title3))
Spacer()
}.padding([.top, .bottom], 8).foregroundColor(.secondary)
}
}.padding(.all, 10)
.background(Color(NSColor.controlBackgroundColor))
}
SearchBar(text: self.$searchTerm)
.frame(height: 30, alignment: .leading)
}
}
func filteredProjectsCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.projects)
} else {
return AnyRealmCollection(self.projects.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func filteredEventsCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.events)
} else {
return AnyRealmCollection(self.events.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func filteredLocationCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.locations)
} else {
return AnyRealmCollection(self.locations.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func filteredCollection() -> AnyRealmCollection<Asset> {
if self.searchTerm.isEmpty {
return AnyRealmCollection(self.assets)
} else {
return AnyRealmCollection(self.assets.filter("project CONTAINS[c] %@ || event CONTAINS[c] %@ || location CONTAINS[c] %@ || tags CONTAINS[c] %@", searchTerm, searchTerm, searchTerm, searchTerm))
}
}
func delete(at offsets: IndexSet) {
if let realm = assets.realm {
try! realm.write {
realm.delete(assets[offsets.first!])
}
} else {
assets.remove(at: offsets.first!)
}
}
}
struct CategoryBrowserView_Previews: PreviewProvider {
static var previews: some View {
CategoryBrowserView()
}
}
struct CheckboxToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
return HStack {
configuration.label
Spacer()
Image(systemName: configuration.isOn ? "checkmark.square" : "square")
.resizable()
.frame(width: 22, height: 22)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
这是最直接的方法:
struct ContentView: View {
@State private var dog: Results<Dog> = try! Realm(configuration: Realm.Configuration(schemaVersion: 1)).objects(Dog.self)
var body: some View {
ForEach(dog, id: \.name) { i in
Text(String((i.name)!))
}
}
}
...就是这样,而且有效!