thread 1: "object has been deleted or invalidated."
スワイプして削除が失敗する???
Realmでアプリ作りを作ってみたのだけど、追加と表示はできるのに、削除が失敗する。データは消せるけど、UIのエラーが出るみたいだ???
Listに表示してるデータを削除するのをやってみたがうまくいかない???
offsetなるものが必要らしい?
公式によると
このビューを指定された水平距離と垂直距離だけオフセットする。
func offset(
x: CGFloat = 0,
y: CGFloat = 0
) -> some View
パラメータ
x
このビューをオフセットする水平距離。
y
このビューをオフセットする垂直距離。
戻り値
このビューを x と y でオフセットしたビュー。
ディスカッション
offset(x:y:)を使用すると、表示された内容をxとyのパラメータで指定された量だけずらすことができます。
下の例では、このビューによって描画された灰色の境界線が、テキストの元の位置を囲んでいます:
Text("Offset by passing horizontal & vertical distance")
.border(Color.green)
.offset(x: 20, y: 50)
.border(Color.gray)
今回エラーを解決するにはこのコードが必要だった
private func deleteBook(at offsets: IndexSet) {
offsets.map { realmManager.books[$0] }.forEach(realmManager.deleteBook)
}
アプリを作ってみた!
パッケージは、GUIで追加する。こちらを参考にしてね。
ディレクトリは分けてます
Realem
を追加したら、Bookというモデルを作りましょう!、これは公式のコードをそのまま使ってます。
import Foundation
import RealmSwift
// 本のデータを保存するモデル
class Book: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name: String = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
DBの操作と接続するファイル
追加、表示、削除をするクラスを作成します。ロジックだけなら作るのは難しくないように思えたがSwiftUIはUI側の配列を操作して要素を削除しないと、エラーの原因になるらしい?
import Foundation
import RealmSwift
// 状態が変更されたときに、Viewに更新を通知するクラス
// [ObservableObject]は、データの変更を監視し、それに応じてビューを更新するために使用されるプロトコル
class RealmManager: ObservableObject {
// localRealm という名前の変数を作成し、そこにレルムを保存します。
private(set) var localRealm: Realm?
// [@Published]は、プロパティを監視して変更があったらViewに通知をする
@Published var books: [Book] = []
// initはRealmManagerが生成されたときに呼ばれる
init() {
// Realmを開くメソッドを実行
openRealm()
// 保存されている練習帳のデータを取得
getBooks()
}
// Realmとの接続を開くメソッド
func openRealm() {
do {
let config = Realm.Configuration(schemaVersion: 1)
Realm.Configuration.defaultConfiguration = config
localRealm = try Realm()
} catch {
print("Error opening Realm", error)
}
}
// nameを追加するメソッド
func addCounter(name: String) {
// if letでlocalRealmがnilでないことを確認
if let localRealm = localRealm {
// do-catchでエラー処理
do {
// Realmに書き込み
try localRealm.write {
let newBook = Book()
newBook.name = name
localRealm.add(newBook)
getBooks()
}
} catch {
print("Error adding counter to Realm: \(error)")
}
}
}
// 練習帳の情報を取得するメソッド
func getBooks() {
// if letでlocalRealmがnilでないことを確認
if let localRealm = localRealm {
// Realmから全てのnameを取得
let allBooks = localRealm.objects(Book.self)
// booksに全てのカウンターを代入
books = []
// forEachでallBooksの要素をbookに代入。配列なので、appendでbooksに追加
allBooks.forEach { book in
books.append(book)
}
}
}
// 練習帳の情報を削除するメソッド
func deleteBook(book: Book) {
// if letでlocalRealmがnilでないことを確認
if let localRealm = localRealm, !book.isInvalidated {
// do-catchでエラー処理
do {
// Realmからbookを削除
try localRealm.write {
localRealm.delete(book)
getBooks()
}
} catch {
print("Error deleting book from Realm: \(error)")
}
}
}
}
Uiのコードを書く
追加、表示、削除をするUIを作っていきましょう!
本の名前を追加するだけのForm。15文字未満しか入力できないようにバリデーションがあって、ライトモードとダークモードに対応してる。
import SwiftUI
import RealmSwift
// 本の追加ページ
struct CreateBook: View {
@EnvironmentObject var realmManager: RealmManager
@Environment(\.presentationMode) var presentationMode
@State private var name: String = ""
@State private var showingAlert = false
var body: some View {
NavigationView {
Form {
Section(header: Text("Book Information")) {
TextField("Name", text: $name)
.onChange(of: name) { newValue in
if newValue.count > 15 {
name = String(newValue.prefix(15))
showingAlert = true
}
}
}
}
.navigationBarTitle("練習帳を作成", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
realmManager.addCounter(name: name)
name = ""
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save")
})
.alert(isPresented: $showingAlert) {
Alert(title: Text("Warning"), message: Text("本のタイトルは15文字未満です"), dismissButton: .default(Text("OK")))
}
}
}
}
struct CreateBook_Previews: PreviewProvider {
static var previews: some View {
CreateBook().environmentObject(RealmManager())
}
}
追加されたデータの表示と削除ができるページ:
このコードでは、まずListビューでrealmManager.books配列の各要素を表示しています。そして、.onDelete(perform: deleteBook)修飾子を使用して、スワイプして削除する機能を追加しています。
import SwiftUI
// 本の情報の表示と削除をするページ
struct ReadBook: View {
@EnvironmentObject var realmManager: RealmManager
var body: some View {
NavigationView {
List {
ForEach(realmManager.books.indices, id: \.self) { index in
Text(realmManager.books[index].name)
}
.onDelete(perform: deleteBook)
}
.navigationBarTitle("練習帳", displayMode: .inline)
.navigationBarItems(trailing: NavigationLink(destination: CreateBook().environmentObject(realmManager)) {
Image(systemName: "plus")
})
}
}
private func deleteBook(at offsets: IndexSet) {
offsets.map { realmManager.books[$0] }.forEach(realmManager.deleteBook)
}
}
struct ReadBook_Previews: PreviewProvider {
static var previews: some View {
ReadBook().environmentObject(RealmManager())
}
}
deleteBook(at offsets: IndexSet)メソッドは、削除するbookオブジェクトをrealmManager.books配列から取得し、それをrealmManager.deleteBookメソッドに渡して削除しています。
エントリーポイントになるファイルも設定しておく
import SwiftUI
@main
struct ExerciseBookApp: App {
// Realmにデータを追加すると画面が更新される様にする。
@StateObject var realmManager = RealmManager()
var body: some Scene {
WindowGroup {
ReadBook()
.environmentObject(realmManager)
}
}
}
最後に
簡単なアプリを作るだけで難しかったです💦
まさか、データを消したら配列の要素の削除も必要とは知らなかった💦
なんで気づいたかというと、リリースした個人アプリで同じ機能を作ったことがあったので、それをそのまま使えば解決できました😅
皆さんもSwiftUIでアプリを作るのを楽しんでね💚💙💛
Discussion