【サンプルコードあり】SwiftUIとCoreDataでメモアプリを作ってみた#3 実装編

Swift

はじめに

こちらの記事は前回の記事の続きになります。

前回は、CoreDataを利用する準備まで実施しました。今回は実際にCoreDataを利用しながら、メモアプリの作成をしていきます!

メモアプリ説明

今回作成するメモアプリは、メニュー画面とメモ編集画面です。実際には以下のよう形です。

メニュー画面で、メモの追加しメモの一覧を表示します。メニュー画面を選択することで編集画面に遷移します。Backボタンでメニュー画面に遷移できます。Backボタン押下時にメモを保存します。

メモアプリ作成

メニュー画面

まずはメニュー画面の実装です。

ContentView.swiftを選択してください。

struct ContentView: View {
    //CoreDataのための宣言
    @Environment(\.managedObjectContext) var viewContext
    @FetchRequest(entity: Memo.entity(), sortDescriptors: []) var fatchedMemoList: FetchedResults<Memo>
    
    ...
    
}

ContentViewに、@Environmentと@FetchRequestを設定します。

@Environment

環境編数を利用するための宣言になります。
環境変数としてMemoApp.swiftで前回設定した「persistentController.container.viewContext」
をContentView内で利用できるようになります。

@FetchRequest

CoreDataで保存したデータを取得するためのAttributeになります。
FetchRequestは以下のような形で利用します。

 @FetchRequest(entity:エンティティ, sortDescriptors: ソート条件) 変数名: FetchedResults<エンティティ名>

今回はエンティティはMemo型、ソート条件はなしとして取得しました。

次に保存されているメモの一覧を表示する機能を作成してきます。

 ...
 var body: some View {
   VStack {
        
          
  }      
}
...

まずは上記のようにVstackの中身をすべて削除してください。

 ...
  var body: some View {
   VStack {
     List(fatchedMemoList) { memo in
       
     
     }     
  }      
}
...
 

次に「List(fatchedMemoList) { memo in }」を追加します。この処理は@FeatchRequestで取得した値で一覧を取得するために設定しています。

 ...
  var body: some View {
   VStack {
     List(fatchedMemoList) { memo in
       if let title = memo.title,
       let updateTime = memo.upDateTime {
         VStack(alignment: .leading) {
           Text("タイトル: \(title)")
             .font(.headline)
           Text("更新日時: \(updateTime)")
            .font(.caption)
            .foregroundColor(.gray)
          }
          .padding()
       }
     }     
  }      
}
...

次に「if let title = …」の部分を追加します。これによりfatchedMemoListから取得したmemoのtitleとupdateTime値を取得します。さらに、そこで取得した値を利用するためにText()を利用して画面に表示します。これでCoreDataで登録されたメモデータの一覧を表示することができます。

次にメモを追加する処理を作成していきます。

 ....
   var body: some View {
     VStack {
       ....
     }
   }
 
     //新規メモを作成するためのメソッド
    func createNewMemo() {
    
    }
  ...

先ほど利用していたbodyの下に、createNewMemoメソッドを定義してください。

createNewMemoメソッドの中身を記述していきます。

... 
     //新規メモを作成するためのメソッド
    func createNewMemo() {
      let memo = Memo(context: viewContext)
        memo.title = ""
        memo.contents = ""
        memo.id = UUID()
        
        //作成日時を表示するために初期値を取得する
        let now = Date()
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        memo.upDateTime = formatter.string(from: now)
        
         //CoreDataにメモの内容を保存
        do {
            try viewContext.save()
        } catch {
            fatalError("Fail to save")
        }
      
    }
...

まず、メモの内容を保持するための変数としてmemoを宣言し初期値を設定します。これにより、title、contents、id、upDateTimeの初期値を追加しました。
最後にデータを保存する処理を追加してcreateNewMemoメソッドは完成です。

次にcreateNewMemoメソッドを利用する新規メモ追加ボタンを作成していきます。先ほど作成したbodyの中に追加していきます。

...
    var body: some View {
        VStack {
            List(fatchedMemoList) { memo in
                if let title = memo.title,
                   let updateTime = memo.upDateTime {
                    VStack(alignment: .leading) {
                        Text("タイトル: \(title)")
                            .font(.headline)
                        Text("更新日時: \(updateTime)")
                            .font(.caption)
                            .foregroundColor(.gray)
                    }
                    .padding()
                }
                
            }
            //メモを新規追加するボタン
            VStack {
                Button(action: createNewMemo) {
                    Text("メモを追加")
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                }
                .padding()
            }
        }
    }
...

上記のようにListの下にメモ追加ボタンを追加し、押下時のアクションとしてcreateNewMemoを実行します。

ここまでできたら、実行してみましょう。

はじめは左のような画面が出てくると思います。メモ追加を押下することで右のような画面になると思います。

これで初期の画面は完成です。次にメモ編集画面を作成していきます。

メモ編集画面

メモ編集画面を作成していきます。

メモ編集画面は、メニュー画面で選択されたメモを受け取り内容を編集できるようにします。また、戻るボタンを押下したタイミングでメモを保存しメニュー画面で表示されているタイトルや更新日時を編集後の値に更新します。

それではメモ画面の作成を始めます。

Memoフォルダを右クリックし、「New File from Template…」を選択してください。

「SwiftUi View」を選択します。

「MemoView.swift」と名前をつけて「Create」を押下してください。

上の画像のような状態になると思います。

import SwiftUI

struct MemoView: View{
  var body: some View{
  
  }
}

まずは今回不要なbodyの中のTextと、#Previewを削除します。

