BAD_ACCESS when calling CSSearchableIndex indexSearchableItems - ios

I am trying to implement the CoreSpotlight API in my app but can't seem to figure out why I am getting a BAD_ACCESS exception with my implementation:
CSSearchableItemAttributeSet * attributeSet = [CSSearchableItemAttributeSet new];
attributeSet.title = route.Options.name;
attributeSet.keywords = [route.Options.name componentsSeparatedByString:#" "];
UIImage *image = [UIImage imageNamed:#"pin_busstop.png"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
CSSearchableItem * item = [[CSSearchableItem alloc] initWithUniqueIdentifier:route.ObjectID domainIdentifier:#"com.whatever.itsmyappsname.loadwithroute" attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:[NSArray arrayWithObject:item] completionHandler:^(NSError * _Nullable error) {
NSLog(#"It worked");
}];
Looking at the call stack for the exception, I can see that it occurs on the CSSearchableIndex indexSearchableItems: completionHandler: call. However I can step past that call without the exception triggering, maybe it has to do with the completion handler, however it happens regardless of if I have one or not. I have CoreSpotlight/CoreSpotlight.h and MobileCoreServices/MobileCoreServices.h imported both in my .h file and in the target.

You're (I'm) creating the CSSearchableItemAttributeSet object incorrectly. Instead of:
CSSearchableItemAttributeSet * attributeSet = [CSSearchableItemAttributeSet new];
Use:
CSSearchableItemAttributeSet * attributeSet = [[CSSearchableItemAttributeSet alloc]
initWithItemContentType:(NSString *)kUTTypeImage];

Related

get dropbox thumbnails with getThumbnailBatch

I'm using the dropbox objc API and I'm trying to get all thumbnails in a specific dropbox folder.
But I'm completely stuck at DBFILESGetThumbnailBatchArg. How do I initiate paths to all images in a folder?
This is the line I'm stuck at:
[[client.filesRoutes getThumbnailBatch:<#(nonnull NSArray<DBFILESThumbnailArg *> *)#>]
setResponseBlock:^(
DBFILESGetThumbnailBatchResult * _Nullable result,
DBFILESGetThumbnailBatchError * _Nullable routeError,
DBRequestError * _Nullable networkError) { etc etc..
Documentation says
DBFILESThumbnailArg *arg = [[DBFILESThumbnailArg alloc] initWithPath:<#(nonnull NSString *)#>];
DBFILESGetThumbnailBatchArg *batchArg = [[DBFILESGetThumbnailBatchArg alloc]
initWithEntries:<#(nonnull NSArray<DBFILESThumbnailArg *> *)#>];
How do I init a list of paths of DBFILESThumbnailArg?
Link to documentation:
https://dropbox.github.io/dropbox-sdk-obj-c/api-docs/latest/Classes/DBFILESRouteObjects.html#/c:objc(cs)DBFILESRouteObjects(cm)DBFILESGetThumbnailBatch
As you found, the getThumbnailBatch method expects an NSArray<DBFILESThumbnailArg *>, so calling it would look like this:
NSArray<DBFILESThumbnailArg *> *entries = #[[[DBFILESThumbnailArg alloc] initWithPath:#"/test1.jpg"], [[DBFILESThumbnailArg alloc] initWithPath:#"/test2.jpg"]];
[[client.filesRoutes getThumbnailBatch:entries]
setResponseBlock:^(DBFILESGetThumbnailBatchResult *result, DBFILESGetThumbnailBatchError *routeError, DBRequestError *networkError) {
if (result) {
NSLog(#"result:");
NSLog(#"%#", result);
} else if (routeError) {
NSLog(#"routeError:");
NSLog(#"%#", routeError);
} else if (networkError) {
NSLog(#"networkError:");
NSLog(#"%#", networkError);
};
}];
I solved this using a NSMutableArray, posting my solution if others come looking:
//Create a temporary NSMutableArray
NSMutableArray *thumbArgMutable = [[NSMutableArray alloc] init];
for (NSString* image in _images)
{
//Create DBFILESThumbnailArg from NSString
DBFILESThumbnailArg *arg = [[DBFILESThumbnailArg alloc] initWithPath:image];
//Add path as DBFILESThumbnailArg to NSMutableArray
[thumbArgMutable addObject:arg];
}
//Copy NSMutableArray to a new DBFILESThumbnailArg
DBFILESThumbnailArg *thumbArg = [thumbArgMutable copy];
//create a DBFILESGetThumbnailBatchArg and init with the copied DBFILESThumbnailArg
DBFILESGetThumbnailBatchArg *thumbArgBatch = [[DBFILESGetThumbnailBatchArg alloc] initWithEntries:thumbArg];
DBUserClient *client = [[DBUserClient alloc] initWithAccessToken:#"TOKEN"];
//use property entries from DBFILESGetThumbnailBatchArg
[[client.filesRoutes getThumbnailBatch:thumbArgBatch.entries]
setResponseBlock:^(DBFILESGetThumbnailBatchResult * _Nullable result,
DBFILESGetThumbnailBatchError * _Nullable routeError,
DBRequestError * _Nullable networkError)
{
if (result) {
NSLog(#"%#\n", result);
//loop all downloaded thumbnails
for (DBFILESGetThumbnailBatchResultEntry *data in result.entries)
{
//extract data from each base64 encoded thumbnail string
NSData *thumbData = [[NSData alloc] initWithBase64EncodedString:data.success.thumbnail options:0];
//create UIImage from data
UIImage *thumbImage = [UIImage imageWithData:thumbData];
}
}
else { //if download failed
NSLog(#"%#\n%#\n", routeError, networkError);
}

Set title property from NSarray in CSSearchableItemAttributeSet

I am trying to using CoreSpotlight API in application , I have plist file which has a several items on it for example animals' name . So I need to set title string equal to on of those object , for example if users search Lion , the line name and for example its features appears on the spotlight . Here is my code :
- (void)setupCoreSpotlightSearch
{
CSSearchableItemAttributeSet *attibuteSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(__bridge NSString *)kUTTypeImage];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"animals" withExtension:#"plist"];
NSArray *playDictionariesArray = [[NSArray alloc ] initWithContentsOfURL:url];
NSString *getNames = [NSString stringWithFormat:#"%#",playDictionariesArray];
NSLog(#"%#",getNames) ;
attibuteSet.title =getNames;
attibuteSet.contentDescription = #"blah blah ";
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"app name"
domainIdentifier:#"com.compont.appname"
attributeSet:attibuteSet];
if (item) {
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(#"Search item indexed");
}
}];
}
}
The problem is getNames returns all names !!! how can I filter it when is user is searching an specific word from animals.plist
Thanks .
EDIT [Plist Image]:
You can maintain NSArray and iterate through playDictionariesArray, creating & initialising CSSearchableItem object with that particular entry in your data source.
- (void)setupCoreSpotlightSearch
{
NSURL *url = [[NSBundle mainBundle] URLForResource:#"animals" withExtension:#"plist"];
NSArray *playDictionariesArray = [[NSArray alloc ] initWithContentsOfURL:url];
NSMutableArray * searchableItems = [[NSMutableArray alloc]init];
for(object in playDictionariesArray)
{
CSSearchableItemAttributeSet *attibuteSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(__bridge NSString *)kUTTypeImage];
attibuteSet.title =[NSString stringWithFormat:#"%#",object]; //retrive title from object and add here
//attibuteSet.contentDescription = #"blah blah "; // retrieve description from object and add here
CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:#"app name"
domainIdentifier:#"com.compont.appname"
attributeSet:attibuteSet];
[searchableItems addObject:item];
}
if (searchableItems) {
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:searchableItems completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(#"Search item indexed");
}
}];
}
}
I haven't ran and tested the code.
you are not looping through each key. Use the code provided in this question.
CoreSpotlight indexing
Try it on a device that supports indexing.
NOT iPhone 4/4s or iPad.

CoreSpotlight Thumbnail Image not showing up on Spotlight search

I am trying to make my in app content searchable with help of Core Spotlight. Everything works great, title and description comes but without thumbnail.
here is what I am trying:
CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc]initWithItemContentType:(NSString *)kUTTypeImage];
attributeSet.title = #"My First Spotlight Search";
attributeSet.contentDescription = #"This is my first spotlight Search";
attributeSet.keywords = [NSArray arrayWithObjects:#"Hello", #"Welcome",#"Spotlight", nil];
UIImage *image = [UIImage imageNamed:#"searchIcon.png"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
CSSearchableItem *item = [[CSSearchableItem alloc]initWithUniqueIdentifier:#"com.deeplink" domainIdentifier:#"spotlight.sample" attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:#[item] completionHandler: ^(NSError * __nullable error) {
if (!error)
NSLog(#"Search item indexed");
}];
The iOS 9 beta 1 release notes state that thumbnails aren't yet working for search results. See: https://developer.apple.com/library/prerelease/ios/releasenotes/General/RN-iOSSDK-9.0/index.html

"Missing __block type specifier" compilation error after refactoring code but ok before refactor

I have some code that downloads an image and assigns it within a block. The code currently works however I want to refactor it into a separate method, however after the refactoring I get a compilation error.
This is the original code which compiles and runs with the downloaded image being assigned successfully:
- (void) someMethod
{
…
MyObject* object = [[MyObject alloc] init];
[self.objects addObject: object];
NSString* graphRequest = [NSString stringWithFormat:#"%#%#%#", #"https://graph.facebook.com/",
fbid,
#"/picture?type=square"];
FBRequest *fbRequest = [FBRequest requestForGraphPath: graphRequest];
[fbRequest startWithCompletionHandler:
^(FBRequestConnection *connection, id result, NSError *theError)
{
NSDictionary<FBGraphObject> *dict = (NSDictionary<FBGraphObject> *) result;
if (dict)
{
NSString* urlAsString = [dict objectForKey:#"id"];
if ([urlAsString length] > 0)
{
NSURL *url = [NSURL URLWithString: urlAsString];
NSData *data = [NSData dataWithContentsOfURL:url];
object.image = [UIImage imageWithData:data];
}
}
}];
}
If I refactor it to the following then I get a compilation error
- (void) someMethod
{
…
MyObject* object = [[MyObject alloc] init];
[self.objects addObject: object];
[self fetchFbImage: object.image forFbid:fbid];
}
- (void) fetchFbImage:(UIImage*) targetImage forFbid:(NSString*) fbid
{
NSString* graphRequest = [NSString stringWithFormat:#"%#%#%#", #"https://graph.facebook.com/",
fbid,
#"/picture?type=square"];
FBRequest *fbRequest = [FBRequest requestForGraphPath: graphRequest];
[fbRequest startWithCompletionHandler:
^(FBRequestConnection *connection, id result, NSError *theError)
{
NSDictionary<FBGraphObject> *dict = (NSDictionary<FBGraphObject> *) result;
if (dict)
{
NSString* urlAsString = [dict objectForKey:#"id"];
if ([urlAsString length] > 0)
{
NSURL *url = [NSURL URLWithString: urlAsString];
NSData *data = [NSData dataWithContentsOfURL:url];
targetImage = [UIImage imageWithData:data];
}
}
}];
}
The compilation error is the line assigning into targetImage, "Variable is not assignable (missing __block type specifier)".
Where should I add the __block type specifier? And why is there an issue after the refactoring but not before?
Thanks
You seem to be confused over how parameters work in Objective-C.
In your original code you have:
MyObject* object = [[MyObject alloc] init];
which declares object as a local variable of the method. Then within the block you write:
object.image = [UIImage imageWithData:data];
So your block references the local variable object and (as you have no __block attribute on the declaration) the block gets a constant copy of the contents of variable. This is fine because you are not changing what is in object, which is a reference to your MyObject instance, but are calling a method on that instance which changes the internal state of that instance.[*]
Now let's look at your refactoring. You remove a chunk of your someMethod's code and place it in a new method fetchFbImage. In someMethod you call fetchFbImage:
[self fetchFbImage:object.image forFbid:fbid];
This passes the current value of object.image to the method it does not pass the property in such a way that it can be assigned to in fetchFbImage. The type of the argument targetImage is UIImage * - a reference to an UIImage - it is not "a property of type UIImage *" - you cannot have arguments of such a type, properties cannot be passed around only their values or reference to objects which have properties.
When a method is called each parameter is effectively a local variable which is initialised to the argument value passed, so the above method call effectively performs:
UIImage *targetImage = object.image;
where targetImage is a local variable in the scope of fetchFbImage and object is the local variable in the scope of someMethod.
Now within the block inside of fetchFbImage you write:
targetImage = [UIImage imageWithData:data];
This is wrong for two reasons:
You cannot assign to targetImage within the block. This is a local variable belonging to fetchFbImage, that local variable is not attributed with __block, and so the block has a constant copy of it - and you cannot assign to constants. This is why the compiler issues the error message.
However your issue is bigger than this, you are assuming that an assignment to fetchFbImage's local variable targetImage will some how invoke the property object.image - and there is no way it can do that. targetImage is just a local variable which was initialised with the value of object.image by the method call.
The only way to fix this is to pass fetchFbImage a reference to your MyObject instance and then within the block inside of fetchFbImage to assign to the image property of that object just as your pre-refectored code did.
So your code will look something like:
- (void) fetchFbImage:(MyObject *)targetObject forFbid:(NSString *)fbid
{
...
targetObject.image = [UIImage imageWithData:data];
...
}
...
[self fetchFbImage:object forFbid:fbid];
HTH
Addendum
Seeing your comment on another answer it appears you would like fetchFbImage to have no knowledge of MyObject and be able to fetch images regardless of where they will be referenced from.
A simple way to do this is to follow the same design you have for FBRequest - use a completion handler. For convenience define a type for the completion block:
typedef void (^ImageConsumer)(NSImage *image);
Now define your method to take one of these:
- (void) fetchFbImageForFbid:(NSString *)fbid completionHandler:(ImageConsumer)handler
In your block within fetchFbImageForFbid pass the image to the handler:
handler([UIImage imageWithData:data]);
And in the call in someMethod pass a block which assigns the value to your property:
[self fetchFbImageForFbid:fbid
completionHandler:^(NSImage *image) { object.image = image; }
];
[*] If this is confusing think of the reference as the address of a house. The address is constant, how many people are in the house is not. You can tell someone "go to this address, there is a great party on" - the contents ("state") of the house changes, its address does not.
in your first method (with out refactoring), you can set the image to object becuase you have reference to the object, so you did
object.image = [UIImage imageWithData:data];
But after refactoring , you can't set image in the way you are doing,you should send the object also.
- (void) someMethod
{
…
MyObject* object = [[MyObject alloc] init];
[self.objects addObject: object];
[self fetchFbImageForObj:object forFbid:fbid];
}
- (void) fetchFbImageForObject:(MyObject*)object forFbid:(NSString*) fbid
{
NSString* graphRequest = [NSString stringWithFormat:#"%#%#%#", #"https://graph.facebook.com/",
fbid,
#"/picture?type=square"];
FBRequest *fbRequest = [FBRequest requestForGraphPath: graphRequest];
[fbRequest startWithCompletionHandler:
^(FBRequestConnection *connection, id result, NSError *theError)
{
NSDictionary<FBGraphObject> *dict = (NSDictionary<FBGraphObject> *) result;
if (dict)
{
NSString* urlAsString = [dict objectForKey:#"id"];
if ([urlAsString length] > 0)
{
NSURL *url = [NSURL URLWithString: urlAsString];
NSData *data = [NSData dataWithContentsOfURL:url];
object.image = [UIImage imageWithData:data];
}
}
}];
}
try it.If any error or something,comment below.

iOS html/xml parsing google shopping results with TFHpple

is there any way to parsing google shopping results using TFHpple without using google API (deprecated) but simple using url like for example this: https://www.google.com/search?hl=en&tbm=shop&q=AudiR8 ?
I've tried many types of tags:
...
myCar = #"Audi R8";
myURL = [NSString stringWithFormat:#"https://www.google.com/search?hl=en&tbm=shop&q=%#",myCar];
NSData *htmlData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
TFHpple *xpath = [[TFHpple alloc] initWithHTMLData:htmlData];
//use xpath to search element
NSArray *elements = [NSArray new];
elements = [xpath searchWithXPathQuery:#"//html//body"]; // <-- tags
...
but nothing to do, always the same output console message: UNABLE TO PARSE.
I've found various problem and finally i've solved all.
First of all it's necessary to encoding URL adding:
myURL = [myURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
Then, inside original (and actual) TFHPPLE code (for exactly XPathQuery.m) parsing phase going to crash 'cause any time nodeContent and Raw are NIL.
So, to solve this crash I've changed
[resultForNode setObject:currentNodeContent forKey:#"nodeContent"];
with (ATTENTION FOR BOTH ROWS [resultForNode...:
if (currentNodeContent != nil)
[resultForNode setObject:currentNodeContent forKey:#"nodeContent"];
and:
[resultForNode setObject:rawContent forKey:#"raw"];
with:
if (rawContent != nil)
[resultForNode setObject:rawContent forKey:#"raw"];
I want to remember that, 'cause the harder html code used by google, i decide to use these xpathqueries:
...
NSArray *elementsImages = [NSArray new];
NSArray *elementsPrices = [NSArray new];
elementsImages = [xpath searchWithXPathQuery:#"//html//*[#class=\"psliimg\"]"];
elementsPrices = [xpath searchWithXPathQuery:#"//html//*[#class=\"psliprice\"]"];
...
Another inconvenience is when you decide to use a for or while cycle to retrieve various html pages, in fact if you use:
NSData *htmlData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
initWithContenctsOfURL many times during the cycle cannot get correctly page (and debug console write the famous UNABLE TO PARSE )so I've decide to change it with:
// Send a synchronous request
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:myURL]];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil)
{
// Parse data here
}
And if you don't want to waiting this cycle 'cause it's maded by syncronous NSURLRequests try to call parent method with (and your viewcontroller don't freeze waiting for parser):
_dispatch_queue_t *queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async( _queue, // now i call my google shopping parser cycle
^{
[self GShoppingParser];
});
Can you try changing the below line
NSData *htmlData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
to
NSData *Data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
and also the below line
TFHpple *xpath = [[TFHpple alloc] initWithHTMLData:htmlData];
to
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
Let me know if this helps else there is one more line that you may need to change in your code.
happy coding!

Resources