HMSegmentedControl.m 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139
  1. //
  2. // HMSegmentedControl.m
  3. // HMSegmentedControl
  4. //
  5. // Created by Hesham Abd-Elmegid on 23/12/12.
  6. // Copyright (c) 2012-2015 Hesham Abd-Elmegid. All rights reserved.
  7. //
  8. #import "HMSegmentedControl.h"
  9. #import <QuartzCore/QuartzCore.h>
  10. #import <math.h>
  11. NSUInteger HMSegmentedControlNoSegment = (NSUInteger)-1;
  12. @protocol HMAccessibilityDelegate <NSObject>
  13. @required
  14. -(void)scrollToAccessibilityElement:(id)sender;
  15. @end
  16. @interface HMAccessibilityElement : UIAccessibilityElement
  17. @property (nonatomic, weak) id<HMAccessibilityDelegate> delegate;
  18. @end
  19. @interface HMScrollView : UIScrollView
  20. @end
  21. @interface HMSegmentedControl () <UIScrollViewDelegate, HMAccessibilityDelegate>
  22. @property (nonatomic, strong) CALayer *selectionIndicatorStripLayer;
  23. @property (nonatomic, strong) CALayer *selectionIndicatorBoxLayer;
  24. @property (nonatomic, strong) CALayer *selectionIndicatorArrowLayer;
  25. @property (nonatomic, readwrite) CGFloat segmentWidth;
  26. @property (nonatomic, readwrite) NSArray<NSNumber *> *segmentWidthsArray;
  27. @property (nonatomic, strong) HMScrollView *scrollView;
  28. @property (nonatomic, strong) NSMutableArray *accessibilityElements;
  29. @property (nonatomic, strong) NSMutableArray *titleBackgroundLayers;
  30. @end
  31. @implementation HMAccessibilityElement
  32. - (void)accessibilityElementDidBecomeFocused
  33. {
  34. if (_delegate!=nil && [_delegate respondsToSelector:@selector(scrollToAccessibilityElement:)])
  35. [_delegate performSelector:@selector(scrollToAccessibilityElement:) withObject:self];
  36. }
  37. @end
  38. @implementation HMScrollView
  39. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  40. if (!self.dragging) {
  41. [self.nextResponder touchesBegan:touches withEvent:event];
  42. } else {
  43. [super touchesBegan:touches withEvent:event];
  44. }
  45. }
  46. - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  47. if (!self.dragging) {
  48. [self.nextResponder touchesMoved:touches withEvent:event];
  49. } else{
  50. [super touchesMoved:touches withEvent:event];
  51. }
  52. }
  53. - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  54. if (!self.dragging) {
  55. [self.nextResponder touchesEnded:touches withEvent:event];
  56. } else {
  57. [super touchesEnded:touches withEvent:event];
  58. }
  59. }
  60. @end
  61. @implementation HMSegmentedControl
  62. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  63. self = [super initWithCoder:aDecoder];
  64. if (self) {
  65. [self commonInit];
  66. }
  67. return self;
  68. }
  69. - (instancetype)initWithFrame:(CGRect)frame {
  70. self = [super initWithFrame:frame];
  71. if (self) {
  72. [self commonInit];
  73. }
  74. return self;
  75. }
  76. - (instancetype)initWithSectionTitles:(NSArray<NSString *> *)sectiontitles {
  77. self = [super initWithFrame:CGRectZero];
  78. if (self) {
  79. [self commonInit];
  80. self.sectionTitles = sectiontitles;
  81. self.type = HMSegmentedControlTypeText;
  82. }
  83. return self;
  84. }
  85. - (instancetype)initWithSectionImages:(NSArray<UIImage *> *)sectionImages sectionSelectedImages:(NSArray<UIImage *> *)sectionSelectedImages {
  86. self = [super initWithFrame:CGRectZero];
  87. if (self) {
  88. [self commonInit];
  89. self.sectionImages = sectionImages;
  90. self.sectionSelectedImages = sectionSelectedImages;
  91. self.type = HMSegmentedControlTypeImages;
  92. }
  93. return self;
  94. }
  95. - (instancetype)initWithSectionImages:(NSArray<UIImage *> *)sectionImages sectionSelectedImages:(NSArray<UIImage *> *)sectionSelectedImages titlesForSections:(NSArray<NSString *> *)sectiontitles {
  96. self = [super initWithFrame:CGRectZero];
  97. if (self) {
  98. [self commonInit];
  99. if (sectionImages.count != sectiontitles.count) {
  100. [NSException raise:NSRangeException format:@"***%s: Images bounds (%ld) Don't match Title bounds (%ld)", sel_getName(_cmd), (unsigned long)sectionImages.count, (unsigned long)sectiontitles.count];
  101. }
  102. self.sectionImages = sectionImages;
  103. self.sectionSelectedImages = sectionSelectedImages;
  104. self.sectionTitles = sectiontitles;
  105. self.type = HMSegmentedControlTypeTextImages;
  106. }
  107. return self;
  108. }
  109. - (void)awakeFromNib {
  110. [super awakeFromNib];
  111. self.segmentWidth = 0.0f;
  112. }
  113. - (void)commonInit {
  114. self.scrollView = [[HMScrollView alloc] init];
  115. self.scrollView.delegate = self;
  116. self.scrollView.scrollsToTop = NO;
  117. self.scrollView.showsVerticalScrollIndicator = NO;
  118. self.scrollView.showsHorizontalScrollIndicator = NO;
  119. [self addSubview:self.scrollView];
  120. _backgroundColor = [UIColor whiteColor];
  121. self.opaque = NO;
  122. _selectionIndicatorColor = [UIColor colorWithRed:52.0f/255.0f green:181.0f/255.0f blue:229.0f/255.0f alpha:1.0f];
  123. _selectionIndicatorBoxColor = _selectionIndicatorColor;
  124. self.selectedSegmentIndex = 0;
  125. self.segmentEdgeInset = UIEdgeInsetsMake(0, 5, 0, 5);
  126. self.selectionIndicatorHeight = 5.0f;
  127. self.selectionIndicatorEdgeInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f);
  128. self.selectionStyle = HMSegmentedControlSelectionStyleTextWidthStripe;
  129. self.selectionIndicatorLocation = HMSegmentedControlSelectionIndicatorLocationTop;
  130. self.segmentWidthStyle = HMSegmentedControlSegmentWidthStyleFixed;
  131. self.userDraggable = YES;
  132. self.touchEnabled = YES;
  133. self.verticalDividerEnabled = NO;
  134. self.type = HMSegmentedControlTypeText;
  135. self.verticalDividerWidth = 1.0f;
  136. _verticalDividerColor = [UIColor blackColor];
  137. self.borderColor = [UIColor blackColor];
  138. self.borderWidth = 1.0f;
  139. self.shouldAnimateUserSelection = YES;
  140. self.selectionIndicatorArrowLayer = [CALayer layer];
  141. self.selectionIndicatorStripLayer = [CALayer layer];
  142. self.selectionIndicatorBoxLayer = [CALayer layer];
  143. self.selectionIndicatorBoxLayer.opacity = self.selectionIndicatorBoxOpacity;
  144. self.selectionIndicatorBoxLayer.borderWidth = 1.0f;
  145. self.selectionIndicatorBoxOpacity = 0.2;
  146. self.contentMode = UIViewContentModeRedraw;
  147. }
  148. - (void)layoutSubviews {
  149. [super layoutSubviews];
  150. [self updateSegmentsRects];
  151. }
  152. - (void)setFrame:(CGRect)frame {
  153. [super setFrame:frame];
  154. [self updateSegmentsRects];
  155. }
  156. - (void)setSectionTitles:(NSArray<NSString *> *)sectionTitles {
  157. _sectionTitles = sectionTitles;
  158. [self setNeedsLayout];
  159. [self setNeedsDisplay];
  160. }
  161. - (void)setSectionImages:(NSArray<UIImage *> *)sectionImages {
  162. _sectionImages = sectionImages;
  163. [self setNeedsLayout];
  164. [self setNeedsDisplay];
  165. }
  166. - (void)setSelectionIndicatorLocation:(HMSegmentedControlSelectionIndicatorLocation)selectionIndicatorLocation {
  167. _selectionIndicatorLocation = selectionIndicatorLocation;
  168. if (selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationNone) {
  169. self.selectionIndicatorHeight = 0.0f;
  170. }
  171. }
  172. - (void)setSelectionIndicatorBoxOpacity:(CGFloat)selectionIndicatorBoxOpacity {
  173. _selectionIndicatorBoxOpacity = selectionIndicatorBoxOpacity;
  174. self.selectionIndicatorBoxLayer.opacity = _selectionIndicatorBoxOpacity;
  175. }
  176. - (void)setSegmentWidthStyle:(HMSegmentedControlSegmentWidthStyle)segmentWidthStyle {
  177. // Force HMSegmentedControlSegmentWidthStyleFixed when type is HMSegmentedControlTypeImages.
  178. if (self.type == HMSegmentedControlTypeImages) {
  179. _segmentWidthStyle = HMSegmentedControlSegmentWidthStyleFixed;
  180. } else {
  181. _segmentWidthStyle = segmentWidthStyle;
  182. }
  183. }
  184. - (void)setBorderType:(HMSegmentedControlBorderType)borderType {
  185. _borderType = borderType;
  186. [self setNeedsDisplay];
  187. }
  188. - (NSMutableArray *)titleBackgroundLayers {
  189. if (_titleBackgroundLayers) {
  190. return _titleBackgroundLayers;
  191. }
  192. _titleBackgroundLayers = @[].mutableCopy;
  193. return _titleBackgroundLayers;
  194. }
  195. #pragma mark - Drawing
  196. - (CGSize)measureTitleAtIndex:(NSUInteger)index {
  197. if (index >= self.sectionTitles.count) {
  198. return CGSizeZero;
  199. }
  200. id title = self.sectionTitles[index];
  201. CGSize size = CGSizeZero;
  202. BOOL selected = (index == self.selectedSegmentIndex) ? YES : NO;
  203. if ([title isKindOfClass:[NSString class]] && !self.titleFormatter) {
  204. NSDictionary *titleAttrs = selected ? [self resultingSelectedTitleTextAttributes] : [self resultingTitleTextAttributes];
  205. size = [(NSString *)title sizeWithAttributes:titleAttrs];
  206. UIFont *font = titleAttrs[@"NSFont"];
  207. size = CGSizeMake(ceil(size.width), ceil(size.height-font.descender));
  208. } else if ([title isKindOfClass:[NSString class]] && self.titleFormatter) {
  209. size = [self.titleFormatter(self, title, index, selected) size];
  210. } else if ([title isKindOfClass:[NSAttributedString class]]) {
  211. size = [(NSAttributedString *)title size];
  212. } else {
  213. NSAssert(title == nil, @"Unexpected type of segment title: %@", [title class]);
  214. size = CGSizeZero;
  215. }
  216. return CGRectIntegral((CGRect){CGPointZero, size}).size;
  217. }
  218. - (NSAttributedString *)attributedTitleAtIndex:(NSUInteger)index {
  219. id title = self.sectionTitles[index];
  220. BOOL selected = (index == self.selectedSegmentIndex) ? YES : NO;
  221. if ([title isKindOfClass:[NSAttributedString class]]) {
  222. return (NSAttributedString *)title;
  223. } else if (!self.titleFormatter) {
  224. NSDictionary *titleAttrs = selected ? [self resultingSelectedTitleTextAttributes] : [self resultingTitleTextAttributes];
  225. // the color should be cast to CGColor in order to avoid invalid context on iOS7
  226. UIColor *titleColor = titleAttrs[NSForegroundColorAttributeName];
  227. if (titleColor) {
  228. NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:titleAttrs];
  229. dict[NSForegroundColorAttributeName] = titleColor;
  230. titleAttrs = [NSDictionary dictionaryWithDictionary:dict];
  231. }
  232. return [[NSAttributedString alloc] initWithString:(NSString *)title attributes:titleAttrs];
  233. } else {
  234. return self.titleFormatter(self, title, index, selected);
  235. }
  236. }
  237. - (void)drawRect:(CGRect)rect {
  238. [self.backgroundColor setFill];
  239. UIRectFill([self bounds]);
  240. self.selectionIndicatorArrowLayer.backgroundColor = self.selectionIndicatorColor.CGColor;
  241. self.selectionIndicatorStripLayer.backgroundColor = self.selectionIndicatorColor.CGColor;
  242. self.selectionIndicatorBoxLayer.backgroundColor = self.selectionIndicatorBoxColor.CGColor;
  243. self.selectionIndicatorBoxLayer.borderColor = self.selectionIndicatorBoxColor.CGColor;
  244. // Remove all sublayers to avoid drawing images over existing ones
  245. self.scrollView.layer.sublayers = nil;
  246. CGRect oldRect = rect;
  247. if (self.accessibilityElements==nil)
  248. self.accessibilityElements = [NSMutableArray arrayWithCapacity:0];
  249. if (self.type == HMSegmentedControlTypeText) {
  250. [self removeTitleBackgroundLayers];
  251. [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
  252. CGFloat stringWidth = 0;
  253. CGFloat stringHeight = 0;
  254. CGSize size = [self measureTitleAtIndex:idx];
  255. stringWidth = size.width;
  256. stringHeight = size.height;
  257. CGRect rectDiv = CGRectZero;
  258. CGRect fullRect = CGRectZero;
  259. // Text inside the CATextLayer will appear blurry unless the rect values are rounded
  260. BOOL locationUp = (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationTop);
  261. BOOL selectionStyleNotBox = (self.selectionStyle != HMSegmentedControlSelectionStyleBox);
  262. CGFloat y = roundf((CGRectGetHeight(self.frame) - selectionStyleNotBox * self.selectionIndicatorHeight) / 2 - stringHeight / 2 + self.selectionIndicatorHeight * locationUp);
  263. CGRect rect;
  264. if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  265. rect = CGRectMake((self.segmentWidth * idx) + (self.segmentWidth - stringWidth) / 2, y, stringWidth, stringHeight);
  266. rectDiv = CGRectMake((self.segmentWidth * idx) - (self.verticalDividerWidth / 2), self.selectionIndicatorHeight * 2, self.verticalDividerWidth, self.frame.size.height - (self.selectionIndicatorHeight * 4));
  267. fullRect = CGRectMake(self.segmentWidth * idx, 0, self.segmentWidth, oldRect.size.height);
  268. } else {
  269. // When we are drawing dynamic widths, we need to loop the widths array to calculate the xOffset
  270. CGFloat xOffset = 0;
  271. NSUInteger i = 0;
  272. for (NSNumber *width in self.segmentWidthsArray) {
  273. if (idx == i)
  274. break;
  275. xOffset = xOffset + [width floatValue];
  276. i++;
  277. }
  278. CGFloat widthForIndex = [[self.segmentWidthsArray objectAtIndex:idx] floatValue];
  279. rect = CGRectMake(xOffset, y, widthForIndex, stringHeight);
  280. fullRect = CGRectMake(xOffset, 0, widthForIndex, oldRect.size.height);
  281. rectDiv = CGRectMake(xOffset - (self.verticalDividerWidth / 2), self.selectionIndicatorHeight * 2, self.verticalDividerWidth, self.frame.size.height - (self.selectionIndicatorHeight * 4));
  282. }
  283. // Fix rect position/size to avoid blurry labels
  284. rect = CGRectMake(ceilf(rect.origin.x), ceilf(rect.origin.y), ceilf(rect.size.width), ceilf(rect.size.height));
  285. CATextLayer *titleLayer = [CATextLayer layer];
  286. titleLayer.frame = rect;
  287. titleLayer.alignmentMode = kCAAlignmentCenter;
  288. if ([UIDevice currentDevice].systemVersion.floatValue < 10.0 ) {
  289. titleLayer.truncationMode = kCATruncationEnd;
  290. }
  291. titleLayer.string = [self attributedTitleAtIndex:idx];
  292. titleLayer.contentsScale = [[UIScreen mainScreen] scale];
  293. [self.scrollView.layer addSublayer:titleLayer];
  294. // Vertical Divider
  295. if (self.isVerticalDividerEnabled && idx > 0) {
  296. CALayer *verticalDividerLayer = [CALayer layer];
  297. verticalDividerLayer.frame = rectDiv;
  298. verticalDividerLayer.backgroundColor = self.verticalDividerColor.CGColor;
  299. [self.scrollView.layer addSublayer:verticalDividerLayer];
  300. }
  301. if ([self.accessibilityElements count]<=idx) {
  302. HMAccessibilityElement *element = [[HMAccessibilityElement alloc] initWithAccessibilityContainer:self];
  303. element.delegate = self;
  304. element.accessibilityLabel = (self.sectionTitles!=nil&&[self.sectionTitles count]>idx)?[self.sectionTitles objectAtIndex:idx]:[NSString stringWithFormat:@"item %u", (unsigned)idx+1];
  305. element.accessibilityFrame = [self convertRect:fullRect toView:nil];
  306. if (self.selectedSegmentIndex==idx)
  307. element.accessibilityTraits = UIAccessibilityTraitButton|UIAccessibilityTraitSelected;
  308. else
  309. element.accessibilityTraits = UIAccessibilityTraitButton;
  310. [self.accessibilityElements addObject:element];
  311. } else {
  312. CGFloat offset = 0.f;
  313. for (NSUInteger i = 0; i<idx; i++) {
  314. HMAccessibilityElement *accessibilityItem = [self.accessibilityElements objectAtIndex:i];
  315. offset += accessibilityItem.accessibilityFrame.size.width;
  316. }
  317. HMAccessibilityElement *element = [self.accessibilityElements objectAtIndex:idx];
  318. CGRect newRect = CGRectMake(offset-self.scrollView.contentOffset.x, 0, element.accessibilityFrame.size.width, element.accessibilityFrame.size.height);
  319. element.accessibilityFrame = [self convertRect:newRect toView:nil];
  320. if (self.selectedSegmentIndex==idx)
  321. element.accessibilityTraits = UIAccessibilityTraitButton|UIAccessibilityTraitSelected;
  322. else
  323. element.accessibilityTraits = UIAccessibilityTraitButton;
  324. }
  325. [self addBackgroundAndBorderLayerWithRect:fullRect];
  326. }];
  327. } else if (self.type == HMSegmentedControlTypeImages) {
  328. [self removeTitleBackgroundLayers];
  329. [self.sectionImages enumerateObjectsUsingBlock:^(id iconImage, NSUInteger idx, BOOL *stop) {
  330. UIImage *icon = iconImage;
  331. CGFloat imageWidth = icon.size.width;
  332. CGFloat imageHeight = icon.size.height;
  333. CGFloat y = roundf(CGRectGetHeight(self.frame) - self.selectionIndicatorHeight) / 2 - imageHeight / 2 + ((self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationTop) ? self.selectionIndicatorHeight : 0);
  334. CGFloat x = self.segmentWidth * idx + (self.segmentWidth - imageWidth)/2.0f;
  335. CGRect rect = CGRectMake(x, y, imageWidth, imageHeight);
  336. CALayer *imageLayer = [CALayer layer];
  337. imageLayer.frame = rect;
  338. if (self.selectedSegmentIndex == idx && self.selectedSegmentIndex < self.sectionSelectedImages.count) {
  339. if (self.sectionSelectedImages) {
  340. UIImage *highlightIcon = [self.sectionSelectedImages objectAtIndex:idx];
  341. imageLayer.contents = (id)highlightIcon.CGImage;
  342. } else {
  343. imageLayer.contents = (id)icon.CGImage;
  344. }
  345. } else {
  346. imageLayer.contents = (id)icon.CGImage;
  347. }
  348. [self.scrollView.layer addSublayer:imageLayer];
  349. // Vertical Divider
  350. if (self.isVerticalDividerEnabled && idx>0) {
  351. CALayer *verticalDividerLayer = [CALayer layer];
  352. verticalDividerLayer.frame = CGRectMake((self.segmentWidth * idx) - (self.verticalDividerWidth / 2), self.selectionIndicatorHeight * 2, self.verticalDividerWidth, self.frame.size.height-(self.selectionIndicatorHeight * 4));
  353. verticalDividerLayer.backgroundColor = self.verticalDividerColor.CGColor;
  354. [self.scrollView.layer addSublayer:verticalDividerLayer];
  355. }
  356. if ([self.accessibilityElements count]<=idx) {
  357. HMAccessibilityElement *element = [[HMAccessibilityElement alloc] initWithAccessibilityContainer:self];
  358. element.delegate = self;
  359. element.accessibilityLabel = (self.sectionTitles!=nil&&[self.sectionTitles count]>idx)?[self.sectionTitles objectAtIndex:idx]:[NSString stringWithFormat:@"item %u", (unsigned)idx+1];
  360. element.accessibilityFrame = [self convertRect:rect toView:nil];
  361. if (self.selectedSegmentIndex==idx)
  362. element.accessibilityTraits = UIAccessibilityTraitButton|UIAccessibilityTraitSelected;
  363. else
  364. element.accessibilityTraits = UIAccessibilityTraitButton;
  365. [self.accessibilityElements addObject:element];
  366. } else {
  367. CGFloat offset = 0.f;
  368. for (NSUInteger i = 0; i<idx; i++) {
  369. HMAccessibilityElement *accessibilityItem = [self.accessibilityElements objectAtIndex:i];
  370. offset += accessibilityItem.accessibilityFrame.size.width;
  371. }
  372. HMAccessibilityElement *element = [self.accessibilityElements objectAtIndex:idx];
  373. CGRect newRect = CGRectMake(offset-self.scrollView.contentOffset.x, 0, element.accessibilityFrame.size.width, element.accessibilityFrame.size.height);
  374. element.accessibilityFrame = [self convertRect:newRect toView:nil];
  375. if (self.selectedSegmentIndex==idx)
  376. element.accessibilityTraits = UIAccessibilityTraitButton|UIAccessibilityTraitSelected;
  377. else
  378. element.accessibilityTraits = UIAccessibilityTraitButton;
  379. }
  380. [self addBackgroundAndBorderLayerWithRect:rect];
  381. }];
  382. } else if (self.type == HMSegmentedControlTypeTextImages){
  383. [self removeTitleBackgroundLayers];
  384. [self.sectionImages enumerateObjectsUsingBlock:^(id iconImage, NSUInteger idx, BOOL *stop) {
  385. UIImage *icon = iconImage;
  386. CGFloat imageWidth = icon.size.width;
  387. CGFloat imageHeight = icon.size.height;
  388. CGSize stringSize = [self measureTitleAtIndex:idx];
  389. CGFloat stringHeight = stringSize.height;
  390. CGFloat stringWidth = stringSize.width;
  391. CGFloat imageXOffset = self.segmentWidth * idx; // Start with edge inset
  392. CGFloat textXOffset = self.segmentWidth * idx;
  393. CGFloat imageYOffset = ceilf((self.frame.size.height - imageHeight) / 2.0); // Start in center
  394. CGFloat textYOffset = ceilf((self.frame.size.height - stringHeight) / 2.0);
  395. if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  396. BOOL isImageInLineWidthText = self.imagePosition == HMSegmentedControlImagePositionLeftOfText || self.imagePosition == HMSegmentedControlImagePositionRightOfText;
  397. if (isImageInLineWidthText) {
  398. CGFloat whitespace = self.segmentWidth - stringSize.width - imageWidth - self.textImageSpacing;
  399. if (self.imagePosition == HMSegmentedControlImagePositionLeftOfText) {
  400. imageXOffset += whitespace / 2.0;
  401. textXOffset = imageXOffset + imageWidth + self.textImageSpacing;
  402. } else {
  403. textXOffset += whitespace / 2.0;
  404. imageXOffset = textXOffset + stringWidth + self.textImageSpacing;
  405. }
  406. } else {
  407. imageXOffset = self.segmentWidth * idx + (self.segmentWidth - imageWidth) / 2.0f; // Start with edge inset
  408. textXOffset = self.segmentWidth * idx + (self.segmentWidth - stringWidth) / 2.0f;
  409. CGFloat whitespace = CGRectGetHeight(self.frame) - imageHeight - stringHeight - self.textImageSpacing;
  410. if (self.imagePosition == HMSegmentedControlImagePositionAboveText) {
  411. imageYOffset = ceilf(whitespace / 2.0);
  412. textYOffset = imageYOffset + imageHeight + self.textImageSpacing;
  413. } else if (self.imagePosition == HMSegmentedControlImagePositionBelowText) {
  414. textYOffset = ceilf(whitespace / 2.0);
  415. imageYOffset = textYOffset + stringHeight + self.textImageSpacing;
  416. }
  417. }
  418. } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  419. // When we are drawing dynamic widths, we need to loop the widths array to calculate the xOffset
  420. CGFloat xOffset = 0;
  421. NSUInteger i = 0;
  422. for (NSNumber *width in self.segmentWidthsArray) {
  423. if (idx == i) {
  424. break;
  425. }
  426. xOffset = xOffset + [width floatValue];
  427. i++;
  428. }
  429. BOOL isImageInLineWidthText = self.imagePosition == HMSegmentedControlImagePositionLeftOfText || self.imagePosition == HMSegmentedControlImagePositionRightOfText;
  430. if (isImageInLineWidthText) {
  431. if (self.imagePosition == HMSegmentedControlImagePositionLeftOfText) {
  432. imageXOffset = xOffset;
  433. textXOffset = imageXOffset + imageWidth + self.textImageSpacing;
  434. } else {
  435. textXOffset = xOffset;
  436. imageXOffset = textXOffset + stringWidth + self.textImageSpacing;
  437. }
  438. } else {
  439. imageXOffset = xOffset + ([self.segmentWidthsArray[i] floatValue] - imageWidth) / 2.0f; // Start with edge inset
  440. textXOffset = xOffset + ([self.segmentWidthsArray[i] floatValue] - stringWidth) / 2.0f;
  441. CGFloat whitespace = CGRectGetHeight(self.frame) - imageHeight - stringHeight - self.textImageSpacing;
  442. if (self.imagePosition == HMSegmentedControlImagePositionAboveText) {
  443. imageYOffset = ceilf(whitespace / 2.0);
  444. textYOffset = imageYOffset + imageHeight + self.textImageSpacing;
  445. } else if (self.imagePosition == HMSegmentedControlImagePositionBelowText) {
  446. textYOffset = ceilf(whitespace / 2.0);
  447. imageYOffset = textYOffset + stringHeight + self.textImageSpacing;
  448. }
  449. }
  450. }
  451. CGRect imageRect = CGRectMake(imageXOffset, imageYOffset, imageWidth, imageHeight);
  452. CGRect textRect = CGRectMake(ceilf(textXOffset), ceilf(textYOffset), ceilf(stringWidth), ceilf(stringHeight));
  453. CATextLayer *titleLayer = [CATextLayer layer];
  454. titleLayer.frame = textRect;
  455. titleLayer.alignmentMode = kCAAlignmentCenter;
  456. titleLayer.string = [self attributedTitleAtIndex:idx];
  457. if ([UIDevice currentDevice].systemVersion.floatValue < 10.0 ) {
  458. titleLayer.truncationMode = kCATruncationEnd;
  459. }
  460. CALayer *imageLayer = [CALayer layer];
  461. imageLayer.frame = imageRect;
  462. if (self.selectedSegmentIndex == idx) {
  463. if (self.sectionSelectedImages) {
  464. UIImage *highlightIcon = [self.sectionSelectedImages objectAtIndex:idx];
  465. imageLayer.contents = (id)highlightIcon.CGImage;
  466. } else {
  467. imageLayer.contents = (id)icon.CGImage;
  468. }
  469. } else {
  470. imageLayer.contents = (id)icon.CGImage;
  471. }
  472. [self.scrollView.layer addSublayer:imageLayer];
  473. titleLayer.contentsScale = [[UIScreen mainScreen] scale];
  474. [self.scrollView.layer addSublayer:titleLayer];
  475. if ([self.accessibilityElements count]<=idx) {
  476. HMAccessibilityElement *element = [[HMAccessibilityElement alloc] initWithAccessibilityContainer:self];
  477. element.delegate = self;
  478. element.accessibilityLabel = (self.sectionTitles!=nil&&[self.sectionTitles count]>idx)?[self.sectionTitles objectAtIndex:idx]:[NSString stringWithFormat:@"item %u", (unsigned)idx+1];
  479. element.accessibilityFrame = [self convertRect:CGRectUnion(textRect, imageRect) toView:nil];
  480. if (self.selectedSegmentIndex==idx)
  481. element.accessibilityTraits = UIAccessibilityTraitButton|UIAccessibilityTraitSelected;
  482. else
  483. element.accessibilityTraits = UIAccessibilityTraitButton;
  484. [self.accessibilityElements addObject:element];
  485. } else {
  486. CGFloat offset = 0.f;
  487. for (NSUInteger i = 0; i<idx; i++) {
  488. HMAccessibilityElement *accessibilityItem = [self.accessibilityElements objectAtIndex:i];
  489. offset += accessibilityItem.accessibilityFrame.size.width;
  490. }
  491. HMAccessibilityElement *element = [self.accessibilityElements objectAtIndex:idx];
  492. CGRect newRect = CGRectMake(offset-self.scrollView.contentOffset.x, 0, element.accessibilityFrame.size.width, element.accessibilityFrame.size.height);
  493. element.accessibilityFrame = [self convertRect:newRect toView:nil];
  494. if (self.selectedSegmentIndex==idx)
  495. element.accessibilityTraits = UIAccessibilityTraitButton|UIAccessibilityTraitSelected;
  496. else
  497. element.accessibilityTraits = UIAccessibilityTraitButton;
  498. }
  499. [self addBackgroundAndBorderLayerWithRect:imageRect];
  500. }];
  501. }
  502. // Add the selection indicators
  503. if (self.selectedSegmentIndex != HMSegmentedControlNoSegment) {
  504. if (self.selectionStyle == HMSegmentedControlSelectionStyleArrow) {
  505. if (!self.selectionIndicatorArrowLayer.superlayer) {
  506. [self setArrowFrame];
  507. [self.scrollView.layer addSublayer:self.selectionIndicatorArrowLayer];
  508. }
  509. } else {
  510. if (!self.selectionIndicatorStripLayer.superlayer) {
  511. self.selectionIndicatorStripLayer.frame = [self frameForSelectionIndicator];
  512. [self.scrollView.layer addSublayer:self.selectionIndicatorStripLayer];
  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. - (void)removeTitleBackgroundLayers {
  522. [self.titleBackgroundLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
  523. [self.titleBackgroundLayers removeAllObjects];
  524. }
  525. - (void)addBackgroundAndBorderLayerWithRect:(CGRect)fullRect {
  526. // Background layer
  527. CALayer *backgroundLayer = [CALayer layer];
  528. backgroundLayer.frame = fullRect;
  529. [self.layer insertSublayer:backgroundLayer atIndex:0];
  530. [self.titleBackgroundLayers addObject:backgroundLayer];
  531. // Border layer
  532. if (self.borderType & HMSegmentedControlBorderTypeTop) {
  533. CALayer *borderLayer = [CALayer layer];
  534. borderLayer.frame = CGRectMake(0, 0, fullRect.size.width, self.borderWidth);
  535. borderLayer.backgroundColor = self.borderColor.CGColor;
  536. [backgroundLayer addSublayer: borderLayer];
  537. }
  538. if (self.borderType & HMSegmentedControlBorderTypeLeft) {
  539. CALayer *borderLayer = [CALayer layer];
  540. borderLayer.frame = CGRectMake(0, 0, self.borderWidth, fullRect.size.height);
  541. borderLayer.backgroundColor = self.borderColor.CGColor;
  542. [backgroundLayer addSublayer: borderLayer];
  543. }
  544. if (self.borderType & HMSegmentedControlBorderTypeBottom) {
  545. CALayer *borderLayer = [CALayer layer];
  546. borderLayer.frame = CGRectMake(0, fullRect.size.height - self.borderWidth, fullRect.size.width, self.borderWidth);
  547. borderLayer.backgroundColor = self.borderColor.CGColor;
  548. [backgroundLayer addSublayer: borderLayer];
  549. }
  550. if (self.borderType & HMSegmentedControlBorderTypeRight) {
  551. CALayer *borderLayer = [CALayer layer];
  552. borderLayer.frame = CGRectMake(fullRect.size.width - self.borderWidth, 0, self.borderWidth, fullRect.size.height);
  553. borderLayer.backgroundColor = self.borderColor.CGColor;
  554. [backgroundLayer addSublayer: borderLayer];
  555. }
  556. }
  557. - (void)setArrowFrame {
  558. self.selectionIndicatorArrowLayer.frame = [self frameForSelectionIndicator];
  559. self.selectionIndicatorArrowLayer.mask = nil;
  560. UIBezierPath *arrowPath = [UIBezierPath bezierPath];
  561. CGPoint p1 = CGPointZero;
  562. CGPoint p2 = CGPointZero;
  563. CGPoint p3 = CGPointZero;
  564. if (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationBottom) {
  565. p1 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width / 2, 0);
  566. p2 = CGPointMake(0, self.selectionIndicatorArrowLayer.bounds.size.height);
  567. p3 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width, self.selectionIndicatorArrowLayer.bounds.size.height);
  568. }
  569. if (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationTop) {
  570. p1 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width / 2, self.selectionIndicatorArrowLayer.bounds.size.height);
  571. p2 = CGPointMake(self.selectionIndicatorArrowLayer.bounds.size.width, 0);
  572. p3 = CGPointMake(0, 0);
  573. }
  574. [arrowPath moveToPoint:p1];
  575. [arrowPath addLineToPoint:p2];
  576. [arrowPath addLineToPoint:p3];
  577. [arrowPath closePath];
  578. CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
  579. maskLayer.frame = self.selectionIndicatorArrowLayer.bounds;
  580. maskLayer.path = arrowPath.CGPath;
  581. self.selectionIndicatorArrowLayer.mask = maskLayer;
  582. }
  583. - (CGRect)frameForSelectionIndicator {
  584. CGFloat indicatorYOffset = 0.0f;
  585. if (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationBottom) {
  586. indicatorYOffset = self.bounds.size.height - self.selectionIndicatorHeight + self.selectionIndicatorEdgeInsets.bottom;
  587. }
  588. if (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationTop) {
  589. indicatorYOffset = self.selectionIndicatorEdgeInsets.top;
  590. }
  591. CGFloat sectionWidth = 0.0f;
  592. if (self.type == HMSegmentedControlTypeText) {
  593. CGFloat stringWidth = [self measureTitleAtIndex:self.selectedSegmentIndex].width;
  594. sectionWidth = stringWidth;
  595. } else if (self.type == HMSegmentedControlTypeImages) {
  596. UIImage *sectionImage = [self.sectionImages objectAtIndex:self.selectedSegmentIndex];
  597. CGFloat imageWidth = sectionImage.size.width;
  598. sectionWidth = imageWidth;
  599. } else if (self.type == HMSegmentedControlTypeTextImages) {
  600. CGFloat stringWidth = [self measureTitleAtIndex:self.selectedSegmentIndex].width;
  601. UIImage *sectionImage = [self.sectionImages objectAtIndex:self.selectedSegmentIndex];
  602. CGFloat imageWidth = sectionImage.size.width;
  603. sectionWidth = MAX(stringWidth, imageWidth);
  604. }
  605. if (self.selectionStyle == HMSegmentedControlSelectionStyleArrow) {
  606. CGFloat widthToEndOfSelectedSegment = (self.segmentWidth * self.selectedSegmentIndex) + self.segmentWidth;
  607. CGFloat widthToStartOfSelectedIndex = (self.segmentWidth * self.selectedSegmentIndex);
  608. CGFloat x = widthToStartOfSelectedIndex + ((widthToEndOfSelectedSegment - widthToStartOfSelectedIndex) / 2) - (self.selectionIndicatorHeight/2);
  609. return CGRectMake(x - (self.selectionIndicatorHeight / 2), indicatorYOffset, self.selectionIndicatorHeight * 2, self.selectionIndicatorHeight);
  610. } else {
  611. if (self.selectionStyle == HMSegmentedControlSelectionStyleTextWidthStripe &&
  612. sectionWidth <= self.segmentWidth &&
  613. self.segmentWidthStyle != HMSegmentedControlSegmentWidthStyleDynamic) {
  614. CGFloat widthToEndOfSelectedSegment = (self.segmentWidth * self.selectedSegmentIndex) + self.segmentWidth;
  615. CGFloat widthToStartOfSelectedIndex = (self.segmentWidth * self.selectedSegmentIndex);
  616. CGFloat x = ((widthToEndOfSelectedSegment - widthToStartOfSelectedIndex) / 2) + (widthToStartOfSelectedIndex - sectionWidth / 2);
  617. return CGRectMake(x + self.selectionIndicatorEdgeInsets.left, indicatorYOffset, sectionWidth - self.selectionIndicatorEdgeInsets.right, self.selectionIndicatorHeight);
  618. } else {
  619. if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  620. CGFloat selectedSegmentOffset = 0.0f;
  621. NSUInteger i = 0;
  622. for (NSNumber *width in self.segmentWidthsArray) {
  623. if (self.selectedSegmentIndex == i)
  624. break;
  625. selectedSegmentOffset = selectedSegmentOffset + [width floatValue];
  626. i++;
  627. }
  628. if (self.selectionStyle == HMSegmentedControlSelectionStyleTextWidthStripe) {
  629. return CGRectMake(selectedSegmentOffset + self.selectionIndicatorEdgeInsets.left + self.segmentEdgeInset.left, indicatorYOffset, [[self.segmentWidthsArray objectAtIndex:self.selectedSegmentIndex] floatValue] - self.selectionIndicatorEdgeInsets.right - self.segmentEdgeInset.left - self.segmentEdgeInset.right, self.selectionIndicatorHeight + self.selectionIndicatorEdgeInsets.bottom);
  630. } else {
  631. return CGRectMake(selectedSegmentOffset + self.selectionIndicatorEdgeInsets.left, indicatorYOffset, [[self.segmentWidthsArray objectAtIndex:self.selectedSegmentIndex] floatValue] - self.selectionIndicatorEdgeInsets.right, self.selectionIndicatorHeight + self.selectionIndicatorEdgeInsets.bottom);
  632. }
  633. }
  634. return CGRectMake(self.segmentWidth * self.selectedSegmentIndex + self.selectionIndicatorEdgeInsets.left, indicatorYOffset, self.segmentWidth - self.selectionIndicatorEdgeInsets.left - self.selectionIndicatorEdgeInsets.right, self.selectionIndicatorHeight);
  635. }
  636. }
  637. }
  638. - (CGRect)frameForFillerSelectionIndicator {
  639. if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  640. CGFloat selectedSegmentOffset = 0.0f;
  641. NSUInteger i = 0;
  642. for (NSNumber *width in self.segmentWidthsArray) {
  643. if (self.selectedSegmentIndex == i) {
  644. break;
  645. }
  646. selectedSegmentOffset = selectedSegmentOffset + [width floatValue];
  647. i++;
  648. }
  649. return CGRectMake(selectedSegmentOffset, 0, [[self.segmentWidthsArray objectAtIndex:self.selectedSegmentIndex] floatValue], CGRectGetHeight(self.frame));
  650. }
  651. return CGRectMake(self.segmentWidth * self.selectedSegmentIndex, 0, self.segmentWidth, CGRectGetHeight(self.frame));
  652. }
  653. - (void)updateSegmentsRects {
  654. self.scrollView.contentInset = UIEdgeInsetsZero;
  655. self.scrollView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
  656. if ([self sectionCount] > 0) {
  657. self.segmentWidth = self.frame.size.width / [self sectionCount];
  658. }
  659. if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  660. [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
  661. CGFloat stringWidth = [self measureTitleAtIndex:idx].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  662. self.segmentWidth = MAX(stringWidth, self.segmentWidth);
  663. }];
  664. } else if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  665. NSMutableArray *mutableSegmentWidths = [NSMutableArray array];
  666. __block CGFloat totalWidth = 0.0;
  667. [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
  668. CGFloat stringWidth = [self measureTitleAtIndex:idx].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  669. totalWidth += stringWidth;
  670. [mutableSegmentWidths addObject:[NSNumber numberWithFloat:stringWidth]];
  671. }];
  672. if (self.shouldStretchSegmentsToScreenSize && totalWidth < self.bounds.size.width) {
  673. CGFloat whitespace = self.bounds.size.width - totalWidth;
  674. CGFloat whitespaceForSegment = whitespace / [mutableSegmentWidths count];
  675. [mutableSegmentWidths enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  676. CGFloat extendedWidth = whitespaceForSegment + [obj floatValue];
  677. [mutableSegmentWidths replaceObjectAtIndex:idx withObject:[NSNumber numberWithFloat:extendedWidth]];
  678. }];
  679. }
  680. self.segmentWidthsArray = [mutableSegmentWidths copy];
  681. } else if (self.type == HMSegmentedControlTypeImages) {
  682. for (UIImage *sectionImage in self.sectionImages) {
  683. CGFloat imageWidth = sectionImage.size.width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  684. self.segmentWidth = MAX(imageWidth, self.segmentWidth);
  685. }
  686. } else if (self.type == HMSegmentedControlTypeTextImages && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed){
  687. //lets just use the title.. we will assume it is wider then images...
  688. [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
  689. CGFloat stringWidth = [self measureTitleAtIndex:idx].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  690. self.segmentWidth = MAX(stringWidth, self.segmentWidth);
  691. }];
  692. } else if (self.type == HMSegmentedControlTypeTextImages && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  693. NSMutableArray *mutableSegmentWidths = [NSMutableArray array];
  694. __block CGFloat totalWidth = 0.0;
  695. int i = 0;
  696. [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
  697. CGFloat stringWidth = [self measureTitleAtIndex:idx].width + self.segmentEdgeInset.right;
  698. UIImage *sectionImage = [self.sectionImages objectAtIndex:i];
  699. CGFloat imageWidth = sectionImage.size.width + self.segmentEdgeInset.left;
  700. CGFloat combinedWidth = 0.0;
  701. if (self.imagePosition == HMSegmentedControlImagePositionLeftOfText || self.imagePosition == HMSegmentedControlImagePositionRightOfText) {
  702. combinedWidth = imageWidth + stringWidth + self.textImageSpacing;
  703. } else {
  704. combinedWidth = MAX(imageWidth, stringWidth);
  705. }
  706. totalWidth += combinedWidth;
  707. [mutableSegmentWidths addObject:[NSNumber numberWithFloat:combinedWidth]];
  708. }];
  709. if (self.shouldStretchSegmentsToScreenSize && totalWidth < self.bounds.size.width) {
  710. CGFloat whitespace = self.bounds.size.width - totalWidth;
  711. CGFloat whitespaceForSegment = whitespace / [mutableSegmentWidths count];
  712. [mutableSegmentWidths enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  713. CGFloat extendedWidth = whitespaceForSegment + [obj floatValue];
  714. [mutableSegmentWidths replaceObjectAtIndex:idx withObject:[NSNumber numberWithFloat:extendedWidth]];
  715. }];
  716. }
  717. self.segmentWidthsArray = [mutableSegmentWidths copy];
  718. }
  719. self.scrollView.scrollEnabled = self.isUserDraggable;
  720. self.scrollView.contentSize = CGSizeMake([self totalSegmentedControlWidth], self.frame.size.height);
  721. }
  722. - (NSUInteger)sectionCount {
  723. if (self.type == HMSegmentedControlTypeText) {
  724. return self.sectionTitles.count;
  725. } else if (self.type == HMSegmentedControlTypeImages ||
  726. self.type == HMSegmentedControlTypeTextImages) {
  727. return self.sectionImages.count;
  728. }
  729. return 0;
  730. }
  731. - (void)willMoveToSuperview:(UIView *)newSuperview {
  732. // Control is being removed
  733. if (newSuperview == nil)
  734. return;
  735. if (self.sectionTitles || self.sectionImages) {
  736. [self updateSegmentsRects];
  737. }
  738. }
  739. #pragma mark - Touch
  740. - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  741. UITouch *touch = [touches anyObject];
  742. CGPoint touchLocation = [touch locationInView:self];
  743. CGRect enlargeRect = CGRectMake(self.bounds.origin.x - self.enlargeEdgeInset.left,
  744. self.bounds.origin.y - self.enlargeEdgeInset.top,
  745. self.bounds.size.width + self.enlargeEdgeInset.left + self.enlargeEdgeInset.right,
  746. self.bounds.size.height + self.enlargeEdgeInset.top + self.enlargeEdgeInset.bottom);
  747. if (CGRectContainsPoint(enlargeRect, touchLocation)) {
  748. NSUInteger segment = 0;
  749. if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  750. segment = (touchLocation.x + self.scrollView.contentOffset.x) / self.segmentWidth;
  751. } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  752. // To know which segment the user touched, we need to loop over the widths and substract it from the x position.
  753. CGFloat widthLeft = (touchLocation.x + self.scrollView.contentOffset.x);
  754. for (NSNumber *width in self.segmentWidthsArray) {
  755. widthLeft = widthLeft - [width floatValue];
  756. // When we don't have any width left to substract, we have the segment index.
  757. if (widthLeft <= 0)
  758. break;
  759. segment++;
  760. }
  761. }
  762. NSUInteger sectionsCount = 0;
  763. if (self.type == HMSegmentedControlTypeImages) {
  764. sectionsCount = [self.sectionImages count];
  765. } else if (self.type == HMSegmentedControlTypeTextImages || self.type == HMSegmentedControlTypeText) {
  766. sectionsCount = [self.sectionTitles count];
  767. }
  768. if (segment != self.selectedSegmentIndex && segment < sectionsCount) {
  769. // Check if we have to do anything with the touch event
  770. if (self.isTouchEnabled)
  771. [self setSelectedSegmentIndex:segment animated:self.shouldAnimateUserSelection notify:YES];
  772. }
  773. }
  774. }
  775. #pragma mark - Scrolling
  776. - (CGFloat)totalSegmentedControlWidth {
  777. if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  778. return self.sectionTitles.count * self.segmentWidth;
  779. } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
  780. return [[self.segmentWidthsArray valueForKeyPath:@"@sum.self"] floatValue];
  781. } else {
  782. return self.sectionImages.count * self.segmentWidth;
  783. }
  784. }
  785. - (void)scrollToSelectedSegmentIndex:(BOOL)animated {
  786. [self scrollTo:self.selectedSegmentIndex animated:animated];
  787. }
  788. - (void)scrollTo:(NSUInteger)index animated:(BOOL)animated {
  789. CGRect rectForSelectedIndex = CGRectZero;
  790. CGFloat selectedSegmentOffset = 0;
  791. if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
  792. rectForSelectedIndex = CGRectMake(self.segmentWidth * index,
  793. 0,
  794. self.segmentWidth,
  795. self.frame.size.height);
  796. selectedSegmentOffset = (CGRectGetWidth(self.frame) / 2) - (self.segmentWidth / 2);
  797. } else {
  798. NSUInteger i = 0;
  799. CGFloat offsetter = 0;
  800. for (NSNumber *width in self.segmentWidthsArray) {
  801. if (index == i)
  802. break;
  803. offsetter = offsetter + [width floatValue];
  804. i++;
  805. }
  806. rectForSelectedIndex = CGRectMake(offsetter,
  807. 0,
  808. [[self.segmentWidthsArray objectAtIndex:index] floatValue],
  809. self.frame.size.height);
  810. selectedSegmentOffset = (CGRectGetWidth(self.frame) / 2) - ([[self.segmentWidthsArray objectAtIndex:index] floatValue] / 2);
  811. }
  812. CGRect rectToScrollTo = rectForSelectedIndex;
  813. rectToScrollTo.origin.x -= selectedSegmentOffset;
  814. rectToScrollTo.size.width += selectedSegmentOffset * 2;
  815. [self.scrollView scrollRectToVisible:rectToScrollTo animated:animated];
  816. }
  817. #pragma mark - Index Change
  818. - (void)setSelectedSegmentIndex:(NSUInteger)index {
  819. [self setSelectedSegmentIndex:index animated:NO notify:NO];
  820. }
  821. - (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated {
  822. [self setSelectedSegmentIndex:index animated:animated notify:NO];
  823. }
  824. - (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated notify:(BOOL)notify {
  825. _selectedSegmentIndex = index;
  826. [self setNeedsDisplay];
  827. if (index == HMSegmentedControlNoSegment) {
  828. [self.selectionIndicatorArrowLayer removeFromSuperlayer];
  829. [self.selectionIndicatorStripLayer removeFromSuperlayer];
  830. [self.selectionIndicatorBoxLayer removeFromSuperlayer];
  831. } else {
  832. [self scrollToSelectedSegmentIndex:animated];
  833. if (animated) {
  834. // If the selected segment layer is not added to the super layer, that means no
  835. // index is currently selected, so add the layer then move it to the new
  836. // segment index without animating.
  837. if(self.selectionStyle == HMSegmentedControlSelectionStyleArrow) {
  838. if ([self.selectionIndicatorArrowLayer superlayer] == nil) {
  839. [self.scrollView.layer addSublayer:self.selectionIndicatorArrowLayer];
  840. [self setSelectedSegmentIndex:index animated:NO notify:YES];
  841. return;
  842. }
  843. }else {
  844. if ([self.selectionIndicatorStripLayer superlayer] == nil) {
  845. [self.scrollView.layer addSublayer:self.selectionIndicatorStripLayer];
  846. if (self.selectionStyle == HMSegmentedControlSelectionStyleBox && [self.selectionIndicatorBoxLayer superlayer] == nil)
  847. [self.scrollView.layer insertSublayer:self.selectionIndicatorBoxLayer atIndex:0];
  848. [self setSelectedSegmentIndex:index animated:NO notify:YES];
  849. return;
  850. }
  851. }
  852. if (notify)
  853. [self notifyForSegmentChangeToIndex:index];
  854. // Restore CALayer animations
  855. self.selectionIndicatorArrowLayer.actions = nil;
  856. self.selectionIndicatorStripLayer.actions = nil;
  857. self.selectionIndicatorBoxLayer.actions = nil;
  858. // Animate to new position
  859. [CATransaction begin];
  860. [CATransaction setAnimationDuration:0.15f];
  861. [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
  862. [self setArrowFrame];
  863. self.selectionIndicatorBoxLayer.frame = [self frameForSelectionIndicator];
  864. self.selectionIndicatorStripLayer.frame = [self frameForSelectionIndicator];
  865. self.selectionIndicatorBoxLayer.frame = [self frameForFillerSelectionIndicator];
  866. [CATransaction commit];
  867. } else {
  868. // Disable CALayer animations
  869. NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"position", [NSNull null], @"bounds", nil];
  870. self.selectionIndicatorArrowLayer.actions = newActions;
  871. [self setArrowFrame];
  872. self.selectionIndicatorStripLayer.actions = newActions;
  873. self.selectionIndicatorStripLayer.frame = [self frameForSelectionIndicator];
  874. self.selectionIndicatorBoxLayer.actions = newActions;
  875. self.selectionIndicatorBoxLayer.frame = [self frameForFillerSelectionIndicator];
  876. if (notify)
  877. [self notifyForSegmentChangeToIndex:index];
  878. }
  879. }
  880. }
  881. - (void)notifyForSegmentChangeToIndex:(NSInteger)index {
  882. if (self.superview)
  883. [self sendActionsForControlEvents:UIControlEventValueChanged];
  884. if (self.indexChangeBlock)
  885. self.indexChangeBlock(index);
  886. }
  887. #pragma mark - Styling Support
  888. - (NSDictionary *)resultingTitleTextAttributes {
  889. NSDictionary *defaults = @{
  890. NSFontAttributeName : [UIFont systemFontOfSize:19.0f],
  891. NSForegroundColorAttributeName : [UIColor blackColor],
  892. };
  893. NSMutableDictionary *resultingAttrs = [NSMutableDictionary dictionaryWithDictionary:defaults];
  894. if (self.titleTextAttributes) {
  895. [resultingAttrs addEntriesFromDictionary:self.titleTextAttributes];
  896. }
  897. return [resultingAttrs copy];
  898. }
  899. - (NSDictionary *)resultingSelectedTitleTextAttributes {
  900. NSMutableDictionary *resultingAttrs = [NSMutableDictionary dictionaryWithDictionary:[self resultingTitleTextAttributes]];
  901. if (self.selectedTitleTextAttributes) {
  902. [resultingAttrs addEntriesFromDictionary:self.selectedTitleTextAttributes];
  903. }
  904. return [resultingAttrs copy];
  905. }
  906. #pragma mark - UIScrollViewDelegate
  907. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  908. for (HMAccessibilityElement *element in self.accessibilityElements) {
  909. NSUInteger idx = [self.accessibilityElements indexOfObject:element];
  910. CGFloat offset = 0.f;
  911. for (NSUInteger i = 0; i<idx; i++) {
  912. HMAccessibilityElement *elem = [self.accessibilityElements objectAtIndex:i];
  913. offset += elem.accessibilityFrame.size.width;
  914. }
  915. CGRect rect = CGRectMake(offset-scrollView.contentOffset.x, 0, element.accessibilityFrame.size.width, element.accessibilityFrame.size.height);
  916. element.accessibilityFrame = [self convertRect:rect toView:nil];
  917. }
  918. }
  919. #pragma mark - HMAccessibilityDelegate
  920. - (void)scrollToAccessibilityElement:(id)sender {
  921. NSUInteger index = [self.accessibilityElements indexOfObject:sender];
  922. if (index!=NSNotFound)
  923. [self scrollTo:index animated:NO];
  924. }
  925. #pragma mark - UIAccessibilityContainer
  926. - (NSArray *)accessibilityElements {
  927. return _accessibilityElements;
  928. }
  929. - (BOOL)isAccessibilityElement {
  930. return NO;
  931. }
  932. - (NSInteger)accessibilityElementCount {
  933. return [[self accessibilityElements] count];
  934. }
  935. - (NSInteger)indexOfAccessibilityElement:(id)element {
  936. return [[self accessibilityElements] indexOfObject:element];
  937. }
  938. - (id)accessibilityElementAtIndex:(NSInteger)index {
  939. return [[self accessibilityElements] objectAtIndex:index];
  940. }
  941. @end