Core Spotlight Userinfo is always empty - ios

I am using a combination of CoreSpotlight api and NSUserActivity api to index app content. Everything goes well until I tap a search result. The userInfo passed with userActivity in continueUserActivity method contains only one item i.e kCSSearchableItemActivityIdentifier. My other custom keys are nil.
Here is my code for indexing items..
class MyTestViewController:UIViewController{
viewDidLoad(){
searchHandler = SearchHandler()
searchHandler.index(items)
}
}
class SearchHandler{
var activity: NSUserActivity!
func index(items:[Item]){
for item in items{
let attributeSet = getSearchItemAttribute(item)
if let attributeSet = attributeSet{
let searchableItem = CSSearchableItem(uniqueIdentifier: item.uniqueId, domainIdentifier:itemType.groupId(), attributeSet: attributeSet)
searchableItem.expirationDate = item.expirationDate
addToSpotlight([searchableItem])
}
activity = NSUserActivity(activityType: searchPrivacy.activity())
activity.delegate = delegate
//meta data
activity.title = item.title
var userInfoDic = [NSObject:AnyObject]()
userInfoDic["indexItemType"] = itemType.rawValue
userInfoDic["address"] = item.title
activity.userInfo = userInfoDic
if item.expirationDate != nil { activity.expirationDate = item.expirationDate! }
if item.keywords != nil { activity.keywords = item.keywords! }
activity.contentAttributeSet = attributeSet
//eligibility
activity.eligibleForHandoff = false
activity.eligibleForSearch = true
activity.eligibleForPublicIndexing = true
activity.requiredUserInfoKeys = Set(["indexItemType","address"])
activity.becomeCurrent()
}
}
private func getSearchItemAttribute(item:Item) ->CSSearchableItemAttributeSet?{
if item.contentType != nil { // add an entry to core spot light
let attributeSet = CSSearchableItemAttributeSet(itemContentType: item.contentType!)
attributeSet.relatedUniqueIdentifier = item.uniqueId
HALog.i("item.uniqueId= \(item.uniqueId)")
attributeSet.title = item.title
attributeSet.thumbnailData = item.thumbnailData
attributeSet.thumbnailURL = item.thumbnailUrl
attributeSet.rating = item.ratings
attributeSet.ratingDescription = item.ratingDescription
attributeSet.contentDescription = item.contentDescription
return attributeSet
}
return nil
}
private func addToSpotlight(searchableItems:[CSSearchableItem]) {
CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(searchableItems) { (error) -> Void in
if let error = error {
HALog.e("Deindexing error: \(error.localizedDescription)")
} else {
HALog.i("Search item successfully indexed!")
}
}
}
}
Whenever I try to access indexItemType or address keys in userInfo its always nil.
I have tried all the solutions from these threads:
iOS 9 - NSUserActivity userinfo property showing null
http://helpprogramming.xyz/question/31328032/ios-9-nsuseractivity-userinfo-property-showing-null
None of the above solved my problem.
I am currently using Xcode 7 with iOS 9.

If you use CSSearchableIndex then you will only receive the kCSSearchableItemActivityIdentifier in the userInfo. To set and retrieve custom properties in the userInfo you should only set the userActivity to becomeCurrent.
Stub out your call for the CSSearchableIndex and see if it works (you should also make sure your nsuseractivity object is a strong property on your view/model, as it can get deallocated before it has a chance to save the userInfo).
The info on the thread below helped me:
https://forums.developer.apple.com/thread/9690

I had the same issue here, and i searched so many answers not working for me...
fortunately i got my solution in ios-9-tutorial-series-search-api
I recommend you read all the content above. I will give you two alternative solutions:
use NSUserActivity instead of CSSearchableItem: when you done your data setting, call userActivity.becomeCurrent, then your can found your data in spotlight(just two more things: I don't have a iOS 9 device, so only tested in iOS 10 Device, and the article I mentioned above said NSUserActivity is limited to one activity per navigation point, but I don't have such kind issue...)
use CSSearchableItem, set the uniqueIdentifier to a JSON string that contains your custom userInfo.
NOTE: Do not index a CSSearchableItem and call userActivity.becomeCurrent both, otherwise you will got two spotlight results. One of them is com.apple.corespotlightitem, other one is your custom activityType

Related

NSBatchDeleteRequest causes Merge Conflict

