Advertisement
dinophanhk

[Obj-C] HMSegmentControl

Jun 18th, 2014
294
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /* .h file */
  2.  
  3. #import <UIKit/UIKit.h>
  4.  
  5. typedef void (^IndexChangeBlock)(NSInteger index);
  6.  
  7. typedef enum {
  8.     HMSegmentedControlSelectionStyleTextWidthStripe, // Indicator width will only be as big as the text width
  9.     HMSegmentedControlSelectionStyleFullWidthStripe, // Indicator width will fill the whole segment
  10.     HMSegmentedControlSelectionStyleBox, // A rectangle that covers the whole segment
  11.     HMSegmentedControlSelectionStyleArrow // An arrow in the middle of the segment pointing up or down depending on `HMSegmentedControlSelectionIndicatorLocation`
  12. } HMSegmentedControlSelectionStyle;
  13.  
  14. typedef enum {
  15.     HMSegmentedControlSelectionIndicatorLocationUp,
  16.     HMSegmentedControlSelectionIndicatorLocationDown,
  17.     HMSegmentedControlSelectionIndicatorLocationNone // No selection indicator
  18. } HMSegmentedControlSelectionIndicatorLocation;
  19.  
  20. typedef enum {
  21.     HMSegmentedControlSegmentWidthStyleFixed, // Segment width is fixed
  22.     HMSegmentedControlSegmentWidthStyleDynamic, // Segment width will only be as big as the text width (including inset)
  23. } HMSegmentedControlSegmentWidthStyle;
  24.  
  25. enum {
  26.     HMSegmentedControlNoSegment = -1   // Segment index for no selected segment
  27. };
  28.  
  29. typedef enum {
  30.     HMSegmentedControlTypeText,
  31.     HMSegmentedControlTypeImages,
  32.     HMSegmentedControlTypeTextImages
  33. } HMSegmentedControlType;
  34.  
  35. @interface HMSegmentedControl : UIControl
  36.  
  37. @property (nonatomic, strong) NSArray *sectionTitles;
  38. @property (nonatomic, strong) NSArray *sectionImages;
  39. @property (nonatomic, strong) NSArray *sectionSelectedImages;
  40.  
  41. /*
  42.  Provide a block to be executed when selected index is changed.
  43.  
  44.  Alternativly, you could use `addTarget:action:forControlEvents:`
  45.  */
  46. @property (nonatomic, copy) IndexChangeBlock indexChangeBlock;
  47.  
  48. /*
  49.  Font for segments names when segmented control type is `HMSegmentedControlTypeText`
  50.  
  51.  Default is [UIFont fontWithName:@"STHeitiSC-Light" size:18.0f]
  52.  */
  53. @property (nonatomic, strong) UIFont *font;
  54.  
  55. /*
  56.  Text color for segments names when segmented control type is `HMSegmentedControlTypeText`
  57.  
  58.  Default is [UIColor blackColor]
  59.  */
  60. @property (nonatomic, strong) UIColor *textColor;
  61.  
  62. /*
  63.  Text color for selected segment name when segmented control type is `HMSegmentedControlTypeText`
  64.  
  65.  Default is [UIColor blackColor]
  66.  */
  67. @property (nonatomic, strong) UIColor *selectedTextColor;
  68.  
  69. /*
  70.  Segmented control background color.
  71.  
  72.  Default is [UIColor whiteColor]
  73.  */
  74. @property (nonatomic, strong) UIColor *backgroundColor;
  75.  
  76. /*
  77.  Color for the selection indicator stripe/box
  78.  
  79.  Default is R:52, G:181, B:229
  80.  */
  81. @property (nonatomic, strong) UIColor *selectionIndicatorColor;
  82.  
  83. /*
  84.  Specifies the style of the control
  85.  
  86.  Default is `HMSegmentedControlTypeText`
  87.  */
  88. @property (nonatomic, assign) HMSegmentedControlType type;
  89.  
  90. /*
  91.  Specifies the style of the selection indicator.
  92.  
  93.  Default is `HMSegmentedControlSelectionStyleTextWidthStripe`
  94.  */
  95. @property (nonatomic, assign) HMSegmentedControlSelectionStyle selectionStyle;
  96.  
  97. /*
  98.  Specifies the style of the segment's width.
  99.  
  100.  Default is `HMSegmentedControlSegmentWidthStyleFixed`
  101.  */
  102. @property (nonatomic, assign) HMSegmentedControlSegmentWidthStyle segmentWidthStyle;
  103.  
  104. /*
  105.  Specifies the location of the selection indicator.
  106.  
  107.  Default is `HMSegmentedControlSelectionIndicatorLocationUp`
  108.  */
  109. @property (nonatomic, assign) HMSegmentedControlSelectionIndicatorLocation selectionIndicatorLocation;
  110.  
  111. /*
  112.  Default is NO. Set to YES to allow for adding more tabs than the screen width could fit.
  113.  
  114.  When set to YES, segment width will be automatically set to the width of the biggest segment's text or image,
  115.  otherwise it will be equal to the width of the control's frame divided by the number of segments.
  116.  
  117.  As of v 1.4 this is no longer needed. The control will manage scrolling automatically based on tabs sizes.
  118.  */
  119. @property(nonatomic, getter = isScrollEnabled) BOOL scrollEnabled DEPRECATED_ATTRIBUTE;
  120.  
  121. /*
  122.  Default is YES. Set to NO to deny scrolling by dragging the scrollView by the user.
  123.  */
  124. @property(nonatomic, getter = isUserDraggable) BOOL userDraggable;
  125.  
  126. /*
  127.  Default is YES. Set to NO to deny any touch events by the user.
  128.  */
  129. @property(nonatomic, getter = isTouchEnabled) BOOL touchEnabled;
  130.  
  131.  
  132. /*
  133.  Index of the currently selected segment.
  134.  */
  135. @property (nonatomic, assign) NSInteger selectedSegmentIndex;
  136.  
  137. /*
  138.  Height of the selection indicator. Only effective when `HMSegmentedControlSelectionStyle` is either `HMSegmentedControlSelectionStyleTextWidthStripe` or `HMSegmentedControlSelectionStyleFullWidthStripe`.
  139.  
  140.  Default is 5.0
  141.  */
  142. @property (nonatomic, readwrite) CGFloat selectionIndicatorHeight;
  143.  
  144. /*
  145.  Inset left and right edges of segments. Only effective when `scrollEnabled` is set to YES.
  146.  
  147.  Default is UIEdgeInsetsMake(0, 5, 0, 5)
  148.  */
  149. @property (nonatomic, readwrite) UIEdgeInsets segmentEdgeInset;
  150.  
  151. /*
  152.  Default is YES. Set to NO to disable animation during user selection.
  153.  */
  154. @property (nonatomic) BOOL shouldAnimateUserSelection;
  155.  
  156. - (id)initWithSectionTitles:(NSArray *)sectiontitles;
  157. - (id)initWithSectionImages:(NSArray *)sectionImages sectionSelectedImages:(NSArray *)sectionSelectedImages;
  158. - (instancetype)initWithSectionImages:(NSArray *)sectionImages sectionSelectedImages:(NSArray *)sectionSelectedImages titlesForSections:(NSArray *)sectiontitles;
  159. - (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated;
  160. - (void)setIndexChangeBlock:(IndexChangeBlock)indexChangeBlock;
  161.  
  162. @end
  163.  
  164.  
  165.  
  166. /* .m file */
  167.  
  168. #import "HMSegmentedControl.h"
  169. #import <QuartzCore/QuartzCore.h>
  170. #import <math.h>
  171.  
  172. #define segmentImageTextPadding 7
  173.  
  174. @interface HMScrollView : UIScrollView
  175. @end
  176.  
  177. @interface HMSegmentedControl ()
  178.  
  179. @property (nonatomic, strong) CALayer *selectionIndicatorStripLayer;
  180. @property (nonatomic, strong) CALayer *selectionIndicatorBoxLayer;
  181. @property (nonatomic, strong) CALayer *selectionIndicatorArrowLayer;
  182. @property (nonatomic, readwrite) CGFloat segmentWidth;
  183. @property (nonatomic, readwrite) NSArray *segmentWidthsArray;
  184. @property (nonatomic, strong) HMScrollView *scrollView;
  185.  
  186. @end
  187.  
  188. @implementation HMScrollView
  189.  
  190. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  191.     if (!self.dragging) {
  192.         [self.nextResponder touchesBegan:touches withEvent:event];
  193.     } else {
  194.         [super touchesBegan:touches withEvent:event];
  195.     }
  196. }
  197.  
  198. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
  199.     if (!self.dragging) {
  200.         [self.nextResponder touchesMoved:touches withEvent:event];
  201.     } else{
  202.         [super touchesMoved:touches withEvent:event];
  203.     }
  204. }
  205.  
  206. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  207.     if (!self.dragging) {
  208.         [self.nextResponder touchesEnded:touches withEvent:event];
  209.     } else {
  210.         [super touchesEnded:touches withEvent:event];
  211.     }
  212. }
  213.  
  214. @end
  215.  
  216. @implementation HMSegmentedControl
  217.  
  218. - (id)initWithCoder:(NSCoder *)aDecoder {
  219.     self = [super initWithCoder:aDecoder];
  220.     if (self) {
  221.         [self commonInit];
  222.     }
  223.     return self;
  224. }
  225.  
  226. - (id)initWithFrame:(CGRect)frame {
  227.     self = [super initWithFrame:frame];
  228.    
  229.     if (self) {
  230.         [self commonInit];
  231.     }
  232.    
  233.     return self;
  234. }
  235.  
  236. - (id)initWithSectionTitles:(NSArray *)sectiontitles {
  237.     self = [self initWithFrame:CGRectZero];
  238.    
  239.     if (self) {
  240.         [self commonInit];
  241.         self.sectionTitles = sectiontitles;
  242.         self.type = HMSegmentedControlTypeText;
  243.     }
  244.    
  245.     return self;
  246. }
  247.  
  248. - (id)initWithSectionImages:(NSArray*)sectionImages sectionSelectedImages:(NSArray*)sectionSelectedImages {
  249.     self = [super initWithFrame:CGRectZero];
  250.    
  251.     if (self) {
  252.         [self commonInit];
  253.         self.sectionImages = sectionImages;
  254.         self.sectionSelectedImages = sectionSelectedImages;
  255.         self.type = HMSegmentedControlTypeImages;
  256.     }
  257.    
  258.     return self;
  259. }
  260.  
  261. - (instancetype)initWithSectionImages:(NSArray *)sectionImages sectionSelectedImages:(NSArray *)sectionSelectedImages titlesForSections:(NSArray *)sectiontitles {
  262.     self = [super initWithFrame:CGRectZero];
  263.    
  264.     if (self) {
  265.         [self commonInit];
  266.        
  267.         if (sectionImages.count != sectiontitles.count) {
  268.             [NSException raise:NSRangeException format:@"***%s: Images bounds (%ld) Dont match Title bounds (%ld)", sel_getName(_cmd), (unsigned long)sectionImages.count, (unsigned long)sectiontitles.count];
  269.         }
  270.        
  271.         self.sectionImages = sectionImages;
  272.         self.sectionSelectedImages = sectionSelectedImages;
  273.         self.sectionTitles = sectiontitles;
  274.         self.type = HMSegmentedControlTypeTextImages;
  275.     }
  276.    
  277.     return self;
  278. }
  279.  
  280. - (void)awakeFromNib {
  281.     [super awakeFromNib];
  282.    
  283.     self.segmentWidth = 0.0f;
  284.     [self commonInit];
  285. }
  286.  
  287. - (void)commonInit {
  288.     self.scrollView = [[HMScrollView alloc] init];
  289.     self.scrollView.scrollsToTop = NO;
  290.     self.scrollView.showsVerticalScrollIndicator = NO;
  291.     self.scrollView.showsHorizontalScrollIndicator = NO;
  292.     [self addSubview:self.scrollView];
  293.    
  294.     self.font = [UIFont fontWithName:@"STHeitiSC-Light" size:18.0f];
  295.     self.textColor = [UIColor blackColor];
  296.     self.selectedTextColor = [UIColor blackColor];
  297.     self.backgroundColor = [UIColor whiteColor];
  298.     self.opaque = NO;
  299.     self.selectionIndicatorColor = [UIColor colorWithRed:52.0f/255.0f green:181.0f/255.0f blue:229.0f/255.0f alpha:1.0f];
  300.    
  301.     self.selectedSegmentIndex = 0;
  302.     self.segmentEdgeInset = UIEdgeInsetsMake(0, 5, 0, 5);
  303.     self.selectionIndicatorHeight = 5.0f;
  304.     self.selectionStyle = HMSegmentedControlSelectionStyleTextWidthStripe;
  305.     self.selectionIndicatorLocation = HMSegmentedControlSelectionIndicatorLocationUp;
  306.     self.segmentWidthStyle = HMSegmentedControlSegmentWidthStyleFixed;
  307.     self.userDraggable = YES;
  308.     self.touchEnabled = YES;
  309.     self.type = HMSegmentedControlTypeText;
  310.    
  311.     self.shouldAnimateUserSelection = YES;
  312.    
  313.     self.selectionIndicatorArrowLayer = [CALayer layer];
  314.    
  315.     self.selectionIndicatorStripLayer = [CALayer layer];
  316.    
  317.     self.selectionIndicatorBoxLayer = [CALayer layer];
  318.     self.selectionIndicatorBoxLayer.opacity = 0.2;
  319.     self.selectionIndicatorBoxLayer.borderWidth = 1.0f;
  320.    
  321.     self.contentMode = UIViewContentModeRedraw;
  322. }
  323.  
  324. - (void)layoutSubviews {
  325.     [super layoutSubviews];
  326.    
  327.     [self updateSegmentsRects];
  328. }
  329.  
  330. - (void)setFrame:(CGRect)frame {
  331.     [super setFrame:frame];
  332.    
  333.     [self updateSegmentsRects];
  334. }
  335.  
  336. - (void)setSectionTitles:(NSArray *)sectionTitles {
  337.     _sectionTitles = sectionTitles;
  338.    
  339.     [self setNeedsLayout];
  340. }
  341.  
  342. - (void)setSectionImages:(NSArray *)sectionImages {
  343.     _sectionImages = sectionImages;
  344.    
  345.     [self setNeedsLayout];
  346. }
  347.  
  348. - (void)setSelectionIndicatorLocation:(HMSegmentedControlSelectionIndicatorLocation)selectionIndicatorLocation {
  349.     _selectionIndicatorLocation = selectionIndicatorLocation;
  350.    
  351.     if (selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationNone) {
  352.         self.selectionIndicatorHeight = 0.0f;
  353.     }
  354. }
  355.  
  356. #pragma mark - Drawing
  357.  
  358. - (void)drawRect:(CGRect)rect {
  359.     [self.backgroundColor setFill];
  360.     UIRectFill([self bounds]);
  361.  
  362.     self.selectionIndicatorArrowLayer.backgroundColor = self.selectionIndicatorColor.CGColor;
  363.    
  364.     self.selectionIndicatorStripLayer.backgroundColor = self.selectionIndicatorColor.CGColor;
  365.    
  366.     self.selectionIndicatorBoxLayer.backgroundColor = self.selectionIndicatorColor.CGColor;
  367.     self.selectionIndicatorBoxLayer.borderColor = self.selectionIndicatorColor.CGColor;
  368.    
  369.     // Remove all sublayers to avoid drawing images over existing ones
  370.     self.scrollView.layer.sublayers = nil;
  371.    
  372.     if (self.type == HMSegmentedControlTypeText) {
  373.         [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
  374.             CGFloat stringHeight = roundf([titleString sizeWithFont:self.font].height);
  375.             CGFloat stringWidth = roundf([titleString sizeWithFont:self.font].width);
  376.            
  377.             // Text inside the CATextLayer will appear blurry unless the rect values are rounded
  378.             CGFloat y = roundf(CGRectGetHeight(self.frame) - self.selectionIndicatorHeight)/2 - stringHeight/2 + ((self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationUp) ? self.selectionIndicatorHeight : 0);
  379.            
  380.             CGRect rect;
  381.             if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  382.                 rect = CGRectMake((self.segmentWidth * idx) + (self.segmentWidth - stringWidth)/2, y, stringWidth, stringHeight);
  383.             } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  384.                 // When we are drawing dynamic widths, we need to loop the widths array to calculate the xOffset
  385.                 CGFloat xOffset = 0;
  386.                 NSInteger i = 0;
  387.                 for (NSNumber *width in self.segmentWidthsArray) {
  388.                     if (idx == i)
  389.                         break;
  390.                     xOffset = xOffset + [width floatValue];
  391.                     i++;
  392.                 }
  393.                
  394.                 rect = CGRectMake(xOffset, y, [[self.segmentWidthsArray objectAtIndex:idx] floatValue], stringHeight);
  395.             }
  396.            
  397.             CATextLayer *titleLayer = [CATextLayer layer];
  398.             titleLayer.frame = rect;
  399.             titleLayer.font = (__bridge CFTypeRef)(self.font.fontName);
  400.             titleLayer.fontSize = self.font.pointSize;
  401.             titleLayer.alignmentMode = kCAAlignmentCenter;
  402.             titleLayer.string = titleString;
  403.             titleLayer.truncationMode = kCATruncationEnd;
  404.            
  405.             if (self.selectedSegmentIndex == idx) {
  406.                 titleLayer.foregroundColor = self.selectedTextColor.CGColor;
  407.             } else {
  408.                 titleLayer.foregroundColor = self.textColor.CGColor;
  409.             }
  410.            
  411.             titleLayer.contentsScale = [[UIScreen mainScreen] scale];
  412.             [self.scrollView.layer addSublayer:titleLayer];
  413.         }];
  414.     } else if (self.type == HMSegmentedControlTypeImages) {
  415.         [self.sectionImages enumerateObjectsUsingBlock:^(id iconImage, NSUInteger idx, BOOL *stop) {
  416.             UIImage *icon = iconImage;
  417.             CGFloat imageWidth = icon.size.width;
  418.             CGFloat imageHeight = icon.size.height;
  419.             CGFloat y = roundf(CGRectGetHeight(self.frame) - self.selectionIndicatorHeight) / 2 - imageHeight / 2 + ((self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationUp) ? self.selectionIndicatorHeight : 0);
  420.             CGFloat x = self.segmentWidth * idx + (self.segmentWidth - imageWidth)/2.0f;
  421.             CGRect rect = CGRectMake(x, y, imageWidth, imageHeight);
  422.            
  423.             CALayer *imageLayer = [CALayer layer];
  424.             imageLayer.frame = rect;
  425.                        
  426.             if (self.selectedSegmentIndex == idx) {
  427.                 if (self.sectionSelectedImages) {
  428.                     UIImage *highlightIcon = [self.sectionSelectedImages objectAtIndex:idx];
  429.                     imageLayer.contents = (id)highlightIcon.CGImage;
  430.                 } else {
  431.                     imageLayer.contents = (id)icon.CGImage;
  432.                 }
  433.             } else {
  434.                 imageLayer.contents = (id)icon.CGImage;
  435.             }
  436.            
  437.             [self.scrollView.layer addSublayer:imageLayer];
  438.         }];
  439.     } else if (self.type == HMSegmentedControlTypeTextImages){
  440.         [self.sectionImages enumerateObjectsUsingBlock:^(id iconImage, NSUInteger idx, BOOL *stop) {
  441.             // When we have both an image and a title, we start with the image and use segmentImageTextPadding before drawing the text.
  442.             // So the image will be left to the text, centered in the middle
  443.             UIImage *icon = iconImage;
  444.             CGFloat imageWidth = icon.size.width;
  445.             CGFloat imageHeight = icon.size.height;
  446.            
  447.             CGFloat stringHeight = roundf([self.sectionTitles[idx] sizeWithFont:self.font].height);
  448.             CGFloat yOffset = roundf(CGRectGetHeight(self.frame) - self.selectionIndicatorHeight) / 2 - stringHeight / 2 + ((self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationUp) ? self.selectionIndicatorHeight : 0);
  449.             CGFloat imageXOffset = self.segmentEdgeInset.left; // Start with edge inset
  450.             if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed)
  451.                 imageXOffset = self.segmentWidth * idx;
  452.             else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  453.                 // When we are drawing dynamic widths, we need to loop the widths array to calculate the xOffset
  454.                 NSInteger i = 0;
  455.                 for (NSNumber *width in self.segmentWidthsArray) {
  456.                     if (idx == i)
  457.                         break;
  458.                     imageXOffset = imageXOffset + [width floatValue];
  459.                     i++;
  460.                 }
  461.             }
  462.            
  463.             CGRect imageRect = CGRectMake(imageXOffset, yOffset, imageWidth, imageHeight);
  464.            
  465.             // Use the image offset and padding to calculate the text offset
  466.             CGFloat textXOffset = imageXOffset + imageWidth + segmentImageTextPadding;
  467.            
  468.             // The text rect's width is the segment width without the image, image padding and insets
  469.             CGRect textRect = CGRectMake(textXOffset, yOffset, [[self.segmentWidthsArray objectAtIndex:idx] floatValue]-imageWidth-segmentImageTextPadding-self.segmentEdgeInset.left-self.segmentEdgeInset.right, stringHeight);
  470.             CATextLayer *titleLayer = [CATextLayer layer];
  471.             titleLayer.frame = textRect;
  472.             titleLayer.font = (__bridge CFTypeRef)(self.font.fontName);
  473.             titleLayer.fontSize = self.font.pointSize;
  474.             titleLayer.alignmentMode = kCAAlignmentCenter;
  475.             titleLayer.string = self.sectionTitles[idx];
  476.             titleLayer.truncationMode = kCATruncationEnd;
  477.            
  478.             CALayer *imageLayer = [CALayer layer];
  479.             imageLayer.frame = imageRect;
  480.            
  481.             if (self.selectedSegmentIndex == idx) {
  482.                 if (self.sectionSelectedImages) {
  483.                     UIImage *highlightIcon = [self.sectionSelectedImages objectAtIndex:idx];
  484.                     imageLayer.contents = (id)highlightIcon.CGImage;
  485.                 } else {
  486.                     imageLayer.contents = (id)icon.CGImage;
  487.                 }
  488.                 titleLayer.foregroundColor = self.selectedTextColor.CGColor;
  489.             } else {
  490.                 imageLayer.contents = (id)icon.CGImage;
  491.                 titleLayer.foregroundColor = self.textColor.CGColor;
  492.             }
  493.            
  494.             [self.scrollView.layer addSublayer:imageLayer];
  495.             titleLayer.contentsScale = [[UIScreen mainScreen] scale];
  496.             [self.scrollView.layer addSublayer:titleLayer];
  497.            
  498.         }];
  499.     }
  500.  
  501.     // Add the selection indicators
  502.     if (self.selectedSegmentIndex != HMSegmentedControlNoSegment) {
  503.         if (self.selectionStyle == HMSegmentedControlSelectionStyleArrow) {
  504.             if (!self.selectionIndicatorArrowLayer.superlayer) {
  505.                 [self setArrowFrame];
  506.                 [self.scrollView.layer addSublayer:self.selectionIndicatorArrowLayer];
  507.             }
  508.         } else {
  509.             if (!self.selectionIndicatorStripLayer.superlayer) {
  510.                 self.selectionIndicatorStripLayer.frame = [self frameForSelectionIndicator];
  511.                 [self.scrollView.layer addSublayer:self.selectionIndicatorStripLayer];
  512.                
  513.                 if (self.selectionStyle == HMSegmentedControlSelectionStyleBox && !self.selectionIndicatorBoxLayer.superlayer) {
  514.                     self.selectionIndicatorBoxLayer.frame = [self frameForFillerSelectionIndicator];
  515.                     [self.scrollView.layer insertSublayer:self.selectionIndicatorBoxLayer atIndex:0];
  516.                 }
  517.             }
  518.         }
  519.     }
  520. }
  521.  
  522. - (void)setArrowFrame {
  523.     self.selectionIndicatorArrowLayer.frame = [self frameForSelectionIndicator];
  524.    
  525.     self.selectionIndicatorArrowLayer.mask = nil;
  526.    
  527.     UIBezierPath *arrowPath = [UIBezierPath bezierPath];
  528.    
  529.     CGPoint p1 = CGPointZero;
  530.     CGPoint p2 = CGPointZero;
  531.     CGPoint p3 = CGPointZero;
  532.    
  533.     if (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationDown) {
  534.         p1 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width / 2, 0);
  535.         p2 = CGPointMake(0, self.selectionIndicatorArrowLayer.bounds.size.height);
  536.         p3 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width, self.selectionIndicatorArrowLayer.bounds.size.height);
  537.     }
  538.    
  539.     if (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationUp) {
  540.         p1 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width / 2, self.selectionIndicatorArrowLayer.bounds.size.height);
  541.         p2 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width, 0);
  542.         p3 = CGPointMake(0, 0);
  543.     }
  544.    
  545.     [arrowPath moveToPoint:p1];
  546.     [arrowPath addLineToPoint:p2];
  547.     [arrowPath addLineToPoint:p3];
  548.     [arrowPath closePath];
  549.    
  550.     CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
  551.     maskLayer.frame = self.selectionIndicatorArrowLayer.bounds;
  552.     maskLayer.path = arrowPath.CGPath;
  553.     self.selectionIndicatorArrowLayer.mask = maskLayer;
  554. }
  555.  
  556. - (CGRect)frameForSelectionIndicator {
  557.     CGFloat indicatorYOffset = 0.0f;
  558.        
  559.     if (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationDown) {
  560.         indicatorYOffset = self.bounds.size.height - self.selectionIndicatorHeight;
  561.     }
  562.    
  563.     CGFloat sectionWidth = 0.0f;
  564.  
  565.     if (self.type == HMSegmentedControlTypeText) {
  566.         CGFloat stringWidth = [[self.sectionTitles objectAtIndex:self.selectedSegmentIndex] sizeWithFont:self.font].width;
  567.         sectionWidth = stringWidth;
  568.     } else if (self.type == HMSegmentedControlTypeImages) {
  569.         UIImage *sectionImage = [self.sectionImages objectAtIndex:self.selectedSegmentIndex];
  570.         CGFloat imageWidth = sectionImage.size.width;
  571.         sectionWidth = imageWidth;
  572.     } else if (self.type == HMSegmentedControlTypeTextImages) {
  573.         CGFloat stringWidth = [[self.sectionTitles objectAtIndex:self.selectedSegmentIndex] sizeWithFont:self.font].width;
  574.         UIImage *sectionImage = [self.sectionImages objectAtIndex:self.selectedSegmentIndex];
  575.         CGFloat imageWidth = sectionImage.size.width;
  576.         if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  577.             sectionWidth = MAX(stringWidth, imageWidth);
  578.         } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  579.             sectionWidth = imageWidth + segmentImageTextPadding + stringWidth;
  580.         }
  581.     }
  582.    
  583.     if (self.selectionStyle == HMSegmentedControlSelectionStyleArrow) {
  584.         CGFloat widthToEndOfSelectedSegment = (self.segmentWidth * self.selectedSegmentIndex) + self.segmentWidth;
  585.         CGFloat widthToStartOfSelectedIndex = (self.segmentWidth * self.selectedSegmentIndex);
  586.        
  587.         CGFloat x = widthToStartOfSelectedIndex + ((widthToEndOfSelectedSegment - widthToStartOfSelectedIndex) / 2) - (self.selectionIndicatorHeight/2);
  588.         return CGRectMake(x, indicatorYOffset, self.selectionIndicatorHeight, self.selectionIndicatorHeight);
  589.     } else {
  590.         if (self.selectionStyle == HMSegmentedControlSelectionStyleTextWidthStripe &&
  591.             sectionWidth <= self.segmentWidth &&
  592.             self.segmentWidthStyle != HMSegmentedControlSegmentWidthStyleDynamic) {
  593.             CGFloat widthToEndOfSelectedSegment = (self.segmentWidth * self.selectedSegmentIndex) + self.segmentWidth;
  594.             CGFloat widthToStartOfSelectedIndex = (self.segmentWidth * self.selectedSegmentIndex);
  595.            
  596.             CGFloat x = ((widthToEndOfSelectedSegment - widthToStartOfSelectedIndex) / 2) + (widthToStartOfSelectedIndex - sectionWidth / 2);
  597.             return CGRectMake(x, indicatorYOffset, sectionWidth, self.selectionIndicatorHeight);
  598.         } else {
  599.             if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  600.                 CGFloat selectedSegmentOffset = 0.0f;
  601.                
  602.                 NSInteger i = 0;
  603.                 for (NSNumber *width in self.segmentWidthsArray) {
  604.                     if (self.selectedSegmentIndex == i)
  605.                         break;
  606.                     selectedSegmentOffset = selectedSegmentOffset + [width floatValue];
  607.                     i++;
  608.                 }
  609.                
  610.                 return CGRectMake(selectedSegmentOffset, indicatorYOffset, [[self.segmentWidthsArray objectAtIndex:self.selectedSegmentIndex] floatValue], self.selectionIndicatorHeight);
  611.             }
  612.            
  613.             return CGRectMake(self.segmentWidth * self.selectedSegmentIndex, indicatorYOffset, self.segmentWidth, self.selectionIndicatorHeight);
  614.         }
  615.     }
  616. }
  617.  
  618. - (CGRect)frameForFillerSelectionIndicator {
  619.     if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  620.         CGFloat selectedSegmentOffset = 0.0f;
  621.        
  622.         NSInteger i = 0;
  623.         for (NSNumber *width in self.segmentWidthsArray) {
  624.             if (self.selectedSegmentIndex == i) {
  625.                 break;
  626.             }
  627.             selectedSegmentOffset = selectedSegmentOffset + [width floatValue];
  628.  
  629.             i++;
  630.         }
  631.        
  632.         return CGRectMake(selectedSegmentOffset, 0, [[self.segmentWidthsArray objectAtIndex:self.selectedSegmentIndex] floatValue], CGRectGetHeight(self.frame));
  633.     }
  634.     return CGRectMake(self.segmentWidth * self.selectedSegmentIndex, 0, self.segmentWidth, CGRectGetHeight(self.frame));
  635. }
  636.  
  637. - (void)updateSegmentsRects {
  638.     self.scrollView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
  639.    
  640.     // When `scrollEnabled` is set to YES, segment width will be automatically set to the width of the biggest segment's text or image,
  641.     // otherwise it will be equal to the width of the control's frame divided by the number of segments.
  642.     if ([self sectionCount] > 0) {
  643.         self.segmentWidth = self.frame.size.width / [self sectionCount];
  644.     }
  645.    
  646.     if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  647.         for (NSString *titleString in self.sectionTitles) {
  648. #if  __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  649.             CGFloat stringWidth = [titleString sizeWithAttributes:@{NSFontAttributeName: self.font}].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  650. #else
  651.             CGFloat stringWidth = [titleString sizeWithFont:self.font].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  652. #endif
  653.             self.segmentWidth = MAX(stringWidth, self.segmentWidth);
  654.         }
  655.     } else if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  656.         NSMutableArray *mutableSegmentWidths = [NSMutableArray array];
  657.        
  658.         for (NSString *titleString in self.sectionTitles) {
  659. #if  __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  660.             CGFloat stringWidth = [titleString sizeWithAttributes:@{NSFontAttributeName: self.font}].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  661. #else
  662.             CGFloat stringWidth = [titleString sizeWithFont:self.font].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  663. #endif
  664.             [mutableSegmentWidths addObject:[NSNumber numberWithFloat:stringWidth]];
  665.         }
  666.         self.segmentWidthsArray = [mutableSegmentWidths copy];
  667.     } else if (self.type == HMSegmentedControlTypeImages) {
  668.         for (UIImage *sectionImage in self.sectionImages) {
  669.             CGFloat imageWidth = sectionImage.size.width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  670.             self.segmentWidth = MAX(imageWidth, self.segmentWidth);
  671.         }
  672.     } else if (self.type == HMSegmentedControlTypeTextImages && HMSegmentedControlSegmentWidthStyleFixed){
  673.         //lets just use the title.. we will assume it is wider then images...
  674.         for (NSString *titleString in self.sectionTitles) {
  675. #if  __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  676.             CGFloat stringWidth = [titleString sizeWithAttributes:@{NSFontAttributeName: self.font}].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  677. #else
  678.             CGFloat stringWidth = [titleString sizeWithFont:self.font].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  679. #endif
  680.             self.segmentWidth = MAX(stringWidth, self.segmentWidth);
  681.         }
  682.     } else if (self.type == HMSegmentedControlTypeTextImages && HMSegmentedControlSegmentWidthStyleDynamic) {
  683.         NSMutableArray *mutableSegmentWidths = [NSMutableArray array];
  684.        
  685.         int i = 0;
  686.         for (NSString *titleString in self.sectionTitles) {
  687. #if  __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  688.             CGFloat stringWidth = [titleString sizeWithAttributes:@{NSFontAttributeName: self.font}].width + self.segmentEdgeInset.right;
  689. #else
  690.             CGFloat stringWidth = [titleString sizeWithFont:self.font].width + self.segmentEdgeInset.right;
  691. #endif
  692.             UIImage *sectionImage = [self.sectionImages objectAtIndex:i];
  693.             CGFloat imageWidth = sectionImage.size.width + self.segmentEdgeInset.left;
  694.            
  695.             CGFloat combinedWidth = imageWidth + segmentImageTextPadding + stringWidth;
  696.            
  697.             [mutableSegmentWidths addObject:[NSNumber numberWithFloat:combinedWidth]];
  698.            
  699.             i++;
  700.         }
  701.         self.segmentWidthsArray = [mutableSegmentWidths copy];
  702.     }
  703.  
  704.     self.scrollView.scrollEnabled = self.isUserDraggable;
  705.     self.scrollView.contentSize = CGSizeMake([self totalSegmentedControlWidth], self.frame.size.height);
  706. }
  707.  
  708. - (NSUInteger)sectionCount {
  709.     if (self.type == HMSegmentedControlTypeText) {
  710.         return self.sectionTitles.count;
  711.     } else if (self.type == HMSegmentedControlTypeImages ||
  712.                self.type == HMSegmentedControlTypeTextImages) {
  713.         return self.sectionImages.count;
  714.     }
  715.    
  716.     return 0;
  717. }
  718.  
  719. - (void)willMoveToSuperview:(UIView *)newSuperview {
  720.     // Control is being removed
  721.     if (newSuperview == nil)
  722.         return;
  723.    
  724.     if (self.sectionTitles || self.sectionImages) {
  725.         [self updateSegmentsRects];
  726.     }
  727. }
  728.  
  729. #pragma mark - Touch
  730.  
  731. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  732.     UITouch *touch = [touches anyObject];
  733.     CGPoint touchLocation = [touch locationInView:self];
  734.    
  735.     if (CGRectContainsPoint(self.bounds, touchLocation)) {
  736.         NSInteger segment = 0;
  737.         if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  738.             segment = (touchLocation.x + self.scrollView.contentOffset.x) / self.segmentWidth;
  739.         } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  740.             // To know which segment the user touched, we need to loop over the widths and substract it from the x position.
  741.             CGFloat widthLeft = (touchLocation.x + self.scrollView.contentOffset.x);
  742.             for (NSNumber *width in self.segmentWidthsArray) {
  743.                 widthLeft = widthLeft - [width floatValue];
  744.                
  745.                 // When we don't have any width left to substract, we have the segment index.
  746.                 if (widthLeft <= 0)
  747.                     break;
  748.                
  749.                 segment++;
  750.             }
  751.         }
  752.        
  753.         if (segment != self.selectedSegmentIndex && segment < [self.sectionTitles count]) {
  754.             // Check if we have to do anything with the touch event
  755.             if (self.isTouchEnabled)
  756.                 [self setSelectedSegmentIndex:segment animated:self.shouldAnimateUserSelection notify:YES];
  757.         }
  758.     }
  759. }
  760.  
  761. #pragma mark - Scrolling
  762.  
  763. - (CGFloat)totalSegmentedControlWidth {
  764.     if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  765.         return self.sectionTitles.count * self.segmentWidth;
  766.     } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  767.         return [[self.segmentWidthsArray valueForKeyPath:@"@sum.self"] floatValue];
  768.     } else {
  769.         return self.sectionImages.count * self.segmentWidth;
  770.     }
  771. }
  772.  
  773. - (void)scrollToSelectedSegmentIndex {
  774.     CGRect rectForSelectedIndex;
  775.     CGFloat selectedSegmentOffset = 0;
  776.     if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  777.         rectForSelectedIndex = CGRectMake(self.segmentWidth * self.selectedSegmentIndex,
  778.                                                  0,
  779.                                                  self.segmentWidth,
  780.                                                  self.frame.size.height);
  781.        
  782.         selectedSegmentOffset = (CGRectGetWidth(self.frame) / 2) - (self.segmentWidth / 2);
  783.     } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  784.         NSInteger i = 0;
  785.         CGFloat offsetter = 0;
  786.         for (NSNumber *width in self.segmentWidthsArray) {
  787.             if (self.selectedSegmentIndex == i)
  788.                 break;
  789.             offsetter = offsetter + [width floatValue];
  790.             i++;
  791.         }
  792.        
  793.         rectForSelectedIndex = CGRectMake(offsetter,
  794.                                           0,
  795.                                           [[self.segmentWidthsArray objectAtIndex:self.selectedSegmentIndex] floatValue],
  796.                                           self.frame.size.height);
  797.        
  798.         selectedSegmentOffset = (CGRectGetWidth(self.frame) / 2) - ([[self.segmentWidthsArray objectAtIndex:self.selectedSegmentIndex] floatValue] / 2);
  799.     }
  800.    
  801.    
  802.     CGRect rectToScrollTo = rectForSelectedIndex;
  803.     rectToScrollTo.origin.x -= selectedSegmentOffset;
  804.     rectToScrollTo.size.width += selectedSegmentOffset * 2;
  805.     [self.scrollView scrollRectToVisible:rectToScrollTo animated:YES];
  806. }
  807.  
  808. #pragma mark - Index change
  809.  
  810. - (void)setSelectedSegmentIndex:(NSInteger)index {
  811.     [self setSelectedSegmentIndex:index animated:NO notify:NO];
  812. }
  813.  
  814. - (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated {
  815.     [self setSelectedSegmentIndex:index animated:animated notify:NO];
  816. }
  817.  
  818. - (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated notify:(BOOL)notify {
  819.     _selectedSegmentIndex = index;
  820.     [self setNeedsDisplay];
  821.    
  822.     if (index == HMSegmentedControlNoSegment) {
  823.         [self.selectionIndicatorArrowLayer removeFromSuperlayer];
  824.         [self.selectionIndicatorStripLayer removeFromSuperlayer];
  825.         [self.selectionIndicatorBoxLayer removeFromSuperlayer];
  826.     } else {
  827.         [self scrollToSelectedSegmentIndex];
  828.        
  829.         if (animated) {
  830.             // If the selected segment layer is not added to the super layer, that means no
  831.             // index is currently selected, so add the layer then move it to the new
  832.             // segment index without animating.
  833.             if(self.selectionStyle == HMSegmentedControlSelectionStyleArrow) {
  834.                 if ([self.selectionIndicatorArrowLayer superlayer] == nil) {
  835.                     [self.scrollView.layer addSublayer:self.selectionIndicatorArrowLayer];
  836.                    
  837.                     [self setSelectedSegmentIndex:index animated:NO notify:YES];
  838.                     return;
  839.                 }
  840.             }else {
  841.                 if ([self.selectionIndicatorStripLayer superlayer] == nil) {
  842.                     [self.scrollView.layer addSublayer:self.selectionIndicatorStripLayer];
  843.                    
  844.                     if (self.selectionStyle == HMSegmentedControlSelectionStyleBox && [self.selectionIndicatorBoxLayer superlayer] == nil)
  845.                         [self.scrollView.layer insertSublayer:self.selectionIndicatorBoxLayer atIndex:0];
  846.                    
  847.                     [self setSelectedSegmentIndex:index animated:NO notify:YES];
  848.                     return;
  849.                 }
  850.             }
  851.            
  852.             if (notify)
  853.                 [self notifyForSegmentChangeToIndex:index];
  854.            
  855.             // Restore CALayer animations
  856.             self.selectionIndicatorArrowLayer.actions = nil;
  857.             self.selectionIndicatorStripLayer.actions = nil;
  858.             self.selectionIndicatorBoxLayer.actions = nil;
  859.            
  860.             // Animate to new position
  861.             [CATransaction begin];
  862.             [CATransaction setAnimationDuration:0.15f];
  863.             [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
  864.             [self setArrowFrame];
  865.             self.selectionIndicatorBoxLayer.frame = [self frameForSelectionIndicator];
  866.             self.selectionIndicatorStripLayer.frame = [self frameForSelectionIndicator];
  867.             self.selectionIndicatorBoxLayer.frame = [self frameForFillerSelectionIndicator];
  868.             [CATransaction commit];
  869.         } else {
  870.             // Disable CALayer animations
  871.             NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"position", [NSNull null], @"bounds", nil];
  872.             self.selectionIndicatorArrowLayer.actions = newActions;
  873.             [self setArrowFrame];
  874.            
  875.             self.selectionIndicatorStripLayer.actions = newActions;
  876.             self.selectionIndicatorStripLayer.frame = [self frameForSelectionIndicator];
  877.            
  878.             self.selectionIndicatorBoxLayer.actions = newActions;
  879.             self.selectionIndicatorBoxLayer.frame = [self frameForFillerSelectionIndicator];
  880.            
  881.             if (notify)
  882.                 [self notifyForSegmentChangeToIndex:index];
  883.         }
  884.     }
  885. }
  886.  
  887. - (void)notifyForSegmentChangeToIndex:(NSInteger)index {
  888.     if (self.superview)
  889.         [self sendActionsForControlEvents:UIControlEventValueChanged];
  890.    
  891.     if (self.indexChangeBlock)
  892.         self.indexChangeBlock(index);
  893. }
  894.  
  895. @end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement