UISwitch的那些坑
2017-08-16 本文已影响518人
ibingewin
主要就是基于事件
UIControlEventValueChanged
的回调问题
坑1:事件频繁触发
触发条件:拖动UISwitch
控件,划出控件区域,直至改变状态后,持续拖动不松手,事件仍会不断的触发,如下所示:
如果回调里处理的是网络相关的业务,这样会导致不断地进行网络请求,无端的浪费服务器资源。
ps:具体哪个版本存在这个问题,我不确定,猜测是iOS 10.0
之前,上图使用iOS 9.3.5
解决方法:
- 1.最简单的就是搞个变量,判断一下前后
UISwtich
的on
状态是否一样,如果是相同的,return
,不一样,才执行业务代码
@property (nonatomic, strong)NSNumber *lb_on;
- (void)switchAction:(UISwitch *)sender {
if (self.lb_on && self.lb_on.boolValue == sender.isOn) return;
self.lb_on = @(sender.isOn);
[self doSomething];
}
- 2.如果项目中有多处用到
UISwitch
的地方,这样一个地方写一份代码,很明显逼格不够,所以就要寻求一个通用的方法,通用方法可以对其进行抽象,也可以用runtime,我这里用的是runtime。写在UIControl
的分类里就好了
@interface UIControl (Extension)
@property (nonatomic, copy)NSNumber *lb_on;
@end
@implementation UIControl (Extension)
- (NSNumber *)lb_on {
return objc_getAssociatedObject(self, @selector(lb_on));
}
- (void)setLb_on:(NSNumber *)lb_on {
objc_setAssociatedObject(self, @selector(lb_on), lb_on, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(class_getInstanceMethod(self, @selector(sendAction:to:forEvent:)), class_getInstanceMethod(self, @selector(lb_sendAction:to:forEvent:)));
});
}
- (void)lb_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
if ([self isKindOfClass:[UISwitch class]]) {
UISwitch *sw = (UISwitch *)self;
if (sw.lb_on && sw.lb_on.boolValue == sw.isOn) return;
sw.lb_on = @(sw.isOn);
[self lb_sendAction:action to:target forEvent:event];
}
}
@end
坑2:事件重复触发
新版本的SDK解决了上一个坑,但是又带出了这一个坑,我一直以为事件都是用户交互产生的,
UISwitch
的on
属性,只是用来设置其的开关UI状态
的,没想到还能触发事件,简直颠覆我的认知!
- (void)switchAction:(UISwitch *)sender {
self.sw.on = !self.sw.isOn;
LBLog(@"switch is %@", self.sw.isOn ? @"on" : @"off");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touch began");
self.sw.on = !self.sw.isOn;
}
效果如下:在事件回调之外,设置on
,表现都是如期的,可是如果在事件回调内设置on
,居然又触发了一次事件,WTF!!!:
如果事件中是网络相关业务,当当前没网,或者请求失败时,还需要还原UISwitch
的状态,即设置UISwitch
的on
属性,但悲剧的是,当还原状态时,又触发了一次事件,再对还原后的状态还原,负负得正,结果是并没有还原,导致出现Bug
。
ps:同上个坑一样,我也不确定具体哪个版本存在这个问题,应该是解决了上个bug,才带出这个bug的,猜测是iOS 10.0
之后,上图是iOS 10.3.3
解决办法:
- 1.在还原状态时,搞个变量,标记一下是在事件中设置开关状态的,规避接下来的重复触发问题,注意要兼容以前的系统,以前的系统是没这个问题的
@property (nonatomic, assign)BOOL setOnInEvent;
- (void)switchAction:(UISwitch *)sender {
__weak typeof(self) weakSelf = self;
if ([UIDevice currentDevice].systemVersion.floatValue >= 10.0) {
if (self.onInEvent) {
self.onInEvent = NO;
return;
}
[self doNetworkFailure:^{
weakSelf.onInEvent = YES;
weakSelf.sw.on = !weakSelf.sw.isOn;
}];
}else {
[self doNetworkFailure:^{
weakSelf.sw.on = !weakSelf.sw.isOn;
}];
}
}
- 2.一个比较奇葩的方法,亲测有效
- (void)switchAction:(UISwitch *)sender {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.sw.on = !self.sw.isOn;
});
}