I m very new to iOS, as stated in the question above; im trying to do these 3 simple step.
Show Alert view
Do parsing stuff
Dismiss Alert
I was looking for something like we have in android i.e Pre Execute, doInBackground and Post Execute().
This is what i have tried.
parserAlert = [[UIAlertView alloc] initWithTitle:#"Loading" message:#"Please Wait" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[parserAlert show];
dispatch_queue_t queue = dispatch_queue_create("com.abc.testing", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue,^{
DBHandler *myDB= [[DBHandler alloc] init];
[myDB fetchResults];
dispatch_async(dispatch_get_main_queue(),^{
[parserAlert dismissWithClickedButtonIndex:0 animated:YES];
});
});
Below is the fetchResult method.
- (void) fetchResults
{
IParser *i = [[IParser alloc] init];
[i startParse];
AGParser *ag = [[AGParser alloc] init];
[ag startParse];
GParser *g = [[GParser alloc] init];
[g startParse];
HParser *h = [[HParser alloc] init];
[h startParse];
SParser *s = [[SParser alloc] init];
[s startParse];
}
This is startParse.
NSString *url = #"http://abcd.com/Service_URL/Service.asmx/GetNotes";
NSURL *nsUrl = [[NSURL alloc] initWithString:url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:nsUrl];
NSURLConnection *con = [[NSURLConnection alloc] initWithRequest:request delegate:self];
responseData = [[NSMutableData alloc] init];
[con start];
When i run the above code, Alerview show and dismiss within a second. Adding logs on methods i observed that fetchresults method return immediately and alert view gets dismiss. However fetchResults associated threads(Connection methods, Parser methods) keep executing but alerview is dismissed.
I need a guideline how to block the code until all associated methods are finished.
Thanks for your time.
I know this is not the answer you want, but don't use an alert view for this. A nice way to cover for time-consuming activity to is to put up a UIActivityIndicatorView, or a view that contains one, and set it spinning:
http://www.apeth.com/iOSBook/ch25.html#_uiactivityindicatorview
You can also prevent user interaction while the time-consuming activity is happening, with the shared application object's beginIgnoring... (and turn that off with endIgnoring... when you're done). Obviously you can't do that, though, if the user is to be given a Cancel button. In that case, cover everything else with an invisible view (clear background color) whose userInteractionEnabled is YES, so that it eats any touches intended for anything other than the button.
Also, it is almost never the right answer to use dispatch_sync. Once you've frozen the interface in the way I've just described, you can just do your connections (asynchronous) and parsing (on a background thread) and then come back into the main thread to dismiss the activity indicator.
Finally, you're going to want to leave yourself a way out in case things go wrong. You could run an NSTimer, for example.
EDIT: And now for the actual answer to your actual question, i.e. why is my code not pausing even though I used dispatch_sync: it's because [[NSURLConnection alloc] initWithRequest:request delegate:self] returns immediately; the networking is in yet another background thread. So your startParse returns, your fetchResults returns, and meanwhile the networking continues and the NSURLConnection delegate methods are called some time later.
Here is the link what you are looking for MBProgressHUD
First alloc the MBProgressHUD instance of it in the viewDidLoad
MBProgressHUD *progressHUD = [[MBProgressHUD alloc] initWithView:self.view];
progress.delegate=self;
[progressHUD showWhileExecuting:#selector(performBackgroundTask) onTarget:self withObject:nil animated:YES]
and in the background method
-(void)performBackgroundTask
{
//Do some stuff
}
and soon as the task in the )performBackgroundTaskmethod is completed the Activity indicator shown in the MBProgressHUD will hidden and the delegate method called
-(void)hudWasHidden
{
//Do additional stuff after completion of background task
}
Hope it will help you.
Related
In my modal UI there is a "DONE" button linked with IBAction -done:, it will upload a text to (lets say Dropbox server). Its code looks like this
- (IBAction)done:(id)sender {
// must contain text in textview
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
if (![_textView.text isEqualToString:#""]) {
// check to see if we are adding a new note
if (!self.note) {
DBFile *newNote = [[DBFile alloc] init];
newNote.root = #"dropbox";
self.note = newNote;
}
_note.contents = _textView.text;
_note.path = _filename.text;
// - UPLOAD FILE TO DROPBOX - //
NSLog(#"Initializing URL...");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSURL *url = [Dropbox uploadURLForPath:self.note.path];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"PUT"];
NSData *noteContents = [self.note.contents dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"Creating session task...");
NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request
fromData:noteContents
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *resp = (NSHTTPURLResponse *) response;
if (!error && resp.statusCode == 200) {
NSLog(#"OK");
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate noteDetailsViewControllerDoneWithDetails:self];
});
} else {
NSLog(#"Status code: %d", resp.statusCode);
}
}];
[uploadTask resume];
});
} else {
UIAlertView *noTextAlert = [[UIAlertView alloc] initWithTitle:#"No text"
message:#"Need to enter text"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[noTextAlert show];
}
}
The delegate method noteDetailsViewControllerDoneWithDetails: of this class is look like this
-(void)noteDetailsViewControllerDoneWithDetails:(NoteDetailsViewController *)controller{
// refresh to get latest
[self dismissViewControllerAnimated:YES completion:nil];
[self notesOnDropbox];}
(notesOnDropbox is a time-consuming task). When DONE button is tapped, I expect this modal VC/UI to dismiss immediately and it fetches data on background (by notesOnDropbox method). However, when I try tapping DONE button, my UI stop responding for about seconds, after that the modal UI is dismissed. I cannot figure out where I misuse the GCD. Please help me.
if you want to dismiss your modal VC/UI immediately, just ask the delegate to dismiss,
like is:
- (IBAction)done:(id)sender {
[self.delegate noteDetailsViewControllerDoneWithDetails:self];
// ...
}
In your sample code,
you do the dismiss action after the upload task completed, but the upload task is asynchronous.
and you ask the delegate to dismiss use GCD dispatch_async, this is asynchronous task, too.
After all, you have to consider the what time to do upload, who to do upload task and what time to invoke notesOnDropbox.
First, if notesOnDropbox is a time-consuming task, then you should not be performing it on the main thread (as you are doing). If it is sufficiently time-consuming and you do it on the main thread, the WatchDog process will kill your app dead right before the user's eyes.
Second, there is no need to get off the main thread to do an upload. If you use NSURLSession correctly, it will be asynchronous.
Your code only calls noteDetailsViewControllerDoneWithDetails when the whole upload task is completed, because that's how you wrote your code. Actually, the situation seems worse. If the upload task has any kinds of problems, noteDetailsViewControllerDoneWithDetails will never be called.
You need to call noteDetailsViewControllerDoneWithDetails as soon as possible, and then think about what you are going to do when the upload fails - which might easily happen a long time later.
I'm trying to show upload progress in UIAlertView. I have an API with my server subclass of AFHTTPClient and some code where I send progress info to my view
[operation setUploadProgressBlock:^(NSInteger bytesWritten,long long totalBytesWritten,long long totalBytesExpectedToWrite)
{
float progress = totalBytesWritten / (float)totalBytesExpectedToWrite;
request *svc = [[request alloc] init];
[svc setProgress:[NSNumber numberWithFloat:progress]];
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
When uploading process starts I create UIAlertView in my View
API file
if (uploadFile) {
[formData appendPartWithFileData:uploadFile name:#"file" fileName:#"photo.jpg" mimeType:#"image/jpeg"];
request *svcAlert = [[request alloc] init];
[svcAlert showProgressAlert];
}
View file
- (void) showProgressAlert{
progressBarAlert = [[UIAlertView alloc] initWithTitle: #"Идет загрузка"
message: #"0"
delegate: self
cancelButtonTitle: nil
otherButtonTitles: nil];
progressView = [[UIProgressView alloc] initWithProgressViewStyle: UIProgressViewStyleBar];
progressView.frame = CGRectMake (20, 20, 50, 30);
[progressBarAlert addSubview:progressView];
[progressBarAlert show];
}
Then in my View I'm also trying to change UIAlertView message parameter with setMessage but nothing happens. ProgressView doesn't show up in AlertView even thought I'm adding it with addSubview.
-(void) setProgress: (NSNumber *) progress {
progressView.progress = [progress floatValue];
//progressBarAlert.message = [NSString stringWithFormat:#"%#", progress];
[progressBarAlert setMessage:[NSMutableString stringWithFormat:#"Загрузка %#", progress]];
}
And the last question, how do I close UIAlertView without pressing cancel button?
Also I've tried to add subView to my view instead of using alertView, but app crashed with uncaught exception. Maybe someone could give me any advice about this task.
I will refer you to the Apple Documentation in regards to UIAlertView Class reference and specifically
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
What you are trying to do by doing [progressBarAlert addSubview:progressView]; isn't allowed and will get your app rejected from the Apple review process. In iOS7 addSubview: never calls the super on UIView so doesn't actually do anything. So I wouldn't recommend doing this at all. There are however custom alert views like https://github.com/wimagguc/ios-custom-alertview that you can use to get what you are after.
I want to start a task that runs on another thread "just in case it is needed" to minimize the time the user will have to wait on it later. If there is time for it to complete, the user will not have to wait, but if it has not completed then waiting would be necessary.
Something like, opening a database in viewDidLoad: that will be needed when and if the user pushes a button on the screen. If I wait to open the database until the user actually pushes the button there is a lag. So I want to open it early. Since I don't know how long it will take to open and I don't know how long until the user hits the button, I need a way of saying, if that other task has not completed yet then wait, otherwise just go ahead.
For example:
#implementation aViewController
- (void) viewDidLoad {
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ) NSLog( #"There was a problem opening the database" );
}];
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( ???DatabaseNotAvailableYet??? ) {
[self putSpinnerOnScreen];
???BlockProgressHereUntilDatabaseAvailable???
[self takeSpinnerOffScreen];
}
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
My hope is that there is never a spinner on the screen and never any delay for the user but, since I don't know how long it will take for the database connection to be established, I have to be prepared for it not being ready when the user pushes the button.
I can't use the call back for when openOrCreateDbWithCompletionHandler: completes since I don't want to do anything then, only when the user pushes the button.
I thought about using a semaphore but it seems like I would only signal it once (in the completion handler of the openOrCreateDbWithCompletionHandler: call) but would wait on it every time a button was pushed. That seems like it would only work for the first button push.
I thought about using dispatch_group_async() for openOrCreateDbWithCompletionHandler: then dispatch_group_wait() in goButtonTouched: but since openOrCreateDbWithCompletionHandler: does its work on another thread and returns immediately, I don't think the wait state would be set.
I can simply set a my own flag, something like before the openOrCreateDbWithCompletionHandler:, self.notOpenYet = YES;, then in its completion handler do self.notOpenYet = NO;, then in goButtonTouched: replace ???DatabaseNotAvailableYet??? with self.notOpenYet, but then how do I block progress on its state? Putting in loops and timers seems kludgy since I don't know if the wait will be nanoseconds or seconds.
This seems like a common enough situation, I am sure that you have all done this sort of thing commonly and it is poor education on my side, but I have searched stackOverflow and the web and have not found a satisfying answer.
I think, blocking execution is a bad habit unless you are building your own event loop, which is rarely necessary. You also don't need to do any GCD stuff in your case. Just get a feeling for async.
The following should work:
#implementation aViewController
- (void) viewDidLoad {
self.waitingForDB = NO;
self.databaseReady = NO;
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ){
NSLog( #"There was a problem opening the database" )
}else{
[self performSelectorOnMainThread:#selector(handleDatabaseReady) withObject:nil waitUntilDone:NO];
};
}];
}
- (void)handleDatabaseReady{
self.databaseReady = YES;
if(self.waitingForDB){
[self takeSpinnerOffScreen];
[self go];
}
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( !self.databaseReady ) {
self.waitingForDB = YES;
[self putSpinnerOnScreen];
else{
[self go];
}
}
-(void)go{
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
Performing the call to handleDatabaseReady on the main thread guarantees that no race conditions in setting/reading your new properties will appear.
I'd go with the flag. You don't want to block the UI, just show the spinner and return from the goButtonTouched. However, you do need to cancel the spinner, if it is active, in openOrCreateDbWithCompletionHandler:.
This is rather a simple scenario. You make a method that does the stuff. Lets call it doStuff. From main thread, you call performSelectorInBackgroundThread:#selector(doStuff). Do not enable the button by default. Enable it at the end of doStuff so that user won't tap on it until you are ready. To make it more appealing, you can place a spinner in the place of the button and then replace it with the button when doStuff completes.
There are a number of classes and APIs you can use to achieve this kind of thing. You can use NSThread with synchronization primitives like semaphores and events to wait for it to finish when the user actually presses the button. You can use an NSOperation subclass (with an NSOperationQueue), and you can use GCD queues.
I would suggest you take a look at some the information in the Concurrency Programming Guide from Apple.
In your case you would probably be best served adding the operation to a GCD background queue using dispatch_async in combination with a semaphore which you can wait on when the user taps the button. You can check out the question "How do I wait for an asynchronously dispatched block to finish?" for an example.
I have tried some ways to perform NSURLConnection when lock the screen but none of it works.
I have tried as following:
[self performSelectorInBackground:#selector(startConnection) withObject:nil];
I also tried:
dispatch_queue_t request_queue = dispatch_queue_create("com.app.download", NULL);
dispatch_async(request_queue, ^{
[self startConnection];
});
in startConnection:
- (void)startConnection{
... some URL processing
responseData_ = [[NSMutableData alloc] init];
connection_ =
[[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
}
The NSURLConnection delegate methods aren't called by this way.
What is the real code to make it works? Thanks!
A small update that may help:
It only calls this delegate method:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
with message:
A server with the specified hostname could not be found.
I am very sure my wi-fi is connected, still not sure why it is called :(
If you lock your screen, your app will be turn into background mode, not background running mode.
If you want to download while user locks the screen, you should check this method [UIApplication -beginBackgroundTaskWithExpirationHandler:]
I'm trying to pull images from the server for the scrollview. After the user zooms the view in or out, the image should be downloaded:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
Ymin=365000+375000*_scrollView.contentOffset.x/(scale*1024);
Ymax=365000+375000*(_scrollView.contentOffset.x/scale+1024/scale)/1024;
Xmin=6635000-260000*(_scrollView.contentOffset.y/scale+748/scale)/748;
Xmax=6635000-260000*_scrollView.contentOffset.y/(scale*748);
[self looYhendus]; //Creates NSURLConnection and downloads the image according to scale, Ymin, Ymax, Xmin and Xmax values
UIImage *saadudPilt=_kaardiPilt;
imageView=[[UIImageView alloc] initWithImage:saadudPilt];
imageView.frame=CGRectMake(_scrollView.contentOffset.x,_scrollView.contentOffset.y,1024,748);
[_scrollView addSubview:imageView];
}
On some occasions (I can't figure out, on what conditions), it works, but on some occasions NSURLConnection delegate methods won't get fired and the image set as the subview is still the image that is initially downloaded (when the application launches). Then, only after I touch the screen again (the scrollview scrolls), the NSLog message shows that the image is downloaded. What could be the reason of this kind of a behaviour?
EDIT: Added the NSURLConnection delegate methods. I've tried a few other ways but they all end up not executing the delegate methods. Which made me think that it's not about NSURConnection but rather UIScrollView (obviously, I can be wrong about this).
- (void)looYhendus
{
yhendused=CFDictionaryCreateMutable(
kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
NSString *aadress = [NSString stringWithFormat:#"http://xgis.maaamet.ee/wms-pub/alus?version=1.1.1&service=WMS&request=GetMap&layers=MA-ALUSKAART&styles=default&srs=EPSG:3301&BBOX=%d,%d,%d,%d&width=%d&height=%d&format=image/png",Ymin,Xmin,Ymax,Xmax,512,374];
NSURL *url = [[NSURL alloc] initWithString:aadress];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if( theConnection )
{
andmedServerist = [NSMutableData data];
CFDictionaryAddValue(
yhendused,
(__bridge void *)theConnection,
(__bridge_retained CFMutableDictionaryRef)[NSMutableDictionary
dictionaryWithObject:[NSMutableData data]
forKey:#"receivedData"]);
}
CFRunLoopRun();
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[andmedServerist setLength: 0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSMutableDictionary *connectionInfo =
(NSMutableDictionary*)objc_unretainedObject(CFDictionaryGetValue(yhendused, (__bridge void *)connection));
[[connectionInfo objectForKey:#"receivedData"] appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Ühenduse viga" message:#"Kõige tõenäolisemalt on kaardiserveril probleeme või puudub seadmel internetiühendus" delegate:self cancelButtonTitle:#"Sulge" otherButtonTitles:nil];
[alert show];
CFRunLoopStop(CFRunLoopGetCurrent());
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSMutableDictionary *connectionInfo =
(NSMutableDictionary*)objc_unretainedObject(CFDictionaryGetValue(yhendused, (__bridge void *)connection));
[connectionInfo objectForKey:#"receivedData"];
andmedServerist=[connectionInfo objectForKey:#"receivedData"];
_kaardiPilt = [UIImage imageWithData: andmedServerist];
CFDictionaryRemoveValue(yhendused, (__bridge void *)connection);
CFRunLoopStop(CFRunLoopGetCurrent());
}
EDIT: added this:
- (void)viewDidLoad
{
[super viewDidLoad];
Ymin=365000;
Ymax=740000;
Xmin=6375000;
Xmax=6635000;
[self looYhendus];
UIImage *saadudPilt=_kaardiPilt;
imageView=[[UIImageView alloc] initWithImage:saadudPilt];
imageView.frame=CGRectMake(0,0,1024,748);
[_scrollView addSubview:imageView];
[_scrollView setContentSize: CGSizeMake(1024, 748)];
_scrollView.minimumZoomScale = 1.0;
_scrollView.maximumZoomScale = 50.0;
_scrollView.delegate = self;
}
Why are you explicitly calling CFRunLoopRun(); and stopping it in connectionDidFinishLoading: or didFailWithError: methods ? (CFRunLoopStop(CFRunLoopGetCurrent());)
Assuming you are doing this on main thread. You are stopping mainthread's runloop. There can be timers, ScrollView uses events which stop responding because you stopped the main thread's runloop.
If you are calling NSURLConnection on the main thread you don't need to explicitly run it (or stop it). You can just schedule to run on current runloop which is main threads runloop. If you are doing it on a background thread, then your code seems valid (Although you shouldn't show UIAlertView in didFailWithError: if its called on separate thread).
Updated Answer(Based on #Shiim's comment):
In the viewDidLoad method you are calling [self looYhendus]; which returns immediately (as you are using Asynchronous URL Connection for the load). So its working as expected. Move [scrollView addSubview:imageView] to connectionDidFinishLoading: method which would add your downloaded imageView data to scrollView's subView once finished downloading it. Or you can consider using dispatch_queue's to create a thread and load the URL request synchronously then using dispatch_queue's main queue to dispatch drawing of imageView added as subView to ScrollView onto main thread.
My recommendation in your case would be redesigned approach using dispatch_queue's. Which would be give you better understanding of solving problem (in this scenario) and also improves your code readability.
I had the same problem recently. The issue was that I was putting my connection in an asynchronous thread, when connections are already asynchronous.
I found the solution here: NSURLConnection delegate methods are not called
There are also a few links to other people who had similar issues in that thread.
If I were you though, I would try simply using [theConnection start] and initializing the request with a set timeout so you don't have to worry about the background thread shutting out before the image is downloaded.
For example:
[request setTimeoutInterval:30];