View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

その他のビデオ

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • AppデータのSpotlightでの表示

    Core Dataでわずか2行のコードによってAppのデータをSpotlightで表示する方法を紹介します。そのデータをSpotlight検索で検出可能にする方法や、そのデータがデバイス上でユーザにどのように表示されるのかをカスタマイズする方法を確認します。最後に、Spotlightでインデックス化されたデータを使って、App内でフルテキスト検索を実装する方法も紹介します。

    リソース

    • Showcase App Data in Spotlight
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC21

    • SwiftとSwiftUIへのCore Dataの並行処理の導入
  • このビデオを検索

    ♪ (AppデータのSpotlightでの表示) こんにちは 「AppデータのSpotlightでの表示」へ ようこそDavid Stitesです Core Dataチームのエンジニアです このセッションでは NSCoreDataCoreSpotlightDelegate を使用してSpotlightインデックスをAppに 追加する方法をお見せできることを 楽しみにしています このセッションのテーマは NSCoreDataCoreSpotlightDelegateオブジェクトと その使用目的 簡単な実装のセットアップ カスタマイズ方法について説明することです 最後に全文検索を追加してコードを検証します まず Core DataとSpotlightについてです ユーザーはApp内に多くの重要で 素晴らしいコンテンツを作成し 保存するようになるでしょう Appの利用が増え データセットのサイズが大きくなればなるほど App内でもApp外でも 例えばSpotlight検索で そのデータを素早く見つけたいと 考えるようになるだろう App内のデータがSpotlightに表示されたら 素晴らしいと思いませんか? Core Dataが役に立つのはここです NSCoreDataCore SpotlightDelegate オブジェクトは面倒な作業をすべて実行し Appによって提供されるコンテンツを迅速かつ 効率的にインデックス化する一連のAPIを提供します オンにするだけです! インデックス化された検索結果は App外部のSpotlight Search ユーザーインターフェイスにも表示されます Spotlightデレゲートは グラフの管理オブジェクトに対する 変更を自動的に処理しそれに応じて Spotlightインデックスを更新します さらにプライベートなデバイス上 のみのインデックスと対話する ための堅牢なインデックス管理機能を提供し インデックス結果を好みに合わせて調整できます 実際 永続ストアにあるコンテンツはすべて インデックス付けの対象となります Spotlightデレゲートを使用する理由は3つあります (1) Spotlightデリゲートは Core Spotlight APIと同等の機能を維持します (2) これにより必要な 実装コードが大量に削除されます (3) 優れた機能セットが追加されます この点についてこのセッションの後半で説明します 前のポイントを説明するために Core Spotlight APIを使用した非常に単純な実装 を例として挙げます検索インデックスに項目を 追加するだけでそれを...これに縮小します 2列です! シンプルで 読みやすくメンテナンスも簡単です コードを減らしたくない人なんているだろうか? すぐに設定して実行する方法を見てみましょう この簡単な例ではインデックスを 付ける対象の決定とデレゲートの 作成について説明します このセッションでは 自分で書いたTagsという シンプルな写真タグ付けAppについて説明します このサンプルAppには本日説明したAPIの多くが 組み込まれています Spotlightサポートを追加する前に 全タグと写真データがタグ内に トラップされていることを確認できます 「Natural Bridges State Park」の Spotlight検索結果がないからです それを変えましょう! NSCo reDataCoreSpotlightDelegate を使用した実装の最初のステップは Spotlightでインデックス を作成する対象を決定することです Spotlightで何をインデックス化するかはユーザ次第 TagsでエンティティPhotoのuserSpecifiedName 属性とエンティティTagのname属性に インデックスを付けることにしました インデックスを作成するための モデルを準備するために プロジェクトのCore DataモデルをXcodeで開き インデックスを作成する各属性を選択し 属性インスペクタのIndex in Spotlight チェックボックスをオンにしました Core Data Spotlight表示名を設定する 必要があるため Core Dataモデルエディタで作業を続けます Core Data Spotlightの表示名はNSTemptionです インデックス作成時に このエクスプレッションはSpotlight によってインデックス付けされた プロパティを持つ各管理オブジェクトで評価され 結果が保存されます その後Spotlight検索ユーザーインターフェイスが 表示されるとこれらの保存された結果が検索結果の 「表示名」として使用されます NSExpressionとは何ですか? エクスプレッションはキーパス この場合はTag.nameを評価する単純なものです しかしこのオブジェクトには キーパスの評価以外にもで きることがたくさんあります この例では計算を行っています さらに 数値のセットの標準偏差を計算するなど エクスプレッションを 複雑にすることもできます TagsではSpotlight Display Nameは エンティティ「Photo」 ではuserSpecifiedName エンティティ「Tag」 ではNameに設定されます モデルのインデックス作成の準備ができたので 次はSpotlightデレゲートを作成しましょう iOS 15とmacOS Montereyで始まった イニシャライザーforStoreWith: model は非推奨になりました Spotlightデレゲートを初期化する新しい方法は forStoreWith: coordinator:を使用することです 新しい指定イニシャライザを採用することで コーディネータにストアを追加する前に Spotlightデレゲートのインスタンスを ストアオプションに追加する必要がなくなりました ただし Spotlightデレゲートの作業を開始するには startSpotlightIndexingを呼び出す必要がある NSCoreDataCoreSpotlightDelegate を使用する上でのいくつかの要件を 紹介したいと思います インデックスを作成するストアの ストアタイプはSQLiteで 永続的な履歴追跡が有効に なっている必要があります そしてそれで 終わってしまいます これで完了です! 他に何もする必要はなく Spotlightでデータのインデックスが作成されます これまでTags AppにSpotlightのインデックス を追加するのがいかに簡単かを実演してきました 基本的なことを説明したので この実装を少しカスタマイズしましょう 実装をカスタマイズする最初の方法は メインとインデックス名を定義することです まず始めにNSCoreDataCoreSpotlightDelegate のサブクラスであるTagsSpotlightDelegate というクラスを定義します では domainNameとindexName を実装でオーバーライドしてみます これらのセレクタをオーバーライド することでSpotlightに インデックス付きデータの保存場所が指示され 特に複数のインデックスがある 場合は後で識別しやすくなります domainIdentifierをオーバーライドしない場合 デフォルトのドメイン識別子はストア識別子です indexNameをオーバーライドしない場合 デフォルトのインデックス名はnilです Spotlightデレゲートをカスタマイズする次の手順は 属性セットを定義することです このセッションのセットアップ部分では NSCoreDataCore SpotlightDelegate オブジェクトはSpotlight Indexチェックボックスをオン にするだけで属性セット 「Spotlightに返される」 を定義しました 次にインデックス作成に使用する属性を 指定する方法を具体的に説明します インデックスを作成する属性を指定すると インデックスの対象と検索方法を より明確にコントロールできます これを行うには CSSearchableItemAttributeSetを使用 属性セットには指定した管理対象オブジェクトが 検索結果として表示されたときに そのオブジェクトについて表示する メタデータを指定できる 多数の定義済みプロパティが含まれています 選択する属性はドメインによって完全に異なります CSSearchableItemAttributeSet に用意されている定義済みの プロパティを使うこともできますし 独自のプロパティを定義することもできます Tags Appは定義済みのプロパティ キーワードdisplayName とthumbnailDataを使用します 属性セット内のプロパティへの 同時アクセスの動作は 定義されていない為一度に1つのスレッドの 属性セットのみを変更する必要があることに 注意してください TagsSpotlightDelegateクラスに戻ります attributeSet (for object:)を オーバーライドすることによって この動作を確認してみましょう オーバーライド実装では オブジェクトがPhotoタイプの オブジェクトかどうかを判断することから始めます 次にコンテンツタイプが.imageの attributeSetを初期化する 次にPhotoオブジェクトの適切な属性を使用して 属性セットのプロパティ識別子displayName およびthumbnailDataを設定します ここでPhotoオブジェクト タグセットのタグを属性セットの keywords配列に追加します ここでモデルがリレーションシップ をインデックスする場合は attributeSet (for object:) をオーバーライドしてその リレーションシップについて 特に何がインデックスされるかを定義する 必要があることに注意してください 最後に属性セットを戻します モデルはTagオブジェクトの インデックスも作成するため コードはTagのケースを処理する必要があります そのためにはcontentType .text を持つ属性セットを作成し 表示名をタグの名前に設定して 属性セットを戻します 最後の手順として前の手順で モデルエディタで設定した Core Data Spotlight表示名を削除します さらにインデックス 作成を開始および停止するための イベントループを定義します 前述のようにSpotlightデレゲートを設定すると startSpotlightIndexingがSpotlight デレゲートの作成直後に呼び出されます NSCoreDataCoreSpotlightDelegate がインデックス作成作業を実行する タイミングを正確にコントロールできるように stopSpotlightIndexing もフレームワークに追加されました これらの2つのセレクタを組み合わせて使用すると AppがCPUやディスクの負荷の高い処理を 実行している場合など必要に応じて インデックス作成処理 を開始および停止できます ではインデックスの更新が完了したときに通知を 受け取る機能を追加しましょう Spotlightでインデックス付けされた エンティティが変更されると そのインデックスは非同期に更新されます iOS 15とmacOS Montereyでは Core Dataフレームワーク がインデックス更新通知を追加した インデックスの更新が完了した時に 通知を受け取るには Spotlightデリゲートでポストされる NSCoreDataCoreSpotlightDelegate .indexDidUpdateNotificationを サブスクライブします これらの通知はsave:on NSManagedObjectContext の呼び出しを処理した後 またはバッチ処理が完了した後にポストされます 実際にやってみましょう まずインデックス作成が有効に なっているかどうかを確認します 存在する場合は登録します indexDidUpdateNotificationに 次にハンドラで通知を検査します この通知にはリモート変更通知と同様に 2つのキーと値のペアを含む userInfoディクショナリがあります1つは Spotlightデリゲートがインデックスを更新した ストアNSString UUIDですもう1つは Spotlightデリゲートがインデックスを更新した ストアの永続履歴トークンです これらの2つのキーを使用して 目的のストアが最新の永続履歴トークンまで インデックス化されているかどうかを判断できます インデックス作成が有効に なっていない場合はオブザーバとして 通知から自分を削除できます 今年まではAppがインデックス 付けしたデータを削除するには Core Spotlight APIを実装して インデックスエントリを削除するか Core Dataのクライアント グラフ全体を削除するしかなかった 重要なのはiOS 15とmacOS Montereyで 登場したCore Dataが開発者にクライアントグラフを 削除せずにSpotlightインデックスを 管理する新しい方法を提供したことだ まずコードはインデックス付けを停止します 次にdeleteSpotlightIndexを呼び出します 最後に完了ハンドラで発生したエラーを処理します このメソッドを呼び出すとCore Dataや Core Spotlightなどの下位層の依存関係から エラーが返される可能性があるため これらを処理する準備をしておく必要があります Spotlight delegateの実装をカスタマイズする 方法を説明したので Core Spotlight APIを使用してTags Appに 全文検索を追加し設定を検証してみましょう 結果は以前にインデックス化されたものになります まずUISearchResultsUpdatingプロトコルと updateSearchResults(Controller用) 関数を採用する PhotosViewControllerの拡張を定義します Tagsユーザーインタフェースには UISarchControllerがあります その検索コントローラーの 検索バーからユーザー入力を取得します ユーザー入力が空の場合は データプロバイダからすべてのイメージを取得し 検索クエリがないので コレクションビューをリロードします 次に検索クエリがある場合を扱います まずユーザー入力文字列をエスケープして サニタイズします 次にユーザーのサニタイズされた 入力文字列を使用してクエリ文字列を定義します クエリ文字列はCSSearchableItemAttributeSet オブジェクトのプロパティに 関連付けられた値を操作します この場合コードは前のステップで設定された Keywords属性で動作します 検索クエリでは 修飾子c dおよびwが 使用されています cは大文字と小文字を区別しません dは字上符の無視を表します wは単語ベースの検索です ここで作成したフォーマット済みのクエリ文字列と CSSearchableItemAttributeSet で定義されたプロパティに対応する 属性名の配列を指定して CSSearchQueryオブジェクトを作成します この検索クエリオブジェクトは SpotlightデリゲートAPIを使用して 以前にインデックスを作成したAppコンテンツを 検索するときに適用する基準を管理します foundItemsHandlerをその後設定します このハンドラは前に定義した検索クエリに 一致する項目を使用して繰り返し呼び出されます 一度だけ呼び出される問い合わせの completionHandler 内でエラーをチェックし 場合によっては何らかのエラー処理を実行します エラーがない場合は ブロックをメインキューにディスパッチし Spotlightが検出した 項目をデータプロバイダを使用して ユーザーインタフェースにロードします 最後に最重要なこととしてクエリを 開始することを忘れないでください Tags AppにSpotlightデリゲートが コンテンツをインデックスするようになったので データはApp内から解放されました! 以前に追加したタグをSpotlightで検索すると 2つの結果が返ってきます タグ名そのものとキーワード 「Natural Bridges State Park」 でタグ付けした特定の写真です まとめるとNSCoreDataCoreSpotlightDelegate とは何か そしてユーザーがSpotlight検索で あなたのApp内とApp外のコンテンツを見つけるのに どのように役立つか また Spotlightデリゲートを 素早く簡単にセットアップして 大きなコードの負担なしに インデックス作成を開始することと このリリースで利用可能な いくつかの新しいAPIを使用して Spotlightデリゲートをカスタマイズ する方法について学びました この情報が有用であることを確認し プロジェクトで NSCoreDataCoreSpotlightDelegate を採用してユーザーがコンテンツを 検索できるようにすることを検討してください 有意義なWWDCを! ♪

    • 2:40 - Creating a NSCoreDataCoreSpotlightDelegate

      let spotlightDelegate = NSCoreDataCoreSpotlightDelegate(forStoreWith: description,
                                                              coordinator: coordinator)
      spotlightDelegate.startSpotlightIndexing()
    • 5:24 - Adding a NSCoreDataCoreSpotlightDelegate to a CoreDataStack

      import Foundation
      import CoreData
      
      class CoreDataStack {
          private (set) var spotlightIndexer: TagsSpotlightDelegate?
          
          lazy var persistentContainer: NSPersistentContainer = {
              let container = NSPersistentContainer(name: "Tags")
      
              guard let description = container.persistentStoreDescriptions.first else {
                  fatalError("###\(#function): Failed to retrieve a persistent store description.")
              }
      
              description.type = NSSQLiteStoreType
              description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
              description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
                      
              container.loadPersistentStores(completionHandler: { (_, error) in
                  guard let error = error as NSError? else { return }
                  fatalError("###\(#function): Failed to load persistent stores:\(error)")
              })
              
              spotlightIndexer = TagsSpotlightDelegate(forStoreWith: description,
                                                       coordinator: container.persistentStoreCoordinator)
      
              container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
              
              container.viewContext.automaticallyMergesChangesFromParent = true
              do {
                  try container.viewContext.setQueryGenerationFrom(.current)
              } catch {
                  fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
              }
              
              return container
          }()
      }
    • 6:24 - Creating TagsSpotlightDelegate

      class TagsSpotlightDelegate: NSCoreDataCoreSpotlightDelegate {
          override func domainIdentifier() -> String {
              return "com.example.apple-samplecode.tags"
          }
      
          override func indexName() -> String? {
              return "tags-index"
          }
        
          override func attributeSet(for object: NSManagedObject) -> CSSearchableItemAttributeSet? {
              if let photo = object as? Photo {
                  let attributeSet = CSSearchableItemAttributeSet(contentType: .image)
                  attributeSet.identifier = photo.uniqueName
                  attributeSet.displayName = photo.userSpecifiedName
                  attributeSet.thumbnailData = photo.thumbnail?.data
                  for case let tag as Tag in photo.tags ?? [] {
                      if let name = tag.name {
                          if attributeSet.keywords != nil {
                              attributeSet.keywords?.append(name)
                          } else {
                              attributeSet.keywords = [name]
                          }
                      }
                  }
                  return attributeSet
              } else if let object as? Tag {
                  let attributeSet = CSSearchableItemAttributeSet(contentType: .text)
                  attributeSet.displayName = tag.name
                  return attributeSet
              }
              return nil
          }
      }
    • 9:51 - Customizing PhotosViewController with Spotlight delegate functionality

      class PhotosViewController: UICollectionViewController {
          @IBOutlet var generateDefaultPhotosItem: UIBarButtonItem!
          @IBOutlet var deleteSpotlightIndexItem: UIBarButtonItem!
          @IBOutlet var startStopIndexingItem: UIBarButtonItem!
          
          private var isTagging = false
          private var spotlightFoundItems = [CSSearchableItem]()
          private static let defaultSectionNumber = 0
          private var searchQuery: CSSearchQuery?
          var spotlightUpdateObserver: NSObjectProtocol?
      
          private lazy var spotlightIndexer: TagsSpotlightDelegate = {
              let appDelegate = UIApplication.shared.delegate as? AppDelegate
              return appDelegate!.coreDataStack.spotlightIndexer!
          }()
        
          override func viewDidLoad() {
              super.viewDidLoad()
              
              // ...
      
              toggleSpotlightIndexing(enabled: true)
          }
        
          @IBAction func deleteSpotlightIndex(_ sender: Any) {
              toggleSpotlightIndexing(enabled: false)
      
              spotlightIndexer.deleteSpotlightIndex(completionHandler: { (error) in
                  if let err = error {
                      print("Encountered error while deleting Spotlight index data, \(err.localizedDescription)")
                  } else {
                      print("Finished deleting Spotlight index data.")
                  }
              })
          }
      
          @IBAction func toggleSpotlightIndexingEnabled(_ sender: Any) {
              if spotlightIndexer.isIndexingEnabled == true {
                  toggleSpotlightIndexing(enabled: false)
              } else {
                  toggleSpotlightIndexing(enabled: true)
              }
          }
      
          private func toggleSpotlightIndexing(enabled: Bool) {
              if enabled {
                  spotlightIndexer.startSpotlightIndexing()
                  startStopIndexingItem.image = UIImage(systemName: "pause")
              } else {
                  spotlightIndexer.stopSpotlightIndexing()
                  startStopIndexingItem.image = UIImage(systemName: "play")
              }
      
              let center = NotificationCenter.default
              if spotlightIndexer.isIndexingEnabled && spotlightUpdateObserver == nil {
                  let queue = OperationQueue.main
                  spotlightUpdateObserver = center.addObserver(forName: NSCoreDataCoreSpotlightDelegate.indexDidUpdateNotification,
                                                               object: nil,
                                                               queue: queue) { (notification) in
                      let userInfo = notification.userInfo
                      let storeID = userInfo?[NSStoreUUIDKey] as? String
                      let token = userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken
                      if let storeID = storeID, let token = token {
                          print("Store with identifier \(storeID) has completed ",
                                "indexing and has processed history token up through \(String(describing: token)).")
                      }
                  }
              } else {
                  if spotlightUpdateObserver == nil {
                      return
                  }
                  center.removeObserver(spotlightUpdateObserver as Any)
              }
          }
      }
    • 13:13 - Adding full-text search to PhotosViewController

      extension PhotosViewController: UISearchResultsUpdating {
          func updateSearchResults(for searchController: UISearchController) {
              guard let userInput = searchController.searchBar.text, !userInput.isEmpty else {
                  dataProvider.performFetch(predicate: nil)
                  reloadCollectionView()
                  return
              }
              
              let escapedString = userInput.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"")
              let queryString = "(keywords == \"" + escapedString + "*\"cwdt)"
              
              searchQuery = CSSearchQuery(queryString: queryString, attributes: ["displayName", "keywords"])
      
              // Set a handler for results. This will be a called 0 or more times.
              searchQuery?.foundItemsHandler = { items in
                  DispatchQueue.main.async {
                      self.spotlightFoundItems += items
                  }
              }
              
              // Set a completion handler. This will be called once.
              searchQuery?.completionHandler = { error in
                  guard error == nil else {
                      print("CSSearchQuery completed with error: \(error!).")
                      return
                  }
      
                  DispatchQueue.main.async {
                      self.dataProvider.performFetch(searchableItems: self.spotlightFoundItems)
                      self.reloadCollectionView()
                      self.spotlightFoundItems.removeAll()
                  }
              }
      
              // Start the query.
              searchQuery?.start()
          }
      }

Developer Footer

  • ビデオ
  • WWDC21
  • AppデータのSpotlightでの表示
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン