WNSegmentControl.m 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  1. //
  2. // WNSegmentControl.m
  3. #import "WNSegmentControl.h"
  4. #import "WNSegmentItem.h"
  5. #import "MHBeautyParams.h"
  6. #define IsArray(__array) ([(__array) isKindOfClass:[NSArray class]])
  7. #define IsArrayWithAnyItem(__array) (IsArray(__array) && ([((NSArray *)__array) count] > 0))
  8. #define SCREENWidth [UIScreen mainScreen].bounds.size.width
  9. #define SCREENHeight [UIScreen mainScreen].bounds.size.height
  10. typedef WNSegmentItem Item;
  11. typedef NS_ENUM(NSUInteger, WNSegmentedControlContentType) {
  12. WNSegmentedControlContentTypeText,
  13. WNSegmentedControlContentTypeImage,
  14. WNSegmentedControlContentTypeAttributeText,
  15. };
  16. static const NSTimeInterval kAnimationDuration = 0.25;
  17. static const CGFloat kIndicatorDefaultHeight = 3.0;
  18. static const CGFloat kSegmentWidthMinmum = 48.0;
  19. static const NSUInteger WNSegmentTag = 666;
  20. CGFloat kSeparatorDefaultHeight() {
  21. return 0.5;
  22. }
  23. @interface WNSegmentedScrollView : UIScrollView
  24. @end
  25. @interface WNSegmentControl ()<UIScrollViewDelegate>
  26. @property (nonatomic, weak) WNSegmentedScrollView *contentContainer;
  27. @property (nonatomic, weak) CALayer *separatorTop;
  28. @property (nonatomic, weak) CALayer *separatorBottom;
  29. @property (nonatomic, weak) CALayer *bottomShadow;
  30. @property (nonatomic, weak) CAGradientLayer *gradientLayerLeft;
  31. @property (nonatomic, weak) CAGradientLayer *gradientLayerRight;
  32. @property (nonatomic, strong, readwrite) UIView *indicator;
  33. @property (nonatomic, readwrite) CGFloat segmentWidth;
  34. @property (nonatomic, copy) NSDictionary *attributesNormal;
  35. @property (nonatomic, copy) NSDictionary *attributesSelected;
  36. @property (nonatomic, assign) WNSegmentedControlContentType contentType;
  37. @property (nonatomic, copy) NSArray *contents; // text or images as content
  38. @property (nonatomic, copy) NSArray<UIImage *> *selectedImages;
  39. @property (nonatomic, strong) NSMutableArray<Item *> *items; // labels or imageViews as elements
  40. @property (nonatomic, strong) NSMutableArray<NSNumber *> *segmenetWidths;
  41. @property (nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *widthConstraints;
  42. @end
  43. @implementation WNSegmentControl
  44. - (instancetype)initWithTitles:(NSArray <NSString *> *)titles {
  45. self = [super initWithFrame:CGRectZero];
  46. if (self) {
  47. [self copyTitles:titles];
  48. _contentType = WNSegmentedControlContentTypeText;
  49. _attributesNormal = @{NSFontAttributeName: [UIFont systemFontOfSize:16.0],
  50. NSForegroundColorAttributeName: [UIColor blackColor]};
  51. _attributesSelected = [_attributesNormal copy];
  52. [self commonInit];
  53. }
  54. return self;
  55. }
  56. - (instancetype)initWithAttributedTitles:(NSArray <NSAttributedString *> *)titles {
  57. self = [super initWithFrame:CGRectZero];
  58. if (self) {
  59. [self copyAttributeTitles:titles];
  60. _contentType = WNSegmentedControlContentTypeAttributeText;
  61. _attributesNormal = @{NSFontAttributeName: [UIFont systemFontOfSize:16.0],
  62. NSForegroundColorAttributeName: [UIColor blackColor]};
  63. _attributesSelected = [_attributesNormal copy];
  64. [self commonInit];
  65. }
  66. return self;
  67. }
  68. - (instancetype)initWithImages:(NSArray <UIImage *> *)images {
  69. return [self initWithImages:images selectedImages:nil];
  70. }
  71. - (instancetype)initWithImages:(NSArray <UIImage *> *)images
  72. selectedImages:(NSArray <UIImage *> *)selectedImages {
  73. self = [super initWithFrame:CGRectZero];
  74. if (self) {
  75. _contents = [images copy];
  76. _contentType = WNSegmentedControlContentTypeImage;
  77. if (selectedImages) {
  78. NSAssert([images count] == [selectedImages count], @"[Segment]: ERROR the count of parameter images is not equal to the count of parameter selectedImages.");
  79. _selectedImages = [selectedImages copy];
  80. }
  81. [self commonInit];
  82. }
  83. return self;
  84. }
  85. - (CGSize)intrinsicContentSize {
  86. return UILayoutFittingExpandedSize;
  87. }
  88. - (void)commonInit {
  89. self.backgroundColor = [UIColor clearColor];
  90. self.showsTopSeparator = NO;
  91. self.showsBottomSeparator = NO;
  92. self.showsIndicator = NO;
  93. _indicatorHeight = kIndicatorDefaultHeight;
  94. _indicatorLocate = WNSegmentedControlIndicatorLocateBottom;
  95. _indicatorWidthStyle = WNSegmentedControlIndicatorWidthStyleFull;
  96. _widthStyle = WNSegmentedControlWidthStyleFixed;
  97. _showsVerticalDivider = NO;
  98. _selectedSegmentIndex = 0;
  99. _horizontalPadding = 0.0;
  100. _segmentEdgeInset = UIEdgeInsetsMake(0, 5, 0, 5);
  101. _numberOfSegments = [_contents count];
  102. _items = [NSMutableArray array];
  103. _widthConstraints = @[].mutableCopy;
  104. _indicatorAnimate = NO;
  105. _textAnimate = NO;
  106. _showGradient = NO;
  107. _segmentWidth = 0;
  108. // setup views
  109. [self setupViews];
  110. }
  111. #pragma mark - Layout
  112. - (void)layoutSubviews {
  113. [super layoutSubviews];
  114. if (!IsArrayWithAnyItem(self.contents)) {
  115. return;
  116. }
  117. // separator
  118. if (_showsTopSeparator) {
  119. CGRect frame = (CGRect){0, 0, CGRectGetWidth(self.bounds), kSeparatorDefaultHeight()};
  120. _separatorTop.frame = frame;
  121. }
  122. if (_showsBottomSeparator) {
  123. CGRect frame = (CGRect){0, CGRectGetHeight(self.bounds) - kSeparatorDefaultHeight(), CGRectGetWidth(self.bounds), kSeparatorDefaultHeight()};
  124. _separatorBottom.frame = frame;
  125. }
  126. if (_showBottomShadow) {
  127. _bottomShadow.frame = self.bounds;
  128. }
  129. if (_showGradient) {
  130. _gradientLayerLeft.frame = (CGRect){CGPointZero, CGSizeMake(28, self.frame.size.height)};
  131. _gradientLayerRight.frame = CGRectMake(self.frame.size.width - 28, 0, 28, self.frame.size.height);
  132. }
  133. // indicator
  134. if (!_indicator) {
  135. return;
  136. }
  137. self.indicator.frame = [self indicatorFrame];
  138. self.indicator.layer.cornerRadius = self.indicatorHeight / 2;
  139. if (self.indicator.superview == nil && self.showsIndicator) {
  140. [self.contentContainer addSubview:self.indicator];
  141. }
  142. }
  143. #pragma mark - Private
  144. - (void)copyTitles:(NSArray <NSString *> *)titles {
  145. NSMutableArray *contents = [NSMutableArray array];
  146. for (NSString *title in titles) {
  147. [contents addObject:[title copy]];
  148. }
  149. self.contents = contents;
  150. }
  151. - (void)copyAttributeTitles:(NSArray <NSAttributedString *> *)titles {
  152. NSMutableArray *contents = [NSMutableArray array];
  153. for (NSAttributedString *title in titles) {
  154. [contents addObject:[title copy]];
  155. }
  156. self.contents = contents;
  157. }
  158. - (void)segmentDidSelectAtIndex:(NSUInteger)newIndex didDeselectAtIndex:(NSUInteger)oldIndex ignoreAction:(BOOL)ignoreAction {
  159. _selectedSegmentIndex = newIndex;
  160. if (!ignoreAction) {
  161. [self sendActionsForControlEvents:UIControlEventValueChanged];
  162. }
  163. if (newIndex == oldIndex) {
  164. return;
  165. }
  166. [self scrollToSelectedSegmentIndex];
  167. // update UI
  168. if (_contentType == WNSegmentedControlContentTypeText) {
  169. UILabel *selectedLabel = _items[newIndex].label;
  170. NSMutableAttributedString *mutableAttributed = [[NSMutableAttributedString alloc] initWithString:YZMsg(_contents[newIndex]) attributes:_attributesSelected];
  171. selectedLabel.attributedText = mutableAttributed;
  172. UILabel *deselectedLabel = _items[oldIndex].label;
  173. NSMutableAttributedString *deselectedMutableAttributed = [[NSMutableAttributedString alloc] initWithString:YZMsg(_contents[oldIndex]) attributes:_attributesNormal];
  174. deselectedLabel.attributedText = deselectedMutableAttributed;
  175. if (self.textAnimate) {
  176. [self textAnimationFromIndex:oldIndex toIndex:newIndex];
  177. }
  178. } else if (_contentType == WNSegmentedControlContentTypeAttributeText){
  179. UILabel *selectedLabel = _items[newIndex].label;
  180. selectedLabel.attributedText = _contents[newIndex];
  181. if (self.textAnimate) {
  182. [self textAnimationFromIndex:oldIndex toIndex:newIndex];
  183. }
  184. }
  185. else {
  186. if (_selectedImages) {
  187. UIImageView *selectedImageView = _items[newIndex].imageView;
  188. selectedImageView.image = _selectedImages[newIndex];
  189. UIImageView *deselectedImageView = _items[oldIndex].imageView;
  190. deselectedImageView.image = _contents[oldIndex];
  191. }
  192. }
  193. [self moveIndicatorFromIndex:oldIndex toIndex:newIndex];
  194. }
  195. - (void)moveIndicatorFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
  196. CGRect frame = [self indicatorFrame];
  197. if (!self.indicatorAnimate) {
  198. _indicator.frame = frame;
  199. return;
  200. }
  201. // indicator animate
  202. __weak typeof(self) weakSelf = self;
  203. [UIView animateWithDuration:kAnimationDuration
  204. delay:0.0
  205. usingSpringWithDamping:0.66
  206. initialSpringVelocity:3.0
  207. options:UIViewAnimationOptionCurveLinear
  208. animations:^{
  209. weakSelf.indicator.frame = frame;
  210. } completion:^(BOOL finished) {
  211. }];
  212. }
  213. - (void)textAnimationFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex {
  214. UIFont *normalFont = self.attributesNormal[NSFontAttributeName];
  215. UIFont *selectedFont = self.attributesSelected[NSFontAttributeName];
  216. CGFloat scale = selectedFont.pointSize / normalFont.pointSize;
  217. UILabel *selectedButton = self.items[toIndex].label;
  218. UILabel *preButton = self.items[fromIndex].label;
  219. CGAffineTransform oldTransform = selectedButton.transform;
  220. CGAffineTransform preTansform = preButton.transform;
  221. selectedButton.transform = CGAffineTransformScale(selectedButton.transform, 1 / scale, 1 / scale);
  222. preButton.transform = CGAffineTransformScale(preButton.transform, scale, scale);
  223. [UIView animateWithDuration:0.15 animations:^{
  224. selectedButton.transform = oldTransform;
  225. preButton.transform = preTansform;
  226. } completion:^(BOOL finished) {
  227. }];
  228. }
  229. #pragma mark - Scroll
  230. - (void)scrollToSelectedSegmentIndex {
  231. CGRect rectForSelectedIndex = CGRectZero;
  232. CGFloat selectedSegmentOffset = 0;
  233. if (self.widthStyle == WNSegmentedControlWidthStyleFixed) {
  234. rectForSelectedIndex = CGRectMake(self.segmentWidth * self.selectedSegmentIndex,
  235. 0,
  236. self.segmentWidth,
  237. self.frame.size.height);
  238. selectedSegmentOffset = CGRectGetWidth(self.frame) / 2 - self.segmentWidth / 2;
  239. } else {
  240. NSInteger i = 0;
  241. CGFloat offsetter = 0;
  242. for (NSNumber *width in self.segmenetWidths) {
  243. if (self.selectedSegmentIndex == i)
  244. break;
  245. offsetter = offsetter + width.floatValue;
  246. i++;
  247. }
  248. rectForSelectedIndex = CGRectMake(offsetter,
  249. 0,
  250. self.segmenetWidths[self.selectedSegmentIndex].floatValue,
  251. self.frame.size.height);
  252. selectedSegmentOffset = CGRectGetWidth(self.frame) / 2 - self.segmenetWidths[self.selectedSegmentIndex].floatValue / 2;
  253. }
  254. CGRect rectToScrollTo = rectForSelectedIndex;
  255. rectToScrollTo.origin.x -= selectedSegmentOffset;
  256. rectToScrollTo.size.width += selectedSegmentOffset * 2;
  257. [self.contentContainer scrollRectToVisible:rectToScrollTo animated:YES];
  258. }
  259. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  260. CGFloat offsetX = scrollView.contentOffset.x;
  261. CGFloat gradientWidth = self.gradientLayerLeft.frame.size.width;
  262. CGFloat leftRadius = offsetX / gradientWidth;
  263. CGFloat rightRadius = (scrollView.contentSize.width - offsetX - self.frame.size.width) / gradientWidth;
  264. self.gradientLayerLeft.opacity = MIN(leftRadius, 1);
  265. self.gradientLayerRight.opacity = MIN(rightRadius, 1);
  266. }
  267. #pragma mark -
  268. - (NSString *)titleAtIndex:(NSUInteger)index {
  269. if (_contentType == WNSegmentedControlContentTypeImage) {
  270. NSLog(@"[Segment]: WARN use -imageAtIndex: instead.");
  271. return nil;
  272. } else {
  273. NSAssert(index < _numberOfSegments, @"[Segment]: ERROR Index must be a number between 0 and the number of segments (numberOfSegments) minus 1.");
  274. return (NSString *)_contents[index];
  275. }
  276. }
  277. - (UIImage *)imageAtIndex:(NSUInteger)index {
  278. if (_contentType == WNSegmentedControlContentTypeText) {
  279. NSLog(@"[Segment]: WARN use -titleAtIndex: instead.");
  280. return nil;
  281. } else {
  282. NSAssert(index < _numberOfSegments, @"[Segment]: ERROR Index must be a number between 0 and the number of segments (numberOfSegments) minus 1.");
  283. return (UIImage *)_contents[index];
  284. }
  285. }
  286. - (void)setTitle:(NSString *)title atIndex:(NSUInteger)index {
  287. if (_contentType == WNSegmentedControlContentTypeImage) {
  288. NSLog(@"[Segment]: WARN use setImage:atIndex: instead.");
  289. return;
  290. }
  291. NSAssert(title, @"[Segment]: ERROR Title cannot be nil.");
  292. NSAssert(index < _numberOfSegments, @"[Segment]: ERROR Index must be a number between 0 and the number of segments (numberOfSegments) minus 1.");
  293. // update contents
  294. NSMutableArray *contents = [_contents mutableCopy];
  295. contents[index] = [title copy];
  296. self.contents = contents;
  297. // update UI
  298. UILabel *label = _items[index].label;
  299. NSMutableAttributedString *string = [label.attributedText mutableCopy];
  300. [string.mutableString setString:title];
  301. label.attributedText = string;
  302. }
  303. - (void)setImage:(UIImage *)image atIndex:(NSUInteger)index {
  304. [self setImage:image selectedImage:nil atIndex:index];
  305. }
  306. - (void)setImage:(UIImage *)image selectedImage:(UIImage *)selectedImage atIndex:(NSUInteger)index {
  307. if (_contentType == WNSegmentedControlContentTypeText) {
  308. NSLog(@"[Segment]: WARN use setTitle:atIndex: instead.");
  309. return;
  310. }
  311. NSAssert(image, @"[Segment]: ERROR image cannot be nil.");
  312. NSAssert(index < _numberOfSegments, @"[Segment]: ERROR Index must be a number between 0 and the number of segments (numberOfSegments) minus 1.");
  313. NSMutableArray *contents = [_contents mutableCopy];
  314. contents[index] = image;
  315. self.contents = contents;
  316. UIImageView *imageView = _items[index].imageView;
  317. if (selectedImage) {
  318. NSMutableArray *selectedImages = [_selectedImages mutableCopy];
  319. selectedImages[index] = selectedImage;
  320. self.selectedImages = selectedImages;
  321. if (_selectedSegmentIndex == index) {
  322. imageView.image = selectedImage;
  323. } else {
  324. imageView.image = image;
  325. }
  326. }
  327. else {
  328. imageView.image = image;
  329. }
  330. }
  331. - (void)insertTitle:(NSString *)title atIndex:(NSUInteger)index {
  332. if (index >= _numberOfSegments) {
  333. return;
  334. }
  335. if (!title) {
  336. return;
  337. }
  338. NSMutableArray *titles = self.contents.mutableCopy;
  339. [titles insertObject:title atIndex:index];
  340. self.contents = titles.copy;
  341. [self reloadData];
  342. }
  343. - (void)removeTitleAtIndex:(NSUInteger)index {
  344. if (index >= _numberOfSegments) {
  345. return;
  346. }
  347. NSMutableArray *titles = self.contents.mutableCopy;
  348. [titles removeObjectAtIndex:index];
  349. self.contents = titles.copy;
  350. [self reloadData];
  351. }
  352. - (void)setTitles:(NSArray<NSString *> *)titles {
  353. self.contents = titles.copy;
  354. }
  355. - (void)reloadData {
  356. _numberOfSegments = self.contents.count;
  357. _selectedSegmentIndex = 0;
  358. [_contentContainer removeFromSuperview];
  359. _contentContainer = nil;
  360. [_widthConstraints removeAllObjects];
  361. [_items removeAllObjects];
  362. [self setupViews];
  363. [self updateRects];
  364. [self setNeedsDisplay];
  365. [self setNeedsLayout];
  366. [self layoutIfNeeded];
  367. }
  368. - (void)setTextAttributes:(NSDictionary *)attributes forState:(UIControlState)state {
  369. if (state == UIControlStateNormal) {
  370. self.attributesNormal = attributes;
  371. } else {
  372. self.attributesSelected = attributes;
  373. }
  374. [self updateRects];
  375. }
  376. #pragma mark - add extra view
  377. - (void)addTipViewWithGenerator:(UIView *(^)(NSUInteger index))viewGenerator {
  378. if (viewGenerator != nil) {
  379. for (NSUInteger index = 0; index < _items.count; index++) {
  380. UIView *view = viewGenerator(index);
  381. if (view != nil) {
  382. Item *item = _items[index];
  383. [item addTipView:view];
  384. }
  385. }
  386. }
  387. }
  388. #pragma mark - Touch
  389. - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  390. CGPoint touchLocation = [touches.anyObject locationInView:self];
  391. if (CGRectContainsPoint(self.bounds, touchLocation)) {
  392. NSInteger toIndex = 0;
  393. if (self.widthStyle == WNSegmentedControlWidthStyleFixed) {
  394. toIndex = (touchLocation.x + self.contentContainer.contentOffset.x) / self.segmentWidth;
  395. } else if (self.widthStyle == WNSegmentedControlWidthStyleDynamic) {
  396. CGFloat widthLeft = (touchLocation.x + self.contentContainer.contentOffset.x);
  397. for (NSNumber *width in self.segmenetWidths) {
  398. widthLeft = widthLeft - width.floatValue;
  399. if (widthLeft <= 0)
  400. break;
  401. toIndex++;
  402. }
  403. }
  404. if (toIndex != NSNotFound && toIndex < self.numberOfSegments) {
  405. if (_selectedSegmentIndex != toIndex) {
  406. [self segmentDidSelectAtIndex:toIndex didDeselectAtIndex:_selectedSegmentIndex ignoreAction:NO];
  407. } else {
  408. [self sendActionsForControlEvents:UIControlEventTouchUpInside];
  409. }
  410. }
  411. }
  412. }
  413. #pragma mark - Draw
  414. - (void)drawRect:(CGRect)rect {
  415. if (_contentType == WNSegmentedControlContentTypeText) {
  416. for (int i = 0; i < _numberOfSegments; i++) {
  417. UILabel *label = _items[i].label;
  418. if (i == _selectedSegmentIndex) {
  419. NSMutableAttributedString *mutableAttributed = [[NSMutableAttributedString alloc] initWithString:YZMsg(_contents[i]) attributes:_attributesSelected];
  420. label.attributedText = mutableAttributed;
  421. } else {
  422. NSMutableAttributedString *mutableAttributed = [[NSMutableAttributedString alloc] initWithString:YZMsg(_contents[i]) attributes:_attributesNormal];
  423. label.attributedText = mutableAttributed;
  424. }
  425. }
  426. }
  427. else {
  428. if (_selectedImages) {
  429. UIImageView *imageView = _items[_selectedSegmentIndex].imageView;
  430. imageView.image = _selectedImages[_selectedSegmentIndex];
  431. }
  432. }
  433. }
  434. - (CGFloat)totalSegmentedControlWidth {
  435. if (self.widthStyle == WNSegmentedControlWidthStyleFixed) {
  436. return self.numberOfSegments * self.segmentWidth;
  437. } else if (self.contentType == WNSegmentedControlContentTypeText) {
  438. return [[self.segmenetWidths valueForKeyPath:@"@sum.self"] floatValue];
  439. } else {
  440. return self.numberOfSegments * self.segmentWidth;
  441. }
  442. }
  443. - (CGRect)indicatorFrame {
  444. CGFloat x = 0, y = 0, width = 0;
  445. CGFloat height = self.indicatorHeight;
  446. if (self.contentType == WNSegmentedControlContentTypeText &&
  447. (self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleText ||
  448. self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleShort)) {
  449. width = self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleShort ? 20 : [self measureTitleAtIndex:self.selectedSegmentIndex].width;
  450. if (self.widthStyle == WNSegmentedControlWidthStyleDynamic) {
  451. __block CGFloat left = 0;
  452. [self.segmenetWidths enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  453. if (idx < self.selectedSegmentIndex) {
  454. left += obj.floatValue;
  455. } else {
  456. *stop = YES;
  457. }
  458. }];
  459. if (self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleShort) {
  460. x = left + (self.segmenetWidths[self.selectedSegmentIndex].floatValue - width) / 2;
  461. } else {
  462. x = left + self.segmentEdgeInset.left;
  463. }
  464. } else {
  465. CGFloat segmentWidth = self.segmentWidth;
  466. x = _horizontalPadding + (segmentWidth - width) / 2 + segmentWidth * self.selectedSegmentIndex;
  467. }
  468. }
  469. else {
  470. if (self.widthStyle == WNSegmentedControlWidthStyleDynamic) {
  471. width = self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleShort ? 20 : self.segmenetWidths[self.selectedSegmentIndex].floatValue;
  472. __block CGFloat left = 0;
  473. [self.segmenetWidths enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  474. if (idx < self.selectedSegmentIndex) {
  475. left += obj.floatValue;
  476. } else {
  477. *stop = YES;
  478. }
  479. }];
  480. x = left + _horizontalPadding;
  481. if (self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleShort) {
  482. x += (self.segmenetWidths[self.selectedSegmentIndex].floatValue - width) / 2;
  483. }
  484. } else {
  485. width = self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleShort ? 20 : self.segmentWidth;
  486. x = _horizontalPadding + width * _selectedSegmentIndex;
  487. if (self.indicatorWidthStyle == WNSegmentedControlIndicatorWidthStyleShort) {
  488. x += (self.segmentWidth - width) / 2;
  489. }
  490. }
  491. }
  492. switch (self.indicatorLocate) {
  493. case WNSegmentedControlIndicatorLocateBottom: {
  494. y = CGRectGetHeight(self.contentContainer.frame) - height;
  495. if (_showsBottomSeparator) y -= kSeparatorDefaultHeight();
  496. break;
  497. }
  498. case WNSegmentedControlIndicatorLocateTop: {
  499. if (_showsTopSeparator) y += kSeparatorDefaultHeight();
  500. break;
  501. }
  502. }
  503. return (CGRect){x, y, width, height};
  504. }
  505. - (CGSize)measureTitleAtIndex:(NSUInteger)index {
  506. if (index >= self.contents.count) {
  507. return CGSizeZero;
  508. }
  509. NSString *title = self.contents[index];
  510. BOOL selected = index == self.selectedSegmentIndex;
  511. NSDictionary *titleAttributes = selected ? self.attributesSelected : self.attributesNormal;
  512. CGSize size = [title sizeWithAttributes:titleAttributes];
  513. return CGRectIntegral((CGRect){CGPointZero, size}).size;
  514. }
  515. - (void)updateRects {
  516. if (self.numberOfSegments == 0) {
  517. return;
  518. }
  519. if (self.widthStyle == WNSegmentedControlWidthStyleFixed) {
  520. self.segmentWidth = MAX(CGRectGetWidth(self.bounds) / self.numberOfSegments, kSegmentWidthMinmum);
  521. for (NSLayoutConstraint *constraint in self.widthConstraints) {
  522. constraint.constant = self.segmentWidth;
  523. }
  524. } else if (self.contentType == WNSegmentedControlContentTypeText) {
  525. NSMutableArray *temp = @[].mutableCopy;
  526. [self.contents enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  527. CGFloat width = [self measureTitleAtIndex:idx].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
  528. [temp addObject:@(width)];
  529. self.widthConstraints[idx].constant = width;
  530. }];
  531. self.segmenetWidths = [NSMutableArray arrayWithArray:temp];
  532. }
  533. self.contentContainer.contentSize = CGSizeMake([self totalSegmentedControlWidth], CGRectGetHeight(self.bounds));
  534. }
  535. #pragma mark - Setter
  536. - (void)setFrame:(CGRect)frame {
  537. [super setFrame:frame];
  538. if (CGRectIsEmpty(frame)) {
  539. return;
  540. }
  541. [self updateRects];
  542. }
  543. - (void)setWidthStyle:(WNSegmentedControlWidthStyle)widthStyle {
  544. _widthStyle = widthStyle;
  545. [self updateRects];
  546. }
  547. - (void)setShowsVerticalDivider:(BOOL)showsVerticalDivider {
  548. if (_showsVerticalDivider != showsVerticalDivider) {
  549. _showsVerticalDivider = showsVerticalDivider;
  550. // update divider
  551. if (showsVerticalDivider) {
  552. for (int i = 1; i < _numberOfSegments; i++) {
  553. [_items[i] showVerticalDivider];
  554. }
  555. }
  556. else {
  557. for (int i = 1; i < _numberOfSegments; i++) {
  558. [_items[i] hideVerticalDivider];
  559. }
  560. }
  561. }
  562. }
  563. - (void)setShowsTopSeparator:(BOOL)showsTopSeparator {
  564. if (_showsTopSeparator != showsTopSeparator) {
  565. _showsTopSeparator = showsTopSeparator;
  566. // setup separator top
  567. if (showsTopSeparator) {
  568. CALayer *separatorTop = ({
  569. CALayer *layer = [CALayer layer];
  570. layer.backgroundColor = [UIColor blueColor].CGColor;
  571. [self.layer addSublayer:layer];
  572. layer;
  573. });
  574. self.separatorTop = separatorTop;
  575. [self setNeedsLayout];
  576. } else {
  577. if (_separatorTop) {
  578. [_separatorTop removeFromSuperlayer];
  579. }
  580. }
  581. }
  582. }
  583. - (void)setShowsBottomSeparator:(BOOL)showsBottomSeparator {
  584. if (_showsBottomSeparator != showsBottomSeparator) {
  585. _showsBottomSeparator = showsBottomSeparator;
  586. // setup separator bottom
  587. if (showsBottomSeparator) {
  588. CALayer *separatorBottom = ({
  589. CALayer *layer = [CALayer layer];
  590. layer.backgroundColor = [UIColor whiteColor].CGColor;
  591. [self.layer addSublayer:layer];
  592. // layer.shadowColor = UIColorWithRGBA(0x7F8DA0, 0.06).CGColor;
  593. layer.shadowOpacity = 1;
  594. layer.shadowRadius = 5;
  595. layer.shadowOffset = CGSizeMake(0, 3);
  596. layer;
  597. });
  598. self.separatorBottom = separatorBottom;
  599. [self setNeedsLayout];
  600. } else {
  601. if (_separatorBottom) {
  602. [_separatorBottom removeFromSuperlayer];
  603. }
  604. }
  605. }
  606. }
  607. - (void)setShowBottomShadow:(BOOL)showBottomShadow {
  608. if (_showBottomShadow != showBottomShadow) {
  609. _showBottomShadow = showBottomShadow;
  610. if (showBottomShadow) {
  611. CALayer *bottomShadow = ({
  612. CALayer *layer = [CALayer layer];
  613. layer.backgroundColor = [UIColor whiteColor].CGColor;
  614. [self.layer insertSublayer:layer atIndex:0];
  615. //layer.shadowColor = UIColorWithRGBA(0x7F8DA0, 0.06).CGColor;
  616. layer.shadowOpacity = 1;
  617. layer.shadowRadius = 5;
  618. layer.shadowOffset = CGSizeMake(0, 3);
  619. layer;
  620. });
  621. self.bottomShadow = bottomShadow;
  622. [self setNeedsLayout];
  623. } else {
  624. if (_bottomShadow) {
  625. [_bottomShadow removeFromSuperlayer];
  626. }
  627. }
  628. }
  629. }
  630. - (void)setShowsIndicator:(BOOL)showsIndicator {
  631. if (_showsIndicator != showsIndicator) {
  632. _showsIndicator = showsIndicator;
  633. // setup indicator
  634. if (showsIndicator) {
  635. _indicator = ({
  636. UIView *indicator = [UIView new];
  637. indicator.backgroundColor = [UIColor redColor];
  638. [self.contentContainer addSubview:indicator];
  639. indicator;
  640. });
  641. [self setNeedsLayout];
  642. } else {
  643. if (_indicator) {
  644. [_indicator removeFromSuperview];
  645. _indicator = nil;
  646. }
  647. }
  648. }
  649. }
  650. - (void)setShowGradient:(BOOL)showGradient {
  651. if (_showGradient == showGradient) {
  652. return;
  653. }
  654. _showGradient = showGradient;
  655. if (showGradient) {
  656. CAGradientLayer *left = ({
  657. CAGradientLayer *left = [CAGradientLayer layer];
  658. left.colors = @[(__bridge id)[UIColor whiteColor].CGColor,
  659. (__bridge id)[[UIColor whiteColor] colorWithAlphaComponent:0].CGColor];
  660. left.locations = @[@.5, @1];
  661. left.startPoint = CGPointMake(0, 0.5);
  662. left.endPoint = CGPointMake(1, 0.5);
  663. left.opacity = 0;
  664. [self.layer addSublayer:left];
  665. left;
  666. });
  667. CAGradientLayer *right = ({
  668. CAGradientLayer *right = [CAGradientLayer layer];
  669. right.colors = @[(__bridge id)[UIColor whiteColor].CGColor,
  670. (__bridge id)[[UIColor whiteColor] colorWithAlphaComponent:0].CGColor];
  671. right.locations = @[@.5, @1];
  672. right.startPoint = CGPointMake(1, 0.5);
  673. right.endPoint = CGPointMake(0, 0.5);
  674. right.opacity = 1;
  675. [self.layer addSublayer:right];
  676. right;
  677. });
  678. _gradientLayerLeft = left;
  679. _gradientLayerRight = right;
  680. [self setNeedsLayout];
  681. } else {
  682. if (_gradientLayerLeft) {
  683. [_gradientLayerLeft removeFromSuperlayer];
  684. [_gradientLayerRight removeFromSuperlayer];
  685. _gradientLayerLeft = nil;
  686. _gradientLayerRight = nil;
  687. }
  688. }
  689. }
  690. - (void)setHorizontalPadding:(CGFloat)horizontalPadding {
  691. _horizontalPadding = horizontalPadding;
  692. // update constraints
  693. for (NSLayoutConstraint *constraint in self.constraints) {
  694. if (constraint.firstAttribute == NSLayoutAttributeLeading ||
  695. constraint.firstAttribute == NSLayoutAttributeTrailing) {
  696. constraint.constant = horizontalPadding;
  697. }
  698. }
  699. }
  700. - (void)setIndicatorWidthStyle:(WNSegmentedControlIndicatorWidthStyle)indicatorWidthStyle {
  701. _indicatorWidthStyle = indicatorWidthStyle;
  702. [self setNeedsLayout];
  703. }
  704. - (void)setIndicatorBackgroundColor:(UIColor *)indicatorBackgroundColor {
  705. _indicatorBackgroundColor = indicatorBackgroundColor;
  706. self.indicator.backgroundColor = indicatorBackgroundColor;
  707. }
  708. - (void)setSelectedSegmentIndex:(NSUInteger)selectedSegmentIndex {
  709. [self setSelectedSegmentIndex:selectedSegmentIndex ignoreAction:YES];
  710. }
  711. - (void)setSelectedSegmentIndex:(NSUInteger)selectedSegmentIndex ignoreAction:(BOOL)ignoreAction {
  712. if (selectedSegmentIndex >= self.contents.count) {
  713. return;
  714. }
  715. [self segmentDidSelectAtIndex:selectedSegmentIndex didDeselectAtIndex:_selectedSegmentIndex ignoreAction:ignoreAction];
  716. }
  717. #pragma mark - Getter
  718. - (UIView *)indicator {
  719. if (_indicator) {
  720. return _indicator;
  721. }
  722. NSLog(@"[Segment]: WARN indicator is nil.");
  723. return nil;
  724. }
  725. #pragma mark - Setup views
  726. - (void)setupViews {
  727. // prepare content views
  728. if (_contentType == WNSegmentedControlContentTypeText || _contentType == WNSegmentedControlContentTypeAttributeText) {
  729. // content of segmented control is text
  730. for (int i = 0; i < _numberOfSegments; i++) {
  731. UILabel *label = [UILabel new];
  732. label.textAlignment = NSTextAlignmentCenter;
  733. label.numberOfLines = 1;
  734. label.translatesAutoresizingMaskIntoConstraints = NO;
  735. [_items addObject:[[Item alloc] initWithView:label]];
  736. }
  737. }
  738. else {
  739. // content of segmented control is image
  740. for (int i = 0; i < _numberOfSegments; i++) {
  741. UIImageView *imageView = [[UIImageView alloc] initWithImage:_contents[i]];
  742. imageView.contentMode = UIViewContentModeCenter;
  743. imageView.translatesAutoresizingMaskIntoConstraints = NO;
  744. [_items addObject:[[Item alloc] initWithView:imageView]];
  745. }
  746. }
  747. // setup container
  748. WNSegmentedScrollView *container = ({
  749. WNSegmentedScrollView *container = [[WNSegmentedScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREENWidth, 30)];
  750. container.clipsToBounds = NO;
  751. container.scrollsToTop = NO;
  752. container.showsVerticalScrollIndicator = NO;
  753. container.showsHorizontalScrollIndicator = NO;
  754. container.translatesAutoresizingMaskIntoConstraints = NO;
  755. container.delegate = self;
  756. [self addSubview:container];
  757. // layout view
  758. NSDictionary *views = @{@"container": container};
  759. NSArray *h = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[container]|" options:0 metrics:nil views:views];
  760. for (NSLayoutConstraint *constraint in h) {
  761. constraint.constant = self.horizontalPadding;
  762. }
  763. NSArray *w = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[container]|" options:0 metrics:nil views:views];
  764. [self addConstraints:h];
  765. [self addConstraints:w];
  766. container;
  767. });
  768. self.contentContainer = container;
  769. // setup segment views
  770. id lastView;
  771. NSMutableArray *constrains = @[].mutableCopy;
  772. for (int i = 0; i < _numberOfSegments; i++) {
  773. Item *view = _items[i];
  774. [_contentContainer addSubview:view];
  775. NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_contentContainer attribute:NSLayoutAttributeTop multiplier:1 constant:0];
  776. NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_contentContainer attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
  777. [constrains addObject:top];
  778. [constrains addObject:height];
  779. if (lastView) {
  780. NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:lastView attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0.0];
  781. [constrains addObject:leading];
  782. NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:50];
  783. [self.widthConstraints addObject:width];
  784. }
  785. else {
  786. NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:_contentContainer attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0];
  787. [constrains addObject:leading];
  788. NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:50];
  789. [self.widthConstraints addObject:constraint];
  790. }
  791. lastView = view;
  792. }
  793. [constrains addObjectsFromArray:self.widthConstraints];
  794. [_contentContainer addConstraints:constrains];
  795. }
  796. + (BOOL)requiresConstraintBasedLayout {
  797. return YES;
  798. }
  799. @end
  800. @implementation WNSegmentedScrollView
  801. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  802. if (!self.dragging) {
  803. [self.nextResponder touchesBegan:touches withEvent:event];
  804. } else {
  805. [super touchesBegan:touches withEvent:event];
  806. }
  807. }
  808. - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  809. if (!self.dragging) {
  810. [self.nextResponder touchesMoved:touches withEvent:event];
  811. } else{
  812. [super touchesMoved:touches withEvent:event];
  813. }
  814. }
  815. - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  816. if (!self.dragging) {
  817. [self.nextResponder touchesEnded:touches withEvent:event];
  818. } else {
  819. [super touchesEnded:touches withEvent:event];
  820. }
  821. }
  822. @end