【Swift】Core Dataの使い方。フェッチリクエストで取得するデータを絞り込む(Swift 2.1、XCode 7.2)
NSFetchRequestとは
本記事では、Core Dataの機能のNSFetchRequest(以下、フェッチリクエスト)について説明する。
フェッチリクエストとは、Core Dataを使って保存したデータをメモリ上に展開することを要求する機能である。
過去の記事でも何回もフェッチリクエストをしてきた。そのときは特定のエンティテイのデータを丸ごと取得したが、フェッチリクエストに絞り込み条件を付けると、条件に適合するデータのみを取得できる。
フェッチリクエストに絞り込み条件を付けるには、以下のコードのようにフェッチリクエストのpredicateプロパティに値を設定する。以下コードの「name = '広島’」は、属性nameの値が広島であるデータのみを取得するということだ。
1 2 3 4 5 6 7 8 9 |
//フェッチリクエストのインスタンスを生成する。 let fetchRequest = NSFetchRequest(entityName: "Player") //フェッチリクエストに条件を追加する。 fetchRequest.predicate = NSPredicate(format: "name = ’広島’") //フェッチリクエストを実行してをオブジェクトを取得する。 let players = try managedContext.executeFetchRequest(fetchRequest) as! [Player] |
絞り込み条件を試す
絞り込み条件を付けたフェッチリクエストを試してみよう。
検証に使ったXCodeのプロジェクトをGitHubに置いたので試してみる人はご利用されたし。
⇒「テスト用プロジェクト」
Search Barに入力した文字列で保存データを検索し、検索結果をテーブルビューに表示するものを実装しておいた。何も入力しなかった場合は全データが表示される。
以下はViewControllerのコード。フェッチリクエストの条件式(青色網掛け箇所)を変更しながら検証を行った。
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
// // ViewController.swift // import UIKit import CoreData class ViewController: UIViewController, UITableViewDataSource, UISearchBarDelegate{ @IBOutlet weak var testTableView: UITableView! @IBOutlet weak var testSearchBar: UISearchBar! //管理オブジェクトコンテキスト var managedContext:NSManagedObjectContext! //検証用データ let dataList = [["月刊コロコロコミック", "小学館",390,20.2,"2016/5/16 10:30:00"], ["コロコロイチバン!","小学館",540,25.3,"2016/4/23 09:00:00"], ["最強ジャンプ","集英社",420,13.2,"2016/6/9 7:00:00"], ["Vジャンプ","集英社",300,13.4,"2016/1/3 12:00:00"], ["週刊少年サンデー","小学館",280,16.7,"2016/8/23 11:00:00"], ["週刊少年マガジン","講談社",250,40.5,"2016/10/10 7:30:00"], ["週刊少年ジャンプ","集英社",300,60.3,"2016/9/9 10:00:00"], ["週刊少年チャンピオン","秋田書店",280,23.5,"2015/5/1 11:30:00"], ["月刊少年マガジン","講談社",320,45.1,"2016/7/2 13:30:00"], ["月刊少年チャンピオン","秋田書店",220,12.6,"2015/11/10 7:30:00"], ["月刊少年ガンガン","スクウェア",240,33.5,"2016/2/2 7:30:00"], ["月刊少年エース","KADOKAWA", 330,9.8,"2016/7/1 8:30:00"], ["月刊少年シリウス","講談社",350,20.2,"2016/11/26 15:00:00"], ["週刊ヤングジャンプ","集英社",300,33.3,"2014/3/16 8:30:00"], ["ビッグコミックスピリッツ","小学館",240,11.2,"2014/9/29 11:30:00"], ["週刊ヤングマガジン","講談社",310,26.7,"2016/8/8 10:00:00"]] //検索結果配列 var searchResult = [Book]() //本を保存するメソッド func insertBook(){ do { //本のオブジェクトを管理オブジェクトコンテキストに格納する。 for data in dataList { let book = NSEntityDescription.insertNewObjectForEntityForName("Book", inManagedObjectContext: managedContext) as! Book book.name = data[0] as? String //雑誌名 book.publisher = data[1] as? String //出版社 book.price = data[2] as? Int //価格 book.approvalRate = data[3] as? Float //支持率 let dateFormatter = NSDateFormatter() dateFormatter.dateFormat = "yyyy/M/d H:mm:ss" book.releaseDate = dateFormatter.dateFromString(data[4] as! String)! //発売日 } //管理オブジェクトコンテキストの中身を保存する。 try managedContext.save() } catch { print(error) } } //最初からあるメソッド override func viewDidLoad() { super.viewDidLoad() //デリゲート先を自分に設定する。 testSearchBar.delegate = self //何も入力されていなくてもReturnキーを押せるようにする。 testSearchBar.enablesReturnKeyAutomatically = false //管理オブジェクトコンテキストを取得する。 let applicationDelegate = UIApplication.sharedApplication().delegate as! AppDelegate managedContext = applicationDelegate.managedObjectContext //コンフリクトが発生した場合はマージする。 managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy //検証用データを格納する。 insertBook() } //データを返すメソッド func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell { //セルを取得する。 let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath:indexPath) as UITableViewCell //セルのラベルに本のタイトルを設定する。 let book = searchResult[indexPath.row] cell.textLabel?.text = "\(book.name!) \(book.publisher!) \(book.price!)円" return cell } //データの個数を返すメソッド func tableView(tableView:UITableView, numberOfRowsInSection section:Int) -> Int { return searchResult.count } //検索ボタン押下時の呼び出しメソッド func searchBarSearchButtonClicked(searchBar: UISearchBar) { //キーボードをしまう。 testSearchBar.endEditing(true) //検索結果配列を空にする。 searchResult.removeAll() //フェッチリクエストのインスタンスを生成する。 let fetchRequest = NSFetchRequest(entityName: "Book") if(testSearchBar.text != "") { //属性nameが検索文字列と一致するデータをフェッチ対象にする。 fetchRequest.predicate = NSPredicate(format:"name = %@", testSearchBar.text!) } do { //フェッチリクエストを実行する。 searchResult = try managedContext.executeFetchRequest(fetchRequest) as! [Book] } catch { print(error) } //テーブルを再読み込みする。 testTableView.reloadData() } } |
以下にフェッチ要求のフォーマットに指定できるものをまとめる。ただし、フェッチ要求の仕様はとても広いので使われそうなものに絞って掲載している。
詳細を知りたいかたは以下の公式ドキュメントを参照されたし。
⇒「Predicate Programing Guide」
⇒「Predicate Programing Guide(日本語)」
⇒「Core Data Programing Guide(日本語)」
プレースホルダー
プレースホルダーとは、プログラミング実行時に文字列を完成させるための、文字列中の仮置きの値ことである。例えば、「こんにちは、◯さん」の◯をプログラミング実行時に埋めるイメージ。
ホルダー | 説明 |
---|---|
%@ | 文字列、日時などを埋め込む NSPredicate(format:"name = %@", "テスト") |
%K | プロパティを埋め込む NSPredicate(format:"%K => 280", "price") |
%D、%d、%i | 整数を埋め込む NSPredicate(format:"price => %D", 280) |
%f | 不動小数を埋め込む NSPredicate(format:"approvalRate => %f", 25.0) |
数値、日付を比較する
いつも使っている比較演算子をフォーマットでも使用できる。
演算子 | 説明 |
---|---|
=、== | 右と等しい NSPredicate(format:"price = %D", 280 |
!=、<> | 右と等しくない NSPredicate(format:"price != %D", 280 |
< | 右より小さい NSPredicate(format:"price < %D", 200) |
> | 右より大きい NSPredicate(format:"price > %D", 200) |
<=、=< | 右以下 NSPredicate(format:"price <= %D", 300) |
>=、=> | 右以上 NSPredicate(format:"price >= %D", 300) |
BETWEEN | 右の2つの値の間 NSPredicate(format:"price BETWEEN {%D, %D}", 200, 300) |
IN | 右のどれかと一致する NSPredicate(format:"price IN {%D, %D, %D}", 280, 300, 420 |
数字に限らず日付も比較することができる。やってみよう。
ViewController.swiftの本を検索する箇所を以下のコードに変更する。
1 2 3 4 5 6 7 8 9 10 |
//入力された文字列からNSDateインスタンスを生成する。 let dateFormatter = NSDateFormatter() dateFormatter.dateFormat = "yyyy/M/d h:mm:ss" let searchDate = dateFormatter.dateFromString(testSearchBar.text!) //本を検索する。 let fetchRequest = NSFetchRequest(entityName: "Book") fetchRequest.predicate = NSPredicate(format:"releaseDate => %@", searchDate!) searchResult = try managedContext.executeFetchRequest(fetchRequest) as! [Book] |
以下は実際のプレイ動画。指定した日付以降に出版された雑誌が検索された。
文字列を比較する
演算子 | 説明 |
---|---|
=、== | 右と等しい NSPredicate(format:"name == %@","週刊少年ジャンプ") |
!=、<> | 右と等しくない NSPredicate(format:"name != %@","週刊少年ジャンプ") |
CONTAINS | 右の文字列が含まれる NSPredicate(format:"name CONTAINS %@","少年") |
LIKE | 右の文字列パターンが含まれる NSPredicate(format:"name LIKE %@","?刊少年*") |
BEGINSWITH | 右の文字列で始まる NSPredicate(format:"name BEGINSWITH %@","週刊") |
ENDSWITH | 右の文字列で終わる NSPredicate(format:"name ENDSWITH %@","ジャンプ") |
IN | 右のリストのどれかと一致する NSPredicate(format:"name IN {%@, %@}" , "週刊少年ジャンプ", "最強ジャンプ") |
MATCHES | 右の正規表現に当てはまる NSPredicate(format:"name MATCHES %@", "[週刊|月刊].*") |
条件と一致しないものを取得するには、条件式の頭に「NOT」か「!(ビックリマーク)」をつける。
1 2 3 |
//200円〜300円以外の本を検索する。 fetchRequest.predicate = NSPredicate(format:"NOT price BETWEEN {%D, %D}" , 200, 300) |
文字列の比較ではcまたはdのオプションをつけれる。
cは「大文字小文字の区別をしない」、dは「発音記号の有無は同一の文字として扱う(例、ä, ö, ü)」
1 2 |
fetchRequest.predicate = NSPredicate(format:"name BEGINSWITH[cd] %@", "vジャンプ") |
複数の条件を組み合わせる
「かつ」、「または」を使って条件を組み合わせられる。
演算子 | 説明 |
---|---|
AND、&& | 左の条件式 かつ 右の条件式 NSPredicate(format:"name CONTAINS %@ AND price > %d", "週刊", 280) |
OR、|| | 左の条件式 または 右の条件式 NSPredicate(format:"name CONTAINS %@ || price > %d", "週刊", 400) |
条件を増やして複雑になってきたら括弧を使って条件の範囲を分かりやすくすべし。
1 2 |
fetchRequest.predicate = NSPredicate(format:"(name CONTAINS %@ && publisher CONTAINS %@) || (price > %d)", "週刊", "講談社", 400) |
集計結果を利用する
最大値、最小値、平均などの集計結果を条件に利用できる。
演算子 | true条件 | 使用例 |
---|---|---|
@avg | 平均 | NSPredicate(format:"price > @avg.price") |
@max | 最大値 | NSPredicate(format:"price = @max.price") |
@min | 最小値 | NSPredicate(format:"price = @min.price") |
@sum | 合計 | |
@count | 件数 |