質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Realm

RealmとはSQLiteやCore Dataに代わるモバイルデータベースです。iOSとAndroidの両方でサポートされています。

TableView

TableView(UITableView)とは、リスト形式で表示するコントロールで、ほとんどのアプリに使用されています。画面を「行」に分けて管理し、一般的には各行をタップした際に詳細画面に移動します。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

2回答

2138閲覧

Table view cellと遷移先のtextfieldを紐付けて保存したいです

bbbone

総合スコア2

Realm

RealmとはSQLiteやCore Dataに代わるモバイルデータベースです。iOSとAndroidの両方でサポートされています。

TableView

TableView(UITableView)とは、リスト形式で表示するコントロールで、ほとんどのアプリに使用されています。画面を「行」に分けて管理し、一般的には各行をタップした際に詳細画面に移動します。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

1クリップ

投稿2020/05/21 14:11

前提・実現したいこと

プログラミングを始めて2ヶ月の者です。
リストアプリを作成しております。(iPhoneのメモアプリのような仕組み)
tableviewcellをタップした際に、画面遷移させることはできましたが、
遷移先のtextfieldに文字を入力して、それを保存すると別のcellに格納されてしまいます。

tableviewcellと遷移先のtextfieldを紐付けて保存したいです。

発生している問題・エラーメッセージ

textfieldに文字を入力して保存すると、違うcellに保存されてしまう

該当のソースコード

ViewController

