I spent the past few hours making this and felt that it was too cool to not share.
I'd love to hear feedback about it... I don't doubt that I've made a big mistake in here someplace, possibly regarding the fact that this class never removes itself as an observer.
Anyways, I wanted the ability to let the user pick from a few predefined colors. I want to be able to easily change the colors, and to be able to see the color picked directly in my storyboard.
Here's my header for my picker:
And here's the implementation:
In the process, I also wrote a category on UIColor. Here's the header:
And the implementation:
The way you use the class is you just drop in a bunch of UIViews with the background colors that you want the user to be able to pick from. If multiple sets of colors will be present at the same time, you can indicate which group each color belongs to by changing the tag. Then change the class of the UIView to instead be my PickableColorView.
If you want to programmatically set the selected color, use:
If you need to get the selected color from a controller class, add the controller as a listener for OSWPickableColorDidChangeNotification, use the tag (it's in the user dictionary with the key OSWPickableColorGroupIdentifier) and then you'll know the color (it's also in the user dictionary, with the key OSWPickableColorPickedColor). Your controller will need to import OSWPickableColorView, of course.
Alternatively, you can just connect to any of the cells and use the property pickedColor. It doesn't matter which cell you pick, so long as it's in the right group, since they'll use notifications to synchronize the property to be the same across all of them within the group.
I've attached screenshots of an example of my picker (placed in a UITableView). The first shows what it looks like in Xcode's Interface Builder, which I have set to show as iOS 6.1. The second shows what it looks like running in iOS Simulator 7.0.3.
I'd love to hear feedback about it... I don't doubt that I've made a big mistake in here someplace, possibly regarding the fact that this class never removes itself as an observer.
Anyways, I wanted the ability to let the user pick from a few predefined colors. I want to be able to easily change the colors, and to be able to see the color picked directly in my storyboard.
Here's my header for my picker:
Code:
extern NSString * const OSWPickableColorDidChangeNotification;
extern NSString * const OSWPickableColorGroupIdentifier;
extern NSString * const OSWPickableColorPickedColor;
@interface OSWPickableColorView : UIView
@property id groupIdentifier;
@property UIColor *pickedColor;
@end
And here's the implementation:
Code:
#import "OSWPickableColorView.h"
#import "UIColor+Multiply.h"
NSString * const OSWPickableColorDidChangeNotification = @"OSWPickableColorDidChangeNotification";
NSString * const OSWPickableColorGroupIdentifier = @"OSWPickableColorGroupIdentifier";
NSString * const OSWPickableColorPickedColor = @"OSWPickableColorPickedColor";
@implementation OSWPickableColorView {
UIColor *_normalColor;
UIColor *_darkenedColor;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)setup {
self.groupIdentifier = @(self.tag);
_normalColor = self.backgroundColor;
_darkenedColor = [self.backgroundColor multiplyNonAlphaComponentsBy:0.7];
[[NSNotificationCenter defaultCenter] addObserverForName:OSWPickableColorDidChangeNotification
object:nil
queue:nil
usingBlock:^(NSNotification *notification) {
if ([notification.userInfo[OSWPickableColorGroupIdentifier] isEqual:_groupIdentifier]) {
_pickedColor = notification.userInfo[OSWPickableColorPickedColor];
[self setNeedsDisplay];
}
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.backgroundColor = _darkenedColor;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
self.backgroundColor = _normalColor;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.backgroundColor = _normalColor;
if ([self contains:event]) {
self.pickedColor = _normalColor;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
self.backgroundColor = [self contains:event] ? _darkenedColor : _normalColor;
}
- (BOOL)contains:(UIEvent *)event {
CGPoint point = [[[event allTouches] anyObject] locationInView:self];
return [self pointInside:point withEvent:nil];
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
if ([_pickedColor isEqualToColor:_normalColor]) {
[[UIColor blackColor] set];
[[UIBezierPath bezierPathWithRect:self.bounds] stroke];
[[UIColor whiteColor] set];
[[UIBezierPath bezierPathWithRect:CGRectInset(self.bounds, 1, 1)] stroke];
}
}
- (void)setPickedColor:(UIColor *)pickedColor {
if ([_pickedColor isEqualToColor:pickedColor]) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:OSWPickableColorDidChangeNotification
object:nil
userInfo:@{OSWPickableColorGroupIdentifier:_groupIdentifier,
OSWPickableColorPickedColor:pickedColor}];
}
@end
In the process, I also wrote a category on UIColor. Here's the header:
Code:
@interface UIColor (Multiply)
- (bool) isEqualToColor:(UIColor *)color;
- (UIColor *) multiplyNonAlphaComponentsBy:(CGFloat)scaler;
@end
And the implementation:
Code:
#import "UIColor+Multiply.h"
@implementation UIColor (Multiply)
- (bool) isEqualToColor:(UIColor *)color {
return CGColorEqualToColor(self.CGColor, color.CGColor);
}
- (UIColor *)multiplyNonAlphaComponentsBy:(CGFloat)scaler {
// oldComponents is the array INSIDE the original color
// changing these changes the original, so we copy it
CGFloat *oldComponents = (CGFloat *)CGColorGetComponents([self CGColor]);
CGFloat newComponents[4];
switch (CGColorGetNumberOfComponents([self CGColor])) {
case 2: // WA
newComponents[0] = oldComponents[0] * scaler;
newComponents[1] = oldComponents[0] * scaler;
newComponents[2] = oldComponents[0] * scaler;
newComponents[3] = oldComponents[1];
break;
case 4: // RGBA
newComponents[0] = oldComponents[0] * scaler;
newComponents[1] = oldComponents[1] * scaler;
newComponents[2] = oldComponents[2] * scaler;
newComponents[3] = oldComponents[3];
break;
default:
NSLog(@"Unhandled case in UIColor+Multiply!");
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef newColor = CGColorCreate(colorSpace, newComponents);
CGColorSpaceRelease(colorSpace);
UIColor *retColor = [UIColor colorWithCGColor:newColor];
CGColorRelease(newColor);
return retColor;
}
@end
The way you use the class is you just drop in a bunch of UIViews with the background colors that you want the user to be able to pick from. If multiple sets of colors will be present at the same time, you can indicate which group each color belongs to by changing the tag. Then change the class of the UIView to instead be my PickableColorView.
If you want to programmatically set the selected color, use:
Code:
[[NSNotificationCenter defaultCenter] postNotificationName:OSWPickableColorDidChangeNotification
object:nil
userInfo:@{OSWPickableColorGroupIdentifier:@(<Tag of the set you want to pick from>),
OSWPickableColorPickedColor:<Color you want to pick>}];
If you need to get the selected color from a controller class, add the controller as a listener for OSWPickableColorDidChangeNotification, use the tag (it's in the user dictionary with the key OSWPickableColorGroupIdentifier) and then you'll know the color (it's also in the user dictionary, with the key OSWPickableColorPickedColor). Your controller will need to import OSWPickableColorView, of course.
Alternatively, you can just connect to any of the cells and use the property pickedColor. It doesn't matter which cell you pick, so long as it's in the right group, since they'll use notifications to synchronize the property to be the same across all of them within the group.
I've attached screenshots of an example of my picker (placed in a UITableView). The first shows what it looks like in Xcode's Interface Builder, which I have set to show as iOS 6.1. The second shows what it looks like running in iOS Simulator 7.0.3.