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

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

ただいまの
回答率

89.65%

テーブルビューに保存したデータが、編集中に移動することで消えてしまいます。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,455

obataatu

score 12

前提・実現したいこと

初心者です。現在、swiftでiOSの単純なメモアプリを作成しております。
テキストビューに書かれたテキストを移動先のシーンに受け渡し、
テーブルビューセルとして、保存し、削除、編集ができるようにするという内容にしたいと考えています。

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

下記の2点がわからず困っています。お手数ですが、原因がお分かりになる方がおりましたら、ご回答いただけますと幸いです。

① テキストをappAppDelegateを通して受け渡し、NSUserDefaultsを使い、保存、削除をすることができたのですが、作成済みのセルテキストの編集ができず困っています。(移動前の書き込みのページに保存されているテキストを受け渡し、編集後上書きしたいのですが、新たなセルとして保存されてしまいます。)

② わかりづらい説明になり申し訳ありませんが、下記の手順の操作をすると保存されているセルが全て消えてしまい、困っています。
・2つ以上のセルを作成する。
・編集ボタンテーブルビューページの編集ボタンメニューにて一つのセルを削除する。
・その後、画面遷移をし、再度テーブルビューページのを開くとセルが全て消えてしまいます。
エラーメッセージがでたり、止まってしまったりすることはないので、単純に処理をするタイミングがおかしいのかなと考えておりますが、知識が足りず、わかりません。

テーブルビュー側 swift

import UIKit

private let unselectedRow = -1

class memo_list: UIViewController,UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {

    @IBOutlet weak var memoListView: UITableView!

    var memo1:String?
    var memo2:String?
    var memoList: [String] = []
    var memoList2: [String] = []
    var editRow: Int = unselectedRow

    override func viewDidLoad() {
        super.viewDidLoad()
        print(memo1)

        //保存したテキストデータをロード
        let hozon = NSUserDefaults.standardUserDefaults()
        let load = hozon.objectForKey("dly")
        if (load as? [String] != nil) {
            memoList = load as! [String]
        }
        let hozon3 = NSUserDefaults.standardUserDefaults()
        let load3 = hozon3.objectForKey("dly3")
        if (load3 as? [String] != nil) {
            memoList2 = load3 as! [String]
        }


        //配列の個数が合わない場合はリセットする
        if memoList.count != memoList2.count {
            memoList = []
            memoList2 = []
            NSLog("memoListとmemoList2に不整合が生じたので両方リセットしました")
        }
        //テキストを受け取る
        //セルにテキストを入れ込む処理
        applyMemo()

        //編集ボタンを用意
        navigationItem.rightBarButtonItem = editButtonItem()

    }


    override func setEditing(editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        memoListView.editing = editing

    }

    func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {

        memoListView.allowsSelectionDuringEditing = true
        return true
    }

    //セルを削除する処理
    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        if editingStyle == UITableViewCellEditingStyle.Delete {

            //指定したセルの番号の配列番号を削除する
            memoList.removeAtIndex(indexPath.row)
            memoList2.removeAtIndex(indexPath.row)

            tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: indexPath.row, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Automatic)
            //NSUserDefaultsの方のデータも削除(ここでは配列の中の一つを削除しており、下のテキストで再度配列全体を保存し直す)
            let hozon = NSUserDefaults.standardUserDefaults()
            hozon.setObject(memoList, forKey: "dly")
            let hozon3 = NSUserDefaults.standardUserDefaults()
            hozon3.setObject(memoList2, forKey: "dly3")
        }
    }



    //セルにテキストを入れ込む処理
    //新規か編集かを判断
    func applyMemo() {


        if memo1 == nil {
            return }
        if memo1 == "" {
            return }
        if editRow == unselectedRow {
            memoList.append(memo1!)
            memoList2.append(memo2!)
        } else {
            print(memo1)
            memoList[editRow] = memo1!
            memoList2[editRow] = memo2!

        }

        //セル情報を保存する
        let hozon = NSUserDefaults.standardUserDefaults()
        hozon.setObject(memoList, forKey: "dly")
        let hozon3 = NSUserDefaults.standardUserDefaults()
        hozon3.setObject(memoList2, forKey: "dly3")

        //セルの選択状態を非選択に戻る
        editRow = unselectedRow
        //リロード処理(必須)
        memoListView.reloadData()
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return memoList.count
    }


    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let Cell = "Cell"
        let cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: Cell)

        if indexPath.row >= memoList.count {
            return cell
        }
        //タイトルとサブタイトルに配列からテキストを入れ込む
        cell.textLabel?.text = memoList[indexPath.row]
        cell.detailTextLabel?.text = memoList2[indexPath.row]
        return cell
    }


    //作成済みのセルを選択した時の処理
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

        if indexPath.row >= memoList.count {
            return
        }

        editRow = indexPath.row
        memo1 = memoList[editRow]
        memo2 = memoList2[editRow]

        print(memo1)
        //書き込みページに画面遷移する

        // SubViewController へ遷移するために Segue を呼び出す
        performSegueWithIdentifier("settext1",sender: nil)

    }

    @IBAction func memo_tuika(sender: AnyObject) {

        memo1 = ""
        memo2 = ""
        performSegueWithIdentifier("settext1",sender: nil)

    }


    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let send2:SecondViewController = segue.destinationViewController as! SecondViewController
        if segue.identifier == "settext1" {
            if memo1 == nil {
                return
            }
            send2.title2 = memo1!
            send2.honbun2 = memo2!
            print(memo1)
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}



