SPPageMenu.m 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572
  1. //
  2. // SPPageMenu.m
  3. // SPPageMenu
  4. //
  5. // Created by 乐升平 on 17/10/26.
  6. // Copyright © 2017年 iDress. All rights reserved.
  7. //
  8. #import "SPPageMenu.h"
  9. #define tagBaseValue 100
  10. #define scrollViewContentOffset @"contentOffset"
  11. @interface SPPageMenuScrollView : UIScrollView
  12. @end
  13. @implementation SPPageMenuScrollView
  14. // 重写这个方法的目的是:当手指长按按钮时无法滑动scrollView的问题
  15. - (BOOL)touchesShouldCancelInContentView:(UIView *)view {
  16. return YES;
  17. }
  18. @end
  19. @interface SPPageMenuLine : UIImageView
  20. @property (nonatomic, copy) void(^hideBlock)(void);
  21. @end
  22. @implementation SPPageMenuLine
  23. // 当外界设置隐藏和alpha值时,让pageMenu重新布局
  24. - (void)setHidden:(BOOL)hidden {
  25. [super setHidden:hidden];
  26. if (self.hideBlock) {
  27. self.hideBlock();
  28. }
  29. }
  30. - (void)setAlpha:(CGFloat)alpha {
  31. [super setAlpha:alpha];
  32. if (self.hideBlock) {
  33. self.hideBlock();
  34. }
  35. }
  36. @end
  37. @interface SPPageMenuItem : UIButton
  38. - (instancetype)initWithImageRatio:(CGFloat)ratio;
  39. // 图片的高度所占按钮的高度比例,注意要浮点数,如果传分数比如三分之二,要写2.0/3.0,不能写2/3
  40. @property (nonatomic, assign) CGFloat imageRatio;
  41. // 图片的位置
  42. @property (nonatomic, assign) SPItemImagePosition imagePosition;
  43. // 图片与标题之间的间距
  44. @property (nonatomic, assign) CGFloat imageTitleSpace;
  45. @end
  46. @implementation SPPageMenuItem
  47. - (instancetype)initWithImageRatio:(CGFloat)ratio {
  48. if (self = [super init]) {
  49. _imageRatio = ratio;
  50. }
  51. return self;
  52. }
  53. - (instancetype)initWithFrame:(CGRect)frame
  54. {
  55. if (self = [super initWithFrame:frame]) {
  56. [self initialize];
  57. }
  58. return self;
  59. }
  60. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  61. if (self = [super initWithCoder:aDecoder]) {
  62. [self initialize];
  63. }
  64. return self;
  65. }
  66. - (void)initialize {
  67. _imageRatio = 0.5;
  68. _imagePosition = SPItemImagePositionDefault;
  69. self.imageView.contentMode = UIViewContentModeScaleAspectFit;
  70. self.titleLabel.textAlignment = NSTextAlignmentCenter;
  71. }
  72. - (void)setHighlighted:(BOOL)highlighted {}
  73. - (CGRect)imageRectForContentRect:(CGRect)contentRect {
  74. if (!self.currentTitle) { // 如果没有文字,则图片占据整个button,空格算一个文字
  75. return [super imageRectForContentRect:contentRect];
  76. }
  77. switch (self.imagePosition) {
  78. case SPItemImagePositionDefault:
  79. case SPItemImagePositionLeft: { // 图片在左
  80. _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio;
  81. CGFloat imageW = (contentRect.size.width-_imageTitleSpace) * _imageRatio;
  82. CGFloat imageH = contentRect.size.height;
  83. //自定义修改 - 图片在左时,固定图片大小为21
  84. imageW = 18;
  85. return CGRectMake(12, self.contentEdgeInsets.top, imageW, imageH);
  86. // return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH);
  87. }
  88. break;
  89. case SPItemImagePositionTop: {
  90. _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio;
  91. CGFloat imageW = contentRect.size.width;
  92. CGFloat imageH = (contentRect.size.height-_imageTitleSpace) * _imageRatio;
  93. return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH);
  94. }
  95. break;
  96. case SPItemImagePositionRight: {
  97. _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio;
  98. CGFloat imageW = (contentRect.size.width-_imageTitleSpace) * _imageRatio;
  99. CGFloat imageH = contentRect.size.height;
  100. CGFloat imageX = contentRect.size.width - imageW;
  101. return CGRectMake(imageX+self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH);
  102. }
  103. break;
  104. case SPItemImagePositionBottom: {
  105. _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio;
  106. CGFloat imageW = contentRect.size.width;
  107. CGFloat imageH = (contentRect.size.height - _imageTitleSpace) * _imageRatio;
  108. CGFloat imageY = contentRect.size.height-imageH;
  109. return CGRectMake(self.contentEdgeInsets.left, imageY+self.contentEdgeInsets.top, imageW, imageH);
  110. }
  111. break;
  112. default:
  113. break;
  114. }
  115. return CGRectZero;
  116. }
  117. - (CGRect)titleRectForContentRect:(CGRect)contentRect {
  118. if (!self.currentImage) { // 如果没有图片
  119. return [super titleRectForContentRect:contentRect];
  120. }
  121. switch (self.imagePosition) {
  122. case SPItemImagePositionDefault:
  123. case SPItemImagePositionLeft: {
  124. _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio;
  125. CGFloat titleX = (contentRect.size.width-_imageTitleSpace) * _imageRatio + _imageTitleSpace;
  126. CGFloat titleW = contentRect.size.width - titleX;
  127. CGFloat titleH = contentRect.size.height;
  128. return CGRectMake(titleX+self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH);
  129. }
  130. break;
  131. case SPItemImagePositionTop: {
  132. _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio;
  133. CGFloat titleY = (contentRect.size.height-_imageTitleSpace) * _imageRatio + _imageTitleSpace;
  134. CGFloat titleW = contentRect.size.width;
  135. CGFloat titleH = contentRect.size.height - titleY;
  136. return CGRectMake(self.contentEdgeInsets.left, titleY+self.contentEdgeInsets.top, titleW, titleH);
  137. }
  138. break;
  139. case SPItemImagePositionRight: {
  140. _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio;
  141. CGFloat titleW = (contentRect.size.width - _imageTitleSpace) * (1-_imageRatio);
  142. CGFloat titleH = contentRect.size.height;
  143. return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH);
  144. }
  145. break;
  146. case SPItemImagePositionBottom: {
  147. _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio;
  148. CGFloat titleW = contentRect.size.width;
  149. CGFloat titleH = (contentRect.size.height-_imageTitleSpace) * (1 - _imageRatio);
  150. return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH);
  151. }
  152. break;
  153. default:
  154. break;
  155. }
  156. return CGRectZero;
  157. }
  158. - (void)setImagePosition:(SPItemImagePosition)imagePosition {
  159. _imagePosition = imagePosition;
  160. [self setNeedsDisplay];
  161. }
  162. - (void)setImageRatio:(CGFloat)imageRatio {
  163. _imageRatio = imageRatio;
  164. [self setNeedsDisplay];
  165. }
  166. - (void)setImageTitleSpace:(CGFloat)imageTitleSpace {
  167. _imageTitleSpace = imageTitleSpace;
  168. [self setNeedsDisplay];
  169. }
  170. - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets {
  171. [super setContentEdgeInsets:contentEdgeInsets];
  172. [self setNeedsDisplay];
  173. }
  174. @end
  175. @interface SPPageMenu()
  176. @property (nonatomic, assign) SPPageMenuTrackerStyle trackerStyle;
  177. @property (nonatomic, strong) NSArray *items; // 里面装的是字符串或者图片
  178. @property (nonatomic, strong) UIImageView *tracker;
  179. @property (nonatomic, assign) CGFloat trackerHeight;
  180. @property (nonatomic, weak) UIView *backgroundView;
  181. @property (nonatomic, weak) UIImageView *backgroundImageView;
  182. @property (nonatomic, strong) UIImageView *dividingLine;
  183. @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView;
  184. @property (nonatomic, weak) SPPageMenuItem *functionButton;
  185. @property (nonatomic, strong) NSMutableArray *buttons;
  186. @property (nonatomic, strong) NSMutableArray *hLineArray;
  187. @property (nonatomic, strong) SPPageMenuItem *selectedButton;
  188. @property (nonatomic, strong) NSMutableDictionary *setupWidths;
  189. @property (nonatomic, assign) BOOL insert;
  190. // 起始偏移量,为了判断滑动方向
  191. @property (nonatomic, assign) CGFloat beginOffsetX;
  192. /// 开始颜色, 取值范围 0~1
  193. @property (nonatomic, assign) CGFloat startR;
  194. @property (nonatomic, assign) CGFloat startG;
  195. @property (nonatomic, assign) CGFloat startB;
  196. /// 完成颜色, 取值范围 0~1
  197. @property (nonatomic, assign) CGFloat endR;
  198. @property (nonatomic, assign) CGFloat endG;
  199. @property (nonatomic, assign) CGFloat endB;
  200. // 这个高度,是存储itemScrollView的高度
  201. @property (nonatomic, assign) CGFloat itemScrollViewH;
  202. @end
  203. #pragma clang diagnostic push
  204. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  205. @implementation SPPageMenu
  206. #pragma mark - public
  207. + (instancetype)pageMenuWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle {
  208. SPPageMenu *pageMenu = [[SPPageMenu alloc] initWithFrame:frame trackerStyle:trackerStyle];
  209. return pageMenu;
  210. }
  211. - (instancetype)initWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle {
  212. if (self = [super init]) {
  213. self.frame = frame;
  214. self.backgroundColor = [UIColor whiteColor];
  215. self.trackerStyle = trackerStyle;
  216. [self setupStartColor:_selectedItemTitleColor];
  217. [self setupEndColor:_unSelectedItemTitleColor];
  218. }
  219. return self;
  220. }
  221. - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex {
  222. if (selectedItemIndex < 0) selectedItemIndex = 0;
  223. NSAssert(selectedItemIndex <= items.count-1, @"selectedItemIndex 大于了 %ld",items.count-1);
  224. _items = items.copy;
  225. _selectedItemIndex = selectedItemIndex;
  226. self.insert = NO;
  227. if (self.buttons.count) {
  228. for (SPPageMenuItem *button in self.buttons) {
  229. [button removeFromSuperview];
  230. }
  231. }
  232. [self.buttons removeAllObjects];
  233. for (int i = 0; i < items.count; i++) {
  234. id object = items[i];
  235. NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]], @"items中的元素只能是NSString或UIImage类型");
  236. [self addButton:i object:object animated:NO];
  237. }
  238. [self setNeedsLayout];
  239. [self layoutIfNeeded];
  240. if (self.buttons.count) {
  241. // 默认选中selectedItemIndex对应的按钮
  242. SPPageMenuItem *selectedButton = [self.buttons objectAtIndex:selectedItemIndex];
  243. [self buttonInPageMenuClicked:selectedButton];
  244. // SPPageMenuTrackerStyleTextZoom和SPPageMenuTrackerStyleNothing样式跟tracker没有关联
  245. if ([self haveOrNeedsTracker]) {
  246. [self.itemScrollView insertSubview:self.tracker atIndex:0];
  247. // 这里千万不能再去调用setNeedsLayout和layoutIfNeeded,因为如果外界在此之前对selectedButton进行了缩放,调用了layoutSubViews后会重新对selectedButton设置frame,先缩放再重设置frame会导致文字显示不全,所以我们直接跳过layoutSubViews调用resetSetupTrackerFrameWithSelectedButton:只设置tracker的frame
  248. [self resetSetupTrackerFrameWithSelectedButton:selectedButton];
  249. }
  250. }
  251. }
  252. - (void)insertItemWithTitle:(NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated {
  253. self.insert = YES;
  254. NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count);
  255. NSMutableArray *titleArr = self.items.mutableCopy;
  256. [titleArr insertObject:title atIndex:itemIndex];
  257. self.items = titleArr;
  258. [self addButton:itemIndex object:title animated:animated];
  259. if (itemIndex <= self.selectedItemIndex) {
  260. _selectedItemIndex += 1;
  261. }
  262. }
  263. - (void)insertItemWithImage:(UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated {
  264. self.insert = YES;
  265. NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count);
  266. NSMutableArray *objects = self.items.mutableCopy;
  267. [objects insertObject:image atIndex:itemIndex];
  268. self.items = objects.copy;
  269. [self addButton:itemIndex object:image animated:animated];
  270. if (itemIndex <= self.selectedItemIndex) {
  271. _selectedItemIndex += 1;
  272. }
  273. }
  274. - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated {
  275. NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count);
  276. // 被删除的按钮之后的按钮需要修改tag值
  277. for (SPPageMenuItem *button in self.buttons) {
  278. if (button.tag-tagBaseValue > itemIndex) {
  279. button.tag = button.tag - 1;
  280. }
  281. }
  282. if (self.items.count) {
  283. NSMutableArray *objects = self.items.mutableCopy;
  284. // 特别注意的是:不能先通过itemIndex取出对象,然后再将对象删除,因为这样会删除所有相同的对象
  285. [objects removeObjectAtIndex:itemIndex];
  286. self.items = objects.copy;
  287. }
  288. if (itemIndex < self.buttons.count) {
  289. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  290. if (button == self.selectedButton) { // 如果删除的正是选中的item,删除之后,选中的按钮切换为上一个item
  291. self.selectedItemIndex = itemIndex > 0 ? itemIndex-1 : itemIndex;
  292. }
  293. [self.buttons removeObjectAtIndex:itemIndex];
  294. [button removeFromSuperview];
  295. if (self.buttons.count == 0) { // 说明移除了所有
  296. [self.tracker removeFromSuperview];
  297. self.selectedButton = nil;
  298. self.selectedItemIndex = 0;
  299. }
  300. }
  301. if (animated) {
  302. [UIView animateWithDuration:0.5 animations:^{
  303. [self setNeedsLayout];
  304. [self layoutIfNeeded];
  305. }];
  306. } else {
  307. [self setNeedsLayout];
  308. }
  309. }
  310. - (void)removeAllItems {
  311. NSMutableArray *objects = self.items.mutableCopy;
  312. [objects removeAllObjects];
  313. self.items = objects.copy;
  314. self.items = nil;
  315. for (int i = 0; i < self.buttons.count; i++) {
  316. SPPageMenuItem *button = self.buttons[i];
  317. [button removeFromSuperview];
  318. }
  319. [self.buttons removeAllObjects];
  320. [self.tracker removeFromSuperview];
  321. self.selectedButton = nil;
  322. self.selectedItemIndex = 0;
  323. [self setNeedsLayout];
  324. }
  325. - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex {
  326. if (itemIndex < self.buttons.count) {
  327. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  328. [button setImage:nil forState:UIControlStateNormal];
  329. [button setTitle:title forState:UIControlStateNormal];
  330. NSMutableArray *items = self.items.mutableCopy;
  331. [items replaceObjectAtIndex:itemIndex withObject:title];
  332. self.items = items.copy;
  333. }
  334. [self setNeedsLayout];
  335. }
  336. - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex {
  337. if (itemIndex < self.buttons.count) {
  338. SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex];
  339. return item.currentTitle;
  340. }
  341. return nil;
  342. }
  343. - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex {
  344. if (itemIndex < self.buttons.count) {
  345. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  346. [button setTitle:nil forState:UIControlStateNormal];
  347. [button setImage:image forState:UIControlStateNormal];
  348. NSMutableArray *items = self.items.mutableCopy;
  349. [items replaceObjectAtIndex:itemIndex withObject:image];
  350. self.items = items.copy;
  351. }
  352. [self setNeedsLayout];
  353. }
  354. - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex {
  355. if (itemIndex < self.buttons.count) {
  356. SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex];
  357. return item.currentImage;
  358. }
  359. return nil;
  360. }
  361. - (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex {
  362. if (itemIndex < self.buttons.count) {
  363. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  364. [button setEnabled:enaled];
  365. }
  366. }
  367. - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex {
  368. if (self.buttons.count) {
  369. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  370. return button.enabled;
  371. }
  372. return YES;
  373. }
  374. - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex {
  375. if (itemIndex < self.buttons.count) {
  376. [self.setupWidths setObject:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]];
  377. }
  378. }
  379. - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex {
  380. CGFloat setupWidth = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue];
  381. if (setupWidth) {
  382. return setupWidth;
  383. } else {
  384. if (itemIndex < self.buttons.count) {
  385. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  386. return button.bounds.size.width;
  387. }
  388. }
  389. return 0;
  390. }
  391. - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex {
  392. if (itemIndex < self.buttons.count) {
  393. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  394. button.contentEdgeInsets = contentInset;
  395. }
  396. }
  397. - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forItemAtIndex:(NSUInteger)itemIndex {
  398. if (itemIndex < self.buttons.count) {
  399. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  400. button.contentEdgeInsets = contentInset;
  401. }
  402. }
  403. - (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex {
  404. if (itemIndex < self.buttons.count) {
  405. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  406. return button.contentEdgeInsets;
  407. }
  408. return UIEdgeInsetsZero;
  409. }
  410. - (void)setBackgroundImage:(UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics {
  411. if (barMetrics == UIBarMetricsDefault) {
  412. if (UIEdgeInsetsEqualToEdgeInsets(backgroundImage.capInsets, UIEdgeInsetsZero)) {
  413. CGFloat imageWidth = CGImageGetWidth(backgroundImage.CGImage);
  414. CGFloat imageHeight = CGImageGetHeight(backgroundImage.CGImage);
  415. [self.backgroundImageView setImage:[backgroundImage resizableImageWithCapInsets:UIEdgeInsetsMake(imageHeight*0.5, imageWidth*0.5, imageHeight*0.5, imageWidth*0.5) resizingMode:backgroundImage.resizingMode]];
  416. } else {
  417. [self.backgroundImageView setImage:backgroundImage];
  418. }
  419. }
  420. }
  421. - (UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics {
  422. return self.backgroundImageView.image;
  423. }
  424. - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius {
  425. _trackerHeight = trackerHeight;
  426. self.tracker.layer.cornerRadius = cornerRadius;
  427. [self setNeedsLayout];
  428. [self layoutIfNeeded];
  429. }
  430. - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex {
  431. if (itemIndex < self.buttons.count) {
  432. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  433. [button setTitle:title forState:UIControlStateNormal];
  434. [button setImage:image forState:UIControlStateNormal];
  435. button.imagePosition = imagePosition;
  436. button.imageRatio = ratio;
  437. button.imageTitleSpace = imageTitleSpace;
  438. // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字
  439. if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) {
  440. NSMutableArray *items = self.items.mutableCopy;
  441. [items replaceObjectAtIndex:itemIndex withObject:image];
  442. self.items = items.copy;
  443. } else if (image == nil) {
  444. NSMutableArray *items = self.items.mutableCopy;
  445. [items replaceObjectAtIndex:itemIndex withObject:title];
  446. self.items = items.copy;
  447. } else {
  448. NSMutableArray *items = self.items.mutableCopy;
  449. [items replaceObjectAtIndex:itemIndex withObject:image];
  450. self.items = items.copy;
  451. }
  452. [self setNeedsLayout];
  453. [self layoutIfNeeded];
  454. }
  455. }
  456. - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state {
  457. [self.functionButton setTitle:title forState:state];
  458. [self.functionButton setImage:image forState:state];
  459. self.functionButton.imagePosition = imagePosition;
  460. self.functionButton.imageRatio = ratio;
  461. self.functionButton.imageTitleSpace = imageTitleSpace;
  462. }
  463. // 以下2个方法在3.0版本上有升级,可以使用但不推荐
  464. - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex {
  465. if (itemIndex < self.buttons.count) {
  466. SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex];
  467. [button setTitle:title forState:UIControlStateNormal];
  468. [button setImage:image forState:UIControlStateNormal];
  469. button.imagePosition = imagePosition;
  470. button.imageRatio = ratio;
  471. // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字
  472. if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) {
  473. NSMutableArray *items = self.items.mutableCopy;
  474. [items replaceObjectAtIndex:itemIndex withObject:image];
  475. self.items = items.copy;
  476. } else if (image == nil) {
  477. NSMutableArray *items = self.items.mutableCopy;
  478. [items replaceObjectAtIndex:itemIndex withObject:title];
  479. self.items = items.copy;
  480. } else {
  481. NSMutableArray *items = self.items.mutableCopy;
  482. [items replaceObjectAtIndex:itemIndex withObject:image];
  483. self.items = items.copy;
  484. }
  485. [self setNeedsLayout];
  486. [self layoutIfNeeded];
  487. }
  488. }
  489. - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state {
  490. [self.functionButton setTitle:title forState:state];
  491. [self.functionButton setImage:image forState:state];
  492. self.functionButton.imagePosition = imagePosition;
  493. self.functionButton.imageRatio = ratio;
  494. }
  495. - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state {
  496. if (attributes[NSFontAttributeName]) {
  497. self.functionButton.titleLabel.font = attributes[NSFontAttributeName];
  498. }
  499. if (attributes[NSForegroundColorAttributeName]) {
  500. [self.functionButton setTitleColor:attributes[NSForegroundColorAttributeName] forState:state];
  501. }
  502. if (attributes[NSBackgroundColorAttributeName]) {
  503. self.functionButton.backgroundColor = attributes[NSBackgroundColorAttributeName];
  504. }
  505. }
  506. - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView {
  507. // 说明外界传进来了一个scrollView,如果外界传进来了,pageMenu会观察该scrollView的contentOffset自动处理跟踪器的跟踪
  508. if (self.bridgeScrollView == scrollView) { return; }
  509. [self prepareMoveTrackerFollowScrollView:scrollView];
  510. }
  511. #pragma mark - private
  512. - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated {
  513. // 如果是插入,需要改变已有button的tag值
  514. for (SPPageMenuItem *button in self.buttons) {
  515. if (button.tag-tagBaseValue >= index) {
  516. button.tag = button.tag + 1; // 由于有新button的加入,新button后面的button的tag值得+1
  517. }
  518. }
  519. //wcl - 自定义添加按钮分割线
  520. UIView *hLineView = [[UIView alloc] init];
  521. hLineView.backgroundColor = RGB(232, 231, 231);
  522. SPPageMenuItem *button = [SPPageMenuItem buttonWithType:UIButtonTypeCustom];
  523. [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal];
  524. button.titleLabel.font = _itemTitleFont;
  525. [button addTarget:self action:@selector(buttonInPageMenuClicked:) forControlEvents:UIControlEventTouchUpInside];
  526. button.tag = tagBaseValue + index;
  527. if ([object isKindOfClass:[NSString class]]) {
  528. [button setTitle:object forState:UIControlStateNormal];
  529. } else {
  530. [button setImage:object forState:UIControlStateNormal];
  531. }
  532. if (self.insert) {
  533. if ([self haveOrNeedsTracker]) {
  534. if (self.buttons.count == 0) { // 如果是第一个插入,需要将跟踪器加上,第一个插入说明itemScrollView上没有任何子控件
  535. [self.itemScrollView insertSubview:self.tracker atIndex:0];
  536. [self.itemScrollView insertSubview:button atIndex:index+1];
  537. [self.itemScrollView insertSubview:hLineView atIndex:index+1];
  538. } else { // 已经有跟踪器
  539. [self.itemScrollView insertSubview:button atIndex:index+1]; // +1是因为跟踪器
  540. [self.itemScrollView insertSubview:hLineView atIndex:index+1];
  541. }
  542. } else {
  543. [self.itemScrollView insertSubview:button atIndex:index];
  544. [self.itemScrollView insertSubview:hLineView atIndex:index];
  545. }
  546. if (!self.buttons.count) {
  547. [self buttonInPageMenuClicked:button];
  548. }
  549. } else {
  550. [self.itemScrollView insertSubview:button atIndex:index];
  551. [self.itemScrollView insertSubview:hLineView atIndex:index];
  552. }
  553. [self.buttons insertObject:button atIndex:index];
  554. [self.hLineArray addObject:hLineView];
  555. // setNeedsLayout会标记为需要刷新,layoutIfNeeded只有在有标记的情况下才会立即调用layoutSubViews,当然标记为刷新并非只有调用setNeedsLayout,如frame改变,addSubView等都会标记为刷新
  556. if (self.insert && animated) { // 是插入的新按钮,且需要动画
  557. // 取出上一个按钮
  558. SPPageMenuItem *lastButton;
  559. if (index > 0) {
  560. lastButton = self.buttons[index-1];
  561. }
  562. // 先给初始的origin,按钮将会从这个origin开始动画
  563. button.frame = CGRectMake(CGRectGetMaxX(lastButton.frame)+_itemPadding*0.5, 0, 0, 0);
  564. button.titleLabel.frame = button.bounds;
  565. [UIView animateWithDuration:.5 animations:^{
  566. [self setNeedsLayout];
  567. [self layoutIfNeeded];
  568. }];
  569. }
  570. }
  571. // 是否已经或者即将有跟踪器
  572. - (BOOL)haveOrNeedsTracker {
  573. if (self.trackerStyle != SPPageMenuTrackerStyleTextZoom && self.trackerStyle != SPPageMenuTrackerStyleNothing) {
  574. return YES;
  575. }
  576. return NO;
  577. }
  578. - (instancetype)initWithFrame:(CGRect)frame {
  579. if (self = [super initWithFrame:frame]) {
  580. [self initialize];
  581. }
  582. return self;
  583. }
  584. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  585. if (self = [super initWithCoder:aDecoder]) {
  586. [self initialize];
  587. }
  588. return self;
  589. }
  590. - (void)initialize {
  591. _itemPadding = 30.0;
  592. _selectedItemTitleColor = [UIColor redColor];
  593. _unSelectedItemTitleColor = [UIColor blackColor];
  594. _selectedItemTitleFont = [UIFont systemFontOfSize:16];
  595. _unSelectedItemTitleFont = [UIFont systemFontOfSize:16];
  596. _itemTitleFont = [UIFont systemFontOfSize:16];
  597. _trackerHeight = 3.0;
  598. _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; // 适配屏幕分辨率
  599. _contentInset = UIEdgeInsetsZero;
  600. _selectedItemIndex = 0;
  601. _showFuntionButton = NO;
  602. _funtionButtonshadowOpacity = 0.5;
  603. _selectedItemZoomScale = 1;
  604. _needTextColorGradients = YES;
  605. [self setupSubViews];
  606. }
  607. - (void)setupSubViews {
  608. // 必须先添加分割线,再添加backgroundView;假如先添加backgroundView,那也就意味着backgroundView是SPPageMenu的第一个子控件,而scrollView又是backgroundView的第一个子控件,当外界在由导航控制器管理的控制器中将SPPageMenu添加为第一个子控件时,控制器会不断的往下遍历第一个子控件的第一个子控件,直到找到为scrollView为止,一旦发现某子控件的第一个子控件为scrollView,会将scrollView的内容往下偏移64;这时控制器中必须设置self.automaticallyAdjustsScrollViewInsets = NO;为了避免这样做,这里将分割线作为第一个子控件
  609. SPPageMenuLine *dividingLine = [[SPPageMenuLine alloc] init];
  610. dividingLine.backgroundColor = RGB(236, 236, 236);
  611. __weak typeof(self) weakSelf = self;
  612. dividingLine.hideBlock = ^() {
  613. [weakSelf setNeedsLayout];
  614. };
  615. [self addSubview:dividingLine];
  616. _dividingLine = dividingLine;
  617. UIView *backgroundView = [[UIView alloc] init];
  618. backgroundView.layer.masksToBounds = YES;
  619. [self addSubview:backgroundView];
  620. _backgroundView = backgroundView;
  621. UIImageView *backgroundImageView = [[UIImageView alloc] init];
  622. [backgroundView addSubview:backgroundImageView];
  623. _backgroundImageView = backgroundImageView;
  624. SPPageMenuScrollView *itemScrollView = [[SPPageMenuScrollView alloc] init];
  625. itemScrollView.showsVerticalScrollIndicator = NO;
  626. itemScrollView.showsHorizontalScrollIndicator = NO;
  627. itemScrollView.scrollsToTop = NO; // 目的是不要影响到外界的scrollView置顶功能
  628. itemScrollView.bouncesZoom = NO;
  629. itemScrollView.bounces = YES;
  630. if (@available(iOS 11.0, *)) {
  631. itemScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  632. }
  633. [backgroundView addSubview:itemScrollView];
  634. _itemScrollView = itemScrollView;
  635. SPPageMenuItem *functionButton = [SPPageMenuItem buttonWithType:UIButtonTypeCustom];
  636. functionButton.backgroundColor = [UIColor whiteColor];
  637. // [functionButton setTitle:@"+" forState:UIControlStateNormal];
  638. [functionButton setImage:[UIImage imageNamed:@"common_bottomArrow_icon"] forState:UIControlStateNormal];
  639. [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  640. [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
  641. functionButton.layer.shadowColor = [UIColor blackColor].CGColor;
  642. functionButton.layer.shadowOffset = CGSizeMake(0, 0);
  643. functionButton.layer.shadowRadius = 2;
  644. functionButton.layer.shadowOpacity = _funtionButtonshadowOpacity; // 默认是0,为0的话不会显示阴影
  645. functionButton.hidden = !_showFuntionButton;
  646. [backgroundView addSubview:functionButton];
  647. _functionButton = functionButton;
  648. }
  649. // 按钮点击方法
  650. - (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender {
  651. NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue;
  652. NSInteger toIndex = sender.tag - tagBaseValue;
  653. // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的,必须用下划线,用self.会造成死循环
  654. _selectedItemIndex = toIndex;
  655. // 如果sender是新的选中的按钮,则上一次的按钮颜色为非选中颜色,当前选中的颜色为选中颜色
  656. if (self.selectedButton != sender) {
  657. [self.selectedButton setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal];
  658. [sender setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal];
  659. self.selectedButton.titleLabel.font = _unSelectedItemTitleFont;
  660. sender.titleLabel.font = _selectedItemTitleFont;
  661. // 让itemScrollView发生偏移
  662. [self moveItemScrollViewWithSelectedButton:sender];
  663. if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || _selectedItemZoomScale != 1) {
  664. if (labs(toIndex-fromIndex) >= 2) { // 该条件意思是当外界滑动scrollView连续的滑动了超过2页
  665. for (SPPageMenuItem *button in self.buttons) { // 必须遍历将非选中按钮还原缩放,而不是仅仅只让上一个选中的按钮还原缩放。因为当用户快速滑动外界scrollView时,会频繁的调用-zoomForTitleWithProgress:fromButton:toButton:方法,有可能经过的某一个button还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放
  666. if (button != sender && !CGAffineTransformEqualToTransform(button.transform, CGAffineTransformIdentity)) {
  667. button.transform = CGAffineTransformIdentity;
  668. }
  669. }
  670. } else {
  671. self.selectedButton.transform = CGAffineTransformIdentity;
  672. }
  673. sender.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, _selectedItemZoomScale);
  674. }
  675. if (fromIndex != toIndex) { // 如果相等,说明是第1次进来或者2次点了同一个,此时不需要动画
  676. [self moveTrackerWithSelectedButton:sender];
  677. }
  678. self.selectedButton = sender;
  679. if (_selectedItemTitleFont != _unSelectedItemTitleFont) {
  680. [self setNeedsLayout];
  681. [self layoutIfNeeded];
  682. }
  683. } else { // 如果选中的按钮没有发生变化,比如用户往左边滑scrollView,还没滑动结束又开始往右滑动,此时选中的按钮就没变。如果设置了颜色渐变,而且当未选中的颜色带了不等于1的alpha值,如果用户往一边滑动还未结束又往另一边滑,则未选中的按钮颜色不是很准确。这个else就是去除这种不准确现象
  684. // 获取RGB和Alpha
  685. CGFloat red = 0.0;
  686. CGFloat green = 0.0;
  687. CGFloat blue = 0.0;
  688. CGFloat alpha = 0.0;
  689. [_unSelectedItemTitleColor getRed:&red green:&green blue:&blue alpha:&alpha];
  690. // 此时alpha已经获取到了
  691. if (alpha < 1) { // 因为相信alpha=1的情况还是占多数的,如果不做判断,apha=1时也for循环设置未选中按钮的颜色有点浪费.alpha=1时不会产生颜色不准确问题
  692. for (SPPageMenuItem *button in self.buttons) {
  693. if (button == sender) {
  694. [button setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal];
  695. } else {
  696. [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal];
  697. }
  698. }
  699. } else {
  700. [sender setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal];
  701. }
  702. }
  703. [self delegatePerformMethodWithFromIndex:fromIndex toIndex:toIndex];
  704. }
  705. // 点击button让itemScrollView发生偏移
  706. - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton {
  707. if (CGRectEqualToRect(self.backgroundView.frame, CGRectZero)) {
  708. return;
  709. }
  710. // 转换点的坐标位置
  711. CGPoint centerInPageMenu = [self.backgroundView convertPoint:selectedButton.center toView:self];
  712. // CGRectGetMidX(self.backgroundView.frame)指的是屏幕水平中心位置,它的值是固定不变的
  713. CGFloat offSetX = centerInPageMenu.x - CGRectGetMidX(self.backgroundView.frame);
  714. // itemScrollView的容量宽与自身宽之差(难点)
  715. CGFloat maxOffsetX = self.itemScrollView.contentSize.width - self.itemScrollView.frame.size.width;
  716. // 如果选中的button中心x值小于或者等于itemScrollView的中心x值,或者itemScrollView的容量宽度小于itemScrollView本身,此时点击button时不发生任何偏移,置offSetX为0
  717. if (offSetX <= 0 || maxOffsetX <= 0) {
  718. offSetX = 0;
  719. }
  720. // 如果offSetX大于maxOffsetX,说明itemScrollView已经滑到尽头,此时button也发生任何偏移了
  721. else if (offSetX > maxOffsetX){
  722. offSetX = maxOffsetX;
  723. }
  724. [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES];
  725. }
  726. // 移动跟踪器
  727. - (void)moveTrackerWithSelectedButton:(SPPageMenuItem *)selectedButton {
  728. [UIView animateWithDuration:0.25 animations:^{
  729. [self resetSetupTrackerFrameWithSelectedButton:selectedButton];
  730. }];
  731. }
  732. // 执行代理方法
  733. - (void)delegatePerformMethodWithFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex {
  734. if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:itemSelectedFromIndex:toIndex:)]) {
  735. [self.delegate pageMenu:self itemSelectedFromIndex:fromIndex toIndex:toIndex];
  736. } else if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:itemSelectedAtIndex:)]) {
  737. [self.delegate pageMenu:self itemSelectedAtIndex:toIndex];
  738. }
  739. }
  740. // 功能按钮的点击方法
  741. - (void)functionButtonClicked:(SPPageMenuItem *)sender {
  742. if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:functionButtonClicked:)]) {
  743. [self.delegate pageMenu:self functionButtonClicked:sender];
  744. }
  745. }
  746. - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView {
  747. // 这个if条件的意思是scrollView的滑动不是由手指拖拽产生
  748. if (!scrollView.isDragging && !scrollView.isDecelerating) {return;}
  749. // 当滑到边界时,继续通过scrollView的bouces效果滑动时,直接return
  750. if (scrollView.contentOffset.x < 0 || scrollView.contentOffset.x > scrollView.contentSize.width-scrollView.bounds.size.width) {
  751. return;
  752. }
  753. // 当前偏移量
  754. CGFloat currentOffSetX = scrollView.contentOffset.x;
  755. // 偏移进度
  756. CGFloat offsetProgress = currentOffSetX / scrollView.bounds.size.width;
  757. CGFloat progress = offsetProgress - floor(offsetProgress);
  758. NSInteger fromIndex = 0;
  759. NSInteger toIndex = 0;
  760. // 初始值不要等于scrollView.contentOffset.x,因为第一次进入此方法时,scrollView.contentOffset.x的值已经有一点点偏移了,不是很准确
  761. _beginOffsetX = scrollView.bounds.size.width * self.selectedItemIndex;
  762. // 以下注释的“拖拽”一词很准确,不可说成滑动,例如:当手指向右拖拽,还未拖到一半时就松开手,接下来scrollView则会往回滑动,这个往回,就是向左滑动,这也是_beginOffsetX不可时刻纪录的原因,如果时刻纪录,那么往回(向左)滑动时会被视为“向左拖拽”,然而,这个往回却是由“向右拖拽”而导致的
  763. if (currentOffSetX - _beginOffsetX > 0) { // 向左拖拽了
  764. // 求商,获取上一个item的下标
  765. fromIndex = currentOffSetX / scrollView.bounds.size.width;
  766. // 当前item的下标等于上一个item的下标加1
  767. toIndex = fromIndex + 1;
  768. if (toIndex >= self.buttons.count) {
  769. toIndex = fromIndex;
  770. }
  771. } else if (currentOffSetX - _beginOffsetX < 0) { // 向右拖拽了
  772. toIndex = currentOffSetX / scrollView.bounds.size.width;
  773. fromIndex = toIndex + 1;
  774. progress = 1.0 - progress;
  775. } else {
  776. progress = 1.0;
  777. fromIndex = self.selectedItemIndex;
  778. toIndex = fromIndex;
  779. }
  780. if (currentOffSetX == scrollView.bounds.size.width * fromIndex) {// 滚动停止了
  781. progress = 1.0;
  782. toIndex = fromIndex;
  783. }
  784. // 如果滚动停止,直接通过点击按钮选中toIndex对应的item
  785. if (currentOffSetX == scrollView.bounds.size.width*toIndex) { // 这里toIndex==fromIndex
  786. // 这一次赋值起到2个作用,一是点击toIndex对应的按钮,走一遍代理方法,二是弥补跟踪器的结束跟踪,因为本方法是在scrollViewDidScroll中调用,可能离滚动结束还有一丁点的距离,本方法就不调了,最终导致外界还要在scrollView滚动结束的方法里self.selectedItemIndex进行赋值,直接在这里赋值可以让外界不用做此操作
  787. if (_selectedItemIndex != toIndex) {
  788. self.selectedItemIndex = toIndex;
  789. }
  790. // 要return,点击了按钮,跟踪器自然会跟着被点击的按钮走
  791. return;
  792. }
  793. if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeAlways) {
  794. // 这个方法才开始移动跟踪器
  795. [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX];
  796. } else if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeHalf) {
  797. SPPageMenuItem *fromButton;
  798. SPPageMenuItem *toButton;
  799. if (progress > 0.5) {
  800. if (toIndex >= 0 && toIndex < self.buttons.count) {
  801. toButton = self.buttons[toIndex];
  802. fromButton = self.buttons[fromIndex];
  803. if (_selectedItemIndex != toIndex) {
  804. self.selectedItemIndex = toIndex;
  805. }
  806. }
  807. } else {
  808. if (fromIndex >= 0 && fromIndex < self.buttons.count) {
  809. toButton = self.buttons[fromIndex];
  810. fromButton = self.buttons[toIndex];
  811. if (_selectedItemIndex != fromIndex) {
  812. self.selectedItemIndex = fromIndex;
  813. }
  814. }
  815. }
  816. } else { // self.trackerFollowingMode = SPPageMenuTrackerFollowingModeEnd
  817. // 什么都不用做
  818. }
  819. }
  820. // 这个方法才开始真正滑动跟踪器,上面都是做铺垫
  821. - (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex currentOffsetX:(CGFloat)currentOffsetX beginOffsetX:(CGFloat)beginOffsetX {
  822. UIButton *fromButton = self.buttons[fromIndex];
  823. UIButton *toButton = self.buttons[toIndex];
  824. // 2个按钮之间的距离
  825. CGFloat xDistance = toButton.center.x - fromButton.center.x;
  826. // 2个按钮宽度的差值
  827. CGFloat wDistance = toButton.frame.size.width - fromButton.frame.size.width;
  828. CGRect newFrame = self.tracker.frame;
  829. CGPoint newCenter = self.tracker.center;
  830. if (self.trackerStyle == SPPageMenuTrackerStyleLine) {
  831. newCenter.x = fromButton.center.x + xDistance * progress;
  832. newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress);
  833. self.tracker.frame = newFrame;
  834. self.tracker.center = newCenter;
  835. if (_selectedItemZoomScale != 1) {
  836. [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton];
  837. }
  838. } else if (self.trackerStyle == SPPageMenuTrackerStyleLineAttachment) {
  839. // 这种样式的计算比较复杂,有个很关键的技巧,就是参考progress分别为0、0.5、1时的临界值
  840. // 原先的x值
  841. CGFloat originX = fromButton.frame.origin.x+(fromButton.frame.size.width-(_trackerWidth ? _trackerWidth : fromButton.titleLabel.font.pointSize))*0.5;
  842. // 原先的宽度
  843. CGFloat originW = _trackerWidth ? _trackerWidth : fromButton.titleLabel.font.pointSize;
  844. if (currentOffsetX - _beginOffsetX >= 0) { // 向左拖拽了
  845. if (progress < 0.5) {
  846. newFrame.origin.x = originX; // x值保持不变
  847. newFrame.size.width = originW + xDistance * progress * 2;
  848. } else {
  849. newFrame.origin.x = originX + xDistance * (progress-0.5) * 2;
  850. newFrame.size.width = originW + xDistance - xDistance * (progress-0.5) * 2;
  851. }
  852. } else { // 向右拖拽了
  853. // 此时xDistance为负
  854. if (progress < 0.5) {
  855. newFrame.origin.x = originX + xDistance * progress * 2;
  856. newFrame.size.width = originW - xDistance * progress * 2;
  857. } else {
  858. newFrame.origin.x = originX + xDistance;
  859. newFrame.size.width = originW - xDistance + xDistance * (progress-0.5) * 2;
  860. }
  861. }
  862. self.tracker.frame = newFrame;
  863. if (_selectedItemZoomScale != 1) {
  864. [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton];
  865. }
  866. } else if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || self.trackerStyle == SPPageMenuTrackerStyleNothing) {
  867. // 缩放文字
  868. if (_selectedItemZoomScale != 1) {
  869. [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton];
  870. }
  871. } else if (self.trackerStyle == SPPageMenuTrackerStyleRoundedRect) {
  872. newCenter.x = fromButton.center.x + xDistance * progress;
  873. newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress + _itemPadding);
  874. self.tracker.frame = newFrame;
  875. self.tracker.center = newCenter;
  876. if (_selectedItemZoomScale != 1) {
  877. [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton];
  878. }
  879. } else {
  880. newCenter.x = fromButton.center.x + xDistance * progress;
  881. newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress + _itemPadding);
  882. self.tracker.frame = newFrame;
  883. self.tracker.center = newCenter;
  884. if (_selectedItemZoomScale != 1) {
  885. [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton];
  886. }
  887. }
  888. // 文字颜色渐变
  889. if (self.needTextColorGradients) {
  890. [self colorGradientForTitleWithProgress:progress fromButton:fromButton toButton:toButton];
  891. }
  892. }
  893. // 颜色渐变方法
  894. - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton {
  895. // 获取 targetProgress
  896. CGFloat fromProgress = progress;
  897. // 获取 originalProgress
  898. CGFloat toProgress = 1 - fromProgress;
  899. CGFloat r = self.endR - self.startR;
  900. CGFloat g = self.endG - self.startG;
  901. CGFloat b = self.endB - self.startB;
  902. UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:1];
  903. UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:1];
  904. // 设置文字颜色渐变
  905. [fromButton setTitleColor:fromColor forState:UIControlStateNormal];
  906. [toButton setTitleColor:toColor forState:UIControlStateNormal];
  907. }
  908. // 获取颜色的RGB值
  909. - (void)getRGBComponents:(CGFloat [3])components forColor:(UIColor *)color {
  910. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
  911. unsigned char resultingPixel[4];
  912. CGContextRef context = CGBitmapContextCreate(&resultingPixel, 1, 1, 8, 4, rgbColorSpace, 1);
  913. CGContextSetFillColorWithColor(context, [color CGColor]);
  914. CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
  915. CGContextRelease(context);
  916. CGColorSpaceRelease(rgbColorSpace);
  917. for (int component = 0; component < 3; component++) {
  918. components[component] = resultingPixel[component] / 255.0f;
  919. }
  920. }
  921. /// 开始颜色设置
  922. - (void)setupStartColor:(UIColor *)color {
  923. CGFloat components[3];
  924. [self getRGBComponents:components forColor:color];
  925. self.startR = components[0];
  926. self.startG = components[1];
  927. self.startB = components[2];
  928. }
  929. /// 结束颜色设置
  930. - (void)setupEndColor:(UIColor *)color {
  931. CGFloat components[3];
  932. [self getRGBComponents:components forColor:color];
  933. self.endR = components[0];
  934. self.endG = components[1];
  935. self.endB = components[2];
  936. }
  937. - (void)zoomForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton {
  938. CGFloat diff = _selectedItemZoomScale - 1;
  939. fromButton.transform = CGAffineTransformMakeScale((1 - progress) * diff + 1, (1 - progress) * diff + 1);
  940. toButton.transform = CGAffineTransformMakeScale(progress * diff + 1, progress * diff + 1);
  941. }
  942. #pragma mark - KVO
  943. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  944. if (object == self.bridgeScrollView) {
  945. if ([keyPath isEqualToString:scrollViewContentOffset]) {
  946. // 当scrolllView滚动时,让跟踪器跟随scrollView滑动
  947. [self prepareMoveTrackerFollowScrollView:self.bridgeScrollView];
  948. }
  949. } else {
  950. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  951. }
  952. }
  953. #pragma mark - setter
  954. - (void)setBridgeScrollView:(UIScrollView *)bridgeScrollView {
  955. _bridgeScrollView = bridgeScrollView;
  956. if (bridgeScrollView) {
  957. [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil];
  958. } else {
  959. NSLog(@"你传了一个空的scrollView");
  960. }
  961. }
  962. - (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle {
  963. _trackerStyle = trackerStyle;
  964. switch (trackerStyle) {
  965. case SPPageMenuTrackerStyleLine:
  966. case SPPageMenuTrackerStyleLineLongerThanItem:
  967. case SPPageMenuTrackerStyleLineAttachment:
  968. self.tracker.backgroundColor = _selectedItemTitleColor;
  969. break;
  970. case SPPageMenuTrackerStyleRoundedRect:
  971. case SPPageMenuTrackerStyleRect:
  972. self.tracker.backgroundColor = [UIColor redColor];
  973. _selectedItemTitleColor = [UIColor whiteColor];
  974. // _trackerHeight是默认有值的,所有样式都会按照事先询问_trackerHeight有没有值,如果有值则采用_trackerHeight,如果矩形或圆角矩形样式下也用_trackerHeight高度太小了,除非外界用户自己设置了_trackerHeight
  975. _trackerHeight = 0;
  976. break;
  977. case SPPageMenuTrackerStyleTextZoom:
  978. // 此样式下默认1.3
  979. self.selectedItemZoomScale = 1.3;
  980. break;
  981. default:
  982. break;
  983. }
  984. }
  985. - (void)setBounces:(BOOL)bounces {
  986. _bounces = bounces;
  987. self.itemScrollView.bounces = bounces;
  988. }
  989. - (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal {
  990. _alwaysBounceHorizontal = alwaysBounceHorizontal;
  991. self.itemScrollView.alwaysBounceHorizontal = alwaysBounceHorizontal;
  992. }
  993. - (void)setTrackerWidth:(CGFloat)trackerWidth {
  994. _trackerWidth = trackerWidth;
  995. CGRect trackerRect = self.tracker.frame;
  996. trackerRect.size.width = trackerWidth;
  997. self.tracker.frame = trackerRect;
  998. CGPoint trackerCenter = self.tracker.center;
  999. trackerCenter.x = _selectedButton.center.x;
  1000. self.tracker.center = trackerCenter;
  1001. }
  1002. - (void)setDividingLineHeight:(CGFloat)dividingLineHeight {
  1003. _dividingLineHeight = dividingLineHeight;
  1004. [self setNeedsLayout];
  1005. [self layoutIfNeeded];
  1006. }
  1007. - (void)setSelectedItemZoomScale:(CGFloat)selectedItemZoomScale {
  1008. _selectedItemZoomScale = selectedItemZoomScale;
  1009. if (selectedItemZoomScale != 1) {
  1010. _selectedButton.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, _selectedItemZoomScale);
  1011. self.tracker.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, 1);
  1012. } else {
  1013. _selectedButton.transform = CGAffineTransformIdentity;
  1014. self.tracker.transform = CGAffineTransformIdentity;
  1015. }
  1016. }
  1017. - (void)setShowFuntionButton:(BOOL)showFuntionButton {
  1018. _showFuntionButton = showFuntionButton;
  1019. self.functionButton.hidden = !showFuntionButton;
  1020. [self setNeedsLayout];
  1021. [self layoutIfNeeded];
  1022. // 修正scrollView偏移
  1023. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1024. }
  1025. - (void)setFuntionButtonshadowOpacity:(CGFloat)funtionButtonshadowOpacity {
  1026. _funtionButtonshadowOpacity = funtionButtonshadowOpacity;
  1027. self.functionButton.layer.shadowOpacity = funtionButtonshadowOpacity;
  1028. }
  1029. - (void)setItemPadding:(CGFloat)itemPadding {
  1030. _itemPadding = itemPadding;
  1031. [self setNeedsLayout];
  1032. [self layoutIfNeeded];
  1033. // 修正scrollView偏移
  1034. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1035. }
  1036. - (void)setItemTitleFont:(UIFont *)itemTitleFont {
  1037. _itemTitleFont = itemTitleFont;
  1038. _selectedItemTitleFont = itemTitleFont;
  1039. _unSelectedItemTitleFont = itemTitleFont;
  1040. for (SPPageMenuItem *button in self.buttons) {
  1041. button.titleLabel.font = itemTitleFont;
  1042. }
  1043. [self setNeedsLayout];
  1044. [self layoutIfNeeded];
  1045. // 修正scrollView偏移
  1046. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1047. }
  1048. - (void)setUnSelectedItemTitleFont:(UIFont *)unSelectedItemTitleFont {
  1049. _unSelectedItemTitleFont = unSelectedItemTitleFont;
  1050. for (SPPageMenuItem *button in self.buttons) {
  1051. if (button == _selectedButton) {
  1052. continue;
  1053. }
  1054. button.titleLabel.font = unSelectedItemTitleFont;
  1055. }
  1056. [self setNeedsLayout];
  1057. [self layoutIfNeeded];
  1058. // 修正scrollView偏移
  1059. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1060. }
  1061. - (void)setSelectedItemTitleFont:(UIFont *)selectedItemTitleFont {
  1062. _selectedItemTitleFont = selectedItemTitleFont;
  1063. self.selectedButton.titleLabel.font = selectedItemTitleFont;
  1064. [self setNeedsLayout];
  1065. [self layoutIfNeeded];
  1066. // 修正scrollView偏移
  1067. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1068. }
  1069. - (void)setSelectedItemTitleColor:(UIColor *)selectedItemTitleColor {
  1070. _selectedItemTitleColor = selectedItemTitleColor;
  1071. [self setupStartColor:selectedItemTitleColor];
  1072. [self.selectedButton setTitleColor:selectedItemTitleColor forState:UIControlStateNormal];
  1073. }
  1074. - (void)setUnSelectedItemTitleColor:(UIColor *)unSelectedItemTitleColor {
  1075. _unSelectedItemTitleColor = unSelectedItemTitleColor;
  1076. [self setupEndColor:unSelectedItemTitleColor];
  1077. for (SPPageMenuItem *button in self.buttons) {
  1078. if (button == _selectedButton) {
  1079. continue; // 跳过选中的那个button
  1080. }
  1081. [button setTitleColor:unSelectedItemTitleColor forState:UIControlStateNormal];
  1082. }
  1083. }
  1084. - (void)setSelectedItemIndex:(NSInteger)selectedItemIndex {
  1085. _selectedItemIndex = selectedItemIndex;
  1086. if (self.buttons.count) {
  1087. SPPageMenuItem *button = [self.buttons objectAtIndex:selectedItemIndex];
  1088. [self buttonInPageMenuClicked:button];
  1089. }
  1090. }
  1091. - (void)setDelegate:(id<SPPageMenuDelegate>)delegate {
  1092. if (delegate == _delegate) {return;}
  1093. _delegate = delegate;
  1094. if (self.buttons.count) {
  1095. SPPageMenuItem *button = [self.buttons objectAtIndex:_selectedItemIndex];
  1096. [self delegatePerformMethodWithFromIndex:button.tag-tagBaseValue toIndex:button.tag-tagBaseValue];
  1097. [self moveItemScrollViewWithSelectedButton:button];
  1098. }
  1099. }
  1100. - (void)setContentInset:(UIEdgeInsets)contentInset {
  1101. _contentInset = contentInset;
  1102. [self setNeedsLayout];
  1103. [self layoutIfNeeded];
  1104. // 修正scrollView偏移
  1105. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1106. }
  1107. - (void)setPermutationWay:(SPPageMenuPermutationWay)permutationWay {
  1108. _permutationWay = permutationWay;
  1109. [self setNeedsLayout];
  1110. [self layoutIfNeeded];
  1111. // 修正scrollView偏移
  1112. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1113. }
  1114. - (void)setCloseTrackerFollowingMode:(BOOL)closeTrackerFollowingMode {
  1115. _closeTrackerFollowingMode = closeTrackerFollowingMode;
  1116. if (closeTrackerFollowingMode) {
  1117. self.trackerFollowingMode = SPPageMenuTrackerFollowingModeEnd;
  1118. } else {
  1119. self.trackerFollowingMode = SPPageMenuTrackerFollowingModeAlways;
  1120. }
  1121. }
  1122. #pragma mark - getter
  1123. - (NSArray *)items {
  1124. if (!_items) {
  1125. _items = [NSMutableArray array];
  1126. }
  1127. return _items;
  1128. }
  1129. - (NSMutableArray *)buttons {
  1130. if (!_buttons) {
  1131. _buttons = [NSMutableArray array];
  1132. }
  1133. return _buttons;
  1134. }
  1135. //竖起的line,分隔每个按钮
  1136. - (NSMutableArray *)hLineArray {
  1137. if (!_hLineArray) {
  1138. _hLineArray = [NSMutableArray array];
  1139. }
  1140. return _hLineArray;
  1141. }
  1142. - (NSMutableDictionary *)setupWidths {
  1143. if (!_setupWidths) {
  1144. _setupWidths = [NSMutableDictionary dictionary];
  1145. }
  1146. return _setupWidths;
  1147. }
  1148. - (UIImageView *)tracker {
  1149. if (!_tracker) {
  1150. _tracker = [[UIImageView alloc] init];
  1151. _tracker.layer.cornerRadius = _trackerHeight * 0.5;
  1152. _tracker.layer.masksToBounds = YES;
  1153. }
  1154. return _tracker;
  1155. }
  1156. - (NSUInteger)numberOfItems {
  1157. return self.items.count;
  1158. }
  1159. #pragma mark - 布局
  1160. - (void)layoutSubviews {
  1161. [super layoutSubviews];
  1162. CGFloat backgroundViewX = self.bounds.origin.x+_contentInset.left;
  1163. CGFloat backgroundViewY = self.bounds.origin.y+_contentInset.top;
  1164. CGFloat backgroundViewW = self.bounds.size.width-(_contentInset.left+_contentInset.right);
  1165. CGFloat backgroundViewH = self.bounds.size.height-(_contentInset.top+_contentInset.bottom);
  1166. self.backgroundView.frame = CGRectMake(backgroundViewX, backgroundViewY, backgroundViewW, backgroundViewH);
  1167. self.backgroundImageView.frame = self.backgroundView.bounds;
  1168. CGFloat dividingLineW = self.bounds.size.width;
  1169. CGFloat dividingLineH = (self.dividingLine.hidden || self.dividingLine.alpha < 0.01) ? 0 : _dividingLineHeight;
  1170. CGFloat dividingLineX = 0;
  1171. CGFloat dividingLineY = self.bounds.size.height-dividingLineH;
  1172. self.dividingLine.frame = CGRectMake(dividingLineX, dividingLineY, dividingLineW, dividingLineH);
  1173. CGFloat functionButtonH = backgroundViewH-dividingLineH;
  1174. CGFloat functionButtonW = functionButtonH;
  1175. CGFloat functionButtonX = backgroundViewW-functionButtonW;
  1176. CGFloat functionButtonY = 0;
  1177. self.functionButton.frame = CGRectMake(functionButtonX, functionButtonY, functionButtonW, functionButtonH);
  1178. // 通过shadowPath设置功能按钮的单边阴影
  1179. if (self.funtionButtonshadowOpacity > 0) {
  1180. self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath;
  1181. }
  1182. CGFloat itemScrollViewX = 0;
  1183. CGFloat itemScrollViewY = 0;
  1184. CGFloat itemScrollViewW = self.showFuntionButton ? backgroundViewW-functionButtonW : backgroundViewW;
  1185. CGFloat itemScrollViewH = backgroundViewH-dividingLineH;
  1186. self.itemScrollView.frame = CGRectMake(itemScrollViewX, itemScrollViewY, itemScrollViewW, itemScrollViewH);
  1187. // 存储itemScrollViewH,目的是解决选中按钮缩放后高度变化了的问题,我们要让选中的按钮缩放之后,依然保持原始高度
  1188. _itemScrollViewH = itemScrollViewH;
  1189. __block CGFloat buttonW = 0.0;
  1190. __block CGFloat lastButtonMaxX = 0.0;
  1191. CGFloat contentW = 0.0; // 内容宽
  1192. CGFloat contentW_sum = 0.0; // 所有文字宽度之和
  1193. NSMutableArray *buttonWidths = [NSMutableArray array];
  1194. // 提前计算每个按钮的宽度,目的是为了计算间距
  1195. for (int i= 0 ; i < self.buttons.count; i++) {
  1196. SPPageMenuItem *button = self.buttons[i];
  1197. CGFloat textW;
  1198. CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%d",i]] floatValue];
  1199. if (button == _selectedButton) {
  1200. textW = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_selectedItemTitleFont} context:nil].size.width;
  1201. } else {
  1202. textW = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_unSelectedItemTitleFont} context:nil].size.width;
  1203. }
  1204. // CGImageGetWidth获取的图片宽度是图片在@1x、@2x、@3x的位置上的实际宽度
  1205. // button.currentImage.size.width获取的宽度永远是@1x位置上的宽度,比如一张图片在@3x上的位置为300,那么button.currentImage.size.width就为100
  1206. CGFloat imageW = CGImageGetWidth(button.currentImage.CGImage);
  1207. CGFloat imageH = CGImageGetHeight(button.currentImage.CGImage);
  1208. CGFloat ratio = imageW / imageH;
  1209. if (ratio >= 1) { // 宽大于高
  1210. if (imageH > itemScrollViewH) { // 按比例适应在button中
  1211. imageH = itemScrollViewH;
  1212. imageW = imageH * ratio;
  1213. }
  1214. }
  1215. if (button.currentTitle && !button.currentImage) {
  1216. contentW = textW;
  1217. } else if(button.currentImage && !button.currentTitle) {
  1218. contentW = imageW;
  1219. } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft)) {
  1220. contentW = textW + imageW;
  1221. } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) {
  1222. contentW = MAX(textW, imageW);
  1223. }
  1224. if (setupButtonW) {
  1225. contentW_sum += setupButtonW;
  1226. [buttonWidths addObject:@(setupButtonW)];
  1227. } else {
  1228. contentW_sum += contentW;
  1229. [buttonWidths addObject:@(contentW)];
  1230. }
  1231. }
  1232. CGFloat diff = itemScrollViewW - contentW_sum;
  1233. [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuItem *button, NSUInteger idx, BOOL * _Nonnull stop) {
  1234. CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue];
  1235. if (self.permutationWay == SPPageMenuPermutationWayScrollAdaptContent) {
  1236. buttonW = [buttonWidths[idx] floatValue];
  1237. if (idx == 0) {
  1238. button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH);
  1239. } else {
  1240. button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH);
  1241. }
  1242. } else if (self.permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) {
  1243. // 求出外界设置的按钮宽度之和
  1244. CGFloat totalSetupButtonW = [[self.setupWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue];
  1245. // 如果该按钮外界设置了宽,则取外界设置的,如果外界没设置,则其余按钮等宽
  1246. buttonW = setupButtonW ? setupButtonW : (itemScrollViewW-self->_itemPadding*(self.buttons.count)-totalSetupButtonW)/(self.buttons.count-self.setupWidths.count);
  1247. if (buttonW < 0) { // 按钮过多时,有可能会为负数
  1248. buttonW = 0;
  1249. }
  1250. if (idx == 0) {
  1251. button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH);
  1252. } else {
  1253. button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH);
  1254. }
  1255. } else {
  1256. self->_itemPadding = diff/self.buttons.count;
  1257. buttonW = [buttonWidths[idx] floatValue];
  1258. if (idx == 0) {
  1259. button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH);
  1260. } else {
  1261. button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH);
  1262. }
  1263. }
  1264. lastButtonMaxX = CGRectGetMaxX(button.frame);
  1265. if(button.imagePosition == SPItemImagePositionLeft){
  1266. //wcl - 自定义按钮分割线
  1267. if(idx != self.hLineArray.count - 1){
  1268. UIView *hLineView = [self.hLineArray objectAtIndex:idx];
  1269. hLineView.frame = CGRectMake(lastButtonMaxX - 1, 15, 1, 20);
  1270. }
  1271. }
  1272. }];
  1273. // 如果selectedButton有缩放,走完上面代码selectedButton的frame会还原,这会导致文字显示不全问题,为了解决这个问题,这里将selectedButton的frame强制缩放
  1274. if (!CGAffineTransformEqualToTransform(self.selectedButton.transform, CGAffineTransformIdentity)) {
  1275. CGRect selectedButtonRect = self.selectedButton.frame;
  1276. selectedButtonRect.origin.y = selectedButtonRect.origin.y-(selectedButtonRect.size.height*_selectedItemZoomScale - selectedButtonRect.size.height)/2;
  1277. selectedButtonRect.origin.x = selectedButtonRect.origin.x-((selectedButtonRect.size.width*_selectedItemZoomScale - selectedButtonRect.size.width)/2);
  1278. selectedButtonRect.size = CGSizeMake(selectedButtonRect.size.width * _selectedItemZoomScale, selectedButtonRect.size.height*_selectedItemZoomScale);
  1279. self.selectedButton.frame = selectedButtonRect;
  1280. }
  1281. [self resetSetupTrackerFrameWithSelectedButton:self.selectedButton];
  1282. self.itemScrollView.contentSize = CGSizeMake(lastButtonMaxX+_itemPadding*0.5, 0);
  1283. if (self.translatesAutoresizingMaskIntoConstraints == NO) {
  1284. [self moveItemScrollViewWithSelectedButton:self.selectedButton];
  1285. }
  1286. }
  1287. - (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuItem *)selectedButton {
  1288. CGFloat trackerX;
  1289. CGFloat trackerY;
  1290. CGFloat trackerW;
  1291. CGFloat trackerH;
  1292. CGFloat selectedButtonWidth = selectedButton.frame.size.width;
  1293. switch (self.trackerStyle) {
  1294. case SPPageMenuTrackerStyleLine:
  1295. {
  1296. trackerW = _trackerWidth ? _trackerWidth : selectedButtonWidth;
  1297. trackerH = _trackerHeight;
  1298. trackerX = selectedButton.frame.origin.x;
  1299. trackerY = self.itemScrollView.bounds.size.height - trackerH;
  1300. self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH);
  1301. }
  1302. break;
  1303. case SPPageMenuTrackerStyleLineLongerThanItem:
  1304. {
  1305. trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+(selectedButtonWidth ? _itemPadding : 0));
  1306. trackerH = _trackerHeight;
  1307. trackerX = selectedButton.frame.origin.x;
  1308. trackerY = self.itemScrollView.bounds.size.height - trackerH;
  1309. self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH);
  1310. }
  1311. break;
  1312. case SPPageMenuTrackerStyleLineAttachment:
  1313. {
  1314. trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth ? selectedButton.titleLabel.font.pointSize : 0); // 没有自定义宽度就固定宽度为字体大小
  1315. trackerH = _trackerHeight;
  1316. trackerX = selectedButton.frame.origin.x;
  1317. trackerY = self.itemScrollView.bounds.size.height - trackerH;
  1318. self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH);
  1319. }
  1320. break;
  1321. case SPPageMenuTrackerStyleRect:
  1322. {
  1323. trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+(selectedButtonWidth ? _itemPadding : 0));
  1324. trackerH = _trackerHeight ? _trackerHeight : (selectedButton.frame.size.height);
  1325. trackerX = selectedButton.frame.origin.x;
  1326. trackerY = (_itemScrollViewH-trackerH)*0.5;
  1327. self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH);
  1328. self.tracker.layer.cornerRadius = 0;
  1329. }
  1330. break;
  1331. case SPPageMenuTrackerStyleRoundedRect:
  1332. {
  1333. trackerH = _trackerHeight ? _trackerHeight : (_itemTitleFont.lineHeight+10);
  1334. trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+_itemPadding);
  1335. trackerX = selectedButton.frame.origin.x;
  1336. trackerY = (_itemScrollViewH-trackerH)*0.5;
  1337. self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH);
  1338. self.tracker.layer.cornerRadius = MIN(trackerW, trackerH)*0.5;
  1339. self.tracker.layer.masksToBounds = YES;
  1340. }
  1341. break;
  1342. default:
  1343. break;
  1344. }
  1345. CGPoint trackerCenter = self.tracker.center;
  1346. trackerCenter.x = selectedButton.center.x;
  1347. self.tracker.center = trackerCenter;
  1348. }
  1349. - (void)dealloc {
  1350. [self.bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset];
  1351. }
  1352. @end
  1353. #pragma clang diagnostic pop