Skip to content

Commit b254380

Browse files
authored
Show goals in Spotlight (#472)
Uses the integration between CoreData and Spotlight to make goal information available in spotlight search. Testing: Ran on device and saw the entries appear in spotlight Verified clicking on items navigates to the app
1 parent b5a4e72 commit b254380

File tree

6 files changed

+62
-10
lines changed

6 files changed

+62
-10
lines changed

BeeKit/Model/BeeminderModel.xcdatamodeld/BeeminderModel.xcdatamodel/contents

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<attribute name="value" attributeType="Decimal" defaultValueString="0.0"/>
1010
<relationship name="goal" maxCount="1" deletionRule="Nullify" destinationEntity="Goal" inverseName="data" inverseEntity="Goal"/>
1111
</entity>
12-
<entity name="Goal" representedClassName="Goal" syncable="YES">
12+
<entity name="Goal" representedClassName="Goal" syncable="YES" coreSpotlightDisplayNameExpression="slug">
1313
<attribute name="alertStart" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
1414
<attribute name="autodata" optional="YES" attributeType="String"/>
1515
<attribute name="deadline" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
@@ -25,9 +25,9 @@
2525
<attribute name="queued" attributeType="Boolean" usesScalarValueType="YES"/>
2626
<attribute name="safeBuf" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
2727
<attribute name="safeSum" attributeType="String"/>
28-
<attribute name="slug" attributeType="String"/>
28+
<attribute name="slug" attributeType="String" spotlightIndexingEnabled="YES"/>
2929
<attribute name="thumbUrl" attributeType="String"/>
30-
<attribute name="title" attributeType="String"/>
30+
<attribute name="title" attributeType="String" spotlightIndexingEnabled="YES"/>
3131
<attribute name="todayta" attributeType="Boolean" usesScalarValueType="YES"/>
3232
<attribute name="urgencyKey" attributeType="String"/>
3333
<attribute name="useDefaults" attributeType="Boolean" usesScalarValueType="YES"/>

BeeKit/Model/BeeminderPersistantContainer.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import OSLog
44

55
public class BeeminderPersistentContainer: NSPersistentContainer {
66
private static let logger = Logger(subsystem: "com.beeminder.beeminder", category: "BeeminderPersistentContainer")
7+
private var spotlightIndexer: NSCoreDataCoreSpotlightDelegate?
8+
79

810
override open class func defaultDirectoryURL() -> URL {
911
let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.beeminder.beeminder")
@@ -12,6 +14,14 @@ public class BeeminderPersistentContainer: NSPersistentContainer {
1214

1315
static func create() -> BeeminderPersistentContainer {
1416
let container = BeeminderPersistentContainer(name: "BeeminderModel")
17+
18+
guard let description = container.persistentStoreDescriptions.first else {
19+
fatalError("Failed to retrieve a persistent store description.")
20+
}
21+
// Spotlight indexing requires sqlite and history tracking
22+
description.type = NSSQLiteStoreType
23+
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
24+
1525
container.loadPersistentStores { description, error in
1626
if let error = error {
1727
logger.error("Unable to load persistent stores: \(error, privacy: .public)")
@@ -30,6 +40,10 @@ public class BeeminderPersistentContainer: NSPersistentContainer {
3040
}
3141
}
3242
}
43+
44+
container.spotlightIndexer = BeeminderSpotlightDelegate(forStoreWith: description, coordinator: container.persistentStoreCoordinator)
45+
container.spotlightIndexer?.startSpotlightIndexing()
46+
3347
return container
3448
}
3549

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Foundation
2+
import CoreData
3+
import CoreSpotlight
4+
5+
class BeeminderSpotlightDelegate: NSCoreDataCoreSpotlightDelegate {
6+
7+
override func attributeSet(for object: NSManagedObject) -> CSSearchableItemAttributeSet? {
8+
if let goal = object as? Goal {
9+
let attributeSet = CSSearchableItemAttributeSet(contentType: .content)
10+
attributeSet.identifier = goal.slug
11+
attributeSet.displayName = goal.slug
12+
attributeSet.contentDescription = goal.title
13+
return attributeSet
14+
}
15+
return nil
16+
}
17+
}

BeeSwift.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@
129129
E4B0A33228C194CA00055EA7 /* AddDataIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = E540953B260FB6A100C30784 /* AddDataIntents.intentdefinition */; };
130130
E4B6FEC62A776A2900690376 /* GoalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B6FEC52A776A2900690376 /* GoalTests.swift */; };
131131
E4D5BD5D2C5DFBA30007B0BE /* HealthKitMetricMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D5BD5C2C5DFBA30007B0BE /* HealthKitMetricMonitor.swift */; };
132+
E4D5BD5F2C6709060007B0BE /* BeeminderSpotlightDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D5BD5E2C6709060007B0BE /* BeeminderSpotlightDelegate.swift */; };
132133
E4E43D8829F39CE800697116 /* LogsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E43D8729F39CE800697116 /* LogsViewController.swift */; };
133134
E4E63C6D2C3F9083005E00DA /* DataPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E63C6C2C3F9083005E00DA /* DataPointManager.swift */; };
134135
E4E63C732C5DDE98005E00DA /* GoalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E63C722C5DDE98005E00DA /* GoalExtensions.swift */; };
@@ -346,6 +347,7 @@
346347
E4B0833C293810EB00A71564 /* BeeDataPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeeDataPoint.swift; sourceTree = "<group>"; };
347348
E4B6FEC52A776A2900690376 /* GoalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoalTests.swift; sourceTree = "<group>"; };
348349
E4D5BD5C2C5DFBA30007B0BE /* HealthKitMetricMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitMetricMonitor.swift; sourceTree = "<group>"; };
350+
E4D5BD5E2C6709060007B0BE /* BeeminderSpotlightDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeeminderSpotlightDelegate.swift; sourceTree = "<group>"; };
349351
E4E43D8729F39CE800697116 /* LogsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsViewController.swift; sourceTree = "<group>"; };
350352
E4E63C6C2C3F9083005E00DA /* DataPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPointManager.swift; sourceTree = "<group>"; };
351353
E4E63C722C5DDE98005E00DA /* GoalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoalExtensions.swift; sourceTree = "<group>"; };
@@ -641,6 +643,7 @@
641643
E46070EE2B36A4D900305DB4 /* User.swift */,
642644
E4E8DD3A2BE87F890059C64F /* Goal.swift */,
643645
E4E8DD3C2BE881440059C64F /* DataPoint.swift */,
646+
E4D5BD5E2C6709060007B0BE /* BeeminderSpotlightDelegate.swift */,
644647
);
645648
path = Model;
646649
sourceTree = "<group>";
@@ -1221,6 +1224,7 @@
12211224
E458C8022AD11BB9000DCA5C /* CurrentUserManager.swift in Sources */,
12221225
E458C8182AD11CAC000DCA5C /* TimeAsleepHealthKitMetric.swift in Sources */,
12231226
E458C80B2AD11C2B000DCA5C /* ServiceLocator.swift in Sources */,
1227+
E4D5BD5F2C6709060007B0BE /* BeeminderSpotlightDelegate.swift in Sources */,
12241228
E458C81B2AD11CD8000DCA5C /* UIColorExtension.swift in Sources */,
12251229
E458C8082AD11BFB000DCA5C /* BeeDataPoint.swift in Sources */,
12261230
E458C81C2AD11CDE000DCA5C /* UIFontExtension.swift in Sources */,

