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

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

ただいまの
回答率

88.92%

SWIFT メモ帳アプリ 保存の仕方

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 158

watchdogs

score 15

コード

保存ができなくて困っています。
メモ帳アプリを作成しています。
UIには1画面に4つカテゴリ別のテキストフィールドがあります。
保存すると一つしか保存ができず、次回開くとその記録された一つが他のテキストフィールドに記載されてしまっている状態です。
下記プログラム参照して下さい。

具体的にNote2が
noteContentxtFieTextViewとNote1に書きかわり保存されてしまっています。

import UIKit
import Foundation
class DisplayNoteViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate,UITextFieldDelegate
{


    @IBOutlet weak var noteTitleTextField: UITextField!
    @IBOutlet weak var noteContentxtFieTextView: UITextView!
    @IBOutlet weak var Note1: UITextView!
    @IBOutlet weak var Note2: UITextView!


    @IBOutlet weak var ImageView: UIImageView!

    @IBOutlet weak var label1: UILabel!
    @IBOutlet weak var label2: UILabel!
    @IBOutlet weak var label3: UILabel!
    @IBOutlet weak var label4: UILabel!

    var note: Note?


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

    }


 override func viewWillAppear(_ animated: Bool){
        super.viewWillAppear(animated)

        /* 
         noteプロパティの値をアンラップし、実際の値をnoteという名前のローカル変数
         に格納するために、オプションのバインディング手法を使用。
        */
        if let note = note {
            /*
             noteプロパティがnilでない場合にのみ実行される。
             テキストフィールドとテキストビューのプロパティをnoteの
             タイトルと内容にそれぞれ設定。
            */
            noteTitleTextField.text = note.title
            noteContentxtFieTextView.text = note.content
            Note1.text = note.content
            Note2.text = note.content

        } else {
            /*
             noteプロパティがnilの場合に実行される。
             新しいノートを作成する際に呼ばれる。
             テキストフィールドとテキストビューを空の文字列に設定し、
             ユーザーが新しいノートに入力できるようにする。
            */
            noteTitleTextField.text = ""
            noteContentxtFieTextView.text = ""
            Note1.text = ""
            Note2.text = ""
        }

        /*
         *下記のコードは上記の if let / else と同じ意味
         noteTitleTextField.text = note?.title ?? ""
         noteContentTextView.text = note?.content ?? ""
        */
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let notesTableViewController = segue.destination as! NotesTableViewController
        if segue.identifier == "save" {
            if let note = note {
                /*
                 noteのプロパティがnilでない場合(ノートのタイトルと内容を更新するだけで良い場合)、実行される。
                */
                note.title = noteTitleTextField.text ?? ""
                note.content = noteContentxtFieTextView.text ?? ""
                note.content = Note1.text ?? ""
                note.content = Note2.text ?? ""

                // データをリロードするようテーブルビューに指示。

                // これにより、作成した既存ノートの編集内容がノート一覧に反映される。
                notesTableViewController.tableView.reloadData()
            } else {
                // noteのプロパティがnilであれば実行される。(ノートが新規生成される)
                let newNote = Note()
                newNote.title = noteTitleTextField.text ?? ""
                newNote.content = noteContentxtFieTextView.text ?? ""
                newNote.content = Note1.text ?? ""
                newNote.content = Note2.text ?? ""
                notesTableViewController.notes.append(newNote)
            }
        }
    }
}
import UIKit

class NotesTableViewController: UITableViewController {

    /*
     notesプロパティが変更されるたびに、すべてのデータをリロードするように
     didSetプロパティオブザーバを用いてテーブルビューに通知
    */
    var notes = [Note]() {
        didSet {
            tableView.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }

    // 表示すべきセルの数を返す
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return notes.count
    }

    // テーブルの特定の行に対して、表示すべきcellを返す
    // indexPathは、作成するcellがデーブルビュー内のどのセクションと行に属するかを示す
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //テーブルビューに表示される実際のcellをフェッチしている
        //ダウンキャスト <- NotesTableViewCellがUITableViewCellのサブクラスであるため機能する
        //コンパイラに具体的な種類のUITableViewCellを返すことを指示する
        let cell = tableView.dequeueReusableCell(withIdentifier: "notesTableViewCell", for: indexPath) as! NotesTableViewCell

        // indexPathは、cellForRowに渡された引数であり、テーブルビューがセルをどの行に表示するのかを示す
        // indexPathのrowプロパティにアクセスし、検索する。
        let row = indexPath.row

        let note = notes[row]