import SwiftUI

struct MemoView: View{

  //前の画面から渡されるメモ
  @ObservedObject var memo: Memo
  //CoreDataを利用するための値
  @Environment(\.managedObjectContext) private var viewContext
  //変更したメモを保持する変数
  @State var memoTitle: String = ""
  @State var memoContents: String = ""
  
  var body: some View{
  
  }
}

「@ObservedObject var memo: Memo」で、メニュー画面から渡されたメモを取得します。

次にメモを編集できる画面を作成します。

struct MemoView: View {
    ...
    var body: some View {
        VStack {
            TextField("題名", text: $memoTitle)
                .font(.title)
                .padding()
            TextEditor(text: $memoContents)
                .padding()
        }
    }
}

bodyの中にタイトルを編集するTextFieldと内容を編集するTextEditorを追加します。

struct MemoView: View {
    ...
    var body: some View {
        VStack {
            TextField("題名", text: $memoTitle)
                .font(.title)
                .padding()
            TextEditor(text: $memoContents)
                .padding()
        }
        .onAppear {
            memoTitle = memo.title ?? ""
            memoContents = memo.contents ?? ""
        }
    }
}

次に、onAppearを追加追加しメニュー画面から送られてきたmemoの内容を初期表示できるようにします。

struct MemoView: View {
    //前の画面から渡されるメモ
    @ObservedObject var memo: Memo
    //CoreDataを利用するための値
    @Environment(\.managedObjectContext) private var viewContext
    //変更したメモを保持する変数
    @State var memoTitle: String = ""
    @State var memoContents: String = ""
    
    var body: some View {
        VStack {
            TextField("題名", text: $memoTitle)
                .font(.title)
                .padding()
            TextEditor(text: $memoContents)
                .padding()
        }
        .onAppear {
            memoTitle = memo.title ?? ""
            memoContents = memo.contents ?? ""
        }
        .onDisappear {
            //更新したメモを保存する処理
            memo.title = memoTitle
            memo.contents = memoContents
            memo.upDateTime = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .short)

            do {
                try viewContext.save()
            } catch {
                print("保存に失敗しました: \(error)")
            }
        }
    }
}


最後にonDisappearで、viewが閉じたときに編集したメモを保存できるようにします。内容はほぼcerateNewMemoメソッドの作成時と同じなのでそちらを確認してください。

これで、メモ編集画面は完成です。

最後にメニュー画面からメモ編集画面に画面遷移の処理と、Memoの値を送れるようにします。ContentView.swiftを開いてください。処理を追加していきます。

bodyを以下のように変更します。

...
 var body: some View {
        NavigationView {
            VStack {
                List(fatchedMemoList) { memo in
                    if let title = memo.title,
                       let updateTime = memo.upDateTime {
                        VStack(alignment: .leading) {
                            NavigationLink(destination: MemoView(memo: memo)) {
                                VStack(alignment: .leading) {
                                    Text("タイトル: \(title)")
                                        .font(.headline)
                                    Text("更新日時: \(updateTime)")
                                        .font(.caption)
                                        .foregroundColor(.gray)
                                }
                                .padding()
                            }
                        }
                    }
                }
                .id(refreshID)
                .onAppear {
                    refreshID = UUID()
                }
                
                ...
              
            }
        }
        
    }
    ...
}

今回追加したのは以下です。

NavigationView

 var body: some View {
    NavigationView {
      ....
    }
 }

NavigationLink

...
 var body: some View {
        NavigationView {
            VStack {
                List(fatchedMemoList) { memo in
                    if let title = memo.title,
                       let updateTime = memo.upDateTime {
                        VStack(alignment: .leading) {
                            NavigationLink(destination: MemoView(memo: memo)) {
                              ...
                          
                            }
                        }
                    }
                }
                ...              
            }
        }        
    }
    ...
}

NavigationLInkとNavigationViewを利用することにより、メニュー画面からメモ画面に遷移できるようになります。

.id/.onAppear

...
 var body: some View {
        NavigationView {
            VStack {
                List(fatchedMemoList) { memo in
                    if let title = memo.title,
                       let updateTime = memo.upDateTime {
                        VStack(alignment: .leading) {
                            NavigationLink(destination: MemoView(memo: memo)) {
                                VStack(alignment: .leading) {
                                    Text("タイトル: \(title)")
                                        .font(.headline)
                                    Text("更新日時: \(updateTime)")
                                        .font(.caption)
                                        .foregroundColor(.gray)
                                }
                                .padding()
                            }
                        }
                    }
                }
                .id(refreshID)
                .onAppear {
                    refreshID = UUID()
                }
                
                ...
              
            }
        }
        
    }
    ...
}

.idと.onAppearを利用することによって、はじめに宣言したrefreshIDを用いて画面が表示されるたびにrefreshIDをチェックして再描画を行います。これにより、メモ編集画面から戻ってきたときにViewを更新します。

これで完成です!実行してみましょう!

以下のように動けば完成です!

実際の全体のソースはいかにあります!

GitHub - toma3113/Memo: CoreDataを利用したメモアプリ
CoreDataを利用したメモアプリ. Contribute to toma3113/Memo development by creating an account on GitHub.

最後に

最後まで読んでいただきありがとうございました。拙い部分もあったと思いますが皆様の役に少しでもなれたら幸いです。

今後も技術記事を投稿していきますので、よろしくおねがいいたします。

プロフィール

SIer勤めのエンジニア
アプリケーションエンジニアとして、WebやiOSなどのアプリ開発をメインにしてます

tomaをフォローする
SwiftSwiftUI