前言

做iOS开发也有一段时间的了,项目中的开关控件一直都是用的系统级别的,至多就是给UISwitch控件换一个tintColor。这次的UI设计师设计了一个带有动画效果的UISwitch控件。如下图:


一开始为了快速开发就没在在意这个小问题,用的系统的。但是子啊UI做评审的时候说我的这个还原度是不OK的。那怎么办呢,想办法自己写一个吧…(其实我们也想过用Airbnb的那个开原动画框架的,但是…种种原因吧,没用)。

想法

基本的思路就是现在底部放上一个UIView做底,然后上面做一个UIbutton,实现开关的效果,然后再点击开关的时候出发一下动画效果,从而达到UI的还原度要求。

撸起袖子,就是干 ! ! !

首先我们在.h文件中写下如下的代码:

@protocol  SwitchViewDelegate <NSObject>

- (void)switchView_didChangeValue:(SwitchView *)zpswitch value:(BOOL)value;

@end

@interface SwitchView : UIView

@property (nonatomic, assign) BOOL on;

@property (nonatomic, weak) id<SwitchViewDelegate> delegate;

@end

最上面的SwitchViewDelegate的方法是在我们点击按钮切换当前的选中状态的。on这个属性暴出去的目的方便使用者去设置一开始的初始状态和之后的状态切换。

然后我们去点击.m文件。
我们要在这里声明两个属性:

@interface SwitchView()

@property (nonatomic,strong) UIButton *switchButton;

@property (nonatomic,assign) BOOL isFirst;

@end

switchButton这是用来点击的UIButton控件,就和我们平时创建,初始化没什么区别。
isFirst这个是一个标志位,用来判断是不是第一次显示的问题。
我们把相关的属性声明都弄完了之后就开始正题。

初始化

- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.isFirst = YES;
        self.backgroundColor = [UIColor colorWithRed:242/255.0 green:242/255.0 blue:242/255.0 alpha:1];
        self.layer.masksToBounds = YES;
        self.layer.borderColor = [UIColor colorWithRed:218/255.0 green:218/255.0 blue:218/255.0 alpha:1].CGColor;
        self.layer.borderWidth = 1.0f;
        self.layer.cornerRadius = self.bounds.size.height / 2.0;
        self.clipsToBounds = YES;
        [self addSubview:self.switchButton];
        self.switchButton.layer.cornerRadius = self.switchButton.bounds.size.height / 2.0;
    }
    return self;
}
- (UIButton *)switchButton{
    if (!_switchButton) {
        _switchButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _switchButton.frame = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height);
        [_switchButton setImage:[UIImage imageNamed:@"Group 8"] forState:UIControlStateNormal];
        [_switchButton setImage:[UIImage imageNamed:@"Group 2"] forState:UIControlStateSelected];
        [_switchButton addTarget:self action:@selector(switcherButtonTouch:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _switchButton;
}

这里就是懒加载了一个UIButton,还有就是设置有了一些圆角的属性。这么设置圆角的属性并不是最佳的办法,但是就是一个Demo示例。所以并没有计较那么多。

交互

在上面你们看到了,我们给button添加了一个点击方法,这里我们就要去实现这个方法

- (void)switcherButtonTouch:(UIButton *)sender{
    self.on = !self.on;
    if (self.delegate && [self.delegate respondsToSelector:@selector(switchView_didChangeValue:value:)]) {
        [self.delegate switchView_didChangeValue:self value:self.on];
    }
}

这里就是在变化button的值得时候,通过delegate方法传到控制器或者相应的superView

动画

- (void)animationSwitcherButton{
    if (self.on) {
        [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
            rotateAnimation.fromValue = [NSNumber numberWithFloat:-M_PI];
            rotateAnimation.toValue = [NSNumber numberWithFloat:0.0];
            rotateAnimation.duration = 0.45;
            rotateAnimation.cumulative = NO;
            [self.switchButton.layer addAnimation:rotateAnimation forKey:@"rotate"];
            
            self.backgroundColor = [UIColor colorWithRed:0/255.0 green:141/255.0 blue:150/255.0 alpha:1];
            self.layer.masksToBounds = YES;
            self.layer.borderColor = [UIColor colorWithRed:0/255.0 green:141/255.0 blue:150/255.0 alpha:1].CGColor;
            self.layer.borderWidth = 1.0f;
            
            self.switchButton.selected = YES;
            self.switchButton.frame = CGRectMake(self.bounds.size.width - self.bounds.size.height, 0, self.bounds.size.height, self.bounds.size.height);
        } completion:^(BOOL finished) {
            self.switchButton.selected = YES;
            self.switchButton.frame = CGRectMake(self.bounds.size.width - self.bounds.size.height, 0, self.bounds.size.height, self.bounds.size.height);
        }];
    }else{
        [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
            rotateAnimation.toValue = [NSNumber numberWithFloat:-M_PI];
            rotateAnimation.fromValue = [NSNumber numberWithFloat:0.0];
            rotateAnimation.duration = 0.45;
            rotateAnimation.cumulative = NO;
            [self.switchButton.layer addAnimation:rotateAnimation forKey:@"rotate"];
            self.backgroundColor = [UIColor colorWithRed:242/255.0 green:242/255.0 blue:242/255.0 alpha:1];
            self.layer.masksToBounds = YES;
            self.layer.borderColor = [UIColor colorWithRed:218/255.0 green:218/255.0 blue:218/255.0 alpha:1].CGColor;
            self.layer.borderWidth = 1.0f;
            self.switchButton.selected = NO;
            self.switchButton.frame = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height);
        } completion:^(BOOL finished) {
            self.switchButton.selected = NO;
           self.switchButton.frame = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height);
        }];
    }
}

这里面做了一个旋转的动画和平移的动画。到现在为止你就可以实现基本的要求了。当你从不被选中切换到选中的时候,感觉棒棒哒。但是这个交互只能在点击button的时候会有,和系统的那个UISwitch还是有着一定的区别的。还有就是如果一开始就是选中的状态,现在的代码还不能完全满足。
so

优化一下

1.增加一个手势,让点击这个控件的时候就可以响应变化方法:
我们在上面的那个- (instancetype)initWithFrame:(CGRect)frame方法中添加一个tap手势。

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(switcherButtonTouch:)];
[self addGestureRecognizer:tap];

2.让使用者自由的设置on的初始值:

- (void)setOn:(BOOL)on{
    _on = on;
    if (_on && self.isFirst) {
        self.backgroundColor = [UIColor colorWithRed:0/255.0 green:141/255.0 blue:150/255.0 alpha:1];
        self.layer.masksToBounds = YES;
        self.layer.borderColor = [UIColor colorWithRed:0/255.0 green:141/255.0 blue:150/255.0 alpha:1].CGColor;
        self.layer.borderWidth = 1.0f;
        self.switchButton.selected = YES;
        self.switchButton.frame = CGRectMake(self.bounds.size.width - self.bounds.size.height, 0, self.bounds.size.height, self.bounds.size.height);
        self.isFirst = NO;
    }else{
        [self animationSwitcherButton];
        self.isFirst = NO;
    }
}

通过on的值和self.isFirst的联合判断,从而达到优化效果

实现

SwitchView *switchView1 = [[SwitchView alloc]initWithFrame:CGRectMake(100, 200, 46, 32)];
    switchView1.on = NO;
    switchView1.delegate = self;
    [self.view addSubview:switchView1];

效果

传送门

https://github.com/cAibDe/SwitchView