Search code examples
iosobjective-cuitableviewuilabeluiprogressview

UILabel over UIProgressView with multiple colors


So I haven't done this yet, I'm trying to play out how to do this. So I make a UITableView and each cell has an associated NSTimer. Now in each custom UITableViewCell I have a UIProgressView as the background, stretched to fill the cell. Now I want to add a UILabel with remaining time onto of the UIProgressView. But since the progress bar fill color and background color are dramatically different (navy blue progress fill and white background/non filled area), I'm wondering how to dynamically change the text color as the progress bar gets filled. Like the part of the UILabel that is on the navy blue fill, the text color should be white. The part that is on the white background, the text should be black. Something like this, but in objective-c.


Solution

  • Just hacked this up for you :)

    The Result

    Here's a simulator result of ZWProgressView:

    enter image description here

    The View Controller File

    Here's an example usage:

    #import "ViewController.h"
    #import "ZWProgressView.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        ZWProgressView *progressView = [[ZWProgressView alloc] init];
        progressView.frame = CGRectMake((self.view.bounds.size.width - 200) / 2.0, self.view.bounds.size.height / 2.0 - 25.0, 200, 50);
    
        progressView.progress = 0.47;
    
        [self.view addSubview:progressView];
    }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    The ZWProgressView Class

    Note: masking code taken from:

    Simply mask a UIView with a rectangle

    The header file

    #import <UIKit/UIKit.h>
    
    @interface ZWProgressView : UIView
    
    @property (nonatomic, assign) CGFloat progress;
    @property (nonatomic, strong) UIColor *normalTextColor;
    @property (nonatomic, strong) UIColor *maskedTextColor;
    
    @property (nonatomic, strong) UIView *container;
    @property (nonatomic, strong) UIView *progressBar;
    @property (nonatomic, strong) UILabel *progressLabel;
    @property (nonatomic, strong) UILabel *maskedProgressLabel;
    @property (nonatomic, strong) UIView *mask;
    
    @end
    

    The implementation file

    #import "ZWProgressView.h"
    
    @interface ZWProgressView()
    {
        NSLayoutConstraint *progressBarWidthConstraint;
        NSLayoutConstraint *progressBarMaskWidthConstraint;
    }
    
    @end
    
    @implementation ZWProgressView
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            // Initialization code
    
            self.frame = frame;
    
            [self initView];
            [self addAllConstraints];
        }
        return self;
    }
    
    -(void)initView
    {
        self.layer.cornerRadius = 2.0;
        self.backgroundColor = [UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.0];
    
        self.normalTextColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1.0];
        self.maskedTextColor = [UIColor whiteColor];
    
        self.container = [[UIView alloc] init];
        self.container.layer.borderWidth = 1.0;
        self.container.layer.borderColor = [UIColor grayColor].CGColor;
        self.container.backgroundColor = [UIColor whiteColor];
        self.container.layer.cornerRadius = 3.0;
        self.container.clipsToBounds = YES;
    
        self.progressBar = [[UIView alloc] init];
        self.progressBar.backgroundColor = [UIColor colorWithRed:0.2 green:0.3 blue:0.8 alpha:1.0];
    
        self.progressLabel = [[UILabel alloc] init];
        self.progressLabel.font = [UIFont fontWithName:@"Arial-BoldMT" size:30];
        self.progressLabel.textAlignment = NSTextAlignmentCenter;
        self.progressLabel.textColor = self.normalTextColor;
        self.progressLabel.clipsToBounds = YES;
    
        self.maskedProgressLabel = [[UILabel alloc] init];
        self.maskedProgressLabel.font = self.progressLabel.font;
        self.maskedProgressLabel.textAlignment = self.progressLabel.textAlignment;
        self.maskedProgressLabel.textColor = self.maskedTextColor;
        self.maskedProgressLabel.clipsToBounds = YES;
    
        self.mask = [[UIView alloc] init];
    
        [self.container addSubview:self.progressBar];
        [self.container addSubview:self.progressLabel];
        [self.container addSubview:self.maskedProgressLabel];
        [self.container addSubview:self.mask];
    
        [self addSubview:self.container];
    }
    
    -(void)addAllConstraints
    {
        self.container.translatesAutoresizingMaskIntoConstraints = NO;
        self.progressBar.translatesAutoresizingMaskIntoConstraints = NO;
        self.progressLabel.translatesAutoresizingMaskIntoConstraints = NO;
        self.maskedProgressLabel.translatesAutoresizingMaskIntoConstraints = NO;
        self.mask.translatesAutoresizingMaskIntoConstraints = NO;
    
        id views = @{@"container": self.container, @"progressBar": self.progressBar, @"progressLabel": self.progressLabel, @"maskedProgressLabel": self.maskedProgressLabel, @"mask": self.mask};
    
        // container constraint
    
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[container]-5-|" options:0 metrics:nil views:views]];
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-5-[container]-5-|" options:0 metrics:nil views:views]];
    
        // progressBar constraint
    
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[progressBar]" options:0 metrics:nil views:views]];
    
        progressBarWidthConstraint = [NSLayoutConstraint constraintWithItem:self.progressBar attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0];
    
        [self.container addConstraint:progressBarWidthConstraint];
    
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[progressBar]|" options:0 metrics:nil views:views]];
    
        // progressLabel constraint
    
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[progressLabel]|" options:0 metrics:nil views:views]];
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[progressLabel]|" options:0 metrics:nil views:views]];
    
        // maskedProgressLabel constraint
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[maskedProgressLabel]|" options:0 metrics:nil views:views]];
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[maskedProgressLabel]|" options:0 metrics:nil views:views]];
    
        // mask constraint
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mask]" options:0 metrics:nil views:views]];
        [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[mask]|" options:0 metrics:nil views:views]];
    
        progressBarMaskWidthConstraint = [NSLayoutConstraint constraintWithItem:self.mask attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0];
    
        [self.container addConstraint:progressBarMaskWidthConstraint];
    }
    
    -(void)setProgress:(CGFloat)progress
    {
        int percentage = progress * 100;
    
        NSString *strProgress = [[NSString alloc] initWithFormat:@"%d%%", percentage];
    
        self.progressLabel.text = strProgress;
        self.maskedProgressLabel.text = strProgress;
    
        // ------------------------------------------------------------------
        // subtracting 10 pixel for the |-5-[progressBar]-5-| padding in
        // the constraint for the progresBar
        // ------------------------------------------------------------------
        progressBarWidthConstraint.constant = progress * (self.bounds.size.width - 10.0);
        progressBarMaskWidthConstraint.constant = progressBarWidthConstraint.constant;
    
        [self layoutIfNeeded];
    
        [self updateMask];
    }
    
    -(void)updateMask
    {
        // ------------------------------------------------------------------------
        // Masking code taken from:
        //
        // https://stackoverflow.com/questions/11391058/simply-mask-a-uiview-with-a-rectangle
        // ------------------------------------------------------------------------
    
        CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
        CGRect maskRect = CGRectMake(0, 0, progressBarMaskWidthConstraint.constant, self.mask.bounds.size.height);
    
        CGPathRef path = CGPathCreateWithRect(maskRect, NULL);
    
        maskLayer.path = path;
    
        CGPathRelease(path);
    
        self.maskedProgressLabel.layer.mask = maskLayer;
    }
    
    @end