I have an application that will sync with a server with data that can change daily. During the sync, I remove all the data for some entities and reload it with new data. I am using the following code:
func SyncronizeUserComments(theData : [[AnyHashable : Any]])
{
// Delete User Comments for this User and Connection
let commentRequest : NSFetchRequest<NSFetchRequestResult> = PT_UserComments.fetchRequest()
commentRequest.predicate = NSPredicate(format: "connection = %# AND user == %#", Global_CurrentConnection!, Global_CurrentUser!)
coreData.processDeleteRequest(request: commentRequest)
// ADD the Comments to CoreData
for index in 0..<theData.count {
let result : [AnyHashable : Any] = theData[index]
if let commentID = result["Comment_ID"] as? String, let commentText = result["Comment_Text"] as? String, let commentTitle = result["Comment_Title"] as? String
{
let newUserComment = PT_UserComments(context: coreData.persistentContainer.viewContext)
newUserComment.connection = Global_CurrentConnection
newUserComment.user = Global_CurrentUser
newUserComment.comment_ID = commentID
newUserComment.comment_Text = commentText
newUserComment.comment_Title = commentTitle
}
}
// Add the User Comments
print("Added New User Comments: \(theData.count)")
coreData.saveContext()
}
func processDeleteRequest(request : NSFetchRequest<NSFetchRequestResult>)
{
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try coreData.persistentContainer.viewContext.execute(deleteRequest) as? NSBatchDeleteResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSDeletedObjectsKey : objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes as Any as! [AnyHashable : Any], into: [coreData.persistentContainer.viewContext])
} catch {
fatalError("Fatal Error Deleting Data: \(error)")
}
coreData.saveContext()
}
When I call coreData.saveContext() I will get a Merge Conflict against the deleted data.
In reading about CoreData and the NSBatchDeleteRequest, this deletes at the SQL LITE level and bypasses the in memory cache.
The only way I have been able to get this to work is by setting:
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
Is this correct, or am I doing something wrong? I am also setting this merge policy in my saveContext() in the Core Data Stack.
I just spent hours debugging the same issue, hopefully this can help someone.
The problem is that NSManagedObjectContext.mergeChanges(fromRemoteContextSave:, into:) updates the managed object context but does not update the row cache version number of the deleted objects relationships to match the updated version number (Z_OPT) in the database file, causing a mismatch at time of the save.
If you're using NSErrorMergePolicyType this will cause the next save to fail, (or even a later one when the relationships become flagged for save), even though everything but the version numbers match. I've not seen this mentioned in the related docs or WWDC video, but I guess Apple assumed people would always pick a non-default merge policy.
So picking NSMergeByPropertyStoreTrumpMergePolicy solves it, as mentioned in the question, but you might not want this policy for all your save operations. To avoid that I ended up writing a custom merge policy that only resolves version mismatches. The code is below (this is untested Swift as I originally wrote in Obj-C, but should be equivalent):
//Configure the merge as below before saving
context.mergePolicy = AllowVersionMismatchMergePolicy(merge: .errorMergePolicyType)
//...
//The custom merge policy
class AllowVersionMismatchMergePolicy: NSMergePolicy {
override func resolve(optimisticLockingConflicts list: [NSMergeConflict]) throws {
do {
//if the default resolve worked leave it alone
return try super.resolve(optimisticLockingConflicts: list)
} catch {
//if any of the conflict is not a simple version mismatch (all other keys being equal), fail
let hasValueConflict = list.contains { conflict -> Bool in
//compare object and row cache
if let objectSnapshot = conflict.objectSnapshot as NSObject?,
let cachedSnapshot = conflict.cachedSnapshot as NSObject? {
return !objectSnapshot.isEqual(cachedSnapshot)
}
//compare row cache and database
if let cachedSnapshot = conflict.cachedSnapshot as NSObject?,
let persistedSnapshot = conflict.persistedSnapshot as NSObject? {
return !cachedSnapshot.isEqual(persistedSnapshot)
}
//never happens, see NSMergePolicy.h
return true
}
if hasValueConflict {
throw error
}
//Use store rollback merge policy to resolve all the version mismatches
return try NSMergePolicy.rollback.resolve(optimisticLockingConflicts: list)
}
}
}

aws dynamodb how to use object mapper with batch get in ios swift

