|
1 // |
|
2 // MNEValueTrackingSlider |
|
3 // |
|
4 // Copyright 2011 Michael Neuwert |
|
5 // "You can use the code in your own project and modify it as you like." |
|
6 // http://blog.neuwert-media.com/2011/04/customized-uislider-with-visual-value-tracking/ |
|
7 // |
|
8 |
|
9 |
|
10 #import "MNEValueTrackingSlider.h" |
|
11 |
|
12 #pragma mark - |
|
13 #pragma mark Private UIView subclass rendering the popup showing slider value |
|
14 @interface SliderValuePopupView : UIView |
|
15 @property (nonatomic, retain) UIFont *font; |
|
16 @property (nonatomic, copy) NSString *text; |
|
17 @property (nonatomic) float arrowOffset; |
|
18 @end |
|
19 |
|
20 @implementation SliderValuePopupView |
|
21 |
|
22 @synthesize font = _font; |
|
23 @synthesize text = _text; |
|
24 @synthesize arrowOffset = _arrowOffset; |
|
25 |
|
26 -(id) initWithFrame:(CGRect) frame { |
|
27 self = [super initWithFrame:frame]; |
|
28 if (self) { |
|
29 self.font = [UIFont boldSystemFontOfSize:18]; |
|
30 } |
|
31 return self; |
|
32 } |
|
33 |
|
34 -(void) dealloc { |
|
35 self.text = nil; |
|
36 self.font = nil; |
|
37 [super dealloc]; |
|
38 } |
|
39 |
|
40 -(void) drawRect:(CGRect) rect { |
|
41 // Create the path for the rounded rectangle |
|
42 CGRect roundedRect = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, floorf(self.bounds.size.height * 0.8)); |
|
43 UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:6.0]; |
|
44 roundedRectPath.lineWidth = 2.0f; |
|
45 |
|
46 // Create the arrow path |
|
47 UIBezierPath *arrowPath = [UIBezierPath bezierPath]; |
|
48 /* |
|
49 // Make sure the arrow offset is nice |
|
50 if (-self.arrowOffset + 1 > CGRectGetMidX(self.bounds) / 2) |
|
51 self.arrowOffset = -CGRectGetMidX(self.bounds) / 2 + 1; |
|
52 if (self.arrowOffset > CGRectGetMidX(self.bounds) / 2) |
|
53 self.arrowOffset = CGRectGetMidX(self.bounds) / 2 -1; |
|
54 */ |
|
55 |
|
56 CGFloat midX = CGRectGetMidX(self.bounds) + self.arrowOffset; |
|
57 CGPoint p0 = CGPointMake(midX, CGRectGetMaxY(self.bounds)); |
|
58 [arrowPath moveToPoint:p0]; |
|
59 [arrowPath addLineToPoint:CGPointMake((midX - 10.0), CGRectGetMaxY(roundedRect))]; |
|
60 [arrowPath addLineToPoint:CGPointMake((midX + 10.0), CGRectGetMaxY(roundedRect))]; |
|
61 [arrowPath closePath]; |
|
62 |
|
63 // Attach the arrow path to the rounded rect |
|
64 [roundedRectPath appendPath:arrowPath]; |
|
65 |
|
66 // Color various sections |
|
67 [[UIColor blackColor] setFill]; |
|
68 [roundedRectPath fill]; |
|
69 [[UIColor whiteColor] setStroke]; |
|
70 [roundedRectPath stroke]; |
|
71 [[UIColor whiteColor] setFill]; |
|
72 [arrowPath fill]; |
|
73 |
|
74 // Draw the text |
|
75 if (self.text) { |
|
76 [[UIColor lightYellowColor] set]; |
|
77 CGSize s = [_text sizeWithFont:self.font]; |
|
78 CGFloat yOffset = (roundedRect.size.height - s.height) / 2; |
|
79 CGRect textRect = CGRectMake(roundedRect.origin.x, yOffset, roundedRect.size.width, s.height); |
|
80 |
|
81 [_text drawInRect:textRect |
|
82 withFont:self.font |
|
83 lineBreakMode:UILineBreakModeWordWrap |
|
84 alignment:UITextAlignmentCenter]; |
|
85 } |
|
86 } |
|
87 |
|
88 @end |
|
89 |
|
90 #pragma mark - |
|
91 #pragma mark MNEValueTrackingSlider implementations |
|
92 @implementation MNEValueTrackingSlider |
|
93 |
|
94 @synthesize thumbRect, textValue; |
|
95 |
|
96 #pragma Private methods |
|
97 |
|
98 -(void) _constructSlider { |
|
99 valuePopupView = [[SliderValuePopupView alloc] initWithFrame:CGRectZero]; |
|
100 valuePopupView.backgroundColor = [UIColor clearColor]; |
|
101 valuePopupView.alpha = 0.0; |
|
102 [self addSubview:valuePopupView]; |
|
103 } |
|
104 |
|
105 -(void) _fadePopupViewInAndOut:(BOOL)aFadeIn { |
|
106 [UIView beginAnimations:nil context:NULL]; |
|
107 [UIView setAnimationDuration:0.25]; |
|
108 if (aFadeIn) { |
|
109 valuePopupView.alpha = 1.0; |
|
110 } else { |
|
111 valuePopupView.alpha = 0.0; |
|
112 } |
|
113 [UIView commitAnimations]; |
|
114 } |
|
115 |
|
116 -(void) _positionAndUpdatePopupView { |
|
117 CGRect _thumbRect = self.thumbRect; |
|
118 CGRect popupRect = CGRectOffset(_thumbRect, 0, -floorf(_thumbRect.size.height * 1.5)); |
|
119 // (-100, -15) determines the size of the the rect |
|
120 popupRect = CGRectInset(popupRect, -100, -15); |
|
121 |
|
122 // this prevents drawing the popup outside the slider view |
|
123 if (popupRect.origin.x < -self.frame.origin.x+5) |
|
124 popupRect.origin.x = -self.frame.origin.x+5; |
|
125 else if (popupRect.origin.x > self.superview.frame.size.width - popupRect.size.width - self.frame.origin.x - 5) |
|
126 popupRect.origin.x = self.superview.frame.size.width - popupRect.size.width - self.frame.origin.x - 5; |
|
127 //else if (CGRectGetMaxX(popupRect) > CGRectGetMaxX(self.superview.bounds)) |
|
128 // popupRect.origin.x = CGRectGetMaxX(self.superview.bounds) - CGRectGetWidth(popupRect) - 1.0; |
|
129 |
|
130 valuePopupView.arrowOffset = CGRectGetMidX(_thumbRect) - CGRectGetMidX(popupRect); |
|
131 |
|
132 valuePopupView.frame = popupRect; |
|
133 valuePopupView.text = self.textValue; |
|
134 [valuePopupView setNeedsDisplay]; |
|
135 } |
|
136 |
|
137 #pragma mark Memory management |
|
138 |
|
139 -(id) initWithFrame:(CGRect) frame { |
|
140 self = [super initWithFrame:frame]; |
|
141 if (self) { |
|
142 [self _constructSlider]; |
|
143 } |
|
144 return self; |
|
145 } |
|
146 |
|
147 -(id) initWithCoder:(NSCoder *)aDecoder { |
|
148 self = [super initWithCoder:aDecoder]; |
|
149 if (self) { |
|
150 [self _constructSlider]; |
|
151 } |
|
152 return self; |
|
153 } |
|
154 |
|
155 -(void) dealloc { |
|
156 [valuePopupView release]; |
|
157 [textValue release]; |
|
158 [super dealloc]; |
|
159 } |
|
160 |
|
161 #pragma mark - |
|
162 #pragma mark UIControl touch event tracking |
|
163 -(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { |
|
164 // Fade in and update the popup view |
|
165 CGPoint touchPoint = [touch locationInView:self]; |
|
166 // Check if the knob is touched. Only in this case show the popup-view |
|
167 if(CGRectContainsPoint(CGRectInset(self.thumbRect, -14.0, -12.0), touchPoint)) { |
|
168 [self _positionAndUpdatePopupView]; |
|
169 [self _fadePopupViewInAndOut:YES]; |
|
170 } |
|
171 return [super beginTrackingWithTouch:touch withEvent:event]; |
|
172 } |
|
173 |
|
174 -(BOOL) continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { |
|
175 // Update the popup view as slider knob is being moved |
|
176 [self _positionAndUpdatePopupView]; |
|
177 return [super continueTrackingWithTouch:touch withEvent:event]; |
|
178 } |
|
179 |
|
180 -(void) cancelTrackingWithEvent:(UIEvent *)event { |
|
181 [super cancelTrackingWithEvent:event]; |
|
182 } |
|
183 |
|
184 -(void) endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event { |
|
185 // Fade out the popoup view |
|
186 [self _fadePopupViewInAndOut:NO]; |
|
187 [super endTrackingWithTouch:touch withEvent:event]; |
|
188 } |
|
189 |
|
190 #pragma mark - |
|
191 #pragma mark Custom property accessors |
|
192 -(CGRect) thumbRect { |
|
193 CGRect trackRect = [self trackRectForBounds:self.bounds]; |
|
194 CGRect thumbR = [self thumbRectForBounds:self.bounds |
|
195 trackRect:trackRect |
|
196 value:self.value]; |
|
197 return thumbR; |
|
198 } |
|
199 |
|
200 @end |