SwiftUI と CoreData を組み合わせたアプリの作り方を説明します。
Sponsor Link
環境&対象
以下の環境で動作確認を行なっています。
- macOS Catalina 10.15.7
- Xcode 12.2
- iOS 14.2
View/ViewModel の実装
以下の UI を想定しています。
- TODOItem をリスト表示する(表示は、タイトル、詳細)
- 右上の + を押したら、新しい要素が作成される
- 行を左スワイプ or Edit ボタン押下後削除ボタンを押すと、該当要素が削除される
View の実装から使われる ViewModel の実装
View は、ViewModel 経由で Model を操作するので、ViewModel に上記の操作に必要な メソッドが必要となります。
- 表示用の TODOItem の配列
- TODOItem を作成
- (指定した)TODOItem の削除
ViewModel のテスト追加
上記のメソッド向けのテストを作ります。
ViewModel テスト向けに、新しいファイルを追加しました。
ViewModel_Tests
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
// // ViewModel_Tests.swift // // Created by : Tomoaki Yagishita on 2020/12/12 // © 2020 SmallDeskSoftware // import XCTest @testable import MyTODO class ViewModel_Tests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } // (1) func test_initialize_atLaunch_shouldNotFail_ZeroRows() throws { let viewModel = MyTODOViewModel(true) // in-memory core-data XCTAssertNotNil(viewModel) XCTAssertEqual(viewModel.todoItems.count, 0) } // (2) func test_addNewItems_twoItems_shouldBeAppearedInTODOItems() throws { let viewModel = MyTODOViewModel(true) // in-memory core-data _ = viewModel.createTODOItem("Item1", "Item1Detail") XCTAssertEqual(viewModel.todoItems.count, 1) _ = viewModel.createTODOItem("Item2", "Item2Detail") XCTAssertEqual(viewModel.todoItems.count, 2) let item1 = try XCTUnwrap(viewModel.todoItems.first(where: {$0.title == "Item1"})) XCTAssertEqual(item1.title,"Item1") XCTAssertEqual(item1.detail,"Item1Detail") let item2 = try XCTUnwrap(viewModel.todoItems.first(where: {$0.title == "Item2"})) XCTAssertEqual(item2.title,"Item2") XCTAssertEqual(item2.detail,"Item2Detail") } // (3) func test_addAndRemove_oneItemInTheEnd_shouldBeAppearedInTODOItems() throws { let viewModel = MyTODOViewModel(true) let item1 = viewModel.createTODOItem("Item1", "Item1Detail") let _ = viewModel.createTODOItem("Item2", "Item2Detail") let item3 = viewModel.createTODOItem("Item3", "Item3Detail") XCTAssertEqual(viewModel.todoItems.count, 3) viewModel.deleteTODOItem(item1) XCTAssertEqual(viewModel.todoItems.count, 2) XCTAssertNil(viewModel.todoItems.first(where: {$0.title == "Item1"})) XCTAssertNotNil(viewModel.todoItems.first(where: {$0.title == "Item2"})) XCTAssertNotNil(viewModel.todoItems.first(where: {$0.title == "Item3"})) viewModel.deleteTODOItem(item3) XCTAssertEqual(viewModel.todoItems.count, 1) XCTAssertNil(viewModel.todoItems.first(where: {$0.title == "Item1"})) XCTAssertNotNil(viewModel.todoItems.first(where: {$0.title == "Item2"})) XCTAssertNil(viewModel.todoItems.first(where: {$0.title == "Item3"})) } } |
コード解説
- in-memory 設定で作成して、初期の todoItems 配列が 空であることをテストしています
- 要素を作成し、配列が適宜増えていること、追加された要素が指定した値のプロパティを持っていることをテストしています
- 追加後に削除し、残っているべき要素が残っていることを確認しています
なお、実装はまだしていないので、この時点でコンパイルはできません。
ViewModel の実装
View 用に、todoItems という配列を、@Published 指定で持ち、作成と削除のメソッドを追加しました。
todoItems は、何か操作したらアップデートするようにしています。(Observer や Publisher を使うことも考えましたが、最初はシンプルに作りました)
MyTODOViewModel code
作成・削除ともに、Model(MyTODOModel) のメソッドを呼び出し todoItems をアップデートするという実装になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// // MyTODOViewModel.swift // // Created by : Tomoaki Yagishita on 2020/12/10 // © 2020 SmallDeskSoftware // import Foundation import SwiftUI import CoreData import os import Combine class MyTODOViewModel : ObservableObject { static let logger = Logger(subsystem: "com.smalldesksoftware.MyTODO.MyTODOViewModel", category: "MyTODOViewModel") var todoItemStore: TODOItemStore @Published var todoItems:[TODOItem] = [] init(_ inMemory: Bool) { self.todoItemStore = TODOItemStore(inMemory) todoItems = todoItemStore.items } func createTODOItem(_ title: String, _ detail: String = "") -> TODOItem{ let newItem = todoItemStore.createTODOItem(title, detail) todoItems = todoItemStore.items return newItem } func deleteTODOItem(_ item: TODOItem) { todoItemStore.removeTODOItem(item) todoItems = todoItemStore.items } } |
テスト
上記の実装で、先に定義した テストをすべてパスすることが確認できます。
View で使われる ViewModel のメソッドが準備できたので、次こそ、View を実装します。
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link