        cell.noteTitleLabel.text = note.title


        return cell
    }


    // commitEditingStyle : forRowAt indexPath: により、テーブルビューに追加の編集モードを有効にする
    // cellを左スワイプした際に削除オプションが表示される
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        // editingStyleが.deleteであるかどうかを調べる
        if editingStyle == .delete {
            // notesPathから渡されたindexPathのrowプロパティを使用して、選択されたノートをnotes配列から削除する
            notes.remove(at: indexPath.row)
        }
    }


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let identifier = segue.identifier {
            if identifier == "displayNote" {
                print("Table view cell tapped")

                // すべてのテーブルビューは、indexPathForSelecedRowというプロパティを持っている。
                // ユーザーがテーブルビューからcellを選択した場合、特定のcellのindexPathにアクセス可能
                // indexPathにはsection,rowのプロパティがあり、選択したテーブルビューのcellをマッピングする
                // データモデル(ここではnotes配列)に関連付けることができる。
                let indexPath = tableView.indexPathForSelectedRow!

                // テーブルビューにはsectionが1つしかないため、対応するindexPathのrowプロパティを使用して
                // 各cellを一意に識別できる。
                // indexPath.rowで、タッチされたcellに対応するnotes配列からノートを取り出す。
                let note = notes[indexPath.row]

                // ダウンキャスト
                // segueのdestinationプロパティを使用して、DispalyNoteViewControllerにアクセス
                let displayNoteViewController = segue.destination as! DisplayNoteViewController

                // DisplayNoteViewControllerのnoteプロパティを、ユーザーがタッチしたcellに対応するnoteに設定
                displayNoteViewController.note = note

            } else if identifier == "addNote" {
                print("+ button tapped")
            }
        }
    }

    @IBAction func unwindToNotesViewController(_ segue: UIStoryboardSegue) {

    }


}

ここに言語を入力
import Foundation

