Tuesday, October 1, 2013

Grey Out UI with Animating Indicator

One of the function which most of code needs is blocking user out of any interaction when the app needs serious being alone for a few seconds. This function could be found in a lot of published iOS Application, especially when App needs to refresh, temporary short time download/upload, server synchronization, etc. At these moments, the developer would not expect user touch anything to break the app down until it's finished. So, here comes this demo to show you how to make a screen blocker with an spinning indicator.


Several Prerequisites needs to be confirmed: 

We have two components:

  • Blocker, color: grey. (Name: overlayView, Class: UIView)
  • Indicator, color: white. (Name: indicatorView, Class: UIActivityIndicatorView)
1st, What Area Needs to Be Blocked.
        What area should block covered. For this demo, we will use the whole screen as an example, which not just it's very simple, also it's very common to be used. 


    CGRect rect = [UIScreen mainScreen].bounds;
    rect.size.height = MAX(rect.size.height, rect.size.width);
    rect.size.width = MAX(rect.size.height, rect.size.width);
    self.overlayView = [[UIView alloc] initWithFrame:rect];

2nd, What is the Transparency this Blocker Will Be
        Using Transparency could help user be aware of the status of theirs: "Being blocked out, while it won't be long". This could make user feel less confusing about what the app is doing, or what's going on with the app, the user will be more patient if appropriate transparency is used. (Some programmer use real-time updated progress information to make user less confusing and more patient) Here, I'll use 50% transparency to cover our block area.

    self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];

3rd, Starts the Animation of Indicator.
So, the code looks like:

- (void)startSpinning
{
    if (self.indicatorView) {
        CGRect rect = self.indicatorView.superview.bounds;
        self.indicatorView.center = CGPointMake(rect.size.width / 2, rect.size.height / 2);
        [self.indicatorView startAnimating];
    }
}

- (void)stopSpinning
{
    if (self.indicatorView) {
        [self.indicatorView stopAnimating];
    }
}

- (void)drawView
{
    CGRect rect = [UIScreen mainScreen].bounds;
    rect.size.height = MAX(rect.size.height, rect.size.width);
    rect.size.width = MAX(rect.size.height, rect.size.width);
    self.overlayView = [[UIView alloc] initWithFrame:rect];
    self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
    self.indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    [self.view addSubview:self.overlayView];
    [self.view addSubview:self.indicatorView];
    [self startSpinning];
}

- (void)removeView
{
    [self stopSpinning];
    [self.overlayView removeFromSuperview];
    self.indicatorView = nil;
}

We could start blocking by calling drawView, while stop it by calling removeView.

Here, one minor thing needs your attention, the center position of indicatorView.
Usually people believe they could use self.view.center as their new center position, no matter if screen's orientation is portrait or landscape. But it's a wrong assumption I have to say. My Testing data shows on iPad 6.0 simulator:  
Portrait: self.view Center (384.000 502.000), Bounds (768.000 1004.000)
Landscape: self.view Center (394.000 512.000), Bounds (1024.000 748.000)
Which is not exactly what we presume. So we need to calculate the center of screen every time before starting animation of indicator. This will be very useful when the app allows user switch orientation by rotating the iOS device around. So, every time the user rotate the screen when the indicator is still animating, the code needs to stop animation first, then recalculate the position of new screen center, starting the animation again at last.

Here is the code.
// This function is called when rotation is finished.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    NSLog(@"To (%.3f %.3f)", self.view.center.x, self.view.center.y);
    NSLog(@"To Bounds (%.3f %.3f)", self.view.bounds.size.width, self.view.bounds.size.height);
    [self startSpinning];
}

// This function is called when rotation is not started yet.
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { NSLog(@"From (%.3f %.3f)", self.view.center.x, self.view.center.y); NSLog(@"From Bounds (%.3f %.3f)", self.view.bounds.size.width, self.view.bounds.size.height); [self stopSpinning]; }

Summary:

Implementing Block with animating indicator could be:

  1. Init the Block View;
  2. Add Block View to the Should-Be-Covered Area.
  3. Init the Indicator;
  4. Add Indicator to the  Should-Be-Covered Area.
  5. Start Indicator.
  6. Stop Indicator if Task finished.
Key Points:
  • Take the Right Center Position for Indicator from Covered Area.
  • Recalculate the Center Position every time the Area dimension changed.
  • Re-animating the Indicator if it's center position is changed.

The clonable Git rep can be found here.

No comments :

Post a Comment