i want to add multiple passbook passes by running through a array with URLs. The problem is that the loop counts faster than the view controller can present.
Here s my code:
NSArray *passURLArray = [NSArray new];
passURLArray = response;
for (int i = 0; passURLArray.count; i++) {
NSString *passURLString = [NSString stringWithFormat:#"http://test.de%#", [passURLArray objectAtIndex:i]];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:passURLString]];
NSError *error;
PKPass *pass = [[PKPass alloc] initWithData:data error:&error];
[[UIApplication sharedApplication] openURL:[pass passURL]];
PKAddPassesViewController *passVC = [[PKAddPassesViewController alloc] initWithPass:pass];
passVC.delegate = self;
[passVC setDelegate:(id)self];
[self presentViewController:passVC animated:YES completion:nil];
}
I get this error message:
Attempt to present PKAddPassesViewController: 0xca5f7d0 on
PaymentViewController: 0x14882290 which is waiting for a delayed
presention of PKAddPassesViewController: 0xb169470 to complete
Thanks in advance.
Check if you're on the last iteration of the loop. If you are, animate the display, if not, don't animate it.
That said, it's nasty from a user standpoint. You should probably think about a nicer way of presenting, like showing a list or animating between each display when addPassesViewControllerDidFinish: is called.
Related
I have a model object that has a class method that checks if the model object already exists, and if it does it returns it, or if it doesn't it creates it and then returns it. This class makes use of the VLC framework for generating data about video files and to generate a thumbnail. This is where I'm having trouble.
The VLCThumbnailer returns the thumbnail via a delegate method once it's fetchthumbnail method is called. The problem is that the delegate method doesn't get returned until AFTER my class-creation method reaches it's return function. Here's a code example.
-(AnimuProfile*)createnewProfileforFilename:(NSString*)filename{
NSURL *fileURL = [NSURL fileURLWithPath:filename];
VLCMedia *media = [VLCMedia mediaWithURL:fileURL];
FilenameParser *parser = [[FilenameParser alloc]init];
NSArray *parsedFilename = [parser parseFilename:[filename lastPathComponent]];
NSArray *mediaArray = [media tracksInformation];
if (mediaArray.count != 0) {
NSDictionary *videoTrackinfo = [mediaArray objectAtIndex:0];
_fansubGroup = parsedFilename[0];
_seriesTitle = parsedFilename[1];
_episodeNumber = parsedFilename[2];
_filename = [filename lastPathComponent];
_filepathURL = fileURL;
_filepathString = filename;
_watched = NO;
_progress = [VLCTime timeWithInt:0];
_length = [[media length]stringValue];
NSNumber *resolution = [videoTrackinfo valueForKey:#"height"];
_resolution = [NSString stringWithFormat:#"%#p",resolution];
VLCMediaThumbnailer *thumbnailer = [VLCMediaThumbnailer thumbnailerWithMedia:media andDelegate:self];
[thumbnailer fetchThumbnail];
NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *profileName = [[_filename lastPathComponent] stringByAppendingPathExtension:#"prf"];
NSString *pathandProfileName = [libPath stringByAppendingPathComponent:profileName];
[NSKeyedArchiver archiveRootObject:self toFile:pathandProfileName];
return self;
}
And then the delegate methods:
#pragma mark VLC Thumbnailer delegate methods
- (void)mediaThumbnailerDidTimeOut:(VLCMediaThumbnailer *)mediaThumbnailerP{
NSLog(#"Thumbnailer timed out on file %#",_filename);
UIImage *filmstrip = [UIImage imageNamed:#"filmstrip"];
_thumbnail = UIImagePNGRepresentation(filmstrip);
}
- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
UIImage *image = [UIImage imageWithCGImage:thumbnail];
_thumbnail = UIImagePNGRepresentation(image);
}
I know it's a nono to lock the main thread waiting for the delegate method to be called so what should be done in this instance?
I know it's a nono to lock the main thread waiting for the delegate
method to be called so what should be done in this instance?
Those delegate methods are being called on VLC's video processing thread. They aren't the main thread and, therefore, you shouldn't be calling random UIKit API directly in the return blocks.
You need to process the results when they are available. If VLC were implemented using modern patterns, it would be using completion blocks. But it isn't, so...
- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
{
dispatch_async(dispatch_get_main_queue(), ^{ ... process thumbnail and update UI accordingly here ...});
}
That is, your createnewProfileforFilename: method should start the processing, but not expect it to be finished until sometime later. Then, when that sometime later happens, you trigger the updating of the UI with the data that was processed in the background.
And, as you state, you should never block the main queue/thread.
I was able to solve it by creating a separate class to be the delgate, make thumbnail fetch requests and then handle them.
#property NSMutableArray *queue;
#end
#implementation ThumbnailWaiter
+(id)sharedThumbnailWaiter{
static ThumbnailWaiter *singletonInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singletonInstance = [[self alloc] init];
});
return singletonInstance;
}
-(id)init{
self = [super init];
if (self) {
NSMutableArray *queue = [NSMutableArray array];
_queue = queue;
}
return self;
}
-(void)requestThumbnailForProfile:(AnimuProfile*)profile{
VLCMedia *media = [VLCMedia mediaWithURL:profile.filepathURL];
VLCMediaThumbnailer *thumbnailer = [VLCMediaThumbnailer thumbnailerWithMedia:media andDelegate:self];
[_queue addObject:profile];
[thumbnailer fetchThumbnail];
}
#pragma mark VLC Thumbnailer delegate methods
- (void)mediaThumbnailerDidTimeOut:(VLCMediaThumbnailer *)mediaThumbnailerP{
}
- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
UIImage *image = [UIImage imageWithCGImage:thumbnail];
AnimuProfile *profile = _queue.firstObject;
profile.thumbnail = UIImagePNGRepresentation(image);
[profile saveProfile];
[_queue removeObjectAtIndex:0];
}
Seems almost silly to have to do it this way but it seems to be working.
I have been programming for 3 years on iOS, heavily using core data. However, I have never come across a crash like this before and have no idea why this is occurring. This current app is only 4 very simple view controllers, saving to nsdefaults only about 5 different times. The error I am confused on is "Slow defaults access for key ClientState took 0.037632 seconds, tolerance is 0.020000". I have noted where in my code it says the problem is in my code. Also, this is long after the view controller has loaded. This process occurs after a button press. Lastly, this crash only happens half the time, meaning there are occasions when the code actually works without a crash.
NSUserDefaults *ab = [NSUserDefaults standardUserDefaults];
NSString *frcrat = [ab objectForKey:#"frcrat"];
NSString *lapper = [alertView textFieldAtIndex:0].text;
spinner.hidden = NO;
[spinner startAnimating];
delem = NULL;
delem = [[NSMutableArray alloc] init]; //Line after this line gives error Thread 1: Exc_bad_access (code = 1, address=0xe0bb2f85)
NSString *urlString = [[NSString stringWithFormat:#"http://www.mywebsite.com/enum.php?fracat=%#&num=%#&sap=%#", frcrat, lapper, _sna]stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding ];
NSXMLParser *Parser = [[[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:urlString]] autorelease];
[Parser setDelegate:self];
[Parser parse];
I assume this code is placed somewhere in your view controller initialization.
The warning you're getting means that it's taking too long for the view controller to load, and this is clearly due to the initWithContentsOfURL: call in your code.
As you can read here, initWithContentsOfURL: is blocking, meaning that you should never call it on the main thread. You should perform the XML parser initialization asynchronously. Something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *urlString = [[NSString stringWithFormat:#"http://www.mywebsite.com/enum.php?fracat=%#&num=%#&sap=%#", frcrat, lapper, _sna]stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding ];
NSXMLParser *parser = [[[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:urlString]] autorelease];
[parser setDelegate:self];
[parser parse];
}
It was not even related to nsuser defaults. It was because of the string _sna. I instead saved that string in nsuserdefaults and reloaded it when I needed it as NSString *snameri. Thanks apple debugger for giving me an inappropriate error.
I'm experiencing a problem with my login system in my app.
When the app first opens, FVC is the main view controller. FVC then checks if I am logged in/if my session key is still valid, and if not, then it makes the LoginViewController pop up over my entire screen, forcing me to login to continue. Once I login with my right username and password, it checks quickly with a JSON file on the web and if it returns no error, it returns a session key. The problem is, I know it is correctly getting the JSON file and parsing it as I did some tests with NSLog but as soon as I login with the correct info, it dismisses the loginView and for half a second, shows the main view, then the loginView pops right back up! Something isn't right and I hope you can find the problem with my code. Michael.
First view controller:
- (void)viewDidAppear:(BOOL)animated
{
//Put login check here.
LoginViewController *login = [self.storyboard instantiateViewControllerWithIdentifier:#"login"];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// create the URL we'd like to query
[[NSURLCache sharedURLCache] removeAllCachedResponses];
myURL = [[NSURL alloc]initWithString:[NSString stringWithFormat:#"%#%#", #"https://URL/v1/?get&action=getservers&session_key=", login.sessionKey]];
// we'll receive raw data so we'll create an NSData Object with it
NSData *myData = [[NSData alloc]initWithContentsOfURL:myURL];
// now we'll parse our data using NSJSONSerialization
id myJSON = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:nil];
// typecast an array and list its contents
NSDictionary *jsonArray = (NSDictionary *)myJSON;
NSLog(#"%#",[jsonArray objectForKey:#"status"]);
if ([[jsonArray objectForKey:#"status"] isEqualToString:#"ERROR"]) {
[self presentViewController:login animated:NO completion:nil];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
Login view controller:
- (IBAction)loginAction:(id)sender {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// create the URL we'd like to query
NSURL *myURL = [[NSURL alloc]initWithString:[NSString stringWithFormat:#"%#%#%#%#", #"https://URL/v1/?get&action=login&username=", usernameField.text, #"&password=", passwordField.text]];
// we'll receive raw data so we'll create an NSData Object with it
NSData *myData = [[NSData alloc]initWithContentsOfURL:myURL];
// now we'll parse our data using NSJSONSerialization
id myJSON = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:nil];
// typecast an array and list its contents
//NSArray *jsonArray = (NSArray *)myJSON;
NSDictionary *jsonArray = (NSDictionary *)myJSON;
NSLog(#"%#",[jsonArray objectForKey:#"status"]);
if ([[jsonArray objectForKey:#"status"] isEqualToString:#"OK"]) {
FirstViewController *dashView = [self.storyboard instantiateViewControllerWithIdentifier:#"dashView"];
sessionKey = [jsonArray objectForKey:#"new_session_key"];
NSLog(#"%#",sessionKey);
[self dismissViewControllerAnimated:YES completion:nil];
} else {
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
I think the problem is the login.sessionKey. Do NSLog on it. It is probably nil. I don't see where you are setting it. That's probably you get an error from your webservice. Check it out.
I am trying to load data from web with few simple steps:
NSData *JSONData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"******"]];
NSObject *json = [JSONData objectFromJSONData];
NSArray *arrayOfStreams = [json valueForKeyPath:#"programs"];
NSDictionary *stream = [arrayOfStreams objectAtIndex:0];
NSString *str = [[NSString alloc]initWithString:[stream valueForKey:#"image"]];
NSURL *urlForImage1 = [NSURL URLWithString:str];
NSData *imageData1 = [NSData dataWithContentsOfURL:urlForImage1];
_screenForVideo1.image = [UIImage imageWithData:imageData1];
But the problem is I am doing 30 of this right after my application launches...
I want to load about 5 of them, and than load others. Because when I try to load all of them at the same time, my app is not launching all of them loaded...
Is there any way that I can load first few of them, and wait, and than load others?
As for loading them, you should probably display a spinner, start loading the image in background and then replace the spinner with the image once it’s ready.
- (void) viewDidLoad {
UIActivityIndicator *spinner = …;
[self.view addSubview:spinner];
[self performSelectorInBackground:#selector(startLoadingImage)
withObject:nil];
}
- (void) startLoadingImage {
// You need an autorelease pool since you are running
// in a different thread now.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [UIImage imageNamed:#"foo"];
// All GUI updates have to be done from the main thread.
// We wait for the call to finish so that the pool won’t
// claim the image before imageDidFinishLoading: finishes.
[self performSelectorOnMainThread:#selector(imageDidFinishLoading:)
withObject:image waitUntilDone:YES];
[pool drain];
}
- (void) imageDidFinishLoading: (UIImage*) image {
// fade spinner out
// create UIImageView and fade in
}
I'm essentially loading images from the server every time a user taps the refresh button. However, this loading mechanism freezes the main thread, so I decided to try and use GCD to load the images in a different thread. I've looked at tutorials, and it seems that I'm setting it up right, but I'm still encountering the same issue.
My code:
-(IBAction)refreshButton:(id)sender{
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
[[API sharedInstance] commandWithParams:[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"stream",#"command",#"0",#"IdImage",_contentFilter,#"contentFilter",
nil]
onCompletion:^(NSDictionary *json){
NSMutableArray *tempArray = [json objectForKey:#"result"];
NSMutableArray *tempDataArray = [[NSMutableArray alloc] initWithCapacity:[_imagesTemporaryHolder count]];
NSLog(#"loaded temporary image holder");
for (int i=0;i<[_images count];i++) {
NSLog(#" populating _imagesData");
NSString *imageID = [[tempArray objectAtIndex:i] objectForKey:#"IdImage"];
NSString *imageURL = [NSString stringWithFormat:#"http://swiphtapp.com/Swipht/upload/%#.jpg",imageID];
NSData *tempImageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:imageURL]];
[tempDataArray insertObject:tempImageData atIndex:i];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
});
});
}
Could it be that I need to go to the API class and change something there? I'm using AFNNETWORKING by the way.
Thank you so much for the help!
UPDATE 1:
It looks like the API's "onCompletion" block is what's causing the problem...I did the NSLog(isMainThread) check in it, and it returns YES meaning that it's running in the main thread although it shouldn't be as I've encapsulated it in the GCD. Thoughts on how to solve this?
ANSWERED THANKS TO AWESOME PEOPLE
#eric
The completion block of the API call is meant to be performed on the main thread, because it's where you would normally update the views based on the data received. Try placing your GCD stuff INSIDE the completion block instead of encapsulating the whole thing in it. It's taking so long on the main thread because you are getting the NSData from the url returned from your original API call, which can take a long time (relatively speaking)
Basically the GCD needed to be in the API call's completion block.
Thanks again!
I believe this is the answer we came to in the comments under the initial post:
-(IBAction)refreshButton:(id)sender
{
[[API sharedInstance] commandWithParams:[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"stream",#"command",#"0",#"IdImage",_contentFilter,#"contentFilter",
nil]
onCompletion:^(NSDictionary *json){
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSMutableArray *tempArray = [json objectForKey:#"result"];
NSMutableArray *tempDataArray = [[NSMutableArray alloc] initWithCapacity:[_imagesTemporaryHolder count]];
NSLog(#"loaded temporary image holder");
for (int i=0;i<[_images count];i++) {
NSLog(#" populating _imagesData");
NSString *imageID = [[tempArray objectAtIndex:i] objectForKey:#"IdImage"];
NSString *imageURL = [NSString stringWithFormat:#"http://swiphtapp.com/Swipht/upload/%#.jpg",imageID];
NSData *tempImageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:imageURL]];
[tempDataArray insertObject:tempImageData atIndex:i];
}
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
});
});
}];
}