テキスト入力画面 swift

import UIKit

class SecondViewController: UIViewController,UITableViewDelegate,UITextFieldDelegate {


    @IBOutlet weak var title_text: UITextField!
    @IBOutlet weak var honbun_text: textview_custom!

    var title2:String?
    var honbun2:String?


    override func viewDidLoad() {
        super.viewDidLoad()
        title_text.delegate = self
    }

    //変更点:前回はセル選択時のテキストの受け取りをviewDidLoad()で行ってしまっており、navigationController?.popViewControllerAnimated(true)で戻ってきた場合、処理が行われていなかったため、毎回の遷移全てで反応するviewWillAppearにて行うよう、変更しました。

    override func viewWillAppear(animated: Bool) {
        title_text.text = self.title2
        honbun_text.text = self.honbun2
    }



    //移動の際に向こうのプロバティにテキストを引き継ぐ?
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        if segue.identifier == "sendtext" {
            let send:memo_list = segue.destinationViewController as! memo_list
            send.memo1 = title_text.text
            send.memo2 = honbun_text.text

            //その後、初期化する
            title_text.text = ""
            honbun_text.text = ""
        }

    }

    //キーボードを下げる処理
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

        if(title_text.isFirstResponder()){
            title_text.resignFirstResponder()
        }
        if(honbun_text.isFirstResponder()){
            honbun_text.resignFirstResponder()
        }

    }

    //保存ボタンアクション
    @IBAction func save(sender: AnyObject) {
        performSegueWithIdentifier("sendtext",sender: nil)

    }
    //保存(セルの追加)処理をしないで、テーブルビューページに移動する処理
    @IBAction func listbotton(sender: AnyObject) {

        title_text.text = ""
        honbun_text.text = ""

        performSegueWithIdentifier("sendtext",sender: nil)

    }

    //仮で作った戻るボタン
    //prepareForSegueが反応してくれない
    @IBAction func back(sender: UIStoryboardSegue) {

        let x = self.storyboard?.instantiateViewControllerWithIdentifier("memo_list")
        x?.modalTransitionStyle = .CoverVertical

        navigationController?.popViewControllerAnimated(true)

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


}




AppDelegate swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    // メモ用定数
    var title:String?
    var honbun:String?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        return true
    }

    func applicationWillResignActive(application: UIApplication) {

    }

    func applicationDidEnterBackground(application: UIApplication) {

    }

    func applicationWillEnterForeground(application: UIApplication) {

    }

    func applicationDidBecomeActive(application: UIApplication) {

    }

    func applicationWillTerminate(application: UIApplication) {

    }


}

試したこと

初心者で知識が少ないため、テキストを受け渡すタイミングを変えるため、コードを書く位置を変えてみるということしかできず、問題解決ができない状態です。

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

xcode Version 7.3.1 (7D1014)
swift2

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

