LOTAnimatedSwitch.m
6.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//
// LOTAnimatedSwitch.m
// Lottie
//
// Created by brandon_withrow on 8/25/17.
// Copyright © 2017 Airbnb. All rights reserved.
//
#import "LOTAnimatedSwitch.h"
#import "LOTAnimationView.h"
#import "CGGeometry+LOTAdditions.h"
@implementation LOTAnimatedSwitch {
CGFloat _onStartProgress;
CGFloat _onEndProgress;
CGFloat _offStartProgress;
CGFloat _offEndProgress;
CGPoint _touchTrackingStart;
BOOL _on;
BOOL _suppressToggle;
BOOL _toggleToState;
}
/// Convenience method to initialize a control from the Main Bundle by name
+ (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName {
return [self switchNamed:toggleName inBundle:[NSBundle mainBundle]];
}
/// Convenience method to initialize a control from the specified bundle by name
+ (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName inBundle:(NSBundle * _Nonnull)bundle {
LOTComposition *composition = [LOTComposition animationNamed:toggleName inBundle:bundle];
LOTAnimatedSwitch *animatedControl = [[self alloc] initWithFrame:CGRectZero];
if (composition) {
[animatedControl setAnimationComp:composition];
animatedControl.bounds = composition.compBounds;
}
return animatedControl;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.accessibilityHint = NSLocalizedString(@"Double tap to toggle setting.", @"Double tap to toggle setting.");
_onStartProgress = 0;
_onEndProgress = 1;
_offStartProgress = 1;
_offEndProgress = 0;
_on = NO;
[self addTarget:self action:@selector(_toggle) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)setAnimationComp:(LOTComposition *)animationComp {
[super setAnimationComp:animationComp];
[self setOn:_on animated:NO];
}
#pragma mark - External Methods
- (void)setProgressRangeForOnState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress {
_onStartProgress = fromProgress;
_onEndProgress = toProgress;
[self setOn:_on animated:NO];
}
- (void)setProgressRangeForOffState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress {
_offStartProgress = fromProgress;
_offEndProgress = toProgress;
[self setOn:_on animated:NO];
}
- (void)setOn:(BOOL)on {
[self setOn:on animated:NO];
}
- (void)setOn:(BOOL)on animated:(BOOL)animated {
_on = on;
CGFloat startProgress = on ? _onStartProgress : _offStartProgress;
CGFloat endProgress = on ? _onEndProgress : _offEndProgress;
CGFloat finalProgress = endProgress;
if (self.animationView.animationProgress < MIN(startProgress, endProgress) ||
self.animationView.animationProgress > MAX(startProgress, endProgress)) {
if (self.animationView.animationProgress != (!_on ? _onEndProgress : _offEndProgress)) {
// Current progress is in the wrong timeline. Switch.
endProgress = on ? _offStartProgress : _onStartProgress;
startProgress = on ? _offEndProgress : _onEndProgress;
}
}
if (finalProgress == self.animationView.animationProgress) {
return;
}
if (animated) {
[self.animationView pause];
[self.animationView playFromProgress:startProgress toProgress:endProgress withCompletion:^(BOOL animationFinished) {
if (animationFinished) {
self.animationView.animationProgress = finalProgress;
}
}];
} else {
self.animationView.animationProgress = endProgress;
}
}
- (NSString *)accessibilityValue {
return self.isOn ? NSLocalizedString(@"On", @"On") : NSLocalizedString(@"Off", @"Off");
}
#pragma mark - Internal Methods
- (void)_toggle {
if (!_suppressToggle) {
[self _toggleAndSendActions];
}
}
- (void)_toggleAndSendActions {
if (self.isEnabled) {
#ifndef TARGET_OS_TV
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
[generator impactOccurred];
}
#endif
[self setOn:!_on animated:YES];
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super beginTrackingWithTouch:touch withEvent:event];
_suppressToggle = NO;
_touchTrackingStart = [touch locationInView:self];
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
BOOL superContinue = [super continueTrackingWithTouch:touch withEvent:event];
if (!_interactiveGesture) {
return superContinue;
}
CGPoint location = [touch locationInView:self];
CGFloat diff = location.x - _touchTrackingStart.x;
if (LOT_PointDistanceFromPoint(_touchTrackingStart, location) > self.bounds.size.width * 0.25) {
// The touch has moved enough to register as its own gesture. Suppress the touch up toggle.
_suppressToggle = YES;
}
#ifdef __IPHONE_11_0
// Xcode 9+
if (@available(iOS 9.0, *)) {
#else
// Xcode 8-
if ([UIView respondsToSelector:@selector(userInterfaceLayoutDirectionForSemanticContentAttribute:)]) {
#endif
if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) {
diff = diff * -1;
}
}
if (_on) {
diff = diff * -1;
if (diff <= 0) {
self.animationView.animationProgress = _onEndProgress;
_toggleToState = YES;
} else {
diff = MAX(MIN(self.bounds.size.width, diff), 0);
self.animationView.animationProgress = LOT_RemapValue(diff, 0, self.bounds.size.width, _offStartProgress, _offEndProgress);
_toggleToState = (diff / self.bounds.size.width) > 0.5 ? NO : YES;
}
} else {
if (diff <= 0) {
self.animationView.animationProgress = _offEndProgress;
_toggleToState = NO;
} else {
diff = MAX(MIN(self.bounds.size.width, diff), 0);
self.animationView.animationProgress = LOT_RemapValue(diff, 0, self.bounds.size.width, _onStartProgress, _onEndProgress);
_toggleToState = (diff / self.bounds.size.width) > 0.5 ? YES : NO;
}
}
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super endTrackingWithTouch:touch withEvent:event];
if (!_interactiveGesture) {
return;
}
if (_suppressToggle) {
if (_toggleToState != _on) {
[self _toggleAndSendActions];
} else {
[self setOn:_toggleToState animated:YES];
}
}
}
@end