// // SPPageMenu.m // SPPageMenu // // Created by 乐升平 on 17/10/26. // Copyright © 2017年 iDress. All rights reserved. // #import "SPPageMenu.h" #define tagBaseValue 100 #define scrollViewContentOffset @"contentOffset" @interface SPPageMenuScrollView : UIScrollView @end @implementation SPPageMenuScrollView // 重写这个方法的目的是:当手指长按按钮时无法滑动scrollView的问题 - (BOOL)touchesShouldCancelInContentView:(UIView *)view { return YES; } @end @interface SPPageMenuLine : UIImageView @property (nonatomic, copy) void(^hideBlock)(void); @end @implementation SPPageMenuLine // 当外界设置隐藏和alpha值时,让pageMenu重新布局 - (void)setHidden:(BOOL)hidden { [super setHidden:hidden]; if (self.hideBlock) { self.hideBlock(); } } - (void)setAlpha:(CGFloat)alpha { [super setAlpha:alpha]; if (self.hideBlock) { self.hideBlock(); } } @end @interface SPPageMenuItem : UIButton - (instancetype)initWithImageRatio:(CGFloat)ratio; // 图片的高度所占按钮的高度比例,注意要浮点数,如果传分数比如三分之二,要写2.0/3.0,不能写2/3 @property (nonatomic, assign) CGFloat imageRatio; // 图片的位置 @property (nonatomic, assign) SPItemImagePosition imagePosition; // 图片与标题之间的间距 @property (nonatomic, assign) CGFloat imageTitleSpace; @end @implementation SPPageMenuItem - (instancetype)initWithImageRatio:(CGFloat)ratio { if (self = [super init]) { _imageRatio = ratio; } return self; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self initialize]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self initialize]; } return self; } - (void)initialize { _imageRatio = 0.5; _imagePosition = SPItemImagePositionDefault; self.imageView.contentMode = UIViewContentModeScaleAspectFit; self.titleLabel.textAlignment = NSTextAlignmentCenter; } - (void)setHighlighted:(BOOL)highlighted {} - (CGRect)imageRectForContentRect:(CGRect)contentRect { if (!self.currentTitle) { // 如果没有文字,则图片占据整个button,空格算一个文字 return [super imageRectForContentRect:contentRect]; } switch (self.imagePosition) { case SPItemImagePositionDefault: case SPItemImagePositionLeft: { // 图片在左 _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; CGFloat imageW = (contentRect.size.width-_imageTitleSpace) * _imageRatio; CGFloat imageH = contentRect.size.height; //自定义修改 - 图片在左时,固定图片大小为21 imageW = 18; return CGRectMake(12, self.contentEdgeInsets.top, imageW, imageH); // return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); } break; case SPItemImagePositionTop: { _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; CGFloat imageW = contentRect.size.width; CGFloat imageH = (contentRect.size.height-_imageTitleSpace) * _imageRatio; return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); } break; case SPItemImagePositionRight: { _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; CGFloat imageW = (contentRect.size.width-_imageTitleSpace) * _imageRatio; CGFloat imageH = contentRect.size.height; CGFloat imageX = contentRect.size.width - imageW; return CGRectMake(imageX+self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); } break; case SPItemImagePositionBottom: { _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; CGFloat imageW = contentRect.size.width; CGFloat imageH = (contentRect.size.height - _imageTitleSpace) * _imageRatio; CGFloat imageY = contentRect.size.height-imageH; return CGRectMake(self.contentEdgeInsets.left, imageY+self.contentEdgeInsets.top, imageW, imageH); } break; default: break; } return CGRectZero; } - (CGRect)titleRectForContentRect:(CGRect)contentRect { if (!self.currentImage) { // 如果没有图片 return [super titleRectForContentRect:contentRect]; } switch (self.imagePosition) { case SPItemImagePositionDefault: case SPItemImagePositionLeft: { _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; CGFloat titleX = (contentRect.size.width-_imageTitleSpace) * _imageRatio + _imageTitleSpace; CGFloat titleW = contentRect.size.width - titleX; CGFloat titleH = contentRect.size.height; return CGRectMake(titleX+self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH); } break; case SPItemImagePositionTop: { _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; CGFloat titleY = (contentRect.size.height-_imageTitleSpace) * _imageRatio + _imageTitleSpace; CGFloat titleW = contentRect.size.width; CGFloat titleH = contentRect.size.height - titleY; return CGRectMake(self.contentEdgeInsets.left, titleY+self.contentEdgeInsets.top, titleW, titleH); } break; case SPItemImagePositionRight: { _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; CGFloat titleW = (contentRect.size.width - _imageTitleSpace) * (1-_imageRatio); CGFloat titleH = contentRect.size.height; return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH); } break; case SPItemImagePositionBottom: { _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; CGFloat titleW = contentRect.size.width; CGFloat titleH = (contentRect.size.height-_imageTitleSpace) * (1 - _imageRatio); return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH); } break; default: break; } return CGRectZero; } - (void)setImagePosition:(SPItemImagePosition)imagePosition { _imagePosition = imagePosition; [self setNeedsDisplay]; } - (void)setImageRatio:(CGFloat)imageRatio { _imageRatio = imageRatio; [self setNeedsDisplay]; } - (void)setImageTitleSpace:(CGFloat)imageTitleSpace { _imageTitleSpace = imageTitleSpace; [self setNeedsDisplay]; } - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets { [super setContentEdgeInsets:contentEdgeInsets]; [self setNeedsDisplay]; } @end @interface SPPageMenu() @property (nonatomic, assign) SPPageMenuTrackerStyle trackerStyle; @property (nonatomic, strong) NSArray *items; // 里面装的是字符串或者图片 @property (nonatomic, strong) UIImageView *tracker; @property (nonatomic, assign) CGFloat trackerHeight; @property (nonatomic, weak) UIView *backgroundView; @property (nonatomic, weak) UIImageView *backgroundImageView; @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; @property (nonatomic, weak) SPPageMenuItem *functionButton; @property (nonatomic, strong) NSMutableArray *buttons; @property (nonatomic, strong) NSMutableArray *hLineArray; @property (nonatomic, strong) SPPageMenuItem *selectedButton; @property (nonatomic, strong) NSMutableDictionary *setupWidths; @property (nonatomic, assign) BOOL insert; // 起始偏移量,为了判断滑动方向 @property (nonatomic, assign) CGFloat beginOffsetX; /// 开始颜色, 取值范围 0~1 @property (nonatomic, assign) CGFloat startR; @property (nonatomic, assign) CGFloat startG; @property (nonatomic, assign) CGFloat startB; /// 完成颜色, 取值范围 0~1 @property (nonatomic, assign) CGFloat endR; @property (nonatomic, assign) CGFloat endG; @property (nonatomic, assign) CGFloat endB; // 这个高度,是存储itemScrollView的高度 @property (nonatomic, assign) CGFloat itemScrollViewH; @end #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @implementation SPPageMenu #pragma mark - public + (instancetype)pageMenuWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle { SPPageMenu *pageMenu = [[SPPageMenu alloc] initWithFrame:frame trackerStyle:trackerStyle]; return pageMenu; } - (instancetype)initWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle { if (self = [super init]) { self.frame = frame; self.backgroundColor = [UIColor whiteColor]; self.trackerStyle = trackerStyle; [self setupStartColor:_selectedItemTitleColor]; [self setupEndColor:_unSelectedItemTitleColor]; } return self; } - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex { if (selectedItemIndex < 0) selectedItemIndex = 0; NSAssert(selectedItemIndex <= items.count-1, @"selectedItemIndex 大于了 %ld",items.count-1); _items = items.copy; _selectedItemIndex = selectedItemIndex; self.insert = NO; if (self.buttons.count) { for (SPPageMenuItem *button in self.buttons) { [button removeFromSuperview]; } } [self.buttons removeAllObjects]; for (int i = 0; i < items.count; i++) { id object = items[i]; NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]], @"items中的元素只能是NSString或UIImage类型"); [self addButton:i object:object animated:NO]; } [self setNeedsLayout]; [self layoutIfNeeded]; if (self.buttons.count) { // 默认选中selectedItemIndex对应的按钮 SPPageMenuItem *selectedButton = [self.buttons objectAtIndex:selectedItemIndex]; [self buttonInPageMenuClicked:selectedButton]; // SPPageMenuTrackerStyleTextZoom和SPPageMenuTrackerStyleNothing样式跟tracker没有关联 if ([self haveOrNeedsTracker]) { [self.itemScrollView insertSubview:self.tracker atIndex:0]; // 这里千万不能再去调用setNeedsLayout和layoutIfNeeded,因为如果外界在此之前对selectedButton进行了缩放,调用了layoutSubViews后会重新对selectedButton设置frame,先缩放再重设置frame会导致文字显示不全,所以我们直接跳过layoutSubViews调用resetSetupTrackerFrameWithSelectedButton:只设置tracker的frame [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; } } } - (void)insertItemWithTitle:(NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated { self.insert = YES; NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count); NSMutableArray *titleArr = self.items.mutableCopy; [titleArr insertObject:title atIndex:itemIndex]; self.items = titleArr; [self addButton:itemIndex object:title animated:animated]; if (itemIndex <= self.selectedItemIndex) { _selectedItemIndex += 1; } } - (void)insertItemWithImage:(UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated { self.insert = YES; NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count); NSMutableArray *objects = self.items.mutableCopy; [objects insertObject:image atIndex:itemIndex]; self.items = objects.copy; [self addButton:itemIndex object:image animated:animated]; if (itemIndex <= self.selectedItemIndex) { _selectedItemIndex += 1; } } - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count); // 被删除的按钮之后的按钮需要修改tag值 for (SPPageMenuItem *button in self.buttons) { if (button.tag-tagBaseValue > itemIndex) { button.tag = button.tag - 1; } } if (self.items.count) { NSMutableArray *objects = self.items.mutableCopy; // 特别注意的是:不能先通过itemIndex取出对象,然后再将对象删除,因为这样会删除所有相同的对象 [objects removeObjectAtIndex:itemIndex]; self.items = objects.copy; } if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; if (button == self.selectedButton) { // 如果删除的正是选中的item,删除之后,选中的按钮切换为上一个item self.selectedItemIndex = itemIndex > 0 ? itemIndex-1 : itemIndex; } [self.buttons removeObjectAtIndex:itemIndex]; [button removeFromSuperview]; if (self.buttons.count == 0) { // 说明移除了所有 [self.tracker removeFromSuperview]; self.selectedButton = nil; self.selectedItemIndex = 0; } } if (animated) { [UIView animateWithDuration:0.5 animations:^{ [self setNeedsLayout]; [self layoutIfNeeded]; }]; } else { [self setNeedsLayout]; } } - (void)removeAllItems { NSMutableArray *objects = self.items.mutableCopy; [objects removeAllObjects]; self.items = objects.copy; self.items = nil; for (int i = 0; i < self.buttons.count; i++) { SPPageMenuItem *button = self.buttons[i]; [button removeFromSuperview]; } [self.buttons removeAllObjects]; [self.tracker removeFromSuperview]; self.selectedButton = nil; self.selectedItemIndex = 0; [self setNeedsLayout]; } - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; [button setImage:nil forState:UIControlStateNormal]; [button setTitle:title forState:UIControlStateNormal]; NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:title]; self.items = items.copy; } [self setNeedsLayout]; } - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; return item.currentTitle; } return nil; } - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:nil forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } [self setNeedsLayout]; } - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; return item.currentImage; } return nil; } - (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; [button setEnabled:enaled]; } } - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex { if (self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; return button.enabled; } return YES; } - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { [self.setupWidths setObject:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; } } - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { CGFloat setupWidth = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; if (setupWidth) { return setupWidth; } else { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; return button.bounds.size.width; } } return 0; } - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; button.contentEdgeInsets = contentInset; } } - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; button.contentEdgeInsets = contentInset; } } - (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; return button.contentEdgeInsets; } return UIEdgeInsetsZero; } - (void)setBackgroundImage:(UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics { if (barMetrics == UIBarMetricsDefault) { if (UIEdgeInsetsEqualToEdgeInsets(backgroundImage.capInsets, UIEdgeInsetsZero)) { CGFloat imageWidth = CGImageGetWidth(backgroundImage.CGImage); CGFloat imageHeight = CGImageGetHeight(backgroundImage.CGImage); [self.backgroundImageView setImage:[backgroundImage resizableImageWithCapInsets:UIEdgeInsetsMake(imageHeight*0.5, imageWidth*0.5, imageHeight*0.5, imageWidth*0.5) resizingMode:backgroundImage.resizingMode]]; } else { [self.backgroundImageView setImage:backgroundImage]; } } } - (UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics { return self.backgroundImageView.image; } - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius { _trackerHeight = trackerHeight; self.tracker.layer.cornerRadius = cornerRadius; [self setNeedsLayout]; [self layoutIfNeeded]; } - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:title forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; button.imagePosition = imagePosition; button.imageRatio = ratio; button.imageTitleSpace = imageTitleSpace; // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } else if (image == nil) { NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:title]; self.items = items.copy; } else { NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } [self setNeedsLayout]; [self layoutIfNeeded]; } } - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state { [self.functionButton setTitle:title forState:state]; [self.functionButton setImage:image forState:state]; self.functionButton.imagePosition = imagePosition; self.functionButton.imageRatio = ratio; self.functionButton.imageTitleSpace = imageTitleSpace; } // 以下2个方法在3.0版本上有升级,可以使用但不推荐 - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:title forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; button.imagePosition = imagePosition; button.imageRatio = ratio; // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } else if (image == nil) { NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:title]; self.items = items.copy; } else { NSMutableArray *items = self.items.mutableCopy; [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } [self setNeedsLayout]; [self layoutIfNeeded]; } } - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state { [self.functionButton setTitle:title forState:state]; [self.functionButton setImage:image forState:state]; self.functionButton.imagePosition = imagePosition; self.functionButton.imageRatio = ratio; } - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state { if (attributes[NSFontAttributeName]) { self.functionButton.titleLabel.font = attributes[NSFontAttributeName]; } if (attributes[NSForegroundColorAttributeName]) { [self.functionButton setTitleColor:attributes[NSForegroundColorAttributeName] forState:state]; } if (attributes[NSBackgroundColorAttributeName]) { self.functionButton.backgroundColor = attributes[NSBackgroundColorAttributeName]; } } - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView { // 说明外界传进来了一个scrollView,如果外界传进来了,pageMenu会观察该scrollView的contentOffset自动处理跟踪器的跟踪 if (self.bridgeScrollView == scrollView) { return; } [self prepareMoveTrackerFollowScrollView:scrollView]; } #pragma mark - private - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { // 如果是插入,需要改变已有button的tag值 for (SPPageMenuItem *button in self.buttons) { if (button.tag-tagBaseValue >= index) { button.tag = button.tag + 1; // 由于有新button的加入,新button后面的button的tag值得+1 } } //wcl - 自定义添加按钮分割线 UIView *hLineView = [[UIView alloc] init]; hLineView.backgroundColor = RGB(232, 231, 231); SPPageMenuItem *button = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; button.titleLabel.font = _itemTitleFont; [button addTarget:self action:@selector(buttonInPageMenuClicked:) forControlEvents:UIControlEventTouchUpInside]; button.tag = tagBaseValue + index; if ([object isKindOfClass:[NSString class]]) { [button setTitle:object forState:UIControlStateNormal]; } else { [button setImage:object forState:UIControlStateNormal]; } if (self.insert) { if ([self haveOrNeedsTracker]) { if (self.buttons.count == 0) { // 如果是第一个插入,需要将跟踪器加上,第一个插入说明itemScrollView上没有任何子控件 [self.itemScrollView insertSubview:self.tracker atIndex:0]; [self.itemScrollView insertSubview:button atIndex:index+1]; [self.itemScrollView insertSubview:hLineView atIndex:index+1]; } else { // 已经有跟踪器 [self.itemScrollView insertSubview:button atIndex:index+1]; // +1是因为跟踪器 [self.itemScrollView insertSubview:hLineView atIndex:index+1]; } } else { [self.itemScrollView insertSubview:button atIndex:index]; [self.itemScrollView insertSubview:hLineView atIndex:index]; } if (!self.buttons.count) { [self buttonInPageMenuClicked:button]; } } else { [self.itemScrollView insertSubview:button atIndex:index]; [self.itemScrollView insertSubview:hLineView atIndex:index]; } [self.buttons insertObject:button atIndex:index]; [self.hLineArray addObject:hLineView]; // setNeedsLayout会标记为需要刷新,layoutIfNeeded只有在有标记的情况下才会立即调用layoutSubViews,当然标记为刷新并非只有调用setNeedsLayout,如frame改变,addSubView等都会标记为刷新 if (self.insert && animated) { // 是插入的新按钮,且需要动画 // 取出上一个按钮 SPPageMenuItem *lastButton; if (index > 0) { lastButton = self.buttons[index-1]; } // 先给初始的origin,按钮将会从这个origin开始动画 button.frame = CGRectMake(CGRectGetMaxX(lastButton.frame)+_itemPadding*0.5, 0, 0, 0); button.titleLabel.frame = button.bounds; [UIView animateWithDuration:.5 animations:^{ [self setNeedsLayout]; [self layoutIfNeeded]; }]; } } // 是否已经或者即将有跟踪器 - (BOOL)haveOrNeedsTracker { if (self.trackerStyle != SPPageMenuTrackerStyleTextZoom && self.trackerStyle != SPPageMenuTrackerStyleNothing) { return YES; } return NO; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self initialize]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self initialize]; } return self; } - (void)initialize { _itemPadding = 30.0; _selectedItemTitleColor = [UIColor redColor]; _unSelectedItemTitleColor = [UIColor blackColor]; _selectedItemTitleFont = [UIFont systemFontOfSize:16]; _unSelectedItemTitleFont = [UIFont systemFontOfSize:16]; _itemTitleFont = [UIFont systemFontOfSize:16]; _trackerHeight = 3.0; _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; _showFuntionButton = NO; _funtionButtonshadowOpacity = 0.5; _selectedItemZoomScale = 1; _needTextColorGradients = YES; [self setupSubViews]; } - (void)setupSubViews { // 必须先添加分割线,再添加backgroundView;假如先添加backgroundView,那也就意味着backgroundView是SPPageMenu的第一个子控件,而scrollView又是backgroundView的第一个子控件,当外界在由导航控制器管理的控制器中将SPPageMenu添加为第一个子控件时,控制器会不断的往下遍历第一个子控件的第一个子控件,直到找到为scrollView为止,一旦发现某子控件的第一个子控件为scrollView,会将scrollView的内容往下偏移64;这时控制器中必须设置self.automaticallyAdjustsScrollViewInsets = NO;为了避免这样做,这里将分割线作为第一个子控件 SPPageMenuLine *dividingLine = [[SPPageMenuLine alloc] init]; dividingLine.backgroundColor = RGB(236, 236, 236); __weak typeof(self) weakSelf = self; dividingLine.hideBlock = ^() { [weakSelf setNeedsLayout]; }; [self addSubview:dividingLine]; _dividingLine = dividingLine; UIView *backgroundView = [[UIView alloc] init]; backgroundView.layer.masksToBounds = YES; [self addSubview:backgroundView]; _backgroundView = backgroundView; UIImageView *backgroundImageView = [[UIImageView alloc] init]; [backgroundView addSubview:backgroundImageView]; _backgroundImageView = backgroundImageView; SPPageMenuScrollView *itemScrollView = [[SPPageMenuScrollView alloc] init]; itemScrollView.showsVerticalScrollIndicator = NO; itemScrollView.showsHorizontalScrollIndicator = NO; itemScrollView.scrollsToTop = NO; // 目的是不要影响到外界的scrollView置顶功能 itemScrollView.bouncesZoom = NO; itemScrollView.bounces = YES; if (@available(iOS 11.0, *)) { itemScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } [backgroundView addSubview:itemScrollView]; _itemScrollView = itemScrollView; SPPageMenuItem *functionButton = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; functionButton.backgroundColor = [UIColor whiteColor]; // [functionButton setTitle:@"+" forState:UIControlStateNormal]; [functionButton setImage:[UIImage imageNamed:@"common_bottomArrow_icon"] forState:UIControlStateNormal]; [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; functionButton.layer.shadowColor = [UIColor blackColor].CGColor; functionButton.layer.shadowOffset = CGSizeMake(0, 0); functionButton.layer.shadowRadius = 2; functionButton.layer.shadowOpacity = _funtionButtonshadowOpacity; // 默认是0,为0的话不会显示阴影 functionButton.hidden = !_showFuntionButton; [backgroundView addSubview:functionButton]; _functionButton = functionButton; } // 按钮点击方法 - (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue; NSInteger toIndex = sender.tag - tagBaseValue; // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的,必须用下划线,用self.会造成死循环 _selectedItemIndex = toIndex; // 如果sender是新的选中的按钮,则上一次的按钮颜色为非选中颜色,当前选中的颜色为选中颜色 if (self.selectedButton != sender) { [self.selectedButton setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; [sender setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal]; self.selectedButton.titleLabel.font = _unSelectedItemTitleFont; sender.titleLabel.font = _selectedItemTitleFont; // 让itemScrollView发生偏移 [self moveItemScrollViewWithSelectedButton:sender]; if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || _selectedItemZoomScale != 1) { if (labs(toIndex-fromIndex) >= 2) { // 该条件意思是当外界滑动scrollView连续的滑动了超过2页 for (SPPageMenuItem *button in self.buttons) { // 必须遍历将非选中按钮还原缩放,而不是仅仅只让上一个选中的按钮还原缩放。因为当用户快速滑动外界scrollView时,会频繁的调用-zoomForTitleWithProgress:fromButton:toButton:方法,有可能经过的某一个button还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 if (button != sender && !CGAffineTransformEqualToTransform(button.transform, CGAffineTransformIdentity)) { button.transform = CGAffineTransformIdentity; } } } else { self.selectedButton.transform = CGAffineTransformIdentity; } sender.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, _selectedItemZoomScale); } if (fromIndex != toIndex) { // 如果相等,说明是第1次进来或者2次点了同一个,此时不需要动画 [self moveTrackerWithSelectedButton:sender]; } self.selectedButton = sender; if (_selectedItemTitleFont != _unSelectedItemTitleFont) { [self setNeedsLayout]; [self layoutIfNeeded]; } } else { // 如果选中的按钮没有发生变化,比如用户往左边滑scrollView,还没滑动结束又开始往右滑动,此时选中的按钮就没变。如果设置了颜色渐变,而且当未选中的颜色带了不等于1的alpha值,如果用户往一边滑动还未结束又往另一边滑,则未选中的按钮颜色不是很准确。这个else就是去除这种不准确现象 // 获取RGB和Alpha CGFloat red = 0.0; CGFloat green = 0.0; CGFloat blue = 0.0; CGFloat alpha = 0.0; [_unSelectedItemTitleColor getRed:&red green:&green blue:&blue alpha:&alpha]; // 此时alpha已经获取到了 if (alpha < 1) { // 因为相信alpha=1的情况还是占多数的,如果不做判断,apha=1时也for循环设置未选中按钮的颜色有点浪费.alpha=1时不会产生颜色不准确问题 for (SPPageMenuItem *button in self.buttons) { if (button == sender) { [button setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal]; } else { [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; } } } else { [sender setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal]; } } [self delegatePerformMethodWithFromIndex:fromIndex toIndex:toIndex]; } // 点击button让itemScrollView发生偏移 - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { if (CGRectEqualToRect(self.backgroundView.frame, CGRectZero)) { return; } // 转换点的坐标位置 CGPoint centerInPageMenu = [self.backgroundView convertPoint:selectedButton.center toView:self]; // CGRectGetMidX(self.backgroundView.frame)指的是屏幕水平中心位置,它的值是固定不变的 CGFloat offSetX = centerInPageMenu.x - CGRectGetMidX(self.backgroundView.frame); // itemScrollView的容量宽与自身宽之差(难点) CGFloat maxOffsetX = self.itemScrollView.contentSize.width - self.itemScrollView.frame.size.width; // 如果选中的button中心x值小于或者等于itemScrollView的中心x值,或者itemScrollView的容量宽度小于itemScrollView本身,此时点击button时不发生任何偏移,置offSetX为0 if (offSetX <= 0 || maxOffsetX <= 0) { offSetX = 0; } // 如果offSetX大于maxOffsetX,说明itemScrollView已经滑到尽头,此时button也发生任何偏移了 else if (offSetX > maxOffsetX){ offSetX = maxOffsetX; } [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES]; } // 移动跟踪器 - (void)moveTrackerWithSelectedButton:(SPPageMenuItem *)selectedButton { [UIView animateWithDuration:0.25 animations:^{ [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; }]; } // 执行代理方法 - (void)delegatePerformMethodWithFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:itemSelectedFromIndex:toIndex:)]) { [self.delegate pageMenu:self itemSelectedFromIndex:fromIndex toIndex:toIndex]; } else if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:itemSelectedAtIndex:)]) { [self.delegate pageMenu:self itemSelectedAtIndex:toIndex]; } } // 功能按钮的点击方法 - (void)functionButtonClicked:(SPPageMenuItem *)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:functionButtonClicked:)]) { [self.delegate pageMenu:self functionButtonClicked:sender]; } } - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { // 这个if条件的意思是scrollView的滑动不是由手指拖拽产生 if (!scrollView.isDragging && !scrollView.isDecelerating) {return;} // 当滑到边界时,继续通过scrollView的bouces效果滑动时,直接return if (scrollView.contentOffset.x < 0 || scrollView.contentOffset.x > scrollView.contentSize.width-scrollView.bounds.size.width) { return; } // 当前偏移量 CGFloat currentOffSetX = scrollView.contentOffset.x; // 偏移进度 CGFloat offsetProgress = currentOffSetX / scrollView.bounds.size.width; CGFloat progress = offsetProgress - floor(offsetProgress); NSInteger fromIndex = 0; NSInteger toIndex = 0; // 初始值不要等于scrollView.contentOffset.x,因为第一次进入此方法时,scrollView.contentOffset.x的值已经有一点点偏移了,不是很准确 _beginOffsetX = scrollView.bounds.size.width * self.selectedItemIndex; // 以下注释的“拖拽”一词很准确,不可说成滑动,例如:当手指向右拖拽,还未拖到一半时就松开手,接下来scrollView则会往回滑动,这个往回,就是向左滑动,这也是_beginOffsetX不可时刻纪录的原因,如果时刻纪录,那么往回(向左)滑动时会被视为“向左拖拽”,然而,这个往回却是由“向右拖拽”而导致的 if (currentOffSetX - _beginOffsetX > 0) { // 向左拖拽了 // 求商,获取上一个item的下标 fromIndex = currentOffSetX / scrollView.bounds.size.width; // 当前item的下标等于上一个item的下标加1 toIndex = fromIndex + 1; if (toIndex >= self.buttons.count) { toIndex = fromIndex; } } else if (currentOffSetX - _beginOffsetX < 0) { // 向右拖拽了 toIndex = currentOffSetX / scrollView.bounds.size.width; fromIndex = toIndex + 1; progress = 1.0 - progress; } else { progress = 1.0; fromIndex = self.selectedItemIndex; toIndex = fromIndex; } if (currentOffSetX == scrollView.bounds.size.width * fromIndex) {// 滚动停止了 progress = 1.0; toIndex = fromIndex; } // 如果滚动停止,直接通过点击按钮选中toIndex对应的item if (currentOffSetX == scrollView.bounds.size.width*toIndex) { // 这里toIndex==fromIndex // 这一次赋值起到2个作用,一是点击toIndex对应的按钮,走一遍代理方法,二是弥补跟踪器的结束跟踪,因为本方法是在scrollViewDidScroll中调用,可能离滚动结束还有一丁点的距离,本方法就不调了,最终导致外界还要在scrollView滚动结束的方法里self.selectedItemIndex进行赋值,直接在这里赋值可以让外界不用做此操作 if (_selectedItemIndex != toIndex) { self.selectedItemIndex = toIndex; } // 要return,点击了按钮,跟踪器自然会跟着被点击的按钮走 return; } if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeAlways) { // 这个方法才开始移动跟踪器 [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; } else if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeHalf) { SPPageMenuItem *fromButton; SPPageMenuItem *toButton; if (progress > 0.5) { if (toIndex >= 0 && toIndex < self.buttons.count) { toButton = self.buttons[toIndex]; fromButton = self.buttons[fromIndex]; if (_selectedItemIndex != toIndex) { self.selectedItemIndex = toIndex; } } } else { if (fromIndex >= 0 && fromIndex < self.buttons.count) { toButton = self.buttons[fromIndex]; fromButton = self.buttons[toIndex]; if (_selectedItemIndex != fromIndex) { self.selectedItemIndex = fromIndex; } } } } else { // self.trackerFollowingMode = SPPageMenuTrackerFollowingModeEnd // 什么都不用做 } } // 这个方法才开始真正滑动跟踪器,上面都是做铺垫 - (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex currentOffsetX:(CGFloat)currentOffsetX beginOffsetX:(CGFloat)beginOffsetX { UIButton *fromButton = self.buttons[fromIndex]; UIButton *toButton = self.buttons[toIndex]; // 2个按钮之间的距离 CGFloat xDistance = toButton.center.x - fromButton.center.x; // 2个按钮宽度的差值 CGFloat wDistance = toButton.frame.size.width - fromButton.frame.size.width; CGRect newFrame = self.tracker.frame; CGPoint newCenter = self.tracker.center; if (self.trackerStyle == SPPageMenuTrackerStyleLine) { newCenter.x = fromButton.center.x + xDistance * progress; newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress); self.tracker.frame = newFrame; self.tracker.center = newCenter; if (_selectedItemZoomScale != 1) { [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } } else if (self.trackerStyle == SPPageMenuTrackerStyleLineAttachment) { // 这种样式的计算比较复杂,有个很关键的技巧,就是参考progress分别为0、0.5、1时的临界值 // 原先的x值 CGFloat originX = fromButton.frame.origin.x+(fromButton.frame.size.width-(_trackerWidth ? _trackerWidth : fromButton.titleLabel.font.pointSize))*0.5; // 原先的宽度 CGFloat originW = _trackerWidth ? _trackerWidth : fromButton.titleLabel.font.pointSize; if (currentOffsetX - _beginOffsetX >= 0) { // 向左拖拽了 if (progress < 0.5) { newFrame.origin.x = originX; // x值保持不变 newFrame.size.width = originW + xDistance * progress * 2; } else { newFrame.origin.x = originX + xDistance * (progress-0.5) * 2; newFrame.size.width = originW + xDistance - xDistance * (progress-0.5) * 2; } } else { // 向右拖拽了 // 此时xDistance为负 if (progress < 0.5) { newFrame.origin.x = originX + xDistance * progress * 2; newFrame.size.width = originW - xDistance * progress * 2; } else { newFrame.origin.x = originX + xDistance; newFrame.size.width = originW - xDistance + xDistance * (progress-0.5) * 2; } } self.tracker.frame = newFrame; if (_selectedItemZoomScale != 1) { [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } } else if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || self.trackerStyle == SPPageMenuTrackerStyleNothing) { // 缩放文字 if (_selectedItemZoomScale != 1) { [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } } else if (self.trackerStyle == SPPageMenuTrackerStyleRoundedRect) { newCenter.x = fromButton.center.x + xDistance * progress; newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress + _itemPadding); self.tracker.frame = newFrame; self.tracker.center = newCenter; if (_selectedItemZoomScale != 1) { [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } } else { newCenter.x = fromButton.center.x + xDistance * progress; newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress + _itemPadding); self.tracker.frame = newFrame; self.tracker.center = newCenter; if (_selectedItemZoomScale != 1) { [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } } // 文字颜色渐变 if (self.needTextColorGradients) { [self colorGradientForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } } // 颜色渐变方法 - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton { // 获取 targetProgress CGFloat fromProgress = progress; // 获取 originalProgress CGFloat toProgress = 1 - fromProgress; CGFloat r = self.endR - self.startR; CGFloat g = self.endG - self.startG; CGFloat b = self.endB - self.startB; UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:1]; UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:1]; // 设置文字颜色渐变 [fromButton setTitleColor:fromColor forState:UIControlStateNormal]; [toButton setTitleColor:toColor forState:UIControlStateNormal]; } // 获取颜色的RGB值 - (void)getRGBComponents:(CGFloat [3])components forColor:(UIColor *)color { CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); unsigned char resultingPixel[4]; CGContextRef context = CGBitmapContextCreate(&resultingPixel, 1, 1, 8, 4, rgbColorSpace, 1); CGContextSetFillColorWithColor(context, [color CGColor]); CGContextFillRect(context, CGRectMake(0, 0, 1, 1)); CGContextRelease(context); CGColorSpaceRelease(rgbColorSpace); for (int component = 0; component < 3; component++) { components[component] = resultingPixel[component] / 255.0f; } } /// 开始颜色设置 - (void)setupStartColor:(UIColor *)color { CGFloat components[3]; [self getRGBComponents:components forColor:color]; self.startR = components[0]; self.startG = components[1]; self.startB = components[2]; } /// 结束颜色设置 - (void)setupEndColor:(UIColor *)color { CGFloat components[3]; [self getRGBComponents:components forColor:color]; self.endR = components[0]; self.endG = components[1]; self.endB = components[2]; } - (void)zoomForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton { CGFloat diff = _selectedItemZoomScale - 1; fromButton.transform = CGAffineTransformMakeScale((1 - progress) * diff + 1, (1 - progress) * diff + 1); toButton.transform = CGAffineTransformMakeScale(progress * diff + 1, progress * diff + 1); } #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == self.bridgeScrollView) { if ([keyPath isEqualToString:scrollViewContentOffset]) { // 当scrolllView滚动时,让跟踪器跟随scrollView滑动 [self prepareMoveTrackerFollowScrollView:self.bridgeScrollView]; } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } #pragma mark - setter - (void)setBridgeScrollView:(UIScrollView *)bridgeScrollView { _bridgeScrollView = bridgeScrollView; if (bridgeScrollView) { [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil]; } else { NSLog(@"你传了一个空的scrollView"); } } - (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle { _trackerStyle = trackerStyle; switch (trackerStyle) { case SPPageMenuTrackerStyleLine: case SPPageMenuTrackerStyleLineLongerThanItem: case SPPageMenuTrackerStyleLineAttachment: self.tracker.backgroundColor = _selectedItemTitleColor; break; case SPPageMenuTrackerStyleRoundedRect: case SPPageMenuTrackerStyleRect: self.tracker.backgroundColor = [UIColor redColor]; _selectedItemTitleColor = [UIColor whiteColor]; // _trackerHeight是默认有值的,所有样式都会按照事先询问_trackerHeight有没有值,如果有值则采用_trackerHeight,如果矩形或圆角矩形样式下也用_trackerHeight高度太小了,除非外界用户自己设置了_trackerHeight _trackerHeight = 0; break; case SPPageMenuTrackerStyleTextZoom: // 此样式下默认1.3 self.selectedItemZoomScale = 1.3; break; default: break; } } - (void)setBounces:(BOOL)bounces { _bounces = bounces; self.itemScrollView.bounces = bounces; } - (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal { _alwaysBounceHorizontal = alwaysBounceHorizontal; self.itemScrollView.alwaysBounceHorizontal = alwaysBounceHorizontal; } - (void)setTrackerWidth:(CGFloat)trackerWidth { _trackerWidth = trackerWidth; CGRect trackerRect = self.tracker.frame; trackerRect.size.width = trackerWidth; self.tracker.frame = trackerRect; CGPoint trackerCenter = self.tracker.center; trackerCenter.x = _selectedButton.center.x; self.tracker.center = trackerCenter; } - (void)setDividingLineHeight:(CGFloat)dividingLineHeight { _dividingLineHeight = dividingLineHeight; [self setNeedsLayout]; [self layoutIfNeeded]; } - (void)setSelectedItemZoomScale:(CGFloat)selectedItemZoomScale { _selectedItemZoomScale = selectedItemZoomScale; if (selectedItemZoomScale != 1) { _selectedButton.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, _selectedItemZoomScale); self.tracker.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, 1); } else { _selectedButton.transform = CGAffineTransformIdentity; self.tracker.transform = CGAffineTransformIdentity; } } - (void)setShowFuntionButton:(BOOL)showFuntionButton { _showFuntionButton = showFuntionButton; self.functionButton.hidden = !showFuntionButton; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } - (void)setFuntionButtonshadowOpacity:(CGFloat)funtionButtonshadowOpacity { _funtionButtonshadowOpacity = funtionButtonshadowOpacity; self.functionButton.layer.shadowOpacity = funtionButtonshadowOpacity; } - (void)setItemPadding:(CGFloat)itemPadding { _itemPadding = itemPadding; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } - (void)setItemTitleFont:(UIFont *)itemTitleFont { _itemTitleFont = itemTitleFont; _selectedItemTitleFont = itemTitleFont; _unSelectedItemTitleFont = itemTitleFont; for (SPPageMenuItem *button in self.buttons) { button.titleLabel.font = itemTitleFont; } [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } - (void)setUnSelectedItemTitleFont:(UIFont *)unSelectedItemTitleFont { _unSelectedItemTitleFont = unSelectedItemTitleFont; for (SPPageMenuItem *button in self.buttons) { if (button == _selectedButton) { continue; } button.titleLabel.font = unSelectedItemTitleFont; } [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } - (void)setSelectedItemTitleFont:(UIFont *)selectedItemTitleFont { _selectedItemTitleFont = selectedItemTitleFont; self.selectedButton.titleLabel.font = selectedItemTitleFont; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } - (void)setSelectedItemTitleColor:(UIColor *)selectedItemTitleColor { _selectedItemTitleColor = selectedItemTitleColor; [self setupStartColor:selectedItemTitleColor]; [self.selectedButton setTitleColor:selectedItemTitleColor forState:UIControlStateNormal]; } - (void)setUnSelectedItemTitleColor:(UIColor *)unSelectedItemTitleColor { _unSelectedItemTitleColor = unSelectedItemTitleColor; [self setupEndColor:unSelectedItemTitleColor]; for (SPPageMenuItem *button in self.buttons) { if (button == _selectedButton) { continue; // 跳过选中的那个button } [button setTitleColor:unSelectedItemTitleColor forState:UIControlStateNormal]; } } - (void)setSelectedItemIndex:(NSInteger)selectedItemIndex { _selectedItemIndex = selectedItemIndex; if (self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:selectedItemIndex]; [self buttonInPageMenuClicked:button]; } } - (void)setDelegate:(id)delegate { if (delegate == _delegate) {return;} _delegate = delegate; if (self.buttons.count) { SPPageMenuItem *button = [self.buttons objectAtIndex:_selectedItemIndex]; [self delegatePerformMethodWithFromIndex:button.tag-tagBaseValue toIndex:button.tag-tagBaseValue]; [self moveItemScrollViewWithSelectedButton:button]; } } - (void)setContentInset:(UIEdgeInsets)contentInset { _contentInset = contentInset; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } - (void)setPermutationWay:(SPPageMenuPermutationWay)permutationWay { _permutationWay = permutationWay; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } - (void)setCloseTrackerFollowingMode:(BOOL)closeTrackerFollowingMode { _closeTrackerFollowingMode = closeTrackerFollowingMode; if (closeTrackerFollowingMode) { self.trackerFollowingMode = SPPageMenuTrackerFollowingModeEnd; } else { self.trackerFollowingMode = SPPageMenuTrackerFollowingModeAlways; } } #pragma mark - getter - (NSArray *)items { if (!_items) { _items = [NSMutableArray array]; } return _items; } - (NSMutableArray *)buttons { if (!_buttons) { _buttons = [NSMutableArray array]; } return _buttons; } //竖起的line,分隔每个按钮 - (NSMutableArray *)hLineArray { if (!_hLineArray) { _hLineArray = [NSMutableArray array]; } return _hLineArray; } - (NSMutableDictionary *)setupWidths { if (!_setupWidths) { _setupWidths = [NSMutableDictionary dictionary]; } return _setupWidths; } - (UIImageView *)tracker { if (!_tracker) { _tracker = [[UIImageView alloc] init]; _tracker.layer.cornerRadius = _trackerHeight * 0.5; _tracker.layer.masksToBounds = YES; } return _tracker; } - (NSUInteger)numberOfItems { return self.items.count; } #pragma mark - 布局 - (void)layoutSubviews { [super layoutSubviews]; CGFloat backgroundViewX = self.bounds.origin.x+_contentInset.left; CGFloat backgroundViewY = self.bounds.origin.y+_contentInset.top; CGFloat backgroundViewW = self.bounds.size.width-(_contentInset.left+_contentInset.right); CGFloat backgroundViewH = self.bounds.size.height-(_contentInset.top+_contentInset.bottom); self.backgroundView.frame = CGRectMake(backgroundViewX, backgroundViewY, backgroundViewW, backgroundViewH); self.backgroundImageView.frame = self.backgroundView.bounds; CGFloat dividingLineW = self.bounds.size.width; CGFloat dividingLineH = (self.dividingLine.hidden || self.dividingLine.alpha < 0.01) ? 0 : _dividingLineHeight; CGFloat dividingLineX = 0; CGFloat dividingLineY = self.bounds.size.height-dividingLineH; self.dividingLine.frame = CGRectMake(dividingLineX, dividingLineY, dividingLineW, dividingLineH); CGFloat functionButtonH = backgroundViewH-dividingLineH; CGFloat functionButtonW = functionButtonH; CGFloat functionButtonX = backgroundViewW-functionButtonW; CGFloat functionButtonY = 0; self.functionButton.frame = CGRectMake(functionButtonX, functionButtonY, functionButtonW, functionButtonH); // 通过shadowPath设置功能按钮的单边阴影 if (self.funtionButtonshadowOpacity > 0) { self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath; } CGFloat itemScrollViewX = 0; CGFloat itemScrollViewY = 0; CGFloat itemScrollViewW = self.showFuntionButton ? backgroundViewW-functionButtonW : backgroundViewW; CGFloat itemScrollViewH = backgroundViewH-dividingLineH; self.itemScrollView.frame = CGRectMake(itemScrollViewX, itemScrollViewY, itemScrollViewW, itemScrollViewH); // 存储itemScrollViewH,目的是解决选中按钮缩放后高度变化了的问题,我们要让选中的按钮缩放之后,依然保持原始高度 _itemScrollViewH = itemScrollViewH; __block CGFloat buttonW = 0.0; __block CGFloat lastButtonMaxX = 0.0; CGFloat contentW = 0.0; // 内容宽 CGFloat contentW_sum = 0.0; // 所有文字宽度之和 NSMutableArray *buttonWidths = [NSMutableArray array]; // 提前计算每个按钮的宽度,目的是为了计算间距 for (int i= 0 ; i < self.buttons.count; i++) { SPPageMenuItem *button = self.buttons[i]; CGFloat textW; CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; if (button == _selectedButton) { textW = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_selectedItemTitleFont} context:nil].size.width; } else { textW = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_unSelectedItemTitleFont} context:nil].size.width; } // CGImageGetWidth获取的图片宽度是图片在@1x、@2x、@3x的位置上的实际宽度 // button.currentImage.size.width获取的宽度永远是@1x位置上的宽度,比如一张图片在@3x上的位置为300,那么button.currentImage.size.width就为100 CGFloat imageW = CGImageGetWidth(button.currentImage.CGImage); CGFloat imageH = CGImageGetHeight(button.currentImage.CGImage); CGFloat ratio = imageW / imageH; if (ratio >= 1) { // 宽大于高 if (imageH > itemScrollViewH) { // 按比例适应在button中 imageH = itemScrollViewH; imageW = imageH * ratio; } } if (button.currentTitle && !button.currentImage) { contentW = textW; } else if(button.currentImage && !button.currentTitle) { contentW = imageW; } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft)) { contentW = textW + imageW; } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) { contentW = MAX(textW, imageW); } if (setupButtonW) { contentW_sum += setupButtonW; [buttonWidths addObject:@(setupButtonW)]; } else { contentW_sum += contentW; [buttonWidths addObject:@(contentW)]; } } CGFloat diff = itemScrollViewW - contentW_sum; [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuItem *button, NSUInteger idx, BOOL * _Nonnull stop) { CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue]; if (self.permutationWay == SPPageMenuPermutationWayScrollAdaptContent) { buttonW = [buttonWidths[idx] floatValue]; if (idx == 0) { button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); } else { button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } else if (self.permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { // 求出外界设置的按钮宽度之和 CGFloat totalSetupButtonW = [[self.setupWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; // 如果该按钮外界设置了宽,则取外界设置的,如果外界没设置,则其余按钮等宽 buttonW = setupButtonW ? setupButtonW : (itemScrollViewW-self->_itemPadding*(self.buttons.count)-totalSetupButtonW)/(self.buttons.count-self.setupWidths.count); if (buttonW < 0) { // 按钮过多时,有可能会为负数 buttonW = 0; } if (idx == 0) { button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); } else { button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } else { self->_itemPadding = diff/self.buttons.count; buttonW = [buttonWidths[idx] floatValue]; if (idx == 0) { button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); } else { button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } lastButtonMaxX = CGRectGetMaxX(button.frame); if(button.imagePosition == SPItemImagePositionLeft){ //wcl - 自定义按钮分割线 if(idx != self.hLineArray.count - 1){ UIView *hLineView = [self.hLineArray objectAtIndex:idx]; hLineView.frame = CGRectMake(lastButtonMaxX - 1, 15, 1, 20); } } }]; // 如果selectedButton有缩放,走完上面代码selectedButton的frame会还原,这会导致文字显示不全问题,为了解决这个问题,这里将selectedButton的frame强制缩放 if (!CGAffineTransformEqualToTransform(self.selectedButton.transform, CGAffineTransformIdentity)) { CGRect selectedButtonRect = self.selectedButton.frame; selectedButtonRect.origin.y = selectedButtonRect.origin.y-(selectedButtonRect.size.height*_selectedItemZoomScale - selectedButtonRect.size.height)/2; selectedButtonRect.origin.x = selectedButtonRect.origin.x-((selectedButtonRect.size.width*_selectedItemZoomScale - selectedButtonRect.size.width)/2); selectedButtonRect.size = CGSizeMake(selectedButtonRect.size.width * _selectedItemZoomScale, selectedButtonRect.size.height*_selectedItemZoomScale); self.selectedButton.frame = selectedButtonRect; } [self resetSetupTrackerFrameWithSelectedButton:self.selectedButton]; self.itemScrollView.contentSize = CGSizeMake(lastButtonMaxX+_itemPadding*0.5, 0); if (self.translatesAutoresizingMaskIntoConstraints == NO) { [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } } - (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuItem *)selectedButton { CGFloat trackerX; CGFloat trackerY; CGFloat trackerW; CGFloat trackerH; CGFloat selectedButtonWidth = selectedButton.frame.size.width; switch (self.trackerStyle) { case SPPageMenuTrackerStyleLine: { trackerW = _trackerWidth ? _trackerWidth : selectedButtonWidth; trackerH = _trackerHeight; trackerX = selectedButton.frame.origin.x; trackerY = self.itemScrollView.bounds.size.height - trackerH; self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); } break; case SPPageMenuTrackerStyleLineLongerThanItem: { trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+(selectedButtonWidth ? _itemPadding : 0)); trackerH = _trackerHeight; trackerX = selectedButton.frame.origin.x; trackerY = self.itemScrollView.bounds.size.height - trackerH; self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); } break; case SPPageMenuTrackerStyleLineAttachment: { trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth ? selectedButton.titleLabel.font.pointSize : 0); // 没有自定义宽度就固定宽度为字体大小 trackerH = _trackerHeight; trackerX = selectedButton.frame.origin.x; trackerY = self.itemScrollView.bounds.size.height - trackerH; self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); } break; case SPPageMenuTrackerStyleRect: { trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+(selectedButtonWidth ? _itemPadding : 0)); trackerH = _trackerHeight ? _trackerHeight : (selectedButton.frame.size.height); trackerX = selectedButton.frame.origin.x; trackerY = (_itemScrollViewH-trackerH)*0.5; self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); self.tracker.layer.cornerRadius = 0; } break; case SPPageMenuTrackerStyleRoundedRect: { trackerH = _trackerHeight ? _trackerHeight : (_itemTitleFont.lineHeight+10); trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+_itemPadding); trackerX = selectedButton.frame.origin.x; trackerY = (_itemScrollViewH-trackerH)*0.5; self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); self.tracker.layer.cornerRadius = MIN(trackerW, trackerH)*0.5; self.tracker.layer.masksToBounds = YES; } break; default: break; } CGPoint trackerCenter = self.tracker.center; trackerCenter.x = selectedButton.center.x; self.tracker.center = trackerCenter; } - (void)dealloc { [self.bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; } @end #pragma clang diagnostic pop