2

I have the following code:

-(IBAction)showAlertView:(id)sender{

alertView = [[UIAlertView alloc] initWithTitle:@"Atualizando" message:@"\n"delegate:self cancelButtonTitle:nil otherButtonTitles:nil];

spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];   
spinner.center = CGPointMake(139.5, 75.5); // .5 so it doesn't blur
[alertView addSubview:spinner];
[spinner startAnimating];
[alertView show]; 
}


-(IBAction)getContacts:(id)sender {

[self showAlertView:(id)self];

ABAddressBookRef addressBook = ABAddressBookCreate( );
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople( addressBook );
CFIndex nPeople = ABAddressBookGetPersonCount( addressBook );

I want to show the alert before the rest of the IBAction begins, but im seeing the alertView only at the end of the IBAction. What am I doing wrong?

EDIT: i have:

-(IBAction)getContacts:(id)sender {

// display the alert view
[self showAlertView:self];

// do the synchronous operation on a different queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

ABAddressBookRef addressBook = ABAddressBookCreate( );
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople( addressBook );
CFIndex nPeople = ABAddressBookGetPersonCount( addressBook );

....

if ([contact length] == 8) {

            NSString *first = (NSString*)ABRecordCopyValue(person, kABPersonFirstNameProperty);
            NSString *last = (NSString*)ABRecordCopyValue(person, kABPersonLastNameProperty);
            NSString *phone = contact;
            ContactInfo *user1 = [[ContactInfo alloc] init];
            user1.first = first;
            user1.last = last;
            user1.phone = phone;
            user1.person = person;
            user1.phoneIdx = j;
            user1.book = addressBook;
            NSLog(@"phone is %@", phone);
            [secondViewController.users addObject:user1];
        }
        ABRecordSetValue(person, kABPersonPhoneProperty, mutablePhones, &error);
    }
}
bool didSave = ABAddressBookSave(addressBook, &error);
if(!didSave){
    NSLog(@"error!");
}
dispatch_async(dispatch_get_main_queue(), ^{
    [self hideAlertView]; // or however you want to do it
});

UIAlertView *alertAlmost = [[UIAlertView alloc] initWithTitle:@"Quase Pronto" message:@"Os seguintes contatos não tem código de área. Porfavor, selecione os contatos que você deseja adicionar o digito 9 e pressione Ok (caso não queira adicionar em nenhum, pressione Ok) " delegate:self cancelButtonTitle:@"Ok!" otherButtonTitles:nil];
[alertAlmost show];

[self presentViewController: secondViewController animated:YES completion: NULL];
 });
}

I want the alert to dismiss, and then i can call the table view. Any sugestions?

Matt S.
  • 13,305
  • 15
  • 73
  • 129
Daniel Ruhman
  • 123
  • 2
  • 13

3 Answers3

5

Showing a UIAlertView is done asynchronously, so if you call showAlertView: at the top of the method, it'll show the alert view and then return immediately after, then do the rest of your method.

If you want the rest of your method to happen after the alert view is dismissed, you need to add yourself as the alert view's delegate, then implement the method

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex

and do the rest of your stuff in there.


Edit: Okay, I think I get your problem. You're doing some time consuming synchronous operation on the main queue and that's blocking it so that the alert view isn't displayed until later.

You should move the time consuming operation to a different queue like so:

-(IBAction)getContacts:(id)sender {
    // display the alert view
    [self showAlertView:self];

    // do the synchronous operation on a different queue
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        ABAddressBookRef addressBook = ABAddressBookCreate( );
        CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople( addressBook );
        CFIndex nPeople = ABAddressBookGetPersonCount( addressBook );

        // once this operation has finished, you can hide the alert view like so:
        dispatch_async(dispatch_get_main_queue(), ^{
            [self hideAlertView]; // or however you want to do it
        });
    });
}
Anshu Chimala
  • 2,800
  • 2
  • 23
  • 22
  • I dont want that, I want the alert to show while the code runs, and after I dismiss – Daniel Ruhman Jul 19 '12 at 02:07
  • Isn't that exactly what asynchronous means? – ohr Jul 19 '12 at 02:10
  • What is the problem, exactly? You're trying to show an alert view while the other stuff happens (like a "loading..." view or something) but the other stuff happens first? I think your problem is that your address book calls are blocking your main thread and not letting it draw the UIAlertView. I'll update my answer in a sec. – Anshu Chimala Jul 19 '12 at 02:14
  • Perhaps `- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait` with `YES`? – ohr Jul 19 '12 at 02:16
  • Instead of `[self showAlertView:(id)self]` you'd call it `[self performSelectorOnMainThread:@selector(showAlertView:) withObject:(id) self waitUntilDone:YES];` – ohr Jul 19 '12 at 02:18
  • Updated my answer. Are you trying to load this data and then show it in a table view? If so, you should maybe consider not blocking the whole UI with a loading screen alert view. Instead, use GCD like I've shown above to do the loading in the background and then use `insertRowsAtIndexPaths:withRowAnimation:` to add the rows to the table. You can show a "loading cell" in your table while this is happening like many Twitter clients, etc, do. This is much more user friendly. – Anshu Chimala Jul 19 '12 at 02:22
  • So Anshu, I want to display the alert while it is beign displayed a bunch of contacts code is running, after it finishes running I want to dismiss the alert before i go into a table view – Daniel Ruhman Jul 19 '12 at 02:27
  • lol, I literally did it for you... What exactly don't you get? Edit: Yeah, that's what the above code does. It shows the alert view, then does the contacts code in the background, then after the contacts code has finished, it hides the alert (and you can reload your table view there as well) – Anshu Chimala Jul 19 '12 at 02:27
  • Sorry for beign stupid, its my first app.. Anyway im getting an error in the second dispatch, it says: expected ')' – Daniel Ruhman Jul 19 '12 at 02:31
  • Uh I don't see a missing paren anywhere... what line is it on? – Anshu Chimala Jul 19 '12 at 02:33
  • Ok, so I understand what is happening and ive put on my code. But inside the dispatch i have a code that declares my secondViewController(Table View) and i want to call this Table View only when the alert is dismissed. How do i do that? – Daniel Ruhman Jul 19 '12 at 02:35
  • The alert view shows up, but it doesnt dismiss and the table view isnt appearing – Daniel Ruhman Jul 19 '12 at 02:36
  • As Nate pointed out, `performSelectorInBackground:withObject:` is probably less confusing for you, so you should check out his answer below. Anyway, Can you post the code you're using to dismiss the alert view and reload the table view? – Anshu Chimala Jul 19 '12 at 02:41
  • What are you doing in `hideAlertView`? I assume `[alertView dismissWithClickedButtonIndex: animated:]`? – Anshu Chimala Jul 19 '12 at 02:48
  • I also have "UserViewController* secondViewController = [secondStoryboard instantiateViewControllerWithIdentifier:@"UserTable"]; secondViewController.viewC = self; secondViewController.users = [[NSMutableArray alloc] init]; " In the middle of the code, thats why i cant move the });(if I move it up, it wont recognize secondViewController) – Daniel Ruhman Jul 19 '12 at 02:48
  • Just changed the hideAlertView. The alert dismisses, but the tableView doesn't appear :( – Daniel Ruhman Jul 19 '12 at 02:53
  • You need to push the new view controller on the main queue, not in the background. (Any UI updates have to happen on the main queue). Put the new alert view and the `presentViewController` part inside main queue block, under the `[self hideAlertView]` line. – Anshu Chimala Jul 19 '12 at 02:55
  • Ive tried that, but it doesnt recognize secondViewController (it says it is an undeclared identifier) – Daniel Ruhman Jul 19 '12 at 02:57