つっこみどころはいろいろありますが、とりあえず質問にある操作でセルが全て消えてしまう原因は、
tableView(〜 commitEditingStyle 〜) {
のメソッドの中で、
hozon.removeObjectForKey("dly")
で、memoListの内容を保存しているはずのデータを全部削除しているためです。

ここで削除したいのはmemoListの中に入っている1要素だけですから、
memoList.removeAtIndex(indexPath.row)
で対象の要素を削除したら、
hozon.setObject(memoList, forKey: "dly")
でmemoListの内容全体を再度保存しなおせばよいです。

memoList2も同様です。というかmemoList2の方は
memoList2.removeAtIndex(indexPath.row)
も漏れていると思います。


(6/16 23:30追記)
私だったらこうするというサンプルコードを作ってみました。
なお、画面遷移のやり方はpushViewControllerでもセグエでもどっちでもいいんですが、セグエを使う場合、Storyboardの設定を説明するのが面倒なので、最初の質問でやっていたpushViewControllerで遷移する方式でサンプルを作っています。
それと、もともとセルの追加をどうやるつもりだったのか説明がなかったので、テーブルの最後に「メモ追加」セルを加え、それをタップしたら編集画面に移行して入力したデータが追加されるようにしました。

(サンプルコード)

memo_list.swift

import UIKit
class memo_list: UIViewController,UITableViewDelegate, UITableViewDataSource, SecondViewControllerDelegate {

    @IBOutlet weak var memoListView: UITableView!

    let userDefaults = NSUserDefaults.standardUserDefaults()
    var titleList: [String]!
    var honbunList: [String]!

    override func viewDidLoad() {
        super.viewDidLoad()
        // 保存したデータ読み込み
        titleList = userDefaults.stringArrayForKey("dly") ?? []
        honbunList = userDefaults.stringArrayForKey("dly3") ?? []

        // 配列の個数が合わない場合はリセットする
        if titleList.count != honbunList.count {
            titleList = []
            honbunList = []
            NSLog("memoListとmemoList2に不整合が生じたので両方リセットしました")
            userDefaults.removeObjectForKey("dly")
            userDefaults.removeObjectForKey("dly3")
        }
        // 編集ボタンを設定
        navigationItem.rightBarButtonItem = editButtonItem()
        // 編集モードでのセル選択可とする
        memoListView.allowsSelectionDuringEditing = true
    }

    override func setEditing(editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        memoListView.editing = editing
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return titleList.count+1
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")
        if indexPath.row < titleList.count {
            cell.textLabel?.text = titleList[indexPath.row]
            cell.detailTextLabel?.text = honbunList[indexPath.row]
        } else {
            cell.textLabel?.text = "(メモ追加)"
        }
        return cell
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let to_edit = self.storyboard?.instantiateViewControllerWithIdentifier("memo") as! SecondViewController
        to_edit.delegate = self
        if let indexPath = memoListView.indexPathForSelectedRow where indexPath.row < titleList.count {
            to_edit.memoTitle = titleList[indexPath.row]
            to_edit.memoHonbun = honbunList[indexPath.row]
        }
        navigationController?.pushViewController(to_edit, animated: true)
    }

    func didGetMemo(title: String, _ honbun: String) {
        if let indexPath = memoListView.indexPathForSelectedRow where indexPath.row < titleList.count {
            titleList[indexPath.row] = title
            honbunList[indexPath.row] = honbun
        } else {
            titleList.append(title)
            honbunList.append(honbun)
        }
        userDefaults.setObject(titleList, forKey: "dly")
        userDefaults.setObject(honbunList, forKey: "dly3")
        //テーブル更新
        memoListView.reloadData()
    }

    func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
        if (indexPath.row < titleList.count) {
            return .Delete
        } else {
            return .Insert
        }
    }

    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        if editingStyle == .Delete {
            //指定したセルの番号の配列番号を削除する
            titleList.removeAtIndex(indexPath.row)
            honbunList.removeAtIndex(indexPath.row)
            // セルを削除
            tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: indexPath.row, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Automatic)
            // 保存
            userDefaults.setObject(titleList, forKey: "dly")
            userDefaults.setObject(honbunList, forKey: "dly3")
        } else if editingStyle == .Insert {
            // セル追加画面起動
            let to_edit = self.storyboard?.instantiateViewControllerWithIdentifier("memo") as! SecondViewController
            to_edit.delegate = self
            navigationController?.pushViewController(to_edit, animated: true)
        }
    }
}

