ios: UIAlertView show immediately before for loop executes

ぃ、小莉子 提交于 2019-12-11 06:35:54

问题


I have the following code:

 UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Loading Content For the First Time..."
                                                   message:@"\n"
                                                  delegate:self
                                         cancelButtonTitle:nil
                                         otherButtonTitles:nil];

            UIActivityIndicatorView *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];

            for (TCMLevelRemote *level in [obj objectForKey:@"levels"]){
                [[TCMExhibitFeedStore sharedStore] createLevel:level];
            }
            [[TCMExhibitFeedStore sharedStore] loadAllLevels];
            [[TCMExhibitFeedStore sharedStore] setAllLevels:[[TCMExhibitFeedStore sharedStore] storedLevels]];
            [alertView dismissWithClickedButtonIndex:0 animated:YES];

The for loops takes a while to execute because it downloads some information for the first time the app runs. So I want this notification to show so the user doesn't sit waiting at an unresponsive screen. The problem is the alertview doesn't show until the for loop ends. Then it just goes away right away. What do I need to change?


回答1:


Declare your alert-view Object in .h class for using everywhere in .m class.

Put your for-loop code in performSelectorInBackground for running loop in Backgroud so you Alertview Not waiting for Your ForLoop finishing.

 [self performSelectorInBackground: @selector(LoadForLoop) withObject: nil];

-(void)LoadForLoop
{
 for (TCMLevelRemote *level in [obj objectForKey:@"levels"]){
                [[TCMExhibitFeedStore sharedStore] createLevel:level];
            }
            [[TCMExhibitFeedStore sharedStore] loadAllLevels];
            [[TCMExhibitFeedStore sharedStore] setAllLevels:[[TCMExhibitFeedStore sharedStore] storedLevels]];
            [alertView dismissWithClickedButtonIndex:0 animated:YES];

}

Other solution

You can also use Grand Central Dispatch (GCD) like bellow as per your code:-

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Loading Content For the First Time..."
                                                   message:@"\n"
                                                  delegate:self
                                         cancelButtonTitle:nil
                                         otherButtonTitles:nil];

            UIActivityIndicatorView *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];



dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          for (TCMLevelRemote *level in [obj objectForKey:@"levels"]){
                [[TCMExhibitFeedStore sharedStore] createLevel:level];
            }
            [[TCMExhibitFeedStore sharedStore] loadAllLevels];
            [[TCMExhibitFeedStore sharedStore] setAllLevels:[[TCMExhibitFeedStore sharedStore] storedLevels]];

        dispatch_async(dispatch_get_main_queue(), ^{
            [spinner StopAnimating];
            [alertView dismissWithClickedButtonIndex:0 animated:YES];
        });
    });



回答2:


I think what you're looking for is to create your 'levels' while your alert view shows an activity indicator to the user.

Right now you're running your for loop on the same thread as your UI code. Your code will run in sequence, line after line. On iOS and Mac OS, the thread's run loop must be given room to breath to allow for render and timing events, in this case for animation. By blocking the run loop until your for loop ends, your UIAlertView will not have time to animate itself in until after the loop, and then your dismissWithClickedButtonIndex:animated: call immediately hides it.

What you want to do is shift your processing to the background, using something like Grand Central Dispatch:

// Perform all of your UI on the main thread:
// ... set up your alert views, etc

// Then shift your logic to a background thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    // This block is executed on a background thread, so will not block the UI:
    for (TCMLevelRemote *level in [obj objectForKey:@"levels"]){
        [[TCMExhibitFeedStore sharedStore] createLevel:level];
    }
    [[TCMExhibitFeedStore sharedStore] loadAllLevels];
    [[TCMExhibitFeedStore sharedStore] setAllLevels:[[TCMExhibitFeedStore sharedStore] storedLevels]];

    // Finally, now that your background process is complete, you can update the interface accordingly by dismissing the alert view:
    dispatch_async(dispatch_get_main_queue(), ^{
        [alertView dismissWithClickedButtonIndex:0 animated:YES];
    });
});

When dealing with background threads, it is important to note that UI events must be performed back on the main thread.

I like to package my tasks up into NSOperation subclasses, which both helps to separate the UI from the model logic, and also handles GCD for me. I'll leave that as an exercise for you.

A side note regarding your choice of UI: alert views are not meant for notifying the user of some process. They are meant for alerting the user that a single event has occurred. I would instead advise the use of something like MBProgressHUD, especially as it has built in support for GCD methods with doSomethingInBackgroundWithProgressCallback:completionCallback:.




回答3:


The alert view is not displayed because with your loop you are blocking the main thread, which is the thread that must draw the alert view.

In the place where you originally have your code, write this:

// Your original code that creates and sets up the alertView
UIAlertView* alertView = ...

// Add this snippet
NSTimeInterval delay = 0.1;  // arbitrary small delay
[self performSelector:@selector(delayedLoop:) withObject:alertView afterDelay:delay];

// Show the alert. Because the delayedLoop: method is invoked
// "a little bit later", the main thread now should be able to
// display your alert view
[alertView show];

Add this method to your class:

- (void) delayedLoop:(UIAlertView*)alertView
{
  // Add your code that runs the loop and dismisses the alert view
}

This solution is a bit "hackish", but since your loop is still running in the main thread context you won't have any threading problems. If you are willing to execute your loop in a secondary thread, then you should have a look at Nithin Gohel's answer.



来源:https://stackoverflow.com/questions/17306039/ios-uialertview-show-immediately-before-for-loop-executes

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!