Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
294 views
in Technique[技术] by (71.8m points)

objective c - The exact moment iOS takes the view snapshot when entering background?

I have a problem when putting my iPhone app to background by pushing the exit button, and then relaunching by tapping the launch icon on the home screen: the app's view does return to its initial state like I want it to, but before that it flashes the earlier, wrong view state onscreen briefly.

Background

My main view consists basically of a sequence of interlinked UIAnimateWithDuration calls. The behavior I want whenever any interruption occurs, is to reset the animation to its initial state (unless the animations have all finished and the app has entered the static final phase), and start over from there whenever the app returns to active and visible state.

After studying the subject I learned I need two types of interruption handling code to provide good ux: "instant" and "smooth". I have the method resetAnimation that resets the view properties to the initial state instantly, and the method pauseAnimation that animates quickly to the same state, with an additional label stating "paused" fading in on the top of the view.

Double clicking exit button

The reason for this is the "double clicking exit button" use case, that actually does not hide your view or put you in the background state, it just scrolls up a bit to show the multitasking menu at the bottom. So, resetting the view state instantly in this case just looked very ugly. The animated transition and telling the user you're paused seemed like a better idea.

This case works nice and smootly by implementing the applicationWillResignActive delegate method in my App Delegate and calling pauseAnimation from there. I handle returning from that multitasking menu by implementing the applicationDidBecomeActive delegate method and calling from there my resumeAnimation method, that fades out the "paused" label if its there, and starts my animation sequence from the initial state.

This all works fine, no flickering anywhere.

Visiting flipside

My app's built over the Xcode "utility" template, so it has a flipside view to show info/settings. I handle visiting the flipside and returning back to the main view by implementing these two delegate methods in my main view controller:

  • (void)viewDidDisappear:(BOOL)animated

  • (void)viewDidAppear:(BOOL)animated

I call my resetAnimation in the viewDidDisappear method and resumeAnimation in viewDidAppear. This all works fine, the main view is its initial state from the very beginning of the transition to visible state - no unexpected flashing of wrong animation states of anything. But:

Pushing exit button and relaunching from my app icon (the buggy part!)

This is where the trouble starts. When I push exit button once and my app begins its transition to background, two things happen. First, applicationWillResignActive gets called here too, so my pauseAnimation method launches also. It wouldn't need to, since the transition doesn't need to be smooth here – the view just goes static, and "zooms out" to reveal the home screen – but what can you do? Well, it wouldn't do any harm either if I just could call resetAnimation before the exact moment that the system takes the snapshot of the view.

Anyways, secondly, applicationDidEnterBackground in the App Delegate gets called. I tried to call resetAnimation from there so that the view would be in the right state when the app returns, but this doesn't seem to work. It seems the "snapshot" has been taken already and so, when I tap my app launch icon and relauch, the wrong view state does flash briefly on the screen before the correct, initial state shows. After that, it works fine, the animations go about like they're supposed to, but that ugly flicker at that relaunch moment won't go away, no matter what I try.

Fundamentally, what I'm after is, what exact moment does the system take this snapshot? And consequently, what would be the correct delegate method or notification handler to prepare my view for taking the "souvenir photo"?

PS. Then there's the default.png, which doesn't seem to only show at first launch, but also whenever the processor's having a hard time or returning to the app is delayed briefly for some other reason. It's a bit ugly, especially if you're returning to your flipside view that looks totally different from your default view. But this is such a core iOS feature, I'm guessing I shouldn't even try to figure out or control that one :)


Edit: since people were asking for actual code, and my app has already been released after asking this question, I'll post some here. ( The app's called Sweetest Kid, and if you want to see how it actually works, it's here: http://itunes.apple.com/app/sweetest-kid/id476637106?mt=8 )

Here's my pauseAnimation method – resetAnimation is almost identical, except its animation call has zero duration and delay, and it doesn't show the 'Paused' label. One reason I'm using UIAnimation to reset the values instead of just assigning the new values is that for some reason, the animations just didn't stop if I didn't use UIAnimation. Anyway, here's the pauseAnimation method:

    - (void)pauseAnimation {
    if (currentAnimationPhase < 6 || currentAnimationPhase == 255) { 
            // 6 means finished, 255 is a short initial animation only showing at first launch
        self.paused = YES;
        [UIView animateWithDuration:0.3
                              delay:0 
                            options:UIViewAnimationOptionAllowUserInteraction |
         UIViewAnimationOptionBeginFromCurrentState |
         UIViewAnimationOptionCurveEaseInOut |
         UIViewAnimationOptionOverrideInheritedCurve |
         UIViewAnimationOptionOverrideInheritedDuration
                         animations:^{
                             pausedView.alpha = 1.0;
                             cameraImageView.alpha = 0;
                             mirrorGlowView.alpha = 0;
                             infoButton.alpha = 1.0;
                             chantView.alpha = 0; 
                             verseOneLabel.alpha = 1.0;
                             verseTwoLabel.alpha = 0; 
                             verseThreeLabel.alpha = 0;
                             shine1View.alpha = stars1View.alpha = stars2View.alpha = 0;
                             shine1View.transform = CGAffineTransformIdentity;
                             stars1View.transform = CGAffineTransformIdentity;
                             stars2View.transform = CGAffineTransformIdentity;
                             finishedMenuView.alpha = 0;
                             preparingMagicView.alpha = 0;}
                         completion:^(BOOL finished){
                             pausedView.alpha = 1.0;
                             cameraImageView.alpha = 0;
                             mirrorGlowView.alpha = 0;
                             infoButton.alpha = 1.0;
                             chantView.alpha = 0; 
                             verseOneLabel.alpha = 1.0;
                             verseTwoLabel.alpha = 0; 
                             verseThreeLabel.alpha = 0;
                             shine1View.alpha = stars1View.alpha = stars2View.alpha = 0;
                             shine1View.transform = CGAffineTransformIdentity;
                             stars1View.transform = CGAffineTransformIdentity;
                             stars2View.transform = CGAffineTransformIdentity;
                             finishedMenuView.alpha = 0;
                             preparingMagicView.alpha = 0;
                         }];
        askTheMirrorButton.enabled = YES; 
        againButton.enabled = NO;
        shareOnFacebookButton.enabled = NO;
        emailButton.enabled = NO;
        saveButton.enabled = NO;
        currentAnimationPhase = 0;
        [[cameraImageView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; // To remove the video preview layer
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The screenshot is taken immediately after this method returns. I guess your -resetAnimation method completes in the next runloop cycle and not immediately. I've not tried this, but you could try to let the runloop run and then return a little bit later:

- (void) applicationDidEnterBackground:(UIApplication *)application {
    // YOUR CODE HERE

    // Let the runloop run for a brief moment
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}

I hope this helps, Fabian


Update: -pauseAnimation and -resetAnimation distinction

Approach: Delay the animation happening in -applicationWillResignActive: and cancel the delayed animation in -applicationDidEnterBackground:

- (void) applicationWillResignActive:(UIApplication *)application {
    // Measure the time between -applicationWillResignActive: and -applicationDidEnterBackground first!
    [self performSelector:@selector(pauseAnimation) withObject:nil afterDelay:0.1];

    // OTHER CODE HERE
}

- (void) applicationDidEnterBackground:(UIApplication *)application {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(pauseAnimation) object:nil];

    // OTHER CODE HERE
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...