SecondViewController.swift

import UIKit

@objc protocol SecondViewControllerDelegate {
    func didGetMemo(title:String, _ honbun:String)
}

class SecondViewController: UIViewController,UITableViewDelegate,UITextFieldDelegate {

    @IBOutlet weak var title_text: UITextField!
    @IBOutlet weak var honbun_text: textview_custom!

    weak var delegate:SecondViewControllerDelegate!
    var memoTitle:String = ""
    var memoHonbun:String = ""


    override func viewDidLoad() {
        super.viewDidLoad()

        title_text.delegate = self

        // 受け取ったタイトルとテキストを表示
        title_text.text = memoTitle
        honbun_text.text = memoHonbun
    }


    //保存ボタンアクション
    @IBAction func save2(sender: AnyObject) {
        delegate.didGetMemo(title_text.text!, honbun_text.text)
        navigationController?.popViewControllerAnimated(true)
    }

    //キーボードを下げる処理
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

        if(title_text.isFirstResponder()){
            title_text.resignFirstResponder()
        }
        if(honbun_text.isFirstResponder()){
            honbun_text.resignFirstResponder()
        }

    }

    //保存処理をしないで、テーブルビューページに移動する処理
    @IBAction func listbotton(sender: AnyObject) {
        navigationController?.popViewControllerAnimated(true)
    }
}

memo_list.swiftとSecondViewController.swiftを上記の内容に差し替えてみてください。

変数名等が気に入らないものも勝手に変更しましたが、Storyboardと接続しているプロパティ名やメソッド名は変えないようにしておきましたので、そのままコードを差し替えるだけでStoryboardとちゃんと接続して動作できるはずです。
動作確認できたら、どう違うのかコードを比較してみてください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/19 04:14

    連続の投稿失礼します。
    試行錯誤の結果、上記の内容では不安定なコードになってしまいそうですので、TakeOne様に頂いたサンプル内の方法にて開発を進めることにしました。
    複数回にわたり、ご指摘、ご教授頂き、誠にありがとうございました。

    キャンセル

  • 2016/06/19 08:57 編集

    しばらく普段と違う環境にいたので返信が遅れました。

    > 「トップ画面のボタンAから遷移した場合は、”テキスト記入画面”からのスタート、ボタンBから遷移した場合は、頂いたコードのように”テーブルビュー画面からスタート(A,B共有のもの)”」としたい場合は、新たにテキスト記入画面ファイルとテーブルビューファイルを2つ追加で作り、保存場所は共有にする、という作り方で合っておりますでしょうか?

    新たに別のクラスを作ってもできるとは思いますが、私が作ったSecondViewControllerは、情報のカプセル化と整理によりmemo_listから呼び出されることを前提としなくても動作できるように作っていますから、テキスト記入画面はうまくそれを流用すれば、新しい画面を作らなくてもできると思います。
    それと、最初の質問ではmemo_listとSecondViewControllerしかないように書かれていたので、タイトル一覧(titleList)と本文一覧(honbunList)はmemo_listだけが参照するデータだと考えましたが、memo_listのさらに親がいて、そこでもタイトル一覧や本文一覧を扱いたいのであれば、その親にtitleListやhonbunListを定義するか、もっといえば、タイトル一覧や本文一覧はこのアプリの中にただ一つしか存在しない中心データであると言えるなら、いっそのことAppDelegateのプロパティとして定義してもよいと思います。
    要するにそのリストデータをどの範囲で共有するかを考えて適切な場所に定義すればよいということです。

    キャンセル

  • 2016/06/21 07:35

    ご返信が遅くなり、申し訳ありません。
    教えて頂いた考え方で再度書き直し、なんとか理想の形にすることができました。
    一連の回答は、知識が少なく、未熟な私には、大変参考になりました。
    質問への回答、サンプルの作成、などお力添え頂き、誠にありがとうございました。

    キャンセル

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

  • ただいまの回答率 89.65%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる