OK, that was harder than I initially thought
Throughout the system unit flags are used to define dates as components and so on. This unit flaw can be combined in just one value, a bit mask. I use it for my code.
The year unit might be 0....000010
The month unit might be 0....000100
bitmask for both would be
0....000010 NSCalendarUnitYear
0....000100 NSCalendarUnitMonth
------------
& 0....000110
So to save the information that one unit is to be used mean we just have to set the appropriate bit to 1
- (void)viewDidLoad
{
[super viewDidLoad];
self.selectedUnitsBitmask= 0;
self.unitNames = @[ @"Year", @"Month", @"Week", @"Day", @"Hour", @"Minute", @"Second"];
self.units = @[@(NSCalendarUnitYear), @(NSCalendarUnitMonth), @(NSCalendarUnitWeekOfMonth), @(NSCalendarUnitDay), @(NSCalendarUnitHour), @(NSCalendarUnitMinute), @(NSCalendarUnitSecond)];
NSDateComponents *c = [[NSDateComponents alloc] init];
c.year = 2014;
c.month = 12;
c.day = 25;
self.futureDate = [[NSCalendar currentCalendar] dateFromComponents:c];
}
The values of self.unitNames
are used to populate the table view.
self.units
store the unit flag for each available unit.
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier =@"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = _unitNames[indexPath.row];
NSUInteger s;
NSUInteger row = indexPath.row;
s = NSIntegerMax & [self.units[row] integerValue];
cell.accessoryType = (self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
return cell;
}
If a cell is selected, s = NSIntegerMax & [self.units[row] integerValue];
will check in the bit mask, if this unit is to be used.
If a cell is selected, we want to toggle the bit for the unit it represents
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger s;
NSUInteger row = indexPath.row;
s = NSIntegerMax & [self.units[row] integerValue];
self.selectedUnitsBitmask ^= s ;
NSLog(@"%lu, %lu", (unsigned long)s, (unsigned long)self.selectedUnitsBitmask);
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType =(self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
[self updateCountDownLabel:nil];
}
Here
NSUInteger s;
NSUInteger row = indexPath.row;
s = NSIntegerMax & [self.units[row] integerValue];
s
represents the bit that is selected, and
self.selectedUnitsBitmask ^= s ;
will toggle it in the bitmask. bitmask ^= s
is the short form of bitmask = bitmask ^ s
, where ^
is the exclusive or: the nth bit of the bitmask has to be the opposite of the nth bit of s
0011 bitmask
^ 0101 s
----
= 1100
Now we can update the string that will print the time difference according to the unit flags found in the bitmask:
- (IBAction)updateCountDownLabel:(id)sender {
BOOL includeYear = self.selectedUnitsBitmask & NSCalendarUnitYear;
BOOL includeMonth = self.selectedUnitsBitmask & NSCalendarUnitMonth;
BOOL includeDay = self.selectedUnitsBitmask & NSCalendarUnitDay;
BOOL includeHour = self.selectedUnitsBitmask & NSCalendarUnitHour;
BOOL includeMinute= self.selectedUnitsBitmask & NSCalendarUnitMinute;
BOOL includeSecond= self.selectedUnitsBitmask & NSCalendarUnitSecond;
NSDateComponents *diffDateComponents = [[NSCalendar currentCalendar] components:self.selectedUnitsBitmask fromDate:[NSDate date] toDate:self.futureDate options:0];
NSMutableString *outputString = [@"" mutableCopy];
if (includeYear && diffDateComponents.year)
[outputString appendFormat:@"%d Year", diffDateComponents.year];
if (includeMonth && diffDateComponents.month)
[outputString appendFormat:@" %d Month", diffDateComponents.month];
if (diffDateComponents.weekOfMonth < NSIntegerMax && diffDateComponents.weekOfMonth)
[outputString appendFormat:@" %d Week", diffDateComponents.weekOfMonth];
if (includeDay && diffDateComponents.day)
[outputString appendFormat:@" %d Day", diffDateComponents.day];
if (includeHour && diffDateComponents.hour)
[outputString appendFormat:@" %d Hour", diffDateComponents.hour];
if (includeMinute && diffDateComponents.minute)
[outputString appendFormat:@" %d Minute", diffDateComponents.minute];
if (includeSecond && diffDateComponents.second)
[outputString appendFormat:@" %d Second", diffDateComponents.second];
self.countDownLabel.text = [outputString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
To determine if a flag was set, we us the &
AND operator
BOOL includeYear = self.selectedUnitsBitmask & NSCalendarUnitYear;
if the year flag is 0....000010
and the bitmask contains 0....010110
the &
operation will return
0....000010 NSCalendarUnitYear
& 0....010110 bitmask
-------------
= 0....000010 -> NSCalendarUnitYear
we do this for all units exert the week. I have no idea why, but the & operation always return 0 for it. Here we use a hack:
if (diffDateComponents.weekOfYear < NSIntegerMax)
[outputString appendFormat:@" %d Week", diffDateComponents.weekOfYear];
for new NSDateComponents week
is instantiated with the biggest number representable with an integer, NSIntegerMax. If its value is less that NSIntegerMax we assume it was set and we append it to the string.
Result:
The whole Code in one piece:
#import "ViewController.h"
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UILabel *countDownLabel;
@property (nonatomic) NSInteger selectedUnitsBitmask;
@property (strong, nonatomic) NSArray *unitNames;
@property (strong, nonatomic) NSArray *units;
@property (strong, nonatomic) NSDate *futureDate;
- (IBAction)updateCountDownLabel:(id)sender;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.selectedUnitsBitmask= 0;
self.unitNames = @[ @"Year", @"Month", @"Week", @"Day", @"Hour", @"Minute", @"Second"];
self.units = @[@(NSCalendarUnitYear), @(NSCalendarUnitMonth), @(NSCalendarUnitWeekOfMonth), @(NSCalendarUnitDay), @(NSCalendarUnitHour), @(NSCalendarUnitMinute), @(NSCalendarUnitSecond)];
NSDateComponents *c = [[NSDateComponents alloc] init];
c.year = 2014;
c.month = 12;
c.day = 25;
self.futureDate = [[NSCalendar currentCalendar] dateFromComponents:c];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.units count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier =@"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = _unitNames[indexPath.row];
NSUInteger s;
NSUInteger row = indexPath.row;
s = NSIntegerMax & [self.units[row] integerValue];
cell.accessoryType = (self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger s;
NSUInteger row = indexPath.row;
s = NSIntegerMax & [self.units[row] integerValue];
self.selectedUnitsBitmask ^= s ;
NSLog(@"%lu, %lu", (unsigned long)s, (unsigned long)self.selectedUnitsBitmask);
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType =(self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
[self updateCountDownLabel:nil];
}
- (IBAction)updateCountDownLabel:(id)sender {
BOOL includeYear = self.selectedUnitsBitmask & NSCalendarUnitYear;
BOOL includeMonth = self.selectedUnitsBitmask & NSCalendarUnitMonth;
BOOL includeDay = self.selectedUnitsBitmask & NSCalendarUnitDay;
BOOL includeHour = self.selectedUnitsBitmask & NSCalendarUnitHour;
BOOL includeMinute= self.selectedUnitsBitmask & NSCalendarUnitMinute;
BOOL includeSecond= self.selectedUnitsBitmask & NSCalendarUnitSecond;
NSDateComponents *diffDateComponents = [[NSCalendar currentCalendar] components:self.selectedUnitsBitmask fromDate:[NSDate date] toDate:self.futureDate options:0];
NSMutableString *outputString = [@"" mutableCopy];
if (includeYear && diffDateComponents.year)
[outputString appendFormat:@"%d Year", diffDateComponents.year];
if (includeMonth && diffDateComponents.month)
[outputString appendFormat:@" %d Month", diffDateComponents.month];
if (diffDateComponents.weekOfMonth < NSIntegerMax && diffDateComponents.weekOfMonth)
[outputString appendFormat:@" %d Week", diffDateComponents.weekOfMonth];
if (includeDay && diffDateComponents.day)
[outputString appendFormat:@" %d Day", diffDateComponents.day];
if (includeHour && diffDateComponents.hour)
[outputString appendFormat:@" %d Hour", diffDateComponents.hour];
if (includeMinute && diffDateComponents.minute)
[outputString appendFormat:@" %d Minute", diffDateComponents.minute];
if (includeSecond && diffDateComponents.second)
[outputString appendFormat:@" %d Second", diffDateComponents.second];
self.countDownLabel.text = [outputString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}