| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- //
- // CustomSwitch.m
- // JasonDevelop
- //
- // Created by Jason Lee on 3/11/14.
- // Copyright (c) jasondevelop. All rights reserved.
- //
- @import QuartzCore;
- #import "CustomSwitch.h"
- #define kfIOS7Width 51.0f
- #define kfIOS6Width 79.0f
- #define kConstrainsFrameToProportions YES
- #define kHeightWidthRatio 1.6451612903 //Magic number as a result of dividing the height by the width on the default UISwitch size (51/31)
- //NSCoding Keys
- #define kCodingOnKey @"on"
- #define kCodingLockedKey @"off"
- #define kCodingOnTintColorKey @"onColor"
- #define kCodingOnColorKey @"onTintColor" //Not implemented
- #define kCodingTintColorKey @"tintColor"
- #define kCodingThumbTintColorKey @"thumbTintColor"
- #define kCodingOnImageKey @"onImage"
- #define kCodingOffImageKey @"offImage"
- #define kCodingConstrainFrameKey @"constrainFrame"
- //Appearance Defaults - Colors
- //Track Colors
- #define kDefaultTrackOnColor [UIColor colorWithRed:83/255.0 green: 214/255.0 blue: 105/255.0 alpha: 1]
- #define kDefaultTrackOffColor [UIColor colorWithWhite: 0.9f alpha:1.0f]
- #define kDefaultTrackContrastColor [UIColor orangeColor]
- //Thumb Colors
- #define kDefaultThumbTintColor [UIColor whiteColor]
- //#define kDefaultThumbTintColor [UIColor orangeColor]
- #define kDefaultThumbBorderColor [UIColor colorWithWhite: 0.9f alpha:1.0f]
- //Appearance - Layout
- //Size of knob with respect to the control - Must be a multiple of 2
- #define kThumbOffset 1
- #define kThumbTrackingGrowthRatio 1.2f //Amount to grow the thumb on press down
- #define kDefaultPanActivationThreshold 0.7 //Number between 0.0 - 1.0 describing how far user must drag before initiating the switch
- //Appearance - Animations
- #define kDefaultAnimationSlideLength 0.25f //Length of time to slide the thumb from left/right to right/left
- #define kDefaultAnimationScaleLength 0.15f //Length of time for the thumb to grow on press down
- #define kDefaultAnimationContrastResizeLength 0.25f //Length of time for the thumb to grow on press down
- #define kSwitchTrackContrastViewShrinkFactor 0.0001f //Must be very low but not 0 or else causes iOS 5 issues
- typedef enum {
- KLSwitchThumbJustifyLeft,
- KLSwitchThumbJustifyRight
- } KLSwitchThumbJustify;
- @interface KLSwitchThumb : UIView
- @property (nonatomic, assign) BOOL isTracking;
- -(void) growThumbWithJustification:(KLSwitchThumbJustify) justification;
- -(void) shrinkThumbWithJustification:(KLSwitchThumbJustify) justification;
- @end
- @interface KLSwitchTrack : UIView
- @property(nonatomic, getter=isOn) BOOL on;
- @property (nonatomic, strong) UIColor* contrastColor;
- @property (nonatomic, strong) UIColor* onTintColor;
- @property (nonatomic, strong) UIColor* tintColor;
- -(id) initWithFrame:(CGRect)frame
- onColor:(UIColor*) onColor
- offColor:(UIColor*) offColor
- contrastColor:(UIColor*) contrastColor;
- -(void) growContrastView;
- -(void) shrinkContrastView;
- -(void) setOn:(BOOL) on
- animated:(BOOL) animated;
- @end
- @interface CustomSwitch () <UIGestureRecognizerDelegate>
- @property (nonatomic, strong) KLSwitchTrack* track;
- @property (nonatomic, strong) KLSwitchThumb* thumb;
- //Gesture Recognizers
- @property (nonatomic, strong) UIPanGestureRecognizer* panGesture;
- @property (nonatomic, strong) UITapGestureRecognizer* tapGesture;
- -(void) configureSwitch;
- -(void) initializeDefaults;
- -(void) toggleState;
- -(void) setThumbOn:(BOOL) on
- animated:(BOOL) animated;
- @end
- @implementation CustomSwitch
- #pragma mark - Initializers
- - (void)encodeWithCoder:(NSCoder *)aCoder {
- [super encodeWithCoder: aCoder];
-
- [aCoder encodeBool: _on
- forKey: kCodingOnKey];
-
- [aCoder encodeObject: _onTintColor
- forKey: kCodingOnTintColorKey];
-
- [aCoder encodeObject: _tintColor
- forKey: kCodingTintColorKey];
-
- [aCoder encodeObject: _thumbTintColor
- forKey: kCodingThumbTintColorKey];
-
- [aCoder encodeObject: _onImage
- forKey: kCodingOnImageKey];
-
- [aCoder encodeObject: _offImage
- forKey: kCodingOffImageKey];
-
- [aCoder encodeBool: _shouldConstrainFrame
- forKey: kCodingConstrainFrameKey];
-
- }
- - (id)initWithCoder:(NSCoder *)aDecoder {
- [self initializeDefaults];
- if (self = [super initWithCoder: aDecoder]) {
-
- _on = [aDecoder decodeBoolForKey:kCodingOnKey];
- _locked = [aDecoder decodeBoolForKey:kCodingLockedKey];
- _onTintColor = [aDecoder decodeObjectForKey: kCodingOnTintColorKey];
- _tintColor = [aDecoder decodeObjectForKey: kCodingTintColorKey];
- _thumbTintColor = [aDecoder decodeObjectForKey: kCodingThumbTintColorKey];
- _onImage = [aDecoder decodeObjectForKey: kCodingOnImageKey];
- _offImage = [aDecoder decodeObjectForKey: kCodingOffImageKey];
- _onTintColor = [aDecoder decodeObjectForKey: kCodingOnTintColorKey];
- _shouldConstrainFrame = [aDecoder decodeBoolForKey: kCodingConstrainFrameKey];
-
- [self configureSwitch];
-
- }
- return self;
- }
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self) {
- [self configureSwitch];
- }
- return self;
- }
- - (id)initWithFrame:(CGRect)frame
- didChangeHandler:(changeHandler) didChangeHandler {
- if (self = [self initWithFrame: frame]) {
- _didChangeHandler = didChangeHandler;
- }
- return self;
- }
- -(void) setFrame:(CGRect)frame {
- if (self.shouldConstrainFrame) {
- [super setFrame: CGRectMake(frame.origin.x, frame.origin.y, frame.size.height*kHeightWidthRatio, frame.size.height)];
- }
- else [super setFrame: frame];
- }
- - (void)awakeFromNib {
-
- [self setThumbTintColor:[UIColor blueColor]];
- [self setOnTintColor:[UIColor whiteColor]];
- [self setThumbBorderColor:[UIColor whiteColor]];
- [self setBackgroundColor:[UIColor clearColor]];
-
-
- self.layer.cornerRadius = 16.0f;
- self.layer.borderColor = [UIColor lightGrayColor].CGColor;
- self.layer.borderWidth = 1.0f;
- }
- #pragma mark - Defaults and layout/appearance
- -(void) initializeDefaults {
- _onTintColor = kDefaultTrackOnColor;
- _tintColor = kDefaultTrackOffColor;
- _thumbTintColor = kDefaultThumbTintColor;
- _thumbBorderColor = kDefaultThumbBorderColor;
- _contrastColor = kDefaultThumbTintColor;
- _panActivationThreshold = kDefaultPanActivationThreshold;
- _shouldConstrainFrame = kConstrainsFrameToProportions;
- }
- -(void) configureSwitch {
- [self initializeDefaults];
-
- //Configure visual properties of self
- [self setBackgroundColor: [UIColor clearColor]];
-
-
- // tap gesture for toggling the switch
- self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
- action:@selector(didTap:)];
- [self.tapGesture setDelegate:self];
- [self addGestureRecognizer:self.tapGesture];
-
-
- // pan gesture for moving the switch knob manually
- self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self
- action:@selector(didDrag:)];
- [self.panGesture setDelegate:self];
- [self addGestureRecognizer:self.panGesture];
-
- /*
- Subview layering as follows :
-
- TOP
- thumb
- track
- BOTTOM
- */
- // Initialization code
- if (!_track) {
- _track = [[KLSwitchTrack alloc] initWithFrame: self.bounds
- onColor: self.onTintColor
- offColor: self.tintColor
- contrastColor: self.contrastColor];
- [_track setOn: self.isOn
- animated: NO];
- [self addSubview: self.track];
- }
- if (!_thumb) {
- _thumb = [[KLSwitchThumb alloc] initWithFrame:CGRectMake(kThumbOffset, kThumbOffset, self.bounds.size.height - 2 * kThumbOffset, self.bounds.size.height - 2 * kThumbOffset)];
- [self addSubview: _thumb];
- }
- }
- -(void) setOnTintColor:(UIColor *)onTintColor {
- _onTintColor = onTintColor;
- [self.track setOnTintColor: _onTintColor];
- }
- -(void) setTintColor:(UIColor *)tintColor {
- _tintColor = tintColor;
- [self.track setTintColor: _tintColor];
- }
- -(void) setContrastColor:(UIColor *)contrastColor {
- _contrastColor = contrastColor;
- [self.track setContrastColor: _contrastColor];
- }
- -(void) setThumbBorderColor:(UIColor *)thumbBorderColor {
- _thumbBorderColor = thumbBorderColor;
- [self.thumb.layer setBorderColor: [_thumbBorderColor CGColor]];
- }
- - (void)setThumbTintColor:(UIColor *)thumbTintColor {
- _thumbTintColor = thumbTintColor;
- // [self.thumb.layer setBackgroundColor:[_thumbTintColor CGColor]];
- [self.thumb setBackgroundColor:_thumbTintColor];
- }
- - (void)drawRect:(CGRect)rect
- {
- [super drawRect:rect];
- // Drawing code
- //[self.trackingKnob setTintColor: self.thumbTintColor];
- [_thumb setBackgroundColor: [UIColor whiteColor]];
-
- //Make the knob a circle and add a shadow
- CGFloat roundedCornerRadius = _thumb.frame.size.height/2.0f;
- [_thumb.layer setBorderWidth: 0.5];
- [_thumb.layer setBorderColor: [self.thumbBorderColor CGColor]];
- [_thumb.layer setBackgroundColor:[self.thumbTintColor CGColor]];
- [_thumb.layer setCornerRadius: roundedCornerRadius];
- [_thumb.layer setShadowColor: [[UIColor grayColor] CGColor]];
- [_thumb.layer setShadowOffset: CGSizeMake(0, 3)];
- [_thumb.layer setShadowOpacity: 0.40f];
- [_thumb.layer setShadowRadius: 0.8];
- }
- #pragma mark - UIGestureRecognizer implementations
- -(void) didTap:(UITapGestureRecognizer*) gesture {
- if (gesture.state == UIGestureRecognizerStateEnded) {
- [self toggleState];
- }
- }
- -(void) didDrag:(UIPanGestureRecognizer*) gesture {
- if (gesture.state == UIGestureRecognizerStateBegan) {
- //Grow the thumb horizontally towards center by defined ratio
- [self setThumbIsTracking: YES
- animated: YES];
- }
- else if (gesture.state == UIGestureRecognizerStateChanged) {
- //If touch crosses the threshold then toggle the state
- CGPoint locationInThumb = [gesture locationInView: self.thumb];
-
- //Toggle the switch if the user pans left or right past the switch thumb bounds
- if ((self.isOn && locationInThumb.x <= 0)
- || (!self.isOn && locationInThumb.x >= self.thumb.bounds.size.width)) {
- [self toggleState];
- }
-
- CGPoint locationOfTouch = [gesture locationInView:self];
- if (CGRectContainsPoint(self.bounds, locationOfTouch))
- [self sendActionsForControlEvents:UIControlEventTouchDragInside];
- else
- [self sendActionsForControlEvents:UIControlEventTouchDragOutside];
- }
- else if (gesture.state == UIGestureRecognizerStateEnded) {
- [self setThumbIsTracking: NO
- animated: YES];
- }
- }
- #pragma mark - Event Handlers
- -(void) toggleState {
- //Alternate between on/off
- [self setOn: self.isOn ? NO : YES
- animated: YES];
- }
- - (void)setOn:(BOOL)on
- animated:(BOOL)animated {
- //Cancel notification to parent if attempting to set to current state
- if (_on == on) {
- return;
- }
-
- //Move the thumb to the new position
- [self setThumbOn: on
- animated: animated];
-
- //Animate the contrast view of the track
- [self.track setOn: on
- animated: animated];
-
- _on = on;
-
- //Trigger the completion block if exists
- if (self.didChangeHandler) {
- self.didChangeHandler(_on);
- }
- [self sendActionsForControlEvents:UIControlEventValueChanged];
- }
- - (void) setOn:(BOOL)on {
- [self setOn: on animated: NO];
- }
- - (void) setLocked:(BOOL)locked {
- //Cancel notification to parent if attempting to set to current state
- if (_locked == locked) {
- return;
- }
- _locked = locked;
-
- UIImageView *lockImageView = (UIImageView *)[_track viewWithTag:LOCK_IMAGE_SUBVIEW];
-
- if (!locked && (lockImageView != nil)) {
-
- [lockImageView removeFromSuperview];
- lockImageView = nil;
-
- } else if (locked && (lockImageView == nil)) {
-
- UIImage *lockImage = [UIImage imageNamed:@"lock-icon.png"];
-
- lockImageView = [[UIImageView alloc] initWithImage:lockImage];
-
- lockImageView.frame = CGRectMake(7, 8, lockImage.size.width, lockImage.size.height);
- lockImageView.tag = LOCK_IMAGE_SUBVIEW;
-
- [_track addSubview:lockImageView];
- [_track bringSubviewToFront:lockImageView];
- }
- }
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesBegan:touches withEvent:event];
- [self sendActionsForControlEvents:UIControlEventTouchDown];
- }
- - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesEnded:touches withEvent:event];
- [self sendActionsForControlEvents:UIControlEventTouchUpInside];
- }
- - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- {
- [super touchesCancelled:touches withEvent:event];
- [self sendActionsForControlEvents:UIControlEventTouchUpOutside];
- }
- -(void) setThumbIsTracking:(BOOL)isTracking {
- if (isTracking) {
- //Grow
- [self.thumb growThumbWithJustification: self.isOn ? KLSwitchThumbJustifyRight : KLSwitchThumbJustifyLeft];
- }
- else {
- //Shrink
- [self.thumb shrinkThumbWithJustification: self.isOn ? KLSwitchThumbJustifyRight : KLSwitchThumbJustifyLeft];
- }
- [self.thumb setIsTracking: isTracking];
- }
- -(void) setThumbIsTracking:(BOOL)isTracking
- animated:(BOOL) animated {
- __weak id weakSelf = self;
- [UIView animateWithDuration: kDefaultAnimationScaleLength
- delay: fabs(kDefaultAnimationSlideLength - kDefaultAnimationScaleLength)
- options: UIViewAnimationOptionCurveEaseOut
- animations: ^{
- [weakSelf setThumbIsTracking: isTracking];
- }
- completion:nil];
- }
- -(void) setThumbOn:(BOOL) on
- animated:(BOOL) animated {
- if (animated) {
- [UIView animateWithDuration:0.3 animations:^{
- [self setThumbOn:on animated:NO];
- }];
- }
- CGRect thumbFrame = self.thumb.frame;
- if (on) {
- thumbFrame.origin.x = self.bounds.size.width - (thumbFrame.size.width + kThumbOffset);
- }
- else {
- thumbFrame.origin.x = kThumbOffset;
- }
- [self.thumb setFrame: thumbFrame];
- }
- @end
- @implementation KLSwitchThumb
- -(void) growThumbWithJustification:(KLSwitchThumbJustify) justification {
- if (self.isTracking)
- return;
-
- CGRect thumbFrame = self.frame;
-
- CGFloat deltaWidth = self.frame.size.width * (kThumbTrackingGrowthRatio - 1);
- thumbFrame.size.width += deltaWidth;
- if (justification == KLSwitchThumbJustifyRight) {
- thumbFrame.origin.x -= deltaWidth;
- }
- [self setFrame: thumbFrame];
- }
- -(void) shrinkThumbWithJustification:(KLSwitchThumbJustify) justification {
- if (!self.isTracking)
- return;
-
- CGRect thumbFrame = self.frame;
-
- CGFloat deltaWidth = self.frame.size.width * (1 - 1 / (kThumbTrackingGrowthRatio));
- thumbFrame.size.width -= deltaWidth;
- if (justification == KLSwitchThumbJustifyRight) {
- thumbFrame.origin.x += deltaWidth;
- }
- [self setFrame: thumbFrame];
-
- }
- @end
- @interface KLSwitchTrack ()
- @property (nonatomic, strong) UIView* contrastView;
- @property (nonatomic, strong) UIView* onView;
- @end
- @implementation KLSwitchTrack
- -(id) initWithFrame:(CGRect)frame
- onColor:(UIColor*) onColor
- offColor:(UIColor*) offColor
- contrastColor:(UIColor*) contrastColor {
- if (self = [super initWithFrame: frame]) {
- _onTintColor = onColor;
- _tintColor = offColor;
-
- CGFloat cornerRadius = frame.size.height/2.0f;
- [self.layer setCornerRadius: cornerRadius];
- [self setBackgroundColor: _tintColor];
-
- CGRect contrastRect = frame;
- contrastRect.size.width = frame.size.width - 2*kThumbOffset;
- contrastRect.size.height = frame.size.height - 2*kThumbOffset;
- CGFloat contrastRadius = contrastRect.size.height/2.0f;
-
- _contrastView = [[UIView alloc] initWithFrame:contrastRect];
- [_contrastView setBackgroundColor: contrastColor];
- [_contrastView setCenter: self.center];
- [_contrastView.layer setCornerRadius: contrastRadius];
- [self addSubview: _contrastView];
-
- _onView = [[UIView alloc] initWithFrame:frame];
- [_onView setBackgroundColor: _onTintColor];
- [_onView setCenter: self.center];
- [_onView.layer setCornerRadius: cornerRadius];
- [self addSubview: _onView];
-
- }
- return self;
- }
- -(void) setOn:(BOOL)on {
- if (on) {
- [self.onView setAlpha: 1.0];
- [self shrinkContrastView];
- }
- else {
- [self.onView setAlpha: 0.0];
- [self growContrastView];
- }
- }
- -(void) setOn:(BOOL)on
- animated:(BOOL)animated {
- if (animated) {
- __weak id weakSelf = self;
- //First animate the color switch
- [UIView animateWithDuration: kDefaultAnimationContrastResizeLength
- delay: 0.0
- options: UIViewAnimationOptionCurveEaseOut
- animations:^{
- [weakSelf setOn: on
- animated: NO];
- }
- completion:nil];
- }
- else {
- [self setOn: on];
- }
- }
- -(void) setOnTintColor:(UIColor *)onTintColor {
- _onTintColor = onTintColor;
- [self.onView setBackgroundColor: _onTintColor];
- }
- -(void) setTintColor:(UIColor *)tintColor {
- _tintColor = tintColor;
- [self setBackgroundColor: _tintColor];
- }
- -(void) setContrastColor:(UIColor *)contrastColor {
- _contrastColor = contrastColor;
- [self.contrastView setBackgroundColor: _contrastColor];
- }
- -(void) growContrastView {
- //Start out with contrast view small and centered
- [self.contrastView setTransform: CGAffineTransformMakeScale(kSwitchTrackContrastViewShrinkFactor, kSwitchTrackContrastViewShrinkFactor)];
- [self.contrastView setTransform: CGAffineTransformMakeScale(1, 1)];
- }
- -(void) shrinkContrastView {
- //Start out with contrast view the size of the track
- [self.contrastView setTransform: CGAffineTransformMakeScale(1, 1)];
- [self.contrastView setTransform: CGAffineTransformMakeScale(kSwitchTrackContrastViewShrinkFactor, kSwitchTrackContrastViewShrinkFactor)];
- }
- @end
|