0

You need to do the rest of the code in your "getContacts:" when your alert is dismissed. Set a delegate on your UIAlertView of "self" (the view controller showing the alert) and then do the "addressBook" stuff when the user clicks the button to dismiss the alert.

For example, implement the UIAlertViewDelegate method alertView:clickedButtonAtIndex: and do your address book stuff in there (I linked the documentation for you).

Michael Dautermann
  • 88,797
  • 17
  • 166
  • 215
  • I dont want that, I want the alert to show while the code runs, and after I dismiss – Daniel Ruhman Jul 19 '12 at 02:08
  • then detach a separate (background) thread and do your address book stuff in a separate method while your alert is visible and after it's dismissed. If this doesn't answer your question, your original question might be a little too confusing for us to figure out. ;-) – Michael Dautermann Jul 19 '12 at 02:15
0

I think you're saying that you want to popup an alert, like a progress indicator, and while that alert is up, you want to start your other process. Currently, you request to have the alert shown immediately, but as the other answers have said, that call is asynchronous, and the UI thread does not manage to get the alert displayed before starting on the other work.

You could try this:

-(IBAction)getContacts:(id)sender {

    [self showAlertView:(id)self];
    [self performSelectorInBackground: @selector(initAddressBook) withObject: nil];
}

-(void)initAddressBook {
    ABAddressBookRef addressBook = ABAddressBookCreate( );
    CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople( addressBook );
    CFIndex nPeople = ABAddressBookGetPersonCount( addressBook );
}

If there's some problem with running the address book work in the background, you might even be able to give the UI thread enough time to get the alert posted with something like this:

-(IBAction)getContacts:(id)sender {

    [self showAlertView:(id)self];
    [self performSelector: @selector(initAddressBook) withObject: nil afterDelay: 0.1f];
}
Nate
  • 31,017
  • 13
  • 83
  • 207
  • This is probably more Cocoa-ish than my GCD solution, but my GCD solution has the advantage of readability in the sense that everything happens in a linear fashion in one method rather than jumping around. (In this case, you'd have to make another method to dismiss the alert view and use another `performSelector...` call). This will totally work though. – Anshu Chimala Jul 19 '12 at 02:25
  • @Anshu, I **totally** disagree that GCD is more readable. I use GCD myself all the time, and it's a nice tool to have in the toolkit. However, when posting answers to questions that appear to be posed by more beginning-level Obj-C programmers, I think it's advisable **not** to steer them into GCD unless there's a need for what GCD provides, above and beyond the legacy Obj-C interfaces. And this question doesn't pose such a need. – Nate Jul 19 '12 at 02:32
  • I personally find it more readable but I suppose that's a matter of opinion. I definitely agree that it wasn't appropriate for someone this new to ObjC though and I wasn't really thinking about that when I wrote my answer, so thanks for pointing that out. – Anshu Chimala Jul 19 '12 at 02:40
  • @Anshu, you're right ... readability is always going to be a little bit subjective. I don't think your answer was inappropriate. And I could be wrong about the poster being new to Obj-C (although I bet he is). That's the beauty of stack overflow. There's usually more than one way to solve the problem, and it's good to get answers that show you what all your options are. Anyway, I didn't post my answer because I think GCD is a **bad** choice ... at the time I wrote it up, you still hadn't added your edit, so the original problem was still unsolved ... that's all :) – Nate Jul 19 '12 at 06:03