├── README.markdown ├── STHorizontalPicker.h ├── STHorizontalPicker.m └── screenshot.png /README.markdown: -------------------------------------------------------------------------------- 1 | STHorizontalPicker 2 | ================== 3 | 4 | Screenshot 5 | ---------- 6 | 7 | ![STHorizontalPicker](https://github.com/stackthread/STHorizontalPicker/raw/master/screenshot.png "STHorizontalPicker") -------------------------------------------------------------------------------- /STHorizontalPicker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2012 StackThread Software Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | 21 | @class STHorizontalPicker; 22 | 23 | //================================ 24 | // Delegate protocol 25 | //================================ 26 | @protocol STHorizontalPickerDelegate 27 | 28 | @optional 29 | - (CGFloat)minimumValueForPickerView:(STHorizontalPicker *)picker; 30 | - (CGFloat)maximumValueForPickerView:(STHorizontalPicker *)picker; 31 | - (NSUInteger)stepCountForPickerView:(STHorizontalPicker *)picker; 32 | 33 | @required 34 | - (void)pickerView:(STHorizontalPicker *)picker didSelectValue:(CGFloat)value; 35 | 36 | @end 37 | 38 | //================================ 39 | // UIColor category 40 | //================================ 41 | @interface UIColor (STColorComponents) 42 | - (CGFloat)red; 43 | - (CGFloat)green; 44 | - (CGFloat)blue; 45 | - (CGFloat)alpha; 46 | @end 47 | 48 | 49 | //================================ 50 | // STHorizontalPicker interface 51 | //================================ 52 | @interface STHorizontalPicker : UIView { 53 | CGFloat value; 54 | 55 | NSUInteger steps; 56 | CGFloat minimumValue; 57 | CGFloat maximumValue; 58 | 59 | id delegate; 60 | 61 | UIColor *borderColor; 62 | CGFloat fontSize; 63 | 64 | @private 65 | CGFloat scale; // Drawing scale 66 | } 67 | 68 | @property (nonatomic, retain) NSString *name; 69 | 70 | - (void)snapToMarkerAnimated:(BOOL)animated; 71 | 72 | - (CGFloat)minimumValue; 73 | - (void)setMinimumValue:(CGFloat)newMin; 74 | 75 | - (CGFloat)maximumValue; 76 | - (void)setMaximumValue:(CGFloat)newMax; 77 | 78 | - (NSUInteger)steps; 79 | - (void)setSteps:(NSUInteger)newSteps; 80 | 81 | - (CGFloat)value; 82 | - (void)setValue:(CGFloat)newValue; 83 | 84 | - (UIColor *)borderColor; 85 | - (void)setBorderColor:(UIColor *)newBorderColor; 86 | 87 | - (CGFloat)fontSize; 88 | - (void)setFontSize:(CGFloat)newFontSize; 89 | 90 | - (id)delegate; 91 | - (void)setDelegate:(id)newDelegate; 92 | - (void)callDelegateWithNewValueFromOffset:(CGFloat)offset; 93 | 94 | @end 95 | 96 | 97 | 98 | //================================ 99 | // STPointerLayerDelegate interface 100 | //================================ 101 | @interface STPointerLayerDelegate : NSObject {} 102 | 103 | - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context; 104 | 105 | @end -------------------------------------------------------------------------------- /STHorizontalPicker.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2012 StackThread Software Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import "STHorizontalPicker.h" 18 | 19 | const int DISTANCE_BETWEEN_ITEMS = 100; 20 | const int TEXT_LAYER_WIDTH = 40; 21 | const int NUMBER_OF_ITEMS = 15; 22 | const float FONT_SIZE = 16.0f; 23 | const float POINTER_WIDTH = 10.0f; 24 | const float POINTER_HEIGHT = 7.0f; 25 | 26 | //================================ 27 | // UIColor category 28 | //================================ 29 | @implementation UIColor (STColorComponents) 30 | 31 | - (CGColorSpaceModel)colorSpaceModel { 32 | return CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor)); 33 | } 34 | 35 | - (CGFloat)red { 36 | const CGFloat *c = CGColorGetComponents(self.CGColor); 37 | return c[0]; 38 | } 39 | 40 | - (CGFloat)green { 41 | const CGFloat *c = CGColorGetComponents(self.CGColor); 42 | if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0]; 43 | return c[1]; 44 | } 45 | 46 | - (CGFloat)blue { 47 | const CGFloat *c = CGColorGetComponents(self.CGColor); 48 | if (self.colorSpaceModel == kCGColorSpaceModelMonochrome) return c[0]; 49 | return c[2]; 50 | } 51 | 52 | - (CGFloat)alpha { 53 | return CGColorGetAlpha(self.CGColor); 54 | } 55 | 56 | @end 57 | 58 | 59 | 60 | 61 | 62 | //================================ 63 | // STHorizontalPicker 64 | //================================ 65 | 66 | @interface STHorizontalPicker () 67 | 68 | // Private properties 69 | 70 | @property (nonatomic, retain) UIScrollView *scrollView; 71 | @property (nonatomic, retain) UIView *scrollViewMarkerContainerView; 72 | @property (nonatomic, retain) NSMutableArray *scrollViewMarkerLayerArray; 73 | @property (nonatomic, retain) CALayer *pointerLayer; 74 | 75 | @end; 76 | 77 | @implementation STHorizontalPicker 78 | 79 | @synthesize scrollView, scrollViewMarkerContainerView, scrollViewMarkerLayerArray, name, pointerLayer; 80 | 81 | - (id)initWithFrame:(CGRect)frame 82 | { 83 | self = [super initWithFrame:frame]; 84 | if (self) { 85 | steps = 15; 86 | 87 | float leftPadding = self.frame.size.width/2; 88 | float rightPadding = leftPadding; 89 | float contentWidth = leftPadding + (steps * DISTANCE_BETWEEN_ITEMS) + rightPadding + TEXT_LAYER_WIDTH / 2; 90 | 91 | scale = [[UIScreen mainScreen] scale]; 92 | 93 | if ([self respondsToSelector:@selector(setContentScaleFactor:)]) { 94 | self.contentScaleFactor = scale; 95 | } 96 | 97 | // Ensures that the corners are transparent 98 | self.backgroundColor = [UIColor clearColor]; 99 | 100 | self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height)]; 101 | self.scrollView.contentSize = CGSizeMake(contentWidth, self.frame.size.height); 102 | self.scrollView.layer.cornerRadius = 8.0f; 103 | self.scrollView.layer.borderWidth = 1.0f; 104 | self.scrollView.layer.borderColor = borderColor.CGColor ? borderColor.CGColor : [UIColor grayColor].CGColor; 105 | self.scrollView.backgroundColor = [UIColor colorWithRed:0.95 green:0.95 blue:0.95 alpha:1.0]; 106 | self.scrollView.showsVerticalScrollIndicator = NO; 107 | self.scrollView.showsHorizontalScrollIndicator = NO; 108 | self.scrollView.pagingEnabled = NO; 109 | self.scrollView.delegate = self; 110 | 111 | self.scrollViewMarkerContainerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, contentWidth, self.frame.size.height)]; 112 | self.scrollViewMarkerLayerArray = [NSMutableArray arrayWithCapacity:steps]; 113 | 114 | fontSize = 16.0; 115 | 116 | for (int i = 0; i <= steps; i++) { 117 | CATextLayer *textLayer = [CATextLayer layer]; 118 | textLayer.contentsScale = scale; 119 | textLayer.frame = CGRectIntegral(CGRectMake(leftPadding + i * DISTANCE_BETWEEN_ITEMS, self.frame.size.height/2 - self.fontSize / 2 + 1, TEXT_LAYER_WIDTH, 40)); 120 | textLayer.foregroundColor = [UIColor blackColor].CGColor; 121 | textLayer.alignmentMode = kCAAlignmentCenter; 122 | textLayer.fontSize = self.fontSize; 123 | 124 | textLayer.string = [NSString stringWithFormat:@"%.1f", (float) i + 1]; 125 | [self.scrollViewMarkerLayerArray addObject:textLayer]; 126 | [self.scrollViewMarkerContainerView.layer addSublayer:textLayer]; 127 | } 128 | 129 | [self.scrollView addSubview:self.scrollViewMarkerContainerView]; 130 | [self addSubview:self.scrollView]; 131 | [self snapToMarkerAnimated:NO]; 132 | 133 | CAGradientLayer *dropshadowLayer = [CAGradientLayer layer]; 134 | dropshadowLayer.contentsScale = scale; 135 | dropshadowLayer.cornerRadius = 8.0f; 136 | dropshadowLayer.startPoint = CGPointMake(0.0f, 0.0f); 137 | dropshadowLayer.endPoint = CGPointMake(0.0f, 1.0f); 138 | dropshadowLayer.opacity = 1.0; 139 | dropshadowLayer.frame = CGRectMake(1.0f, 1.0f, self.frame.size.width - 2.0, self.frame.size.height - 2.0); 140 | dropshadowLayer.locations = [NSArray arrayWithObjects: 141 | [NSNumber numberWithFloat:0.0f], 142 | [NSNumber numberWithFloat:0.05f], 143 | [NSNumber numberWithFloat:0.2f], 144 | [NSNumber numberWithFloat:0.8f], 145 | [NSNumber numberWithFloat:0.95f], 146 | [NSNumber numberWithFloat:1.0f], nil]; 147 | dropshadowLayer.colors = [NSArray arrayWithObjects: 148 | (id)[[UIColor colorWithRed:0.05f green:0.05f blue:0.05f alpha:0.75] CGColor], 149 | (id)[[UIColor colorWithRed:0.25f green:0.25f blue:0.25f alpha:0.55] CGColor], 150 | (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.05] CGColor], 151 | (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.05] CGColor], 152 | (id)[[UIColor colorWithRed:0.25f green:0.25f blue:0.25f alpha:0.55] CGColor], 153 | (id)[[UIColor colorWithRed:0.05f green:0.05f blue:0.05f alpha:0.75] CGColor], nil]; 154 | 155 | [self.layer insertSublayer:dropshadowLayer above:self.scrollView.layer]; 156 | 157 | CAGradientLayer *gradientLayer = [CAGradientLayer layer]; 158 | gradientLayer.contentsScale = scale; 159 | gradientLayer.cornerRadius = 8.0f; 160 | gradientLayer.startPoint = CGPointMake(0.0f, 0.0f); 161 | gradientLayer.endPoint = CGPointMake(1.0f, 0.0f); 162 | gradientLayer.opacity = 1.0; 163 | gradientLayer.frame = CGRectMake(1.0f, 1.0f, self.frame.size.width - 2.0, self.frame.size.height - 2.0); 164 | gradientLayer.locations = [NSArray arrayWithObjects: 165 | [NSNumber numberWithFloat:0.0f], 166 | [NSNumber numberWithFloat:0.05f], 167 | [NSNumber numberWithFloat:0.3f], 168 | [NSNumber numberWithFloat:0.7f], 169 | [NSNumber numberWithFloat:0.95f], 170 | [NSNumber numberWithFloat:1.0f], nil]; 171 | gradientLayer.colors = [NSArray arrayWithObjects: 172 | (id)[[UIColor colorWithRed:0.05f green:0.05f blue:0.05f alpha:0.95] CGColor], 173 | (id)[[UIColor colorWithRed:0.25f green:0.25f blue:0.25f alpha:0.8] CGColor], 174 | (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.1] CGColor], 175 | (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.1] CGColor], 176 | (id)[[UIColor colorWithRed:0.25f green:0.25f blue:0.25f alpha:0.8] CGColor], 177 | (id)[[UIColor colorWithRed:0.05f green:0.05f blue:0.05f alpha:0.95] CGColor], nil]; 178 | [self.layer insertSublayer:gradientLayer above:dropshadowLayer]; 179 | 180 | self.pointerLayer = [CALayer layer]; 181 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor red]] forKey:@"borderRed"]; 182 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor green]] forKey:@"borderGreen"]; 183 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor blue]] forKey:@"borderBlue"]; 184 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor alpha]] forKey:@"borderAlpha"]; 185 | self.pointerLayer.opacity = 1.0; 186 | self.pointerLayer.contentsScale = scale; 187 | self.pointerLayer.frame = CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height); 188 | self.pointerLayer.delegate = [[[STPointerLayerDelegate alloc] init] autorelease]; 189 | [self.layer insertSublayer:self.pointerLayer above:gradientLayer]; 190 | [self.pointerLayer setNeedsDisplay]; 191 | } 192 | return self; 193 | } 194 | 195 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 196 | [self snapToMarkerAnimated:YES]; 197 | if (delegate && [delegate respondsToSelector:@selector(pickerView:didSelectValue:)]) { 198 | [self callDelegateWithNewValueFromOffset:[self.scrollView contentOffset].x]; 199 | } 200 | } 201 | 202 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 203 | [self snapToMarkerAnimated:YES]; 204 | if (delegate && [delegate respondsToSelector:@selector(pickerView:didSelectValue:)]) { 205 | [self callDelegateWithNewValueFromOffset:[self.scrollView contentOffset].x]; 206 | } 207 | } 208 | 209 | - (void)callDelegateWithNewValueFromOffset:(CGFloat)offset { 210 | 211 | CGFloat itemWidth = (float) DISTANCE_BETWEEN_ITEMS; 212 | 213 | CGFloat offSet = offset / itemWidth; 214 | NSUInteger target = (NSUInteger)(offSet + 0.35f); 215 | target = target > steps ? steps - 1 : target; 216 | CGFloat newValue = target * (maximumValue - minimumValue) / steps + minimumValue; 217 | 218 | [delegate pickerView:self didSelectValue:newValue]; 219 | 220 | } 221 | 222 | - (void)snapToMarkerAnimated:(BOOL)animated { 223 | CGFloat itemWidth = (float)DISTANCE_BETWEEN_ITEMS; 224 | CGFloat position = [self.scrollView contentOffset].x; 225 | 226 | if (position < self.scrollViewMarkerContainerView.frame.size.width - self.frame.size.width / 2) { 227 | CGFloat newPosition = 0.0f; 228 | CGFloat offSet = position / itemWidth; 229 | NSUInteger target = (NSUInteger)(offSet + 0.35f); 230 | target = target > steps ? steps - 1 : target; 231 | newPosition = target * itemWidth + TEXT_LAYER_WIDTH / 2; 232 | [self.scrollView setContentOffset:CGPointMake(newPosition, 0.0f) animated:animated]; 233 | } 234 | } 235 | 236 | - (void)removeAllMarkers { 237 | for (id marker in self.scrollViewMarkerLayerArray) { 238 | [(CATextLayer *)marker removeFromSuperlayer]; 239 | } 240 | [self.scrollViewMarkerLayerArray removeAllObjects]; 241 | } 242 | 243 | - (void)setupMarkers { 244 | [self removeAllMarkers]; 245 | 246 | // Calculate the new size of the content 247 | float leftPadding = self.frame.size.width / 2; 248 | float rightPadding = leftPadding; 249 | float contentWidth = leftPadding + (steps * DISTANCE_BETWEEN_ITEMS) + rightPadding + TEXT_LAYER_WIDTH / 2; 250 | self.scrollView.contentSize = CGSizeMake(contentWidth, self.frame.size.height); 251 | 252 | // Set the size of the marker container view 253 | [self.scrollViewMarkerContainerView setFrame:CGRectMake(0.0f, 0.0f, contentWidth, self.frame.size.height)]; 254 | 255 | // Configure the new markers 256 | self.scrollViewMarkerLayerArray = [NSMutableArray arrayWithCapacity:steps]; 257 | for (int i = 0; i <= steps; i++) { 258 | CATextLayer *textLayer = [CATextLayer layer]; 259 | textLayer.contentsScale = scale; 260 | textLayer.frame = CGRectIntegral(CGRectMake(leftPadding + i*DISTANCE_BETWEEN_ITEMS, self.frame.size.height / 2 - fontSize / 2 + 1, TEXT_LAYER_WIDTH, 40)); 261 | textLayer.foregroundColor = [UIColor blackColor].CGColor; 262 | textLayer.alignmentMode = kCAAlignmentCenter; 263 | textLayer.fontSize = fontSize; 264 | 265 | textLayer.string = [NSString stringWithFormat:@"%.1f", (float) minimumValue + i * (maximumValue - minimumValue) / steps]; 266 | [self.scrollViewMarkerLayerArray addObject:textLayer]; 267 | [self.scrollViewMarkerContainerView.layer addSublayer:textLayer]; 268 | } 269 | } 270 | 271 | - (CGFloat)minimumValue { 272 | return minimumValue; 273 | } 274 | 275 | - (void)setMinimumValue:(CGFloat)newMin { 276 | minimumValue = newMin; 277 | [self setupMarkers]; 278 | } 279 | 280 | - (CGFloat)maximumValue { 281 | return maximumValue; 282 | } 283 | 284 | - (void)setMaximumValue:(CGFloat)newMax { 285 | maximumValue = newMax; 286 | [self setupMarkers]; 287 | } 288 | 289 | - (NSUInteger)steps { 290 | return steps; 291 | } 292 | 293 | - (void)setSteps:(NSUInteger)newSteps { 294 | steps = newSteps; 295 | [self setupMarkers]; 296 | } 297 | 298 | - (CGFloat)value { 299 | return value; 300 | } 301 | 302 | - (UIColor *)borderColor { 303 | return borderColor; 304 | } 305 | 306 | - (void)setBorderColor:(UIColor *)newBorderColor { 307 | if (newBorderColor != borderColor) 308 | { 309 | [newBorderColor retain]; 310 | [borderColor release]; 311 | borderColor = newBorderColor; 312 | 313 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor red]] forKey:@"borderRed"]; 314 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor green]] forKey:@"borderGreen"]; 315 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor blue]] forKey:@"borderBlue"]; 316 | [self.pointerLayer setValue:[NSNumber numberWithFloat:[borderColor alpha]] forKey:@"borderAlpha"]; 317 | [self.pointerLayer setNeedsDisplay]; 318 | 319 | self.scrollView.layer.borderColor = borderColor.CGColor; 320 | } 321 | } 322 | 323 | - (CGFloat)fontSize { 324 | return fontSize; 325 | } 326 | 327 | - (void)setFontSize:(CGFloat)newFontSize { 328 | fontSize = newFontSize; 329 | [self setupMarkers]; 330 | } 331 | 332 | - (void)setValue:(CGFloat)newValue { 333 | value = newValue > maximumValue ? maximumValue : newValue; 334 | value = newValue < minimumValue ? minimumValue : newValue; 335 | 336 | CGFloat itemWidth = (float) DISTANCE_BETWEEN_ITEMS; 337 | CGFloat xValue = (newValue - minimumValue) / ((maximumValue-minimumValue) / steps) * itemWidth + TEXT_LAYER_WIDTH / 2; 338 | 339 | [self.scrollView setContentOffset:CGPointMake(xValue, 0.0f) animated:NO]; 340 | } 341 | 342 | - (id)delegate { 343 | return delegate; 344 | } 345 | 346 | - (void)setDelegate:(id)newDelegate { 347 | delegate = newDelegate; 348 | 349 | BOOL needsReset = FALSE; 350 | 351 | if ([delegate respondsToSelector:@selector(minimumValueForPickerView:)]) { 352 | minimumValue = [delegate minimumValueForPickerView:self]; 353 | needsReset = TRUE; 354 | } 355 | if ([delegate respondsToSelector:@selector(maximumValueForPickerView:)]) { 356 | maximumValue = [delegate maximumValueForPickerView:self]; 357 | needsReset = TRUE; 358 | } 359 | if ([delegate respondsToSelector:@selector(stepCountForPickerView:)]) { 360 | steps = [delegate stepCountForPickerView:self]; 361 | needsReset = TRUE; 362 | } 363 | 364 | if (needsReset) { 365 | [self setupMarkers]; 366 | } 367 | } 368 | 369 | 370 | - (void)dealloc 371 | { 372 | [scrollView release]; 373 | [scrollViewMarkerContainerView release]; 374 | [scrollViewMarkerLayerArray release]; 375 | [pointerLayer release]; 376 | [super dealloc]; 377 | } 378 | 379 | @end 380 | 381 | 382 | //================================ 383 | // STPointerLayerDelegate 384 | //================================ 385 | 386 | @implementation STPointerLayerDelegate 387 | 388 | - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { 389 | CGContextSaveGState(context); 390 | 391 | CGContextSetLineWidth(context, 2.0); 392 | CGContextSetRGBStrokeColor(context, [[layer valueForKey:@"borderRed"] floatValue], [[layer valueForKey:@"borderGreen"] floatValue], [[layer valueForKey:@"borderBlue"] floatValue], [[layer valueForKey:@"borderAlpha"] floatValue]); 393 | CGContextSetRGBFillColor(context, [[layer valueForKey:@"borderRed"] floatValue], [[layer valueForKey:@"borderGreen"] floatValue], [[layer valueForKey:@"borderBlue"] floatValue], [[layer valueForKey:@"borderAlpha"] floatValue]); 394 | 395 | CGContextSetShadowWithColor(context, CGSizeMake(0, 2), 3.0, [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.3].CGColor); 396 | 397 | CGContextMoveToPoint(context, layer.frame.size.width / 2 - POINTER_WIDTH / 2, 0); 398 | CGContextAddLineToPoint(context, layer.frame.size.width / 2, POINTER_HEIGHT); 399 | CGContextAddLineToPoint(context, layer.frame.size.width / 2 + POINTER_WIDTH / 2, 0); 400 | CGContextFillPath(context); 401 | CGContextStrokePath(context); 402 | 403 | CGContextSetShadowWithColor(context, CGSizeMake(0, -2), 3.0, [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.3].CGColor); 404 | 405 | CGContextMoveToPoint(context, layer.frame.size.width / 2 - POINTER_WIDTH / 2, layer.frame.size.height); 406 | CGContextAddLineToPoint(context, layer.frame.size.width / 2, layer.frame.size.height - POINTER_HEIGHT); 407 | CGContextAddLineToPoint(context, layer.frame.size.width / 2 + POINTER_WIDTH / 2, layer.frame.size.height); 408 | CGContextFillPath(context); 409 | CGContextStrokePath(context); 410 | 411 | CGContextRestoreGState(context); 412 | } 413 | 414 | @end -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackthread/STHorizontalPicker/3f69c778374bfeda10e69b801483a3ff2d069735/screenshot.png --------------------------------------------------------------------------------