BeeSwift/AppDelegate.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
// Copyright (c) 2015 APB. All rights reserved.
77
//
88

9+
import CoreSpotlight
10+
import HealthKit
11+
import OSLog
912
import UIKit
13+
1014
import IQKeyboardManagerSwift
11-
import HealthKit
1215
import AlamofireNetworkActivityIndicator
16+
1317
import BeeKit
14-
import OSLog
1518

1619
@UIApplicationMain
1720
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
@@ -171,7 +174,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
171174
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
172175
}
173176
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
174-
if let intent = userActivity.interaction?.intent as? AddDataIntent {
177+
if userActivity.activityType == CSSearchableItemActionType {
178+
guard let goalIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else { return false
179+
}
180+
NotificationCenter.default.post(name: Notification.Name(rawValue: "openGoal"), object: nil, userInfo: ["identifier": goalIdentifier])
181+
} else if let intent = userActivity.interaction?.intent as? AddDataIntent {
175182
guard let goalSlug = intent.goal else { return false }
176183
NotificationCenter.default.post(name: Notification.Name(rawValue: "openGoal"), object: nil, userInfo: ["slug": goalSlug])
177184
} else if let goalSlug = userActivity.userInfo?["slug"] {

BeeSwift/Gallery/GalleryViewController.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,20 @@ class GalleryViewController: UIViewController, UICollectionViewDelegateFlowLayou
486486
}
487487

488488
@objc func openGoalFromNotification(_ notification: Notification) {
489-
let slug = (notification as NSNotification).userInfo!["slug"] as! String
490-
let matchingGoal = self.goals.filter({ (goal) -> Bool in
491-
return goal.slug == slug
492-
}).last
489+
guard let notif = notification as NSNotification? else { return }
490+
var matchingGoal: Goal?
491+
492+
if let identifier = notif.userInfo?["identifier"] as? String {
493+
let context = ServiceLocator.persistentContainer.viewContext
494+
if let url = URL(string: identifier), let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: url) {
495+
matchingGoal = context.object(with: objectID) as? Goal
496+
}
497+
}
498+
else if let slug = notif.userInfo?["slug"] as? String {
499+
matchingGoal = self.goals.filter({ (goal) -> Bool in
500+
return goal.slug == slug
501+
}).last
502+
}
493503
if matchingGoal != nil {
494504
self.navigationController?.popToRootViewController(animated: false)
495505
self.openGoal(matchingGoal!)

0 commit comments

Comments
 (0)