1import UIKit 2import RealmSwift 3 4class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 5 6 private var labelName: String? 7 private var text1Name: String? 8 private var text2Name: String? 9 10 @IBOutlet weak var tableView: UITableView! 11 12 var itemList: Results<Item>! 13 14 15 override func viewDidLoad() { 16 super.viewDidLoad() 17 18 // Realmからデータを取得 19 do{ 20 let realm = try Realm() 21 itemList = realm.objects(Item.self) 22 }catch{ 23 } 24 25 title = "リスト" 26 27 tableView.dataSource = self 28 tableView.delegate = self 29 tableView.tableFooterView = UIView(frame: .zero) 30 31 32 // Do any additional setup after loading the view. 33 } 34 35 36 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 37 if (segue.identifier == "showSecondView") { 38 let secondVC = segue.destination as! SecondViewController 39 secondVC.getName = self.labelName! 40 let thirdVC = segue.destination as! SecondViewController 41 thirdVC.getText1 = self.text1Name! 42 let fourthVC = segue.destination as! SecondViewController 43 fourthVC.getText2 = self.text2Name! 44 45 } 46 } 47 48 49 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 50 return itemList.count 51 } 52 53 // 各行に表示するセルを返す 54 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 55 let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "NameCell", for: indexPath) 56 let item = itemList[indexPath.row] 57 cell.textLabel?.text = item.title 58 return cell 59 } 60 61 // セルタップ時のイベント 62 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 63 let item = itemList[indexPath.row] 64 self.labelName = item.title 65 self.text1Name = item.name1 66 self.text2Name = item.name2 67 performSegue(withIdentifier: "showSecondView",sender: nil) 68 69 } 70 71 //追加ボタンで新規作成 72 @IBAction func addButton(_ sender: Any) { 73 var textField = UITextField() 74 let alert = UIAlertController(title: "アイテムを追加", message: "", preferredStyle: .alert) 75 let action = UIAlertAction(title: "リストに追加", style: .default) { (action) in 76 let newItem = Item() 77 // textFieldに入力したデータをnewItemのtitleに代入 78 newItem.title = textField.text! 79 // インスタンスをRealmに保存 80 do{ 81 let realm = try Realm() 82 try realm.write({ () -> Void in 83 realm.add(newItem) 84 }) 85 }catch{ 86 } 87 //データをリロードする 88 self.tableView.reloadData() 89 } 90 alert.addTextField { (alertTextField) in 91 alertTextField.placeholder = "NEWアイテム" 92 textField = alertTextField 93 } 94 alert.addAction(action) 95 present(alert, animated: true, completion: nil) 96 } 97 98 //スワイプしたセルを削除 99 func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 100 if editingStyle == UITableViewCell.EditingStyle.delete { 101 // Realm内のデータを削除 102 do{ 103 let realm = try Realm() 104 try realm.write { 105 realm.delete(self.itemList[indexPath.row]) 106 } 107 tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.fade) 108 }catch{ 109 } 110 } 111 } 112 113} 114

SecondViewController

1import UIKit 2import RealmSwift 3 4class SecondViewController: UIViewController, UITextFieldDelegate { 5 6 var getName: String = "" 7 var getText1: String = "" 8 var getText2: String = "" 9 10 var itemList: Results<Item>! 11 12 @IBOutlet weak var labelName: UILabel! 13 14 @IBOutlet weak var textField1: UITextField! 15 @IBOutlet weak var textField2: UITextField! 16 17 override func viewDidLoad() { 18 super.viewDidLoad() 19 20 // Labelへ格納 21 self.labelName.text = getName 22 //テキストへ格納 23 self.textField1.text = getText1 24 self.textField2.text = getText2 25 26 27 textField1.delegate = self 28 textField2.delegate = self 29 30 // Do any additional setup after loading the view. 31 } 32 33 //完了ボタンでキーボードを閉じる 34 func textFieldShouldReturn(_ textField: UITextField) -> Bool { 35 self.view.endEditing(true) 36 // Itemクラスのインスタンス作成 37 let newItem = Item() 38 // TextFieldの値を代入 39 newItem.name1 = textField1.text! 40 newItem.name2 = textField2.text! 41 // インスタンスをRealmに保存 42 do{ 43 let realm = try Realm() 44 try realm.write({ () -> Void in 45 realm.add(newItem) 46 }) 47 }catch{ 48 } 49 return true 50 51 } 52 53 //他画面タッチでキーボードを閉じる 54 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { 55 self.view.endEditing(true) 56 // Itemクラスのインスタンス作成 57 let newItem = Item() 58 // TextFieldの値を代入 59 newItem.name1 = textField1.text! 60 newItem.name2 = textField2.text! 61 // インスタンスをRealmに保存 62 do{ 63 let realm = try Realm() 64 try realm.write({ () -> Void in 65 realm.add(newItem) 66 }) 67 }catch{ 68 } 69 return 70 } 71 72} 73

Item

1import Foundation 2import RealmSwift 3 4class Item: Object { 5 // タイトル 6 @objc dynamic var title: String? = "" 7 // 内容 8 @objc dynamic var name1 = "" 9 @objc dynamic var name2 = "" 10}

試したこと

userdefaultの保存など他の方法がないか沢山調べましたが、わかりませんでした、、、
todoリストの作り方は多々あったのですが、画面遷移してその内容を保存するものが見つかりませんでした。
また自分の知識不足で理解することができない内容が多く困っています。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

freemann

2020/05/21 14:49

保存というのは、メモリ上ではなく記憶媒体に保存したいということでしょうか。 もしそうであれば、SecondViewControllerでは保存せず、TextFieldに入力だけさせて、ViewControllerに戻って値を反映させて、そこで保存するようにしてはどうでしょうか。 以下のサイトでは前のViewControllerに戻るときに値を戻す方法が書かれています。 https://www.oborodukiyo.info/Swift/v50/Swift-PassDataBetweenViewControllers
bbbone

2020/05/22 13:42

返答、誠にありがとうございます。 「メモリ上ではなく記憶媒体に保存」について、私の知識不足で自分でもどちらなのか分かっていません。申し訳ございません。 可能であれば、TextFieldに入力完了した段階で保存したいのですが、添付サイトのような方法もあるのですね!こちらの方法も検討いたします、ありがとうございます!
guest

回答2

0

ベストアンサー

基本的にはtyobigorouさんのご指摘通り、遷移先の画面では追加ではなく更新を行う必要があるのですが(書き換えを行っているうちに話も進んでしまったようですが)、コードを眺めていたらいくつか気になる点を発見しました。

  1. Results<Item> 内に保持されたオブジェクトの順番は追加順とはならない。同様に任意のオブジェクトを削除してもその順番は保証されない。
  2. realm.delete(_)でオブジェクトを削除する際、プロパティ(titlename1など)が重複するオブジェクトがあった場合、どのオブジェクトが削除されるのか明確ではない
  3. tyobigorouさんご指摘のとおり、遷移先でアップデートしなければいけない。
  4. 次の画面への値渡しは、Itemクラスのプロパティではなく、Itemクラスのインスタンスをそのまま渡す。

1) についてですが、追加ボタンで追加したオブジェクトの並び順は順番通りになるという保証はありません。これは、いわゆるデータベースと呼ばれるシステムの一般的特性のようです。

削除についても同じで、途中のデータを抜いた場合、それ以外のオブジェクトの順番が保持されるという保証はありません。

一番最初に提示していただいたコードだと、たとえば削除しても順番が保持されているように思えますが、実はコード内(tableView(_:commit:forRowAt:()内)でtableView.reloadData()を書き忘れていらっしゃるようなので、たまたま順番が保持されたように見えているだけだと思います。

2)ですが、たとえば

aaa aaa bbb aaa

という具合にデータを追加し、二番目のaaaを左スワイプで削除した例を考えてみます。この場合、二番目のaaaが削除されたようにも見えますが、実際にはどのaaaが削除されたのか明確ではありません。

また、1)で指摘したreloadData()が抜けていた場合、削除を行うと下記のような順番になることがあります。

aaa aaa bbb

これを防ぐためには、Itemクラスにプライマリーキーを追加し、削除したいオブジェクトをユニークな(重複のない)オブジェクトとして指定する必要があります。

3)については、遷移先でrealm.addを使っていますが、これだと単に新しいオブジェクトを追加するだけとなってしまいます。したがって、どのオブジェクトに追加するのか明確にするためにやはりプライマリーキーをオブジェクトに追加し、どのオブジェクトをadd(実際には .modifiedなどを併記してオブジェクトのアップデートであることを指定する)のか明確にする必要があります。

4)ですが、プロパティを個別に渡しても構わないのですが、今後拡張する際のことを考えると、個別に渡すよりクラスとして渡した方が見通しが良いので、そのように変更するといいかと思います。


