Images in UITableView keep re-loading and wrong images flash while scrolling - ios

i have created a UITableView which populates each cell from URL requests. I have used 'dispatch_queue' to prevent the UItableView from freezing. For some reason when i scroll through the UITableView the images flash and disappear and fill the wrong cell for a second until fixing themselves. Here is my code. I am pulling the feed using restkit
customCell.customCellTextLabel.text = [NSString stringWithFormat:#"%#",feedO.title];
NSString *string = [NSString stringWithFormat:#"%#",feedO.body];
NSURL *urlString;
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
for (NSTextCheckingResult *match in matches) {
if ([match resultType] == NSTextCheckingTypeLink) {
urlString = [match URL];
NSLog(#"found Body URL: %# and title %#", urlString,feedO.title);
}
}
dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
dispatch_async(imageQueue, ^{
NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];
dispatch_async(dispatch_get_main_queue(), ^{
customCell.customCellImageView.image = [UIImage imageWithData: data];
});
});
return customCell;

You should consider using this library SDWebImage, available here https://github.com/rs/SDWebImage for that kind of problems. It handles asynchronous download and cache for remote images very easily.
The simpliest installation is done by using CocoaPods
CocoaPods (http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects.
See the Get Started section for more details.
Use in your Podfile
platform :ios, '6.1'
pod 'SDWebImage', '~>3.6'
After installing the dependency for SDWebImage, just simply use the following lines in your view controller :
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}

your code was missing one line , that i added.
customCell.customCellImageView.image =nil;
customCell.customCellTextLabel.text = [NSString stringWithFormat:#"%#",feedO.title];
NSString *string = [NSString stringWithFormat:#"%#",feedO.body];
NSURL *urlString;
customCell.customCellImageView.image =nil;
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
for (NSTextCheckingResult *match in matches) {
if ([match resultType] == NSTextCheckingTypeLink) {
urlString = [match URL];
NSLog(#"found Body URL: %# and title %#", urlString,feedO.title);
}
}
dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
dispatch_async(imageQueue, ^{
NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];
dispatch_async(dispatch_get_main_queue(), ^{
customCell.customCellImageView.image = [UIImage imageWithData: data];
});
});
return customCell;

The reason for this is the UITableView is reusing the cells created.
So firstly it creates lets say 10 of your CustomCell class.
Cell 0
Cell 1
...
Cell 9
By creating these 10 cells it will initiate 10 async calls to fetch images.
block 0 -> Cell 0
block 1 -> Cell 1
...
block 9 -> Cell 9
This will work fine if you don't scroll until all 10 downloads have finished.
But as soon as you start scrolling, the UITableView will start reusing the cells created and initiate new downloads.
So first it might reuse Cell 0 and create block 10 -> Cell 0.
If block 0 was not finished when Cell 0 was picked for reuse, you will now have two blocks wanting to put their image onto the same cell.
Leading to the following two scenarios:
Block 0 finishes first, then block 10
Block 10 finishes first, then block 0
This is what is causing the "flashing".
Then imagine scrolling through 1000s of cells within seconds :)
Solution
You need to be able to cancel the queued block for your cell being reused.
I would use e.g. SDWebImage or FastImageCache.

How you said. You catch the image in another thread, in order to prevent freezing. And in a tableView the cells are reused, and while in one background thread you are catching the new photo, in the main thread the cell with the previous photo is returned.
Here in the code I fix it and better explanation:
customCell.customCellTextLabel.text = [NSString stringWithFormat:#"%#",feedO.title];
NSString *string = [NSString stringWithFormat:#"%#",feedO.body];
NSURL *urlString;
NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
NSArray *matches = [linkDetector matchesInString:string options:0 range:NSMakeRange(0, [string length])];
for (NSTextCheckingResult *match in matches) {
if ([match resultType] == NSTextCheckingTypeLink) {
urlString = [match URL];
NSLog(#"found Body URL: %# and title %#", urlString,feedO.title);
}
}
// Here you need, at least quit the previous imagen, is better if you implement
// and activity indicator (here start), and also you should implement some imagen cache.
customCell.customCellImageView.image = nil;
// Apart from here, go to another thread.
dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
dispatch_async(imageQueue, ^{
// this operation is slowly that return a cell, this happens after [1]
NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];
dispatch_async(dispatch_get_main_queue(), ^{
//If you implement and activity indicator stop here.
customCell.customCellImageView.image = [UIImage imageWithData: data];
});
});
// [1] the cell is returned before the image is ready.
return customCell;

Above answer is correct. But no need to download image in cell for row. you can download image before table loading occurs in viewWillAppear or ViewDidLoad which fits to your requirement.

When you scroll your table every time your image will download again. to prevent this i prefer you to get image from cache and set in your imageview directly. so as per your code. if you scroll from 0 index to 10 then cell will download image for all 10 cells. after that if you scroll again 10 to index 0 . then image will be download again.
you can use (https://github.com/rs/SDWebImage) to download image async. its very easy and fast.
i prefered this library because it will handle your cache. just write below code
#import "UIImageView+WebCache.h"
// in cellForRowAtIndexPath.
[customCell.customCellImageView sd_setImageWithURL: [NSURL URLWithString:urlString]];
remove below code.
dispatch_queue_t imageQueue = dispatch_queue_create("imageDownloader",nil);
dispatch_async(imageQueue, ^{
NSData *data = [[NSData alloc] initWithContentsOfURL:urlString];
dispatch_async(dispatch_get_main_queue(), ^{
customCell.customCellImageView.image = [UIImage imageWithData: data];
});
});
Maybe this will help you.

Related

NSDictionary constant looping

SO I have a program which calls the FlickR API, gets the URL's puts them into a dictionary and then assigns them into a table view, using an image view.
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
int countAttempts = 0;
[[self.flickr photosForUser:#"James Kinvig"]count];
for (int i = 0; i < [[self.flickr photosForUser:#"James Kinvig"]count]; i++) {
for(NSDictionary *dictionary in photos) {
countAttempts++;
NSString *farmId = [dictionary objectForKey:#"farm"];
NSString *serverId = [dictionary objectForKey:#"server"];
NSString *photoId = [dictionary objectForKey:#"id"];
NSString *secret = [dictionary objectForKey:#"secret"];
self.url= [NSURL URLWithString:[NSString stringWithFormat:#"http://farm%#.staticflickr.com/%#/%#_%#.jpg", farmId, serverId, photoId, secret]];
//NSLog(#"self.url = %#", self.url);
NSLog(#"count = %d", countAttempts);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:self.url];
dispatch_sync(dispatch_get_main_queue(), ^{
UIImage *img = [UIImage imageWithData:imgData];
cell.imageView.image = img;
[cell setNeedsLayout];
});
});
}
}
return cell;
}
This is the method it calls, photosForUser:
- (NSMutableArray *) photosForUser: (NSString *) friendUserName
{
NSString *request = [NSString stringWithFormat: #"https://api.flickr.com/services/rest/?method=flickr.people.findByUsername&username=%#", friendUserName];
NSDictionary *result = [self fetch: request];
NSString *nsid = [result valueForKeyPath: #"user.nsid"];
request = [NSString stringWithFormat: #"https://api.flickr.com/services/rest/?method=flickr.photos.search&per_page=%ld&has_geo=1&user_id=%#&extras=original_format,tags,description,geo,date_upload,owner_name,place_url", (long) self.maximumResults, nsid];
result = [self fetch: request];
return [result valueForKeyPath: #"photos.photo"];
}
Which does a fetch to the flickr API.
What is happening though is that is stuck in an eternal loop. Even with the for statement being less than the count, it still eternal loops. I have NSLog'd the count of the FlickR photos and it = 11.
This may have something to do with it, but whenever I press the button to take me to the table view controller, I get a HUGE lag, close to a minute, and nothing is being calculated (photo-wise) as I've done a count++
Thanks
let me understand this.. By the last line of your first block of code, I conclude that that is the uitableview dataSource method, cellForRowAtIndexPath.. what doesn't really makes sense.. you you have a fetch there, you have A loop inside a loop, that is setting many images (by download them) in one single imageView, and this is happening for all your visible cells at the same time. This will never work!
The solution is:
1 - remove this method from the cellForRow, this is not the place to request the images
2 - create another method that will fetch the content
3 - create a method that will do your loops and store the images on the array so you don't need to do that many times, only one..
4 - reload the tableview after you finish the step 3
5 - use the array of images that is already done to set your images by indexPath.row in your cell..
6 - I recommend you to use a Library for imageCache (i.e https://github.com/rs/SDWebImage)
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
for (int i = 0; i < [[self.flickr photosForUser:#"James Kinvig"] count]; i++)
{
for (NSDictionary *dictionary in photos)
{
You have two nested loops iterating over the same collection. This turns what should be an O(n) operation into O(n^2) and explains why your process is taking a very long time.
Since the loop bodies never use i, I would fix it by getting rid of the outer loop:
NSArray *photos = [self.flickr photosForUser:#"James Kinvig"];
for (NSDictionary *dictionary in photos)
{

Passing parameters makes the code run "slower"?

Background Information
Currently I'm setting the text for each UITableViewCell in my UITableView using the following code:
Scenario A:
cell.textLabel.attributedText = [news formattedSubject];
However, consider if I were to add a parameter for the formattedSubject definition, just a single integer parameter so the code is now:
Scenario B:
cell.textLabel.attributedText = [news formattedSubject:1];
The text in each table view cell is roughly 3-5 lines in length, and is read from an external source and parsed via JSON. Here's a diagram of the desired result, which is what happens in Scenario A:
Scenario A Flow Diagram:
Image A simply displays the default, empty UITableView that I get when the app is still loading the JSON data. After the app retrieves and parses this data, it then populates the data into the UITableView, which results in Image B. This is the desired (and expected) result.
However, if I add a parameter to formattedSubject, I instead get the flow diagram below:
Scenario B Flow Diagram:
Once again, Image A displays the default UITableView. However, it is what happens in Image B that is the problem. In Image B, the data has been parsed, but has not yet been formatted properly by formattedSubject, thus resulting in a single, horizontally-narrow, and lengthy row of unformatted text. After a fraction of a second, the app looks like Image C, the end result which displays the formatted data after it has been parsed.
My question:
The only change I made is the addition of a parameter to formattedSubject. That is, I changed -(NSAttributedString*)formattedSubject { to -(NSAttributedString*)formattedSubject:(int)state {. It doesn't matter that there is nothing within formattedSubject that actually uses the state integer, I'm still getting the results from Scenario B.
This change seems to make the code run more slowly. It creates a delay between when the data is parsed and when it is formatted and displayed in the UITableView. I'm curious as to why this is, and how I can fix/circumvent this issue.
Aside from being an aesthetics issue, what happens in Scenario B also interferes with my automatic loading of new data when the user reaches the end of the UITableView. Because of horizontally-narrowed rows of text, the last row of data will momentarily be displayed in the UITableView when it is first loaded, thus causing data to be retrieved twice upon app startup.
I am nowhere close to an expert in coding, and thus it makes absolutely no sense to me how simply adding a parameter to my NSAttributedString could create the aforementioned delay. I would be very appreciative if someone could:
Explain why this is happening, and
Offer a solution to resolve this issue.
Thank you very much for reading this, any and all comments/help is welcomed.
Edit 1: #Vijay-Apple-Dev.blogspot.com, #txulu
Here is my formattedSubject code:
-(NSAttributedString*)formattedSubject:(int)state {
if(formattedSubject!=nil) return formattedSubject;
NSDictionary *boldStyle = [[NSDictionary alloc] init];
if(state==1) {
boldStyle = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica-Bold" size:16.0],NSForegroundColorAttributeName:[UIColor colorWithRed:0.067 green:0.129 blue:0.216 alpha:1.0]};
}
else {
boldStyle = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica-Bold" size:16.0],NSForegroundColorAttributeName:[UIColor whiteColor]};
}
NSDictionary* normalStyle = #{NSFontAttributeName: [UIFont fontWithName:#"Helvetica" size:14.0]};
NSMutableAttributedString* articleAbstract = [[NSMutableAttributedString alloc] initWithString:subject];
[articleAbstract setAttributes:boldStyle range:NSMakeRange(0, subject.length)];
[articleAbstract appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n"]];
int startIndex = [articleAbstract length];
NSTimeInterval _interval=[datestamp doubleValue];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:_interval];
NSDateFormatter *_formatter=[[NSDateFormatter alloc]init];
[_formatter setDateFormat:#"MM/dd/yy"];
NSString* description = [NSString stringWithFormat:#"By %# on %#",author,[_formatter stringFromDate:date]];
[articleAbstract appendAttributedString:[[NSAttributedString alloc] initWithString: description]];
[articleAbstract setAttributes:normalStyle range:NSMakeRange(startIndex, articleAbstract.length - startIndex)];
formattedSubject = articleAbstract;
return formattedSubject;
}
Please note that as I said before, even if I don't actually use the state parameter, I still get the same results.
Here is my cellForRowAtIndexPath code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
News *news = newsArray[indexPath.row];
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
if([selectedIndexPath isEqual:indexPath]) {
cell.textLabel.attributedText = [news formattedSubject:1];
}
else {
cell.textLabel.attributedText = [news formattedSubject:0];
}
cell.textLabel.numberOfLines = 0;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
UIView *selectedBackgroundViewForCell = [UIView new];
[selectedBackgroundViewForCell setBackgroundColor:[UIColor colorWithRed:0.169 green:0.322 blue:0.525 alpha:1.0]];
cell.selectedBackgroundView = selectedBackgroundViewForCell;
cell.textLabel.highlightedTextColor = [UIColor whiteColor];
if (indexPath.row == [newsArray count] - 1) {
[self parseJSON];
}
return cell;
}
Please let me know if I can post anything else that may help.
Edit 2:
I'm not exactly sure if there is a performance issue. Upon further testing, I am inclined to believe that in Scenario A, the app loads and formats the cell data before displaying it, while in Scenario B, the app loads the data, displays it in the UITableViewCell, and then formats it, which creates the problem I detailed above.
Some people have brought up the code in my parseJSON method, so I'm posting it here for reference. As you can see I do indeed implement multithreading in order to prevent the data loading from lagging the application.
-(void)parseJSON
{
loading.alpha = 1;
loading.image = [UIImage imageNamed:#"loading.png"];
activityIndicator.alpha = 1;
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(checkLoading) userInfo:nil repeats:YES];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
parseNumber = parseNumber + 1;
int offset = parseNumber*20-1;
NSString *URLString = [NSString stringWithFormat:#"http://feedurl.com/feed.php?offset=%d",offset];
NSURL *url=[NSURL URLWithString:URLString];
NSData *data=[NSData dataWithContentsOfURL:url];
NSError* error;
if(data!=nil) {
json = [NSJSONSerialization JSONObjectWithData:data options: NSJSONReadingMutableContainers error: &error];
for(NSDictionary *newsInfo in json) {
News *newsList = [[News alloc] init];
newsList.thread = newsInfo[#"thread"];
newsList.author = newsInfo[#"author"];
newsList.subject = newsInfo[#"subject"];
newsList.body= newsInfo[#"body"];
newsList.datestamp = newsInfo[#"datestamp"];
[jsonTemp addObject:newsList];
}
newsArray = jsonTemp;
}
dispatch_sync(dispatch_get_main_queue(), ^{
if(data!=nil) {
[newsTable reloadData];
}
else {
activityIndicator.alpha = 0;
loading.image = [UIImage imageNamed:#"error.png"];
[self startTimer];
}
});
});
}
Edit:
Okay, there's a difference when calling [news formattedSubject] instead of [news formattedSubject:1]. The first one is like doing news.formattedSubject, this is, access the formattedSubject property that returns the ivar immediately, pretty fast. The second one calls the more complex formattedSubject: method that executes the code you posted, slower.
Original:
Your code seems fine except for some minor details like:
NSDictionary *boldStyle = [[NSDictionary alloc] init];
not being necessary because you assign just afterwards:
boldStyle = #{NSFontAttributeName ...}
Also, what I guess could be causing your problem is:
if (indexPath.row == [newsArray count] - 1) {
[self parseJSON];
}
Calling this inside your cellForRowAtIndexPath: could be a severe performance problem. If this method does a lot of work and does not do it in a background it could cause the delays you mention. As a rule of thumb, you should never do network/data processing in the main thread (cellForRowAtIndexPath will always be called in that thread by the system).
You says like below
"The text in each table view cell is roughly 3-5 lines in length, and is read from an external source and parsed via JSON. Here's a diagram of the desired result, which is what happens in Scenario A:"
I assume that you are reading data from
1.Local Core data Database
or
2.Web server's database.
For case 1, you should use NSFetchedResultsController, follow up this tutorial
https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html
http://www.raywenderlich.com/999/core-data-tutorial-for-ios-how-to-use-nsfetchedresultscontroller
For case 2 you should do in background thread,and update it by Main thread in tableview, when it is available, follow up this tutorial
How to load JSON asynchronously (iOS)

image not showing in UIImage from cached data

I cant possibly get the image that i parsed from the XML to show on my UIImageView using the code below. Am I doing something wrong because I checked it using NSLog to show if there is a link and apparently there is.
NSString *imageURL = [currentData.imageLink];
NSLog(#"this is link = %#", imageURL);
[cachedList addObject:imageURL];
[myCache setObject:cachedList forKey:#"imageURL"];
cachedList = [myCache objectForKey:#"imageURL"]; /where cachedList is NSMutableArray
for(id obj in cachedList){
NSLog(#"value = %#", obj); //to show value
cell.imageShow.image = [UIImage imageNamed:obj];
}
and also I tried doing the below code, but it gives me an error.
if (cachedList != nil) {
cell.imageShow.image = [UIImage imageNamed:[cachedList valueForKey:#"imageURL"]];
}
I think if you are using UITableView then this is the thing i have used and i prefer for tableview
Link: https://github.com/jakemarsh/JMImageCache

Iterating an NSMutableDictionary with UIImage not working

I am trying to add Images fetched from an external service to an NSMutableDictionary and seeing weird results. This is what I am doing:
- (void)fetchImages{
//Fetch Item Brand Images
//self.itemBrands is an NSArray of NSDictionaries
for (NSDictionary *itemBrand in self.itemBrands){
NSString *currentItemId = [itemBrand objectForKey:#"ITEM_ID"];
//Valid Item Id. This Log message is displayed
NSLog(#"Current Item Id: %#",currentItemId);
NSString *currentItemImageUrl = [[IMAGE_URL stringByAppendingString:currentItemId] stringByAppendingString:#".png"];
//Image URL is valid. This log message is displayed
NSLog(#"Current Image URL: %#",currentItemImageUrl);
NSURL *url = [NSURL URLWithString:currentItemImageUrl];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
if (image == nil){
//This log message is displayed when image is not present
NSLog(#"Image not Present 1");
}else{
//This log message is displayed when image is present
NSLog(#"Image Present 1");
[self.itemBrandImages setObject:image forKey:currentItemId];
}
}
//This for loop is not being executed at all. No log messages displayed.
for(id key in self.itemBrandImages){
NSLog(#"Current Item Id2: %#",key);
if ([self.itemBrandImages objectForKey:key] == nil){
NSLog(#"Image Not Present 2");
}else{
NSLog(#"Image Present 2");
}
}
}
The 2nd for loop where I am iterating over self.itemBrandImages is not being executed at all. None of the log messages inside are being displayed.
I tried the following before posting my issue here:
1) Researched similar problems in stack overflow and incorporated suggestion from one of them. The suggestion was "Perform an alloc init of the NSMUtableDictionary" in the init method of the .m file. This didn't help either.
2) To isolate the issue, I even tried adding a simple string to the NSMUtableDictionary instead of the image but even that does not seem to retained.
I am really confused as as to what I am missing or doing wrong here. Inputs are really appreciated.
Thanks,
Mike G
Perhaps:
for(NSString *key in [self.itemBrandImages allKeys])
I did an alloc init of the NSMutableDictianary right in my fetchImages method and it worked! Not sure why the alloc init in the init method did not work.
So here are my takeaways from this issue:
1) If you have an Array or dictionary #property that you are just getting and setting and not really adding or deleting objects to, then you don't need to explicitly alloc init them.
2) If you have an Array or dictionary #property that you are adding or deleting objects to ,you need to explicitly alloc init them.
Are my above statements true? Would love to hear your inputs on this.
Thanks,
Mike
New code:
- (void)fetchImages{
//Fetch Item Brand Images
self.itemBrandImages = [[NSMutableDictionary alloc] init];
for (NSDictionary *itemBrand in self.itemBrands){
NSString *currentItemId = [itemBrand objectForKey:#"ITEM_ID"];
NSLog(#"Current Item Id in ItemList: %#",currentItemId);
NSString *currentItemImageUrl = [[#"http://anythingtogo.freeiz.com/images/"stringByAppendingString:currentItemId] stringByAppendingString:#".png"];
NSLog(#"Current Image URL in ItemList: %#",currentItemImageUrl);
NSURL *url = [NSURL URLWithString:currentItemImageUrl];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
if (image == nil){
NSLog(#"Image not Present 1");
}else{
NSLog(#"Image Present 1");
[self.itemBrandImages setObject:#"Test" forKey:currentItemId];
}
}

Adding bounds to google maps search string causes nil results

I added a search box to my mapping app a little while back which was doing very simple address searching with minimal options.
http://maps.googleapis.com/maps/api/geocode/json?address=sydney+grove&sensor=true
I've just added bounds parameters using the current screen viewport like this
http://maps.googleapis.com/maps/api/geocode/json?address=sydney+grove&bounds=51.198083,-0.830125|51.799930,0.576125&sensor=true
it returns a result when pasted into a browser but always a nil result if entered in code (jsonResponse always equals nil)
-(void) doGeocodingBasedOnStringUsingGoogle:(NSString*) searchString {
GMSCoordinateBounds* bounds=[[self datasource] searchBounds];
//CREATE LOOKUP STRING
NSString *lookUpString = [NSString
stringWithFormat:#"http://maps.googleapis.com/maps/api/geocode/json?
address=%#&bounds=%f,%f|%f,%f&sensor=true",
searchString,
bounds.southWest.latitude,
bounds.southWest.longitude,
bounds.northEast.latitude,
bounds.northEast.longitude];
lookUpString = [lookUpString stringByReplacingOccurrencesOfString:#" "
withString:#"+"];
//SEARCH FOR RESULTS
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSData *jsonResponse = [NSData dataWithContentsOfURL:[NSURL URLWithString:lookUpString]];
if (jsonResponse) {
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonResponse options:kNilOptions error:&error];
self.searchResults = [jsonDict valueForKey:#"results"];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableview reloadData];
});
});
}
This code was fine before I added the bounds condition and is fine if I remove so I'm really out of ideas
I think you need to replace the | with a %7C, for example see here:
How to make an NSURL that contains a | (pipe character)?
As mentioned in the comments on the answer, you could look into using stringByAddingPercentEscapesUsingEncoding for a method to escape the URL for you (so eg you wouldn't need to replace spaces with a +, etc).

Resources