class Note {
var title = ""
var content = ""
}
```

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

0

note.content = noteContentxtFieTextView.text ?? ""
note.content = Note1.text ?? ""
note.content = Note2.text ?? ""


ここで note.content に noteContentxtFieTextView.text を入れてそれを Note1.text で上書きしてさらに Note2.text で上書きしているからでは?
同様にviewWillAppearでも1つのインスタンスに対して上書きし続けているようですが、別のインスタンスに分けるとか、配列にするとか、クラスにするとかしないといけないと思います。

プロパティと同名の変数が出てきたりしてコードが非常に読みづらいのと、実際の保存処理のところが提示されていないのでなんとも言えないところではありますが。

追記 Jul 16, 2020 11:00 AM

「データの管理をする」と「データを画面に表示する」の2つの処理について、役割を分けたほうがよいです。
リスト表示画面 (NotesTableViewController) で生成した Note のインスタンスを詳細表示・編集画面 (DisplayNoteViewController) に渡したり、あるいは施した編集作業をリストに反映させるために詳細表示・編集画面からリスト表示画面に渡したり、というのはコードの見通しが良くないです。
下記はシングルトンを使ってデータの管理をするクラス (StoredNotesManager) を実装する例です。実際に動かしてはいませんので適宜調整が必要だとは思いますが、参考になればと思います。

class Note {
    var title: String = ""
    var content: String = ""
    var memo1: String = ""
    var memo2: String = ""
}

class StoredNotesManager {
    static let shared = StoredNotesManager()
    private var storedNotes: [Note] = []
    private init() {
        // FIXME: 永続化済みのデータがあればここで storedNotes に格納する
    }

    var count: Int {
        get { return self.storedNotes.count }
    }

    func append(_ appendingNote: Note) -> Void {
        self.storedNotes.append(appendingNote)
        self.persistence()
    }

    func remove(at: Int) -> Void {
        self.storedNotes.remove(at: at)
        self.persistence()
    }

    func update(at: Int, newNote: Note) -> Void {
        self.storedNotes[at] = newNote
        self.persistence()
    }

    func get(at: Int) -> Note? {
        if self.storedNotes.count <= at { return nil }
        return self.storedNotes[at]
    }

    /// 永続化
    private func persistence() {
        // FIXME: 永続化処理を実装する
    }
}
// MARK: - 永続化済みの Notes を一覧表示する
class NotesTableViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // 表示すべきセルの数を返す
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return StoredNotesManager.shared.count
    }

    // テーブルの特定の行に対して、表示すべきcellを返す
    // indexPathは、作成するcellがデーブルビュー内のどのセクションと行に属するかを示す
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //テーブルビューに表示される実際のcellをフェッチしている
        //ダウンキャスト <- NotesTableViewCellがUITableViewCellのサブクラスであるため機能する
        //コンパイラに具体的な種類のUITableViewCellを返すことを指示する
        let cell = tableView.dequeueReusableCell(withIdentifier: "notesTableViewCell", for: indexPath) as! NotesTableViewCell

        // indexPathは、cellForRowに渡された引数であり、テーブルビューがセルをどの行に表示するのかを示す
        // indexPathのrowプロパティにアクセスし、検索する。
        cell.noteTitleLabel.text = StoredNotesManager.shared.get(at: indexPath.row)?.title ?? ""

        return cell
    }

    // commitEditingStyle : forRowAt indexPath: により、テーブルビューに追加の編集モードを有効にする
    // cellを左スワイプした際に削除オプションが表示される
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        // editingStyleが.deleteであるかどうかを調べる
        if editingStyle == .delete {
            // notesPathから渡されたindexPathのrowプロパティを使用して、選択されたノートをnotes配列から削除する
            StoredNotesManager.shared.remove(at: indexPath.row)
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let identifier = segue.identifier {
            if identifier == "displayNote" {
                print("Table view cell tapped")

                // すべてのテーブルビューは、indexPathForSelecedRowというプロパティを持っている。
                // ユーザーがテーブルビューからcellを選択した場合、特定のcellのindexPathにアクセス可能
                // indexPathにはsection,rowのプロパティがあり、選択したテーブルビューのcellをマッピングする
                // データモデル(ここではnotes配列)に関連付けることができる。
                let indexPath = tableView.indexPathForSelectedRow!
                (segue.destination as! DisplayNoteViewController).storedAt = indexPath.row
            } else if identifier == "addNote" {
                print("+ button tapped")
                (segue.destination as! DisplayNoteViewController).isNewNote = true
            }
        }
    }

    @IBAction func unwindToNotesViewController(_ segue: UIStoryboardSegue) {

    }
}

// MARK: - Note の詳細表示・編集
class DisplayNoteViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate {
    @IBOutlet weak var noteTitleTextField: UITextField!
    @IBOutlet weak var noteContentxtFieTextView: UITextView!
    @IBOutlet weak var memo1TextView: UITextView!
    @IBOutlet weak var memo2TextView: UITextView!

    var storedAt: Int = 0
    var isNewNote: Bool = false

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

    override func viewWillAppear(_ animated: Bool){
        super.viewWillAppear(animated)

        // 新規作成の場合は空文字を入れる
        if self.isNewNote {
            self.noteTitleTextField.text = ""
            noteContentxtFieTextView.text = ""
            memo1TextView.text = ""
            memo2TextView.text = ""
            return
        }

        // 既存の Note の詳細表示・編集の場合は、永続化済みの Note から値を取得する
        let note: Note? = StoredNotesManager.shared.get(at: self.storedAt)
        self.noteTitleTextField.text       = note?.title
        self.noteContentxtFieTextView.text = note?.content
        self.memo1TextView.text            = note?.memo1
        self.memo2TextView.text            = note?.memo2
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier != "save" { return }
        let newNote: Note = Note()
        newNote.title   = self.noteTitleTextField.text ?? ""
        newNote.content = self.noteContentxtFieTextView.text
        newNote.memo1   = self.memo1TextView.text
        newNote.memo2   = self.memo2TextView.text

        if isNewNote {
            StoredNotesManager.shared.append(newNote)
            return
        }

        StoredNotesManager.shared.update(at: self.storedAt, newNote: newNote)
    }
}

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/07/16 07:53

    返事が遅れまして申し訳ございません。定義を追加しました。
    >保存しているようには見えないですが、「保存」というのは、とくに永続化とかはせず単純にその場限りでTableViewに表示するという意味でおっしゃっていますか?
    タスクを切っても保存している仕様にしたいと思っています。

    キャンセル

  • 2020/07/16 10:03

    そういう仕様にしたいかどうかではなくて、現状の(ご提示のコード上の)実装がどうなっているかを確認しています。

    キャンセル

  • 2020/07/16 10:06

    この質問で目指しているところがよくわからないですね……「1画面に4つカテゴリ別のテキストフィールド」があるということは、保存したいデータというのは4種類のテキストデータなのではないかと思うのですが、Noteの定義を見るとタイトルと本文の2つしか無いのですね。4つのテキストフィールドの値をどのようにNote型の値として保持したいのでしょうか?

    キャンセル

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

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

関連した質問

同じタグがついた質問を見る