以上のことから、必要な修正内容は次のとおりとなります。

まず、オブジェクトモデルを次のように変更する必要があります。

Swift

1import Foundation 2import RealmSwift 3 4class Item: Object { 5 // 中略 6 // MARK: id としてオブジェクトが作成された日付を使う 7 @objc dynamic var id = Date().description 8 9 // MARK: id (プロパティ名が "id")をプライマリーキーとして使う 10 override static func primaryKey() -> String? { 11 return "id" 12 } 13}

変更点についてはMARK:と書かれた行をご参照ください。

この例では、プライマリーキーとしてオブジェクトが作成された瞬間の日時を用いることにしました。日時を使うことによって、今後オブジェクトを取得する際、idをキーとしてソートすることにより作成日付順で自動ソートさせることも可能となります。

ただし、Realmはオブジェクトモデルを変更した場合、そのまま実行すると実行時エラーとなりクラッシュするため、都度後述のマイグレーション処理が必要となります。

次にViewController.swiftですが、下記のように変更します。

Swift

1import UIKit 2import RealmSwift 3 4class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 5 // MARK: 遷移先にデータを付け足したい行のクラスを渡すためのプロパティ 6 private var itemName: Item? 7 8 @IBOutlet weak var tableView: UITableView! 9 10 var itemList: Results<Item>! 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 // Realmからデータを取得 16 do{ 17 let realm = try Realm() 18 // MARK: id をキーとしてソートする 19 itemList = realm.objects(Item.self).sorted(byKeyPath: "id") 20 }catch{ 21 } 22 // 中略 23 } 24 25 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 26 if (segue.identifier == "showSecondView") { 27 let secondVC = segue.destination as! SecondViewController 28 // MARK: 該当する行の item だけ渡す 29 secondVC.item = self.itemName 30 // MARK: 以下の行は不要 31 // ちなみに、thridVC, fourthVC ともに実際は SecondViewController なので毎回ダウンキャストする必要はない 32// secondVC.getName = self.labelName! 33// let thirdVC = segue.destination as! SecondViewController 34// thirdVC.getText1 = self.text1Name! 35// let fourthVC = segue.destination as! SecondViewController 36// fourthVC.getText2 = self.text2Name! 37 } 38 } 39 40 // 中略 41 42 // セルタップ時のイベント 43 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 44 let item = itemList[indexPath.row] 45 // MARK: 遷移先の画面に渡すクラス 46 self.itemName = item 47 // MARK: クラスごと次の画面に渡すので、下記の行は不要 48// self.labelName = item.title 49// self.text1Name = item.name1 50// self.text2Name = item.name2 51 performSegue(withIdentifier: "showSecondView",sender: nil) 52 } 53 54 // 中略 55 56 //スワイプしたセルを削除 57 func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 58 if editingStyle == UITableViewCell.EditingStyle.delete { 59 // Realm内のデータを削除 60 do{ 61 let realm = try Realm() 62 try realm.write { 63 realm.delete(self.itemList[indexPath.row]) 64 // MARK: 65 itemList = realm.objects(Item.self).sorted(byKeyPath: "id") 66 } 67 68 tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.fade) 69 70 // MARK: おそらく記載忘れ。削除したら tableView を更新する 71 tableView.reloadData() 72 }catch{ 73 } 74 } 75 } 76}

なるべく変更点が最小となるように、ご提示していただいたコードのスタイルに沿うように変更してあります。なので、一部冗長とも思われる書き方がありますが、とりあえずはこのような感じでご利用ください。

変更点や注意点は同じくMARK:の部分に書きました。

たとえば、idがプライマリーキーであった場合、それをキーとしてソートする場合には

Swift

1itemList = realm.objects(Item.self).sorted(byKeyPath: "id")

のような感じで記述します。
詳しくはRealmに関する各種資料をご参照ください。

次にSecondViewController.swiftですが、次のような感じとなります。

Swift

1import UIKit 2import RealmSwift 3 4class SecondViewController: UIViewController, UITextFieldDelegate { 5 // MARK: Item クラスの情報を渡してくるので下記3行は不要 6// var getName: String = "" 7// var getText1: String = "" 8// var getText2: String = "" 9 // MARK: Item クラスの item を引き取る 10 var item: Item! 11 12 // MARK: 未使用なのでコメントアウト 13// var itemList: Results<Item>! 14 15 @IBOutlet weak var labelName: UILabel! 16 17 @IBOutlet weak var textField1: UITextField! 18 @IBOutlet weak var textField2: UITextField! 19 20 override func viewDidLoad() { 21 super.viewDidLoad() 22 23 // Labelへ格納 24 // MARK: 25// self.labelName.text = getName 26 labelName.text = item.title 27 //テキストへ格納 28 // MARK: 29// self.textField1.text = getText1 30// self.textField2.text = getText2 31 textField1.text = item.name1 32 textField2.text = item.name2 33 34 textField1.delegate = self 35 textField2.delegate = self 36 37 // Do any additional setup after loading the view. 38 } 39 40 //完了ボタンでキーボードを閉じる 41 func textFieldShouldReturn(_ textField: UITextField) -> Bool { 42 self.view.endEditing(true) 43 // MARK: 不要なので削除 44// // Itemクラスのインスタンス作成 45// let newItem = Item() 46// // TextFieldの値を代入 47// newItem.name1 = textField1.text! 48// newItem.name2 = textField2.text! 49// 50 // インスタンスをRealmに保存 51 do{ 52 let realm = try Realm() 53 54 try realm.write({ () -> Void in 55 // MARK: realm に関連づいたクラスのプロパティは write ブロック内部でのみ更新可能 56 item.name1 = textField1.text! 57 item.name2 = textField2.text! 58 // 更新処理を行う 59 // realm.add(newItem) 60 realm.add(item, update: .modified) 61 }) 62 }catch{ 63 } 64 return true 65 } 66 67 //他画面タッチでキーボードを閉じる 68 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { 69 // ここの処理はtextFieldShouldReturn(_:)と同じなので関数化して共通化したほうがいいかも 70 self.view.endEditing(true) 71 // MARK: 削除 72// // Itemクラスのインスタンス作成 73// let newItem = Item() 74// // TextFieldの値を代入 75// newItem.name1 = textField1.text! 76// newItem.name2 = textField2.text! 77 78 // インスタンスをRealmに保存 79 do{ 80 let realm = try Realm() 81 82 try realm.write({ () -> Void in 83 // MARK: realm に関連づいたクラスのプロパティ(マネージドオブジェクト)は write ブロック内部でのみ更新可能 84 item.name1 = textField1.text! 85 item.name2 = textField2.text! 86 // 更新処理を行う 87 // realm.add(newItem) 88 realm.add(item, update: .modified) 89 }) 90 }catch{ 91 } 92 } 93}

こちらもやはりMARK:の部分を中心にご参照ください。
重要なのは、値の更新部分になるかと思います。

try realm.write({ () -> Void in // MARK: realm に関連づいたクラスのプロパティは write ブロック内部でのみ更新可能 item.name1 = textField1.text! item.name2 = textField2.text! // 更新処理を行う // realm.add(newItem) realm.add(item, update: .modified) }) }catch{ }

ここに示されているとおり、一度Realmに保存したオブジェクトや関連するオブジェクトはマネージドオブジェクトとなるため、Realmのwriteブロック内部でないとプロパティの変更ができません。
一方、オブジェクトにはプライマリーキーを追加したため、add(_:update:)メソッドを使って該当するオブジェクトのみを更新することが可能となりました。

最後に、AppDelegate.swiftです。
ここで前述のマイグレーション処理を行います。

Swift

1 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 2 // Override point for customization after application launch. 3 4 // MARK: マイグレーション処理 5 // Realm.io の記述ほぼそのまま流用 6 let config = Realm.Configuration( 7 // 新しいスキーマバージョンを設定します。以前のバージョンより大きくなければなりません。 8 // (スキーマバージョンを設定したことがなければ、最初は0が設定されています) 9 schemaVersion: 2, 10 11 // マイグレーション処理を記述します。古いスキーマバージョンのRealmを開こうとすると 12 // 自動的にマイグレーションが実行されます。 13 migrationBlock: { migration, oldSchemaVersion in 14 // 最初のマイグレーションの場合、`oldSchemaVersion`は0です 15 if oldSchemaVersion < 2 { 16 // MARK: https://github.com/realm/realm-cocoa/issues/5132 17 migration.enumerateObjects(ofType: Item.className()) { oldObject, newObject in 18 newObject!["id"] = Date().description 19 } 20 } 21 }) 22 // デフォルトRealmに新しい設定を適用します 23 Realm.Configuration.defaultConfiguration = config 24 25 // Realmファイルを開こうとしたときスキーマバージョンが異なれば、 26 // 自動的にマイグレーションが実行されます 27 _ = try! Realm() 28 29 return true 30 }

必要な部分だけ抜き出したので、そっくりそのまま入れ替えて使ってもらえますでしょうか。

ほとんど決まり切った処理なのですが、もしかしたらここが一番厄介かもしれません。
もし、全て書き換えてうまくいかない場合には(実行時エラーが解決しない場合)、新たにプロジェクトを作成した方が早いかもしれません。その場合にはマイグレーション処理は必要ではなく、AppDelegate.swiftの更新も必要ありません。

投稿2020/05/22 14:58

TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bbbone

2020/05/23 08:39

コードの修正だけでなく、それに付随する解説までして頂き誠にありがとうございます。 コードを書き直し実行したところ、自分の思い描いていた処理ができました! プログラミング勉強のモチベーションが下がっていたのですが、これでまた頑張れそうです! 本当にありがとうございました。
guest

0

SecondViewControllertextFieldShouldReturnの中で入力した情報を反映させるんでしょうが、そこでaddになっています、特定データに対する編集?更新処理にしてください。

で、戻ってきてtableViewをリロードしてください。

投稿2020/05/22 11:22

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bbbone

2020/05/22 14:32 編集

```SecondViewController //完了ボタンでキーボードを閉じる func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.view.endEditing(true) // Itemクラスのインスタンス作成 let newItem = Item() // TextFieldの値を代入 newItem.name1 = textField1.text! newItem.name2 = textField2.text! // インスタンスをRealmに上書き do{ let realm = try Realm() try realm.write { newItem.name1 = textField1.text! newItem.name2 = textField2.text! } }catch{ } return true } ``` 上記のような更新処理のコードを書いたのですが、保存できません。知識不足で申し訳ないのですが、ご教示のほどよろしくお願いいたします。
退会済みユーザー

退会済みユーザー

2020/05/22 14:43

編集対象のオブジェクトを取得して、オブジェクトのプロパティの変更をしたらいいのではないでしょうか? オブジェクトの削除の部分で特定オブジェクトを取得することはできてるので、その方法を使えばいいのでは?
bbbone

2020/05/22 15:02

ViewController上では[indexPath.row]を使用して、対象のオブジェクトを取得できるのですが、SecondViewController上で対象のオブジェクトを呼び出す方法が分かっておりません。度々申し訳ございませんが、教えてください、、、
退会済みユーザー

退会済みユーザー

2020/05/22 15:05

画面遷移時にオブジェクトを特定できる情報を遷移先に渡しては? 私適当なので、 TsukubaDepotさんのほうがためになるのでそっちで聞いてみてください。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問