Thanks in advance for any help. I am trying to get Batch items (Load multiple) items from one DynamoDb table using the AWS iOS SDK (Swift). I can load one item using the Block syntax, but I need to load 10 or more than that. I don't want to use 10 Block calls to load them individually. I tried to follow the attach stackoverflow Link (where the similar solution is given) but I am getting the following compiler error message. I come from Java background, hence could also be a syntax issue. Is it the right way to load multiple items? I don't want to use low level API. Any help, where I am going wrong. Thanks.
aws dynamodb how to use object mapper with batch get in ios
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...10 {
tasksList.append(dynamoDBObjectMapper.load(AWSCards.self, hashKey: "SH_"+String(i), rangeKey: nil))
}
AWSTask.init(forCompletionOfAllTasksWithResults: tasksList).continueWithBlock { (task) -> AnyObject? in
if let cards = task.result as? [AWSCards] {
print(cards.count)
}
else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
Have a try with the following codes (Swift 4.1, Feb 9th, 2018):
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...10 {
tasksList.append(dynamoDBObjectMapper.load(AWSCards.self, hashKey: "SH_"+String(i), rangeKey: nil))
}
AWSTask<AnyObject>.init(forCompletionOfAllTasksWithResults: tasksList).continueWith { (task) -> Any? in
if let cards = task.result as? [AWSCards] {
print(cards.count)
}
else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
Your question is "how to use the object mapper" but it might be more efficient for you to not use it.
However, there is a way to use it. See Niklas's answer here and here (he copy & pasted), but something about it strikes me as fishy. I want to make the assertion that it is not as fast as the built-in batch-get function, but I am unsure. I suspect that this does not complete the items in parallel, or at least not as efficiently as in BatchGetItem.
See the docs: "In order to minimize response latency, BatchGetItem retrieves items in parallel."
According to Yosuke, "Currently, AWSDynamoDBObjectMapper does not support the batch get item. You need to load one item at a time if you want to use the object mapper" as of 2016. This still seems to be the case. I am using a version a couple versions behind, but not too far behind. Someone check.
In conclusion, if you are loading one item at a time, you are likely missing out on the whole purpose of BatchGetItem (low latency).
Pulling from various sources, including John Davis's question here, I have tested and ran this BatchGetItem result. Here ya go.
import AWSDynamoDB
let primaryKeyToSortKeyDict : [String : String] = .... // Your stuff
var keys = [Any]()
for key in primaryKeyToSortKeyDict.keys {
let partitionKeyValue = AWSDynamoDBAttributeValue()
partitionKeyValue?.s = String(key)
let sortValue = AWSDynamoDBAttributeValue()
sortValue?.s = String(primaryKeyToSortKeyDict[key]!)
keys.append(["partitionKeyAttributeName": partitionKeyValue, "sortKeyAttributeName": sortValue])
}
let keysAndAttributesMap = AWSDynamoDBKeysAndAttributes()
keysAndAttributesMap?.keys = keys as? [[String : AWSDynamoDBAttributeValue]]
keysAndAttributesMap?.consistentRead = true
let tableMap = [table : keysAndAttributesMap]
let request = AWSDynamoDBBatchGetItemInput()
request?.requestItems = tableMap as? [String : AWSDynamoDBKeysAndAttributes]
request?.returnConsumedCapacity = AWSDynamoDBReturnConsumedCapacity.total
guard request != nil else {
print("Handle some error")
return
}
AWSDynamoDB.default().batchGetItem(request!) { (output, error) in
print("Here is the batchgetitem output")
if error == nil {
// do output stuff
} else {
// handle error
}
}

How to retrieve emails from iPhone Contacts using swift?

I am new to swift . I used APAdressBook Framework for retrieving contacts in iPhone .Every thing working fine up to IOS 8.4 but when checked in IOS 9 of Simulator and iPhone devices it is not accessing contacts and even it not showing alert message like would you like to access contacts any one can face this type of problem if yes please give me your valuable answer?
Apple Inc. introduce Contacts.framework from iOS 9. So, it would be better use this framework to retrieve contacts.
Here is way to fetch email or whole contact from Contacts app.
Add Contacts.framework to your project.
Create or add new file of type header and give name like yourProjectName-Bridging-Header.h write #import <Contacts/Contacts.h> statement into file and save and set appropriate path of this file from build setting.
Now create method
func getAllContacts() {
let status = CNContactStore.authorizationStatusForEntityType(CNEntityType.Contacts) as CNAuthorizationStatus
if status == CNAuthorizationStatus.Denied {
let alert = UIAlertController(title:nil, message:"This app previously was refused permissions to contacts; Please go to settings and grant permission to this app so it can use contacts", preferredStyle:UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title:"OK", style:UIAlertActionStyle.Default, handler:nil))
self.presentViewController(alert, animated:true, completion:nil)
return
}
let store = CNContactStore()
store.requestAccessForEntityType(CNEntityType.Contacts) { (granted:Bool, error:NSError?) -> Void in
if !granted {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// user didn't grant access;
// so, again, tell user here why app needs permissions in order to do it's job;
// this is dispatched to the main queue because this request could be running on background thread
})
return
}
let arrContacts = NSMutableArray() // Declare this array globally, so you can access it in whole class.
let request = CNContactFetchRequest(keysToFetch:[CNContactIdentifierKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey, CNContactPhoneNumbersKey, CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName)])
do {
try store.enumerateContactsWithFetchRequest(request, usingBlock: { (contact:CNContact, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let arrEmail = contact.emailAddresses as NSArray
if arrEmail.count > 0 {
let dict = NSMutableDictionary()
dict.setValue((contact.givenName+" "+contact.familyName), forKey: "name")
let emails = NSMutableArray()
for index in 0...arrEmail.count {
let email:CNLabeledValue = arrEmail.objectAtIndex(index) as! CNLabeledValue
emails .addObject(email.value as! String)
}
dict.setValue(emails, forKey: "email")
arrContacts.addObject(dict) // Either retrieve only those contact who have email and store only name and email
}
arrContacts.addObject(contact) // either store all contact with all detail and simplifies later on
})
} catch {
return;
}
}
}
Call this method where you want self.getAllContacts()
And when you want to retrieve
for var index = 0; index < self.arrContacts.count; ++index {
let dict = self.arrContacts[index] as! NSDictionary
print(dict.valueForKey("name"))
print(dict.valueForKey("email"))
}

Swift Parse: can not retrieve the value of a bool in _User class

I am developing an app using swift and Parse. For some reasons I have implemented a Bool named "modified" in the _User class. I have been playing around with swift and Parse for a few months but this just does not make sense.
When I try to retrieve the value of the "modified" Bool I keep on getting "false" value even though it is set on "true" on Parse server. Here is the code:
var modified: Bool = PFUser.currentUser().objectForKey("modified") as! Bool
println("User Modified Bool is set to: \(modified)")
I have also tried with
self.modified = PFUser.currentUser().valueForKey("modified") as! Bool
println("User Modified Bool is set to: \(modified)")
and
self.modified = PFUser.currentUser()["modified"] as! Bool
println("User Modified Bool is set to: \(modified)")
Do I have to make a specific query or is there a way to access this value directly?
Edit
I have implemented a specific query. Still get a "false" value though
var queryMainUser: PFQuery = PFUser.query()
queryMainUser.whereKey("username", equalTo: PFUser.currentUser().username)
queryMainUser.findObjectsInBackgroundWithBlock { (mainUsersObjects, mainUsersError) -> Void in
if (mainUsersError == nil) {
var theRef = mainUsersObjects[0] as! PFObject
self.modified = theRef["modified"] as! Bool
println("Any improvement? \(self.modified)")
}
}
Edit 2
Following #danh advices, I tried updating the currentuser instance on the device by implementing this code:
var currentUser = PFUser.currentUser()
currentUser.fetchInBackgroundWithBlock { (object, error) -> Void in
println("Refreshed")
currentUser.fetchIfNeededInBackgroundWithBlock { (result, error) -> Void in
self.modified = currentUser.objectForKey("modified") as! Bool
var idOfUser: String = currentUser.objectId
println("User \(idOfUser) UPDATED")
println(self.modified)
if self.modified == true {
self.deleteData()
self.fetchAllObjects()
}
}
}
When running the console gives me this:
Refreshed
User xTbBw6cNzK UPDATED
false
Here is a screenshot I just took of the server side:
Thank you all for your attention
I am not sure what version of swift you are using. But if you are using Swift 2.0 and Xcode 7, this SHOULD do the job.
This will not work:
let modifiedStatus = PFUser.currentUser()?["modified"] // return value will be nil
This will work for sure:
let modifiedStatus = PFUser.currentUser()?.objectForKey("modified")
print(modifiedStatus) // true as per your table
I know this may sound strange, but some I struggle with something for hours later realising my silly mistake. So it is always good to move back of the current task and later recheck after few hours. So just make sure you cross check the following:
The key "modified" in the main user class of parse
You can retrieve other key values (just to see if nothing else is wrong other than your current retrieving of a key bool value(
Though I am on Swift 2.0, but for sure there is no major change from in this specific code when it comes to move from Swift 1.2 to Swift 2.0.
Just see and if it still doesn't work, we can discuss more on your setup.
I have initialised a "doneSetUp" var as a local variable, it is an int.
then I query the user which just logged in
checks if it exists...
check if the variable userDidSetUp exists in parse and if it does I am converting it to an int and assign it to the local variable doneSetUp I made
then I am using the "doneSetUp" variable which now has a value of 0(false) or 1(true) to decide if the user already setup his account or not and then segue the user to the correct view controller.
mention that all of this code is inside of my logininbackgroundwithblock function.
I hope that helped.
query?.getObjectInBackgroundWithId(user!.objectId!, block: {
(user, error) -> Void in
if let user = user{
if var userDidSetUp: AnyObject = user["doneSetUp"] {
self.doneSetUp = userDidSetUp as! Int
if self.doneSetUp == 0 {
self.performSegueWithIdentifier("procceedToSetup", sender: self)
}else{
self.performSegueWithIdentifier("procceedToApp", sender: self)
}
}
}
})
I know it's an old post, but here's what worked for me.
This is inside the viewDidLoad method.
PFUser.currentUser()?.fetchInBackgroundWithBlock({ (object, error) -> Void in
var modified = PFUser.currentUser()?.objectForKey("modified")?.boolValue
if modified == true {
print(modified) // Output console displays "true"
Hope this helps.

execute put item request - AWS DynamoDB ios SDK

Looked all over SO and through Amazon's docs as well, but couldn't find any solid documentation on how to make a put request using iOS SDK, specifically using Swift.
I gather that I need to instantiate an AWSDynamoDBClient first (https://aws.amazon.com/articles/7439603059327617) but don't see that appear as a type when I'm working in xcode.
I've honestly only got two lines of code after all this struggle:
var myDynamoDBPutRequest:AWSDynamoDBPutRequest = AWSDynamoDBPutRequest()
myDynamoDBPutRequest.item = ["fbid": "test"]
I can't figure out how to run it, and doubt that request is set up properly anyway. I've also looked at PutItemInputs, but not sure how that differs from putRequest.item. If anyone can just point me in the right direction I'll be happy to investigate on my own - I'm just running out of places to look for good documentation :/
EDIT:
I've made a bit of progress, but still can't figure out how to properly create a put item input . Here is the code I have now:
var myPutItemInput:AWSDynamoDBPutItemInput = AWSDynamoDBPutItemInput()
myPutItemInput.tableName = "mytable"
var myDynamoDB = AWSDynamoDB.defaultDynamoDB()
myDynamoDB.putItem(myPutItemInput).continueWithBlock { (task:BFTask!) -> AnyObject! in
if(task.result != nil){
let myPutOutput = task.result as AWSDynamoDBPutItemOutput
println(task.result)
}else{
println("task.result was nil for put item request")
}
return nil
}//end put item task
right now I at least figured out how to execute the request, but the result is nil each time.
Here is an example of - putItem:
let dynamoDB = AWSDynamoDB.defaultDynamoDB()
let putItemInput = AWSDynamoDBPutItemInput()
putItemInput.tableName = "testTableName"
let hashValue = AWSDynamoDBAttributeValue()
hashValue.S = "testPutItem"
let stringValue = AWSDynamoDBAttributeValue()
stringValue.S = "stringValue";
putItemInput.item = [
"hashKey" : hashValue,
"stringKey" : stringValue
]
dynamoDB.putItem(putItemInput).continueWithBlock { (task:AWSTask?) -> AnyObject? in
if(task.error != nil) {
println(task.error)
}
if (task.result != nil) {
let putItemOutput = task.result as AWSDynamoDBPutItemOutput
println(putItemOutput)
}
return nil
}
Even though it's in Objective-C, looking at the integration tests may help understand how to use Amazon DynamoDB with the AWS Mobile SDK for iOS v2.

Resources