From eac88584e708f00d4a2d7fd23d275bbb1b2705f3 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 1 Jun 2018 14:14:45 +0900 Subject: [PATCH 01/22] Add .gitignore --- .gitignore | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5ecfe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output From aa442532dbed70511ec4cc1801ceba24109db8e6 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 1 Jun 2018 14:14:54 +0900 Subject: [PATCH 02/22] Update for Xcode 9.4 --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Example/SPPageMenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + From f56569fd1b3d24f5fa56e707a50ee355f89ce547 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 1 Jun 2018 14:17:08 +0900 Subject: [PATCH 03/22] Allow setTrackerStyle: after init --- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index aed06ca..d2c6473 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -111,6 +111,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。之所以高度和半径以一个方法而不是2个属性的形式来设置,是因为不想提供太多的属性,其次跟踪器的高度一般用默认高度就好,自定义高度的情况并不会太多。另外,如果你想用默认高度,但是又不想要圆角半径,你可以设置trackerHeight为3,cornerRadius为0,这是去除默认半径的唯一办法 - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; +// 修改跟踪器样式 +- (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle; + // 插入item,插入和删除操作时,如果itemIndex超过了了items的个数,则不做任何操作 - (void)insertItemWithTitle:(nullable NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; - (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; From 9069a848bc073f6e0219bb966b32814419f570aa Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 1 Jun 2018 14:35:50 +0900 Subject: [PATCH 04/22] Add setTrackerStyle: method in .h --- SPPageMenu/SPPageMenu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index aed06ca..d2c6473 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -111,6 +111,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。之所以高度和半径以一个方法而不是2个属性的形式来设置,是因为不想提供太多的属性,其次跟踪器的高度一般用默认高度就好,自定义高度的情况并不会太多。另外,如果你想用默认高度,但是又不想要圆角半径,你可以设置trackerHeight为3,cornerRadius为0,这是去除默认半径的唯一办法 - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; +// 修改跟踪器样式 +- (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle; + // 插入item,插入和删除操作时,如果itemIndex超过了了items的个数,则不做任何操作 - (void)insertItemWithTitle:(nullable NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; - (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; From cbe1bcf721ec7c6739a3e507064536d84a3d5f3b Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 1 Jun 2018 16:29:07 +0900 Subject: [PATCH 05/22] Expose shadowLine --- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 3 +++ Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index d2c6473..03837f2 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -105,6 +105,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { /** 是否显示功能按钮(功能按钮显示在最右侧),默认为NO */ @property (nonatomic, assign) BOOL showFuntionButton; +/** 分割FunctionButton的分割线 */ +@property (nonatomic, weak) CALayer *shadowLine; + /** 代理 */ @property (nonatomic, weak) id delegate; diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index 89ea250..c5746a9 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -198,7 +198,6 @@ @interface SPPageMenu() @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) UIScrollView *itemScrollView; @property (nonatomic, weak) SPItem *functionButton; -@property (nonatomic, weak) CALayer *shadowLine; @property (nonatomic, strong) NSMutableArray *buttons; @property (nonatomic, strong) SPItem *selectedButton; @property (nonatomic, strong) NSMutableDictionary *setupWidths; From ef2e9cd1fcb230db6ea4da0bbd6bd001f13397c6 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 1 Jun 2018 16:39:42 +0900 Subject: [PATCH 06/22] Expose shadowLine in .h --- SPPageMenu/SPPageMenu.h | 3 +++ SPPageMenu/SPPageMenu.m | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index d2c6473..03837f2 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -105,6 +105,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { /** 是否显示功能按钮(功能按钮显示在最右侧),默认为NO */ @property (nonatomic, assign) BOOL showFuntionButton; +/** 分割FunctionButton的分割线 */ +@property (nonatomic, weak) CALayer *shadowLine; + /** 代理 */ @property (nonatomic, weak) id delegate; diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index 89ea250..c5746a9 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -198,7 +198,6 @@ @interface SPPageMenu() @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) UIScrollView *itemScrollView; @property (nonatomic, weak) SPItem *functionButton; -@property (nonatomic, weak) CALayer *shadowLine; @property (nonatomic, strong) NSMutableArray *buttons; @property (nonatomic, strong) SPItem *selectedButton; @property (nonatomic, strong) NSMutableDictionary *setupWidths; From 54457ed6b77e55bc5a203788d5573c14e62778f3 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 1 Jun 2018 16:40:13 +0900 Subject: [PATCH 07/22] Fix Xcode warning --- Example/SPPageMenu.xcodeproj/project.pbxproj | 6 +++++- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Example/SPPageMenu.xcodeproj/project.pbxproj b/Example/SPPageMenu.xcodeproj/project.pbxproj index 2fa27cb..2a07398 100644 --- a/Example/SPPageMenu.xcodeproj/project.pbxproj +++ b/Example/SPPageMenu.xcodeproj/project.pbxproj @@ -273,7 +273,7 @@ 091A686A1FA1CAC100DAA561 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0920; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = iDress; TargetAttributes = { 091A68711FA1CAC100DAA561 = { @@ -428,6 +428,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -435,6 +436,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -481,6 +483,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -488,6 +491,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index c5746a9..342ecef 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -1113,7 +1113,7 @@ - (void)layoutSubviews { for (int i= 0 ; i < self.buttons.count; i++) { SPItem *button = self.buttons[i]; - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%zd",i]] floatValue]; + CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; CGFloat textW = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_itemTitleFont} context:nil].size.width; CGFloat imageW = button.currentImage.size.width; if (button.currentTitle && !button.currentImage) { From 939d08609f01bc2a8c5ba753ff5d0056a58defb3 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Wed, 6 Jun 2018 11:56:17 +0900 Subject: [PATCH 08/22] Set scrollsToTop = NO to avoid preventing scrollsToTop of other scrollView --- SPPageMenu/SPPageMenu.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index c5746a9..5a1cdc0 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -550,6 +550,7 @@ - (void)initialize { UIScrollView *itemScrollView = [[UIScrollView alloc] init]; itemScrollView.showsVerticalScrollIndicator = NO; itemScrollView.showsHorizontalScrollIndicator = NO; + itemScrollView.scrollsToTop = NO; [backgroundView addSubview:itemScrollView]; _itemScrollView = itemScrollView; From dc1bf9510cb28b6ec65b678f8cac27a4f4a536be Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Thu, 6 Sep 2018 02:14:32 +0900 Subject: [PATCH 09/22] Remove duplicated delegate definition --- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 2 -- SPPageMenu/SPPageMenu.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index 7e7d522..3433ead 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -115,8 +115,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic) CGFloat selectedItemZoomScale; @property (nonatomic, assign) BOOL needTextColorGradients; // 是否需要文字渐变,默认为YES -@property (nonatomic, weak) id delegate; - // 修改跟踪器样式 - (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle; diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index 7e7d522..3433ead 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -115,8 +115,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic) CGFloat selectedItemZoomScale; @property (nonatomic, assign) BOOL needTextColorGradients; // 是否需要文字渐变,默认为YES -@property (nonatomic, weak) id delegate; - // 修改跟踪器样式 - (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle; From 34ee29f07bc23b12415f0fc2f69e136c91d214a3 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 7 Sep 2018 17:53:19 +0900 Subject: [PATCH 10/22] Remove shadowLine, expose funtionButtonShadowColor, fix typo. --- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 6 ++---- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 15 ++++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index 3433ead..ef8589e 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -101,9 +101,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 跟踪器的跟踪模式 @property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; -/** 分割FunctionButton的分割线 */ -@property (nonatomic, weak) CALayer *shadowLine; - /** 代理 */ @property (nonatomic, weak) id delegate; @@ -158,7 +155,8 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, assign) BOOL showFuntionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat funtionButtonshadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, assign) CGFloat funtionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, strong) UIColor *funtionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 /** * 同时为functionButton设置标题和图片 diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index 46b3655..6ac966e 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -677,7 +677,8 @@ - (void)initialize { _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; _showFuntionButton = NO; - _funtionButtonshadowOpacity = 0.5; + _funtionButtonShadowOpacity = 0.5; + _funtionButtonShadowColor = [UIColor blackColor]; _selectedItemZoomScale = 1; _needTextColorGradients = YES; @@ -721,10 +722,10 @@ - (void)setupSubViews { [functionButton setTitle:@"+" forState:UIControlStateNormal]; [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - functionButton.layer.shadowColor = [UIColor blackColor].CGColor; + functionButton.layer.shadowColor = _funtionButtonShadowColor.CGColor; functionButton.layer.shadowOffset = CGSizeMake(0, 0); functionButton.layer.shadowRadius = 2; - functionButton.layer.shadowOpacity = _funtionButtonshadowOpacity; // 默认是0,为0的话不会显示阴影 + functionButton.layer.shadowOpacity = _funtionButtonShadowOpacity; // 默认是0,为0的话不会显示阴影 functionButton.hidden = !_showFuntionButton; [backgroundView addSubview:functionButton]; _functionButton = functionButton; @@ -1158,9 +1159,9 @@ - (void)setShowFuntionButton:(BOOL)showFuntionButton { [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } -- (void)setFuntionButtonshadowOpacity:(CGFloat)funtionButtonshadowOpacity { - _funtionButtonshadowOpacity = funtionButtonshadowOpacity; - self.functionButton.layer.shadowOpacity = funtionButtonshadowOpacity; +- (void)setFuntionButtonShadowOpacity:(CGFloat)funtionButtonShadowOpacity { + _funtionButtonShadowOpacity = funtionButtonShadowOpacity; + self.functionButton.layer.shadowOpacity = funtionButtonShadowOpacity; } - (void)setItemPadding:(CGFloat)itemPadding { @@ -1331,7 +1332,7 @@ - (void)layoutSubviews { CGFloat functionButtonY = 0; self.functionButton.frame = CGRectMake(functionButtonX, functionButtonY, functionButtonW, functionButtonH); // 通过shadowPath设置功能按钮的单边阴影 - if (self.funtionButtonshadowOpacity > 0) { + if (self.funtionButtonShadowOpacity > 0) { self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath; } From 900c7189090769a9023b4c9e548b3aeeb1c4b133 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 7 Sep 2018 17:56:23 +0900 Subject: [PATCH 11/22] Fix in duplicated files --- SPPageMenu/SPPageMenu.h | 6 +- SPPageMenu/SPPageMenu.m | 121 ++++++++++++++++++++-------------------- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index 3433ead..ef8589e 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -101,9 +101,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 跟踪器的跟踪模式 @property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; -/** 分割FunctionButton的分割线 */ -@property (nonatomic, weak) CALayer *shadowLine; - /** 代理 */ @property (nonatomic, weak) id delegate; @@ -158,7 +155,8 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, assign) BOOL showFuntionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat funtionButtonshadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, assign) CGFloat funtionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, strong) UIColor *funtionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 /** * 同时为functionButton设置标题和图片 diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index 46b3655..8292faf 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -261,9 +261,9 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex NSAssert(selectedItemIndex <= items.count-1, @"selectedItemIndex 大于了 %ld",items.count-1); _items = items.copy; _selectedItemIndex = selectedItemIndex; - + self.insert = NO; - + for (int i = 0; i < items.count; i++) { id object = items[i]; NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]], @"items中的元素只能是NSString或UIImage类型"); @@ -272,7 +272,7 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex [self setNeedsLayout]; [self layoutIfNeeded]; - + if (self.buttons.count) { // 默认选中selectedItemIndex对应的按钮 SPPageMenuItem *selectedButton = [self.buttons objectAtIndex:selectedItemIndex]; @@ -346,7 +346,7 @@ - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { } else { [self setNeedsLayout]; } - + } - (void)removeAllItems { @@ -354,19 +354,19 @@ - (void)removeAllItems { [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]; } @@ -500,7 +500,7 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag 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; @@ -515,7 +515,7 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } - + [self setNeedsLayout]; [self layoutIfNeeded]; } @@ -537,7 +537,7 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag [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; @@ -552,7 +552,7 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } - + [self setNeedsLayout]; [self layoutIfNeeded]; } @@ -577,18 +577,18 @@ - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes } - (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) { @@ -623,9 +623,9 @@ - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { [self.itemScrollView insertSubview:button atIndex:index]; } [self.buttons insertObject:button atIndex:index]; - + // setNeedsLayout会标记为需要刷新,layoutIfNeeded只有在有标记的情况下才会立即调用layoutSubViews,当然标记为刷新并非只有调用setNeedsLayout,如frame改变,addSubView等都会标记为刷新 - + if (self.insert && animated) { // 是插入的新按钮,且需要动画 // 取出上一个按钮 SPPageMenuItem *lastButton; @@ -665,7 +665,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { } - (void)initialize { - + _itemPadding = 30; _selectedItemTitleColor = [UIColor redColor]; _unSelectedItemTitleColor = [UIColor blackColor]; @@ -677,10 +677,11 @@ - (void)initialize { _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; _showFuntionButton = NO; - _funtionButtonshadowOpacity = 0.5; + _funtionButtonShadowOpacity = 0.5; + _funtionButtonShadowColor = [UIColor blackColor]; _selectedItemZoomScale = 1; _needTextColorGradients = YES; - + [self setupSubViews]; } @@ -694,16 +695,16 @@ - (void)setupSubViews { }; [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; @@ -715,16 +716,16 @@ - (void)setupSubViews { } [backgroundView addSubview:itemScrollView]; _itemScrollView = itemScrollView; - + SPPageMenuItem *functionButton = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; functionButton.backgroundColor = [UIColor whiteColor]; [functionButton setTitle:@"+" forState:UIControlStateNormal]; [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - functionButton.layer.shadowColor = [UIColor blackColor].CGColor; + functionButton.layer.shadowColor = _funtionButtonShadowColor.CGColor; functionButton.layer.shadowOffset = CGSizeMake(0, 0); functionButton.layer.shadowRadius = 2; - functionButton.layer.shadowOpacity = _funtionButtonshadowOpacity; // 默认是0,为0的话不会显示阴影 + functionButton.layer.shadowOpacity = _funtionButtonShadowOpacity; // 默认是0,为0的话不会显示阴影 functionButton.hidden = !_showFuntionButton; [backgroundView addSubview:functionButton]; _functionButton = functionButton; @@ -742,10 +743,10 @@ - (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { [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页 @@ -800,7 +801,7 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { 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 @@ -813,7 +814,7 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { } [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES]; - + } // 移动跟踪器 @@ -933,12 +934,12 @@ - (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex 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) { @@ -977,7 +978,7 @@ - (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex if (_selectedItemZoomScale != 1) { [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } - + } else if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || self.trackerStyle == SPPageMenuTrackerStyleNothing) { // 缩放文字 if (_selectedItemZoomScale != 1) { @@ -1012,13 +1013,13 @@ - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton 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]; @@ -1081,7 +1082,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N - (void)setBridgeScrollView:(UIScrollView *)bridgeScrollView { _bridgeScrollView = bridgeScrollView; if (bridgeScrollView) { - + [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil]; } else { NSLog(@"你传了一个空的scrollView"); @@ -1158,9 +1159,9 @@ - (void)setShowFuntionButton:(BOOL)showFuntionButton { [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } -- (void)setFuntionButtonshadowOpacity:(CGFloat)funtionButtonshadowOpacity { - _funtionButtonshadowOpacity = funtionButtonshadowOpacity; - self.functionButton.layer.shadowOpacity = funtionButtonshadowOpacity; +- (void)setFuntionButtonShadowOpacity:(CGFloat)funtionButtonShadowOpacity { + _funtionButtonShadowOpacity = funtionButtonShadowOpacity; + self.functionButton.layer.shadowOpacity = funtionButtonShadowOpacity; } - (void)setItemPadding:(CGFloat)itemPadding { @@ -1277,16 +1278,16 @@ - (NSArray *)items { } - (NSMutableArray *)buttons { - + if (!_buttons) { _buttons = [NSMutableArray array]; - + } return _buttons; } - (NSMutableDictionary *)setupWidths { - + if (!_setupWidths) { _setupWidths = [NSMutableDictionary dictionary]; } @@ -1294,7 +1295,7 @@ - (NSMutableDictionary *)setupWidths { } - (UIImageView *)tracker { - + if (!_tracker) { _tracker = [[UIImageView alloc] init]; _tracker.layer.cornerRadius = _trackerHeight * 0.5; @@ -1311,14 +1312,14 @@ - (NSUInteger)numberOfItems { - (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; @@ -1331,7 +1332,7 @@ - (void)layoutSubviews { CGFloat functionButtonY = 0; self.functionButton.frame = CGRectMake(functionButtonX, functionButtonY, functionButtonW, functionButtonH); // 通过shadowPath设置功能按钮的单边阴影 - if (self.funtionButtonshadowOpacity > 0) { + if (self.funtionButtonShadowOpacity > 0) { self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath; } @@ -1340,13 +1341,13 @@ - (void)layoutSubviews { 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]; @@ -1390,7 +1391,7 @@ - (void)layoutSubviews { } } 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) { @@ -1414,7 +1415,7 @@ - (void)layoutSubviews { } else { button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); } - + } else { self->_itemPadding = diff/self.buttons.count; buttonW = [buttonWidths[idx] floatValue]; @@ -1426,7 +1427,7 @@ - (void)layoutSubviews { } lastButtonMaxX = CGRectGetMaxX(button.frame); }]; - + // 如果selectedButton有缩放,走完上面代码selectedButton的frame会还原,这会导致文字显示不全问题,为了解决这个问题,这里将selectedButton的frame强制缩放 if (!CGAffineTransformEqualToTransform(self.selectedButton.transform, CGAffineTransformIdentity)) { CGRect selectedButtonRect = self.selectedButton.frame; @@ -1437,24 +1438,24 @@ - (void)layoutSubviews { } [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: { @@ -1508,7 +1509,7 @@ - (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuItem *)selectedButto default: break; } - + CGPoint trackerCenter = self.tracker.center; trackerCenter.x = selectedButton.center.x; self.tracker.center = trackerCenter; From 02dd0e7837d9b3ba07eddfa111807d67a1cf63af Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Wed, 26 Sep 2018 17:28:32 +0900 Subject: [PATCH 12/22] Fix typo --- Example/SPPageMenu/ParentViewController.m | 4 ++-- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 6 ++--- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 28 +++++++++++----------- SPPageMenu/SPPageMenu.h | 6 ++--- SPPageMenu/SPPageMenu.m | 28 +++++++++++----------- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Example/SPPageMenu/ParentViewController.m b/Example/SPPageMenu/ParentViewController.m index 9db1223..c58eefe 100644 --- a/Example/SPPageMenu/ParentViewController.m +++ b/Example/SPPageMenu/ParentViewController.m @@ -305,7 +305,7 @@ - (void)test15 { SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleLine]; // 传递数组,默认选中第2个 [pageMenu setItems:self.dataArr selectedItemIndex:0]; - pageMenu.showFuntionButton = YES; + pageMenu.showFunctionButton = YES; // 设置代理 pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 @@ -325,7 +325,7 @@ - (void)test16 { // 同时设置图片和文字,如果只想要文字,image传nil,如果只想要图片,title传nil,imagePosition和ratio传0即可 [pageMenu setFunctionButtonTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop imageRatio:0.5 imageTitleSpace:0 forState:UIControlStateNormal]; [pageMenu setFunctionButtonTitleTextAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} forState:UIControlStateNormal]; - pageMenu.showFuntionButton = YES; + pageMenu.showFunctionButton = YES; // 设置代理 pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index ef8589e..474aaac 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -154,9 +154,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex; -@property (nonatomic, assign) BOOL showFuntionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat funtionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 -@property (nonatomic, strong) UIColor *funtionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 +@property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO +@property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 /** * 同时为functionButton设置标题和图片 diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index 6ac966e..185b198 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -676,9 +676,9 @@ - (void)initialize { _dividingLineHeight = 1 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; - _showFuntionButton = NO; - _funtionButtonShadowOpacity = 0.5; - _funtionButtonShadowColor = [UIColor blackColor]; + _showFunctionButton = NO; + _functionButtonShadowOpacity = 0.5; + _functionButtonShadowColor = [UIColor blackColor]; _selectedItemZoomScale = 1; _needTextColorGradients = YES; @@ -722,11 +722,11 @@ - (void)setupSubViews { [functionButton setTitle:@"+" forState:UIControlStateNormal]; [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - functionButton.layer.shadowColor = _funtionButtonShadowColor.CGColor; + functionButton.layer.shadowColor = _functionButtonShadowColor.CGColor; functionButton.layer.shadowOffset = CGSizeMake(0, 0); functionButton.layer.shadowRadius = 2; - functionButton.layer.shadowOpacity = _funtionButtonShadowOpacity; // 默认是0,为0的话不会显示阴影 - functionButton.hidden = !_showFuntionButton; + functionButton.layer.shadowOpacity = _functionButtonShadowOpacity; // 默认是0,为0的话不会显示阴影 + functionButton.hidden = !_showFunctionButton; [backgroundView addSubview:functionButton]; _functionButton = functionButton; } @@ -1150,18 +1150,18 @@ - (void)setSelectedItemZoomScale:(CGFloat)selectedItemZoomScale { } } -- (void)setShowFuntionButton:(BOOL)showFuntionButton { - _showFuntionButton = showFuntionButton; - self.functionButton.hidden = !showFuntionButton; +- (void)setShowFunctionButton:(BOOL)showFunctionButton { + _showFunctionButton = showFunctionButton; + self.functionButton.hidden = !showFunctionButton; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } -- (void)setFuntionButtonShadowOpacity:(CGFloat)funtionButtonShadowOpacity { - _funtionButtonShadowOpacity = funtionButtonShadowOpacity; - self.functionButton.layer.shadowOpacity = funtionButtonShadowOpacity; +- (void)setFunctionButtonShadowOpacity:(CGFloat)functionButtonShadowOpacity { + _functionButtonShadowOpacity = functionButtonShadowOpacity; + self.functionButton.layer.shadowOpacity = functionButtonShadowOpacity; } - (void)setItemPadding:(CGFloat)itemPadding { @@ -1332,13 +1332,13 @@ - (void)layoutSubviews { CGFloat functionButtonY = 0; self.functionButton.frame = CGRectMake(functionButtonX, functionButtonY, functionButtonW, functionButtonH); // 通过shadowPath设置功能按钮的单边阴影 - if (self.funtionButtonShadowOpacity > 0) { + if (self.functionButtonShadowOpacity > 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 itemScrollViewW = self.showFunctionButton ? backgroundViewW-functionButtonW : backgroundViewW; CGFloat itemScrollViewH = backgroundViewH-dividingLineH; self.itemScrollView.frame = CGRectMake(itemScrollViewX, itemScrollViewY, itemScrollViewW, itemScrollViewH); diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index ef8589e..474aaac 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -154,9 +154,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex; -@property (nonatomic, assign) BOOL showFuntionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat funtionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 -@property (nonatomic, strong) UIColor *funtionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 +@property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO +@property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 /** * 同时为functionButton设置标题和图片 diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index 8292faf..e1faa61 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -676,9 +676,9 @@ - (void)initialize { _dividingLineHeight = 1 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; - _showFuntionButton = NO; - _funtionButtonShadowOpacity = 0.5; - _funtionButtonShadowColor = [UIColor blackColor]; + _showFunctionButton = NO; + _functionButtonShadowOpacity = 0.5; + _functionButtonShadowColor = [UIColor blackColor]; _selectedItemZoomScale = 1; _needTextColorGradients = YES; @@ -722,11 +722,11 @@ - (void)setupSubViews { [functionButton setTitle:@"+" forState:UIControlStateNormal]; [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - functionButton.layer.shadowColor = _funtionButtonShadowColor.CGColor; + functionButton.layer.shadowColor = _functionButtonShadowColor.CGColor; functionButton.layer.shadowOffset = CGSizeMake(0, 0); functionButton.layer.shadowRadius = 2; - functionButton.layer.shadowOpacity = _funtionButtonShadowOpacity; // 默认是0,为0的话不会显示阴影 - functionButton.hidden = !_showFuntionButton; + functionButton.layer.shadowOpacity = _functionButtonShadowOpacity; // 默认是0,为0的话不会显示阴影 + functionButton.hidden = !_showFunctionButton; [backgroundView addSubview:functionButton]; _functionButton = functionButton; } @@ -1150,18 +1150,18 @@ - (void)setSelectedItemZoomScale:(CGFloat)selectedItemZoomScale { } } -- (void)setShowFuntionButton:(BOOL)showFuntionButton { - _showFuntionButton = showFuntionButton; - self.functionButton.hidden = !showFuntionButton; +- (void)setShowFunctionButton:(BOOL)showFunctionButton { + _showFunctionButton = showFunctionButton; + self.functionButton.hidden = !showFunctionButton; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } -- (void)setFuntionButtonShadowOpacity:(CGFloat)funtionButtonShadowOpacity { - _funtionButtonShadowOpacity = funtionButtonShadowOpacity; - self.functionButton.layer.shadowOpacity = funtionButtonShadowOpacity; +- (void)setFunctionButtonShadowOpacity:(CGFloat)functionButtonShadowOpacity { + _functionButtonShadowOpacity = functionButtonShadowOpacity; + self.functionButton.layer.shadowOpacity = functionButtonShadowOpacity; } - (void)setItemPadding:(CGFloat)itemPadding { @@ -1332,13 +1332,13 @@ - (void)layoutSubviews { CGFloat functionButtonY = 0; self.functionButton.frame = CGRectMake(functionButtonX, functionButtonY, functionButtonW, functionButtonH); // 通过shadowPath设置功能按钮的单边阴影 - if (self.funtionButtonShadowOpacity > 0) { + if (self.functionButtonShadowOpacity > 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 itemScrollViewW = self.showFunctionButton ? backgroundViewW-functionButtonW : backgroundViewW; CGFloat itemScrollViewH = backgroundViewH-dividingLineH; self.itemScrollView.frame = CGRectMake(itemScrollViewX, itemScrollViewY, itemScrollViewW, itemScrollViewH); From 8f8ac3d4b2355df647926b384e8f53181dc09d29 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Tue, 30 Oct 2018 22:31:50 +0900 Subject: [PATCH 13/22] Merge remote-tracking branch 'upstream/master' --- Example/SPPageMenu.xcodeproj/project.pbxproj | 14 + .../UserInterfaceState.xcuserstate | Bin 61630 -> 34667 bytes Example/SPPageMenu/Info.plist | 2 +- Example/SPPageMenu/JSBadgeView/JSBadgeView.h | 100 ++++ Example/SPPageMenu/JSBadgeView/JSBadgeView.m | 427 ++++++++++++++++++ Example/SPPageMenu/ParentViewController.m | 50 +- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 4 +- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 9 +- Example/SPPageMenu/ViewController.m | 2 +- README.md | 9 + SPPageMenu.podspec | 2 +- SPPageMenu/SPPageMenu.h | 4 +- SPPageMenu/SPPageMenu.m | 7 + 13 files changed, 619 insertions(+), 11 deletions(-) create mode 100755 Example/SPPageMenu/JSBadgeView/JSBadgeView.h create mode 100755 Example/SPPageMenu/JSBadgeView/JSBadgeView.m diff --git a/Example/SPPageMenu.xcodeproj/project.pbxproj b/Example/SPPageMenu.xcodeproj/project.pbxproj index 2148bd7..c78f821 100644 --- a/Example/SPPageMenu.xcodeproj/project.pbxproj +++ b/Example/SPPageMenu.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 095E9C211FA6F8CC0097A889 /* ThidViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 095E9C171FA6F8CC0097A889 /* ThidViewController.m */; }; 095E9C291FA6F92D0097A889 /* SPPageMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 095E9C281FA6F92D0097A889 /* SPPageMenu.m */; }; 6A8808C1213D361D00C3553F /* mateor.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6A8808BF213D361C00C3553F /* mateor.jpg */; }; + 6A9270122179BCF400831045 /* JSBadgeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A9270102179BCF400831045 /* JSBadgeView.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -86,6 +87,8 @@ 095E9C271FA6F92D0097A889 /* SPPageMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPageMenu.h; sourceTree = ""; }; 095E9C281FA6F92D0097A889 /* SPPageMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPageMenu.m; sourceTree = ""; }; 6A8808BF213D361C00C3553F /* mateor.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = mateor.jpg; sourceTree = ""; }; + 6A9270102179BCF400831045 /* JSBadgeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSBadgeView.m; sourceTree = ""; }; + 6A9270112179BCF400831045 /* JSBadgeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBadgeView.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -143,6 +146,7 @@ 091229F81FA5F13000AEE295 /* ParentViewController.m */, 095E9C021FA6F8CC0097A889 /* ChildViewControllers */, DC317C5E213C0BB9000F4159 /* Images */, + 6A92700F2179BCF400831045 /* JSBadgeView */, 091A687E1FA1CAC100DAA561 /* Main.storyboard */, 091A68811FA1CAC100DAA561 /* Assets.xcassets */, 091A68831FA1CAC100DAA561 /* LaunchScreen.storyboard */, @@ -214,6 +218,15 @@ path = SPPageMenu; sourceTree = ""; }; + 6A92700F2179BCF400831045 /* JSBadgeView */ = { + isa = PBXGroup; + children = ( + 6A9270112179BCF400831045 /* JSBadgeView.h */, + 6A9270102179BCF400831045 /* JSBadgeView.m */, + ); + path = JSBadgeView; + sourceTree = ""; + }; DC317C5E213C0BB9000F4159 /* Images */ = { isa = PBXGroup; children = ( @@ -363,6 +376,7 @@ 095E9C1D1FA6F8CC0097A889 /* SecondViewController.m in Sources */, 095E9C1B1FA6F8CC0097A889 /* FiveViewController.m in Sources */, 095E9C191FA6F8CC0097A889 /* EightViewController.m in Sources */, + 6A9270122179BCF400831045 /* JSBadgeView.m in Sources */, 095E9C181FA6F8CC0097A889 /* BaseViewController.m in Sources */, 095E9C291FA6F92D0097A889 /* SPPageMenu.m in Sources */, 091229F91FA5F13000AEE295 /* ParentViewController.m in Sources */, diff --git a/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/devlop1.xcuserdatad/UserInterfaceState.xcuserstate b/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/devlop1.xcuserdatad/UserInterfaceState.xcuserstate index 1c3eae1507d7190076aabafbaa87a14260e034dc..a9fe1ce976fddda9a24e8808e855166b812a648f 100644 GIT binary patch delta 17085 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 diff --git a/Example/SPPageMenu/Info.plist b/Example/SPPageMenu/Info.plist index 878aa13..4604850 100644 --- a/Example/SPPageMenu/Info.plist +++ b/Example/SPPageMenu/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.4.0 + 3.4.1 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/Example/SPPageMenu/JSBadgeView/JSBadgeView.h b/Example/SPPageMenu/JSBadgeView/JSBadgeView.h new file mode 100755 index 0000000..8128e9d --- /dev/null +++ b/Example/SPPageMenu/JSBadgeView/JSBadgeView.h @@ -0,0 +1,100 @@ +/* +Copyright (c) 2013 Javier Soto. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#import + +typedef NS_ENUM(NSUInteger, JSBadgeViewAlignment) +{ + JSBadgeViewAlignmentTopLeft = 1, + JSBadgeViewAlignmentTopRight, + JSBadgeViewAlignmentTopCenter, + JSBadgeViewAlignmentCenterLeft, + JSBadgeViewAlignmentCenterRight, + JSBadgeViewAlignmentBottomLeft, + JSBadgeViewAlignmentBottomRight, + JSBadgeViewAlignmentBottomCenter, + JSBadgeViewAlignmentCenter +}; + +@interface JSBadgeView : UIView + +@property (nonatomic, copy) NSString *badgeText; + +#pragma mark - Customization + +@property (nonatomic, assign) JSBadgeViewAlignment badgeAlignment UI_APPEARANCE_SELECTOR; + +@property (nonatomic, strong) UIColor *badgeTextColor UI_APPEARANCE_SELECTOR; +@property (nonatomic, assign) CGSize badgeTextShadowOffset UI_APPEARANCE_SELECTOR; +@property (nonatomic, strong) UIColor *badgeTextShadowColor UI_APPEARANCE_SELECTOR; + +@property (nonatomic, strong) UIFont *badgeTextFont UI_APPEARANCE_SELECTOR; + +@property (nonatomic, strong) UIColor *badgeBackgroundColor UI_APPEARANCE_SELECTOR; + +/** + * Color of the overlay circle at the top. Default is semi-transparent white. + */ +@property (nonatomic, strong) UIColor *badgeOverlayColor UI_APPEARANCE_SELECTOR; + +/** + * Color of the badge shadow. Default is semi-transparent black. + */ +@property (nonatomic, strong) UIColor *badgeShadowColor UI_APPEARANCE_SELECTOR; + +/** + * Offset of the badge shadow. Default is 3.0 points down. + */ +@property (nonatomic, assign) CGSize badgeShadowSize UI_APPEARANCE_SELECTOR; + +/** + * Width of the circle around the badge. Default is 2.0 points. + */ +@property (nonatomic, assign) CGFloat badgeStrokeWidth UI_APPEARANCE_SELECTOR; + +/** + * Color of the circle around the badge. Default is white. + */ +@property (nonatomic, strong) UIColor *badgeStrokeColor UI_APPEARANCE_SELECTOR; + +/** + * Allows to shift the badge by x and y points. + */ +@property (nonatomic, assign) CGPoint badgePositionAdjustment UI_APPEARANCE_SELECTOR; + +/** + * You can use this to position the view if you're drawing it using drawRect instead of `-addSubview:` + * (optional) If not provided, the superview frame is used. + */ +@property (nonatomic, assign) CGRect frameToPositionInRelationWith UI_APPEARANCE_SELECTOR; + +/** + * The minimum width of a badge circle. We need this to avoid elipse shapes when using small fonts. + */ +@property (nonatomic, assign) CGFloat badgeMinWidth UI_APPEARANCE_SELECTOR; + +/** + * Optionally init using this method to have the badge automatically added to another view. + */ +- (id)initWithParentView:(UIView *)parentView alignment:(JSBadgeViewAlignment)alignment; + +@end diff --git a/Example/SPPageMenu/JSBadgeView/JSBadgeView.m b/Example/SPPageMenu/JSBadgeView/JSBadgeView.m new file mode 100755 index 0000000..e3c5366 --- /dev/null +++ b/Example/SPPageMenu/JSBadgeView/JSBadgeView.m @@ -0,0 +1,427 @@ +/* + Copyright (c) 2013 Javier Soto. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +#import "JSBadgeView.h" + +#import +#include + +#if !__has_feature(objc_arc) +#error JSBadgeView must be compiled with ARC. +#endif + +// Silencing some deprecation warnings if your deployment target is iOS7 that can only be fixed by using methods that +// Are only available on iOS7. +// Soon JSBadgeView will require iOS 7 and we'll be able to use the new methods. +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 + #define JSBadgeViewSilenceDeprecatedMethodStart() _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") + #define JSBadgeViewSilenceDeprecatedMethodEnd() _Pragma("clang diagnostic pop") +#else + #define JSBadgeViewSilenceDeprecatedMethodStart() + #define JSBadgeViewSilenceDeprecatedMethodEnd() +#endif + +static const CGFloat JSBadgeViewShadowRadius = 1.0f; +static const CGFloat JSBadgeViewHeight = 16.0f; +static const CGFloat JSBadgeViewTextSideMargin = 8.0f; +static const CGFloat JSBadgeViewCornerRadius = 10.0f; + +// Thanks to Peter Steinberger: https://gist.github.com/steipete/6526860 +static BOOL JSBadgeViewIsUIKitFlatMode(void) +{ + static BOOL isUIKitFlatMode = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ +#ifndef kCFCoreFoundationVersionNumber_iOS_7_0 +#define kCFCoreFoundationVersionNumber_iOS_7_0 847.2 +#endif +#ifndef UIKitVersionNumber_iOS_7_0 +#define UIKitVersionNumber_iOS_7_0 0xB57 +#endif + // We get the modern UIKit if system is running >= iOS 7 and we were linked with >= SDK 7. + if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) { + isUIKitFlatMode = (NSVersionOfLinkTimeLibrary("UIKit") >> 16) >= UIKitVersionNumber_iOS_7_0; + } + }); + + return isUIKitFlatMode; +} + +@implementation JSBadgeView + ++ (void)applyCommonStyle +{ + JSBadgeView *badgeViewAppearanceProxy = JSBadgeView.appearance; + + badgeViewAppearanceProxy.backgroundColor = UIColor.clearColor; + badgeViewAppearanceProxy.badgeAlignment = JSBadgeViewAlignmentTopRight; + badgeViewAppearanceProxy.badgeBackgroundColor = UIColor.redColor; + badgeViewAppearanceProxy.badgeTextFont = [UIFont boldSystemFontOfSize:UIFont.systemFontSize]; + badgeViewAppearanceProxy.badgeTextColor = UIColor.whiteColor; +} + ++ (void)applyLegacyStyle +{ + JSBadgeView *badgeViewAppearanceProxy = JSBadgeView.appearance; + + badgeViewAppearanceProxy.badgeOverlayColor = [UIColor colorWithWhite:1.0f alpha:0.3]; + badgeViewAppearanceProxy.badgeTextShadowColor = UIColor.clearColor; + badgeViewAppearanceProxy.badgeShadowColor = [UIColor colorWithWhite:0.0f alpha:0.4f]; + badgeViewAppearanceProxy.badgeShadowSize = CGSizeMake(0.0f, 3.0f); + badgeViewAppearanceProxy.badgeStrokeWidth = 2.0f; + badgeViewAppearanceProxy.badgeStrokeColor = UIColor.whiteColor; +} + ++ (void)applyIOS7Style +{ + JSBadgeView *badgeViewAppearanceProxy = JSBadgeView.appearance; + + badgeViewAppearanceProxy.badgeOverlayColor = UIColor.clearColor; + badgeViewAppearanceProxy.badgeTextShadowColor = UIColor.clearColor; + badgeViewAppearanceProxy.badgeShadowColor = UIColor.clearColor; + badgeViewAppearanceProxy.badgeStrokeWidth = 0.0f; + badgeViewAppearanceProxy.badgeStrokeColor = badgeViewAppearanceProxy.badgeBackgroundColor; +} + ++ (void)initialize +{ + if (self == JSBadgeView.class) + { + [self applyCommonStyle]; + + if (JSBadgeViewIsUIKitFlatMode()) + { + [self applyIOS7Style]; + } + else + { + [self applyLegacyStyle]; + } + } +} + +- (id)initWithParentView:(UIView *)parentView alignment:(JSBadgeViewAlignment)alignment +{ + if ((self = [self initWithFrame:CGRectZero])) + { + self.badgeAlignment = alignment; + [parentView addSubview:self]; + } + + return self; +} + +#pragma mark - Layout + +- (CGFloat)marginToDrawInside +{ + return self.badgeStrokeWidth * 2.0f; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + CGRect newFrame = self.frame; + const CGRect superviewBounds = CGRectIsEmpty(_frameToPositionInRelationWith) ? self.superview.bounds : _frameToPositionInRelationWith; + + const CGFloat textWidth = [self sizeOfTextForCurrentSettings].width; + + const CGFloat marginToDrawInside = [self marginToDrawInside]; + const CGFloat viewWidth = MAX(_badgeMinWidth, textWidth + JSBadgeViewTextSideMargin + (marginToDrawInside * 2)); + const CGFloat viewHeight = JSBadgeViewHeight + (marginToDrawInside * 2); + + const CGFloat superviewWidth = superviewBounds.size.width; + const CGFloat superviewHeight = superviewBounds.size.height; + + newFrame.size.width = MAX(viewWidth, viewHeight); + newFrame.size.height = viewHeight; + + switch (self.badgeAlignment) { + case JSBadgeViewAlignmentTopLeft: + newFrame.origin.x = -viewWidth / 2.0f; + newFrame.origin.y = -viewHeight / 2.0f; + break; + case JSBadgeViewAlignmentTopRight: + newFrame.origin.x = superviewWidth - (viewWidth / 2.0f); + newFrame.origin.y = -viewHeight / 2.0f; + break; + case JSBadgeViewAlignmentTopCenter: + newFrame.origin.x = (superviewWidth - viewWidth) / 2.0f; + newFrame.origin.y = -viewHeight / 2.0f; + break; + case JSBadgeViewAlignmentCenterLeft: + newFrame.origin.x = -viewWidth / 2.0f; + newFrame.origin.y = (superviewHeight - viewHeight) / 2.0f; + break; + case JSBadgeViewAlignmentCenterRight: + newFrame.origin.x = superviewWidth - (viewWidth / 2.0f); + newFrame.origin.y = (superviewHeight - viewHeight) / 2.0f; + break; + case JSBadgeViewAlignmentBottomLeft: + newFrame.origin.x = -viewWidth / 2.0f; + newFrame.origin.y = superviewHeight - (viewHeight / 2.0f); + break; + case JSBadgeViewAlignmentBottomRight: + newFrame.origin.x = superviewWidth - (viewWidth / 2.0f); + newFrame.origin.y = superviewHeight - (viewHeight / 2.0f); + break; + case JSBadgeViewAlignmentBottomCenter: + newFrame.origin.x = (superviewWidth - viewWidth) / 2.0f; + newFrame.origin.y = superviewHeight - (viewHeight / 2.0f); + break; + case JSBadgeViewAlignmentCenter: + newFrame.origin.x = (superviewWidth - viewWidth) / 2.0f; + newFrame.origin.y = (superviewHeight - viewHeight) / 2.0f; + break; + default: + NSAssert(NO, @"Unimplemented JSBadgeAligment type %lul", (unsigned long)self.badgeAlignment); + } + + newFrame.origin.x += _badgePositionAdjustment.x; + newFrame.origin.y += _badgePositionAdjustment.y; + + // Do not set frame directly so we do not interfere with any potential transform set on the view. + self.bounds = CGRectIntegral(CGRectMake(0, 0, CGRectGetWidth(newFrame), CGRectGetHeight(newFrame))); + self.center = CGPointMake(ceilf(CGRectGetMidX(newFrame)), ceilf(CGRectGetMidY(newFrame))); + + [self setNeedsDisplay]; +} + +#pragma mark - Private + +- (CGSize)sizeOfTextForCurrentSettings +{ + JSBadgeViewSilenceDeprecatedMethodStart(); + return [self.badgeText sizeWithFont:self.badgeTextFont]; + JSBadgeViewSilenceDeprecatedMethodEnd(); +} + +#pragma mark - Setters + +- (void)setBadgeAlignment:(JSBadgeViewAlignment)badgeAlignment +{ + if (badgeAlignment != _badgeAlignment) + { + _badgeAlignment = badgeAlignment; + + [self setNeedsLayout]; + } +} + +- (void)setBadgePositionAdjustment:(CGPoint)badgePositionAdjustment +{ + _badgePositionAdjustment = badgePositionAdjustment; + + [self setNeedsLayout]; +} + +- (void)setBadgeText:(NSString *)badgeText +{ + if (badgeText != _badgeText) + { + _badgeText = [badgeText copy]; + + [self setNeedsLayout]; + } +} + +- (void)setBadgeTextColor:(UIColor *)badgeTextColor +{ + if (badgeTextColor != _badgeTextColor) + { + _badgeTextColor = badgeTextColor; + + [self setNeedsDisplay]; + } +} + +- (void)setBadgeTextShadowColor:(UIColor *)badgeTextShadowColor +{ + if (badgeTextShadowColor != _badgeTextShadowColor) + { + _badgeTextShadowColor = badgeTextShadowColor; + + [self setNeedsDisplay]; + } +} + +- (void)setBadgeTextShadowOffset:(CGSize)badgeTextShadowOffset +{ + _badgeTextShadowOffset = badgeTextShadowOffset; + + [self setNeedsDisplay]; +} + +- (void)setBadgeTextFont:(UIFont *)badgeTextFont +{ + if (badgeTextFont != _badgeTextFont) + { + _badgeTextFont = badgeTextFont; + + [self setNeedsLayout]; + [self setNeedsDisplay]; + } +} + +- (void)setBadgeBackgroundColor:(UIColor *)badgeBackgroundColor +{ + if (badgeBackgroundColor != _badgeBackgroundColor) + { + _badgeBackgroundColor = badgeBackgroundColor; + + [self setNeedsDisplay]; + } +} + +- (void)setBadgeStrokeWidth:(CGFloat)badgeStrokeWidth +{ + if (badgeStrokeWidth != _badgeStrokeWidth) + { + _badgeStrokeWidth = badgeStrokeWidth; + + [self setNeedsLayout]; + [self setNeedsDisplay]; + } +} + +- (void)setBadgeStrokeColor:(UIColor *)badgeStrokeColor +{ + if (badgeStrokeColor != _badgeStrokeColor) + { + _badgeStrokeColor = badgeStrokeColor; + + [self setNeedsDisplay]; + } +} + +- (void)setBadgeShadowColor:(UIColor *)badgeShadowColor +{ + if (badgeShadowColor != _badgeShadowColor) + { + _badgeShadowColor = badgeShadowColor; + + [self setNeedsDisplay]; + } +} + +- (void)setBadgeShadowSize:(CGSize)badgeShadowSize +{ + if (!CGSizeEqualToSize(badgeShadowSize, _badgeShadowSize)) + { + _badgeShadowSize = badgeShadowSize; + + [self setNeedsDisplay]; + } +} + +#pragma mark - Drawing + +- (void)drawRect:(CGRect)rect +{ + const BOOL anyTextToDraw = (self.badgeText.length > 0); + + if (anyTextToDraw) + { + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + const CGFloat marginToDrawInside = [self marginToDrawInside]; + const CGRect rectToDraw = CGRectInset(rect, marginToDrawInside, marginToDrawInside); + + UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:rectToDraw byRoundingCorners:(UIRectCorner)UIRectCornerAllCorners cornerRadii:CGSizeMake(JSBadgeViewCornerRadius, JSBadgeViewCornerRadius)]; + + /* Background and shadow */ + CGContextSaveGState(ctx); + { + CGContextAddPath(ctx, borderPath.CGPath); + + CGContextSetFillColorWithColor(ctx, self.badgeBackgroundColor.CGColor); + CGContextSetShadowWithColor(ctx, self.badgeShadowSize, JSBadgeViewShadowRadius, self.badgeShadowColor.CGColor); + + CGContextDrawPath(ctx, kCGPathFill); + } + CGContextRestoreGState(ctx); + + const BOOL colorForOverlayPresent = self.badgeOverlayColor && ![self.badgeOverlayColor isEqual:[UIColor clearColor]]; + + if (colorForOverlayPresent) + { + /* Gradient overlay */ + CGContextSaveGState(ctx); + { + CGContextAddPath(ctx, borderPath.CGPath); + CGContextClip(ctx); + + const CGFloat height = rectToDraw.size.height; + const CGFloat width = rectToDraw.size.width; + + const CGRect rectForOverlayCircle = CGRectMake(rectToDraw.origin.x, + rectToDraw.origin.y - ceilf(height * 0.5), + width, + height); + + CGContextAddEllipseInRect(ctx, rectForOverlayCircle); + CGContextSetFillColorWithColor(ctx, self.badgeOverlayColor.CGColor); + + CGContextDrawPath(ctx, kCGPathFill); + } + CGContextRestoreGState(ctx); + } + + /* Stroke */ + CGContextSaveGState(ctx); + { + CGContextAddPath(ctx, borderPath.CGPath); + + CGContextSetLineWidth(ctx, self.badgeStrokeWidth); + CGContextSetStrokeColorWithColor(ctx, self.badgeStrokeColor.CGColor); + + CGContextDrawPath(ctx, kCGPathStroke); + } + CGContextRestoreGState(ctx); + + /* Text */ + + CGContextSaveGState(ctx); + { + CGContextSetFillColorWithColor(ctx, self.badgeTextColor.CGColor); + CGContextSetShadowWithColor(ctx, self.badgeTextShadowOffset, 1.0, self.badgeTextShadowColor.CGColor); + + CGRect textFrame = rectToDraw; + const CGSize textSize = [self sizeOfTextForCurrentSettings]; + + textFrame.size.height = textSize.height; + textFrame.origin.y = rectToDraw.origin.y + floorf((rectToDraw.size.height - textFrame.size.height) / 2.0f); + + JSBadgeViewSilenceDeprecatedMethodStart(); + [self.badgeText drawInRect:textFrame + withFont:self.badgeTextFont + lineBreakMode:NSLineBreakByClipping + alignment:NSTextAlignmentCenter]; + JSBadgeViewSilenceDeprecatedMethodEnd(); + } + CGContextRestoreGState(ctx); + } +} + +@end \ No newline at end of file diff --git a/Example/SPPageMenu/ParentViewController.m b/Example/SPPageMenu/ParentViewController.m index c58eefe..a7e1fb6 100644 --- a/Example/SPPageMenu/ParentViewController.m +++ b/Example/SPPageMenu/ParentViewController.m @@ -18,9 +18,11 @@ #import "SevenViewController.h" #import "EightViewController.h" +#import "JSBadgeView.h" + #define screenW [UIScreen mainScreen].bounds.size.width #define screenH [UIScreen mainScreen].bounds.size.height -#define pageMenuH 35 +#define pageMenuH 40 #define NaviH (screenH == 812 ? 88 : 64) // 812是iPhoneX的高度 #define scrollViewHeight (screenH-NaviH-pageMenuH) @@ -337,7 +339,6 @@ - (void)test16 { // 示例17:含有图片的按钮 - (void)test17 { self.dataArr = @[@"生活",[UIImage imageNamed:@"Expression_1"],@"交通",[UIImage imageNamed:@"Expression_2"],@"搞笑",@"综艺"]; - SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleLineLongerThanItem]; // 传递数组,默认选中第2个 [pageMenu setItems:self.dataArr selectedItemIndex:1]; @@ -391,8 +392,45 @@ - (void)test19 { _pageMenu = pageMenu; } -// 示例20:特别属性说明 +// 示例20:给指定按钮加角标 - (void)test20 { + self.dataArr = @[@"生活",@"军事",@"水木年华",@"综艺"]; + + SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleNothing]; + // 传递数组,默认选中第2个 + [pageMenu setItems:self.dataArr selectedItemIndex:1]; + pageMenu.delegate = self; + pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollAdaptContent; + // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 + pageMenu.bridgeScrollView = self.scrollView; + + // 这里通过KVC的形式取出按钮数组,在通过下标获取指定的按钮。本框架没有特别提供返回指定按钮的方法,因为按钮是不能返回的,一旦返回,该按钮的属性就可以被外界轻松地任意修改,这是一个框架该考虑的安全问题。如果专门提供一个设置角标的方法,那么角标的样式又可以自定义,角标并非本框架的核心功能,所以没必要因为它将框架搞得3过于臃肿。 + NSArray *buttons = [pageMenu valueForKey:@"_buttons"]; + UIButton *button0 = [buttons objectAtIndex:0]; + JSBadgeView *badgeView0 = [[JSBadgeView alloc] initWithParentView:button0.titleLabel alignment:JSBadgeViewAlignmentTopRight]; + badgeView0.badgePositionAdjustment = CGPointMake(10, 0); + badgeView0.badgeBackgroundColor = [UIColor redColor]; + badgeView0.badgeOverlayColor = [UIColor clearColor]; + badgeView0.badgeStrokeColor = [UIColor redColor]; + badgeView0.badgeText = @"3"; + + UIButton *button1 = [buttons objectAtIndex:2]; + JSBadgeView *badgeView2 = [[JSBadgeView alloc] initWithParentView:button1.titleLabel alignment:JSBadgeViewAlignmentTopRight]; + badgeView2.badgePositionAdjustment = CGPointMake(10, 0); + badgeView2.badgeBackgroundColor = [UIColor whiteColor]; + badgeView2.badgeOverlayColor = [UIColor clearColor]; + badgeView2.badgeStrokeColor = [UIColor redColor]; + badgeView2.badgeTextColor = [UIColor redColor]; + badgeView2.badgeMinWidth = 1.0 / [UIScreen mainScreen].scale; + badgeView2.badgeText = @"99"; + + [self.view addSubview:pageMenu]; + + _pageMenu = pageMenu; +} + +// 示例21:特别属性说明 +- (void)test21 { self.dataArr = nil; NSString *text = @"本框架的bridgeScrollView属性是一个很重要但又容易忽略的属性,在外界的viewDidLoad中,每种示例都传了一个scrollView,即:“self.pageMenu.bridgeScrollView = self.scrollView”,这一传递,SPPageMenu内部会监听该scrollView的滚动状况,当该scrollView滚动的时候,就可以让跟踪器时刻跟随;如果你忘了或者不想设置这个属性,也可以在外界的scrollView的代理方法scrollViewDidScroll中调用接口“- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView”,这样也能实现跟踪器时刻跟随scrollView;如果不想让跟踪器时刻跟踪,而直到scrollView滑动结束才跟踪,在上面2种方式采取了任意一种的情况下,可以设置属性”pageMenu.closeTrackerFollowingMode = YES“"; @@ -474,6 +512,12 @@ - (void)viewDidLoad { case 18: [self test19]; break; + case 19: + [self test20]; + break; + case 20: + [self test21]; + break; default: break; } diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index 474aaac..9f3c1b0 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -190,9 +190,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // -------------- 以下方法和属性被废弃 -------------- -// 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.1版本已纠正 +// 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.4.0版本已纠正 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContentEdgeInsets:forItemAtIndex:"); -// 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.1版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 +// 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.4.0版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 @property (nonatomic, assign) BOOL closeTrackerFollowingMode NS_DEPRECATED_IOS(6_0, 6_0,"Use trackerFollowingMode instead"); // 以下2个方法从3.0版本开始有升级,可以使用但不推荐 - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setTitle:image:imagePosition:imageRatio:imageTitleSpace:forItemIndex:"); diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index 185b198..6d3d7c2 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -54,11 +54,11 @@ - (instancetype)initWithImageRatio:(CGFloat)ratio; @property (nonatomic, assign) SPItemImagePosition imagePosition; // 图片与标题之间的间距 @property (nonatomic, assign) CGFloat imageTitleSpace; + @end @implementation SPPageMenuItem - - (instancetype)initWithImageRatio:(CGFloat)ratio { if (self = [super init]) { _imageRatio = ratio; @@ -263,6 +263,13 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex _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]; diff --git a/Example/SPPageMenu/ViewController.m b/Example/SPPageMenu/ViewController.m index b3b9577..0c644ac 100644 --- a/Example/SPPageMenu/ViewController.m +++ b/Example/SPPageMenu/ViewController.m @@ -35,7 +35,7 @@ - (void)viewDidLoad { tableView.sectionFooterHeight = CGFLOAT_MIN; self.titles = @[@"跟踪器样式专区",@"排列方式专区",@"跟踪器跟踪模式专区",@"右侧功能按钮(插入和删除操作)",@"其余功能",@"特别属性"]; - self.dataSource = @[@[@"下划线与按钮等宽(默认)",@"下划线比按钮略长",@"下划线“依恋”样式",@"缩放",@"圆角矩形",@"圆角矩形(与pageMenu同时圆角)",@"矩形",@"无样式"],@[@"可滑动的自适应内容排列",@"不可滑动的等宽排列",@"不可滑动的自适应内容排列"],@[@"跟踪器时刻跟随外界scrollView移动",@"外界scrollVie拖动结束后,跟踪器才开始移动",@"外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动"],@[@"显示右侧功能按钮",@"给功能按钮设置图片和文字"],@[@"含有图片的按钮",@"指定按钮携带图片,或同时携带图片和文字,可以设置图片的位置",@"设置背景图片"],@[@"bridgeScrollView属性"]]; + self.dataSource = @[@[@"下划线与按钮等宽(默认)",@"下划线比按钮略长",@"下划线“依恋”样式",@"缩放",@"圆角矩形",@"圆角矩形(与pageMenu同时圆角)",@"矩形",@"无样式"],@[@"可滑动的自适应内容排列",@"不可滑动的等宽排列",@"不可滑动的自适应内容排列"],@[@"跟踪器时刻跟随外界scrollView移动",@"外界scrollVie拖动结束后,跟踪器才开始移动",@"外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动"],@[@"显示右侧功能按钮",@"给功能按钮设置图片和文字"],@[@"含有图片的按钮",@"指定按钮携带图片,或同时携带图片和文字,可以设置图片的位置",@"设置背景图片",@"角标"],@[@"bridgeScrollView属性"]]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { diff --git a/README.md b/README.md index 392c830..df601a3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,15 @@ * [使用者提问](#使用者提问) ## 如何安装 +#### 版本3.4.1 +``` +target 'MyApp' do +  pod 'SPPageMenu', '~> 3.4.1' +end + +3.4.1版本在3.4.0版本的基础上修复了多次调用setItems:selectedItemIndex:方法引发的问题 +``` + #### 版本3.4.0 ``` target 'MyApp' do diff --git a/SPPageMenu.podspec b/SPPageMenu.podspec index 560d765..b15b966 100644 --- a/SPPageMenu.podspec +++ b/SPPageMenu.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "SPPageMenu" - s.version = "3.4.0" + s.version = "3.4.1" s.summary = "分页菜单." # This description is used to generate tags and improve search results. diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index 474aaac..9f3c1b0 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -190,9 +190,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // -------------- 以下方法和属性被废弃 -------------- -// 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.1版本已纠正 +// 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.4.0版本已纠正 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContentEdgeInsets:forItemAtIndex:"); -// 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.1版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 +// 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.4.0版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 @property (nonatomic, assign) BOOL closeTrackerFollowingMode NS_DEPRECATED_IOS(6_0, 6_0,"Use trackerFollowingMode instead"); // 以下2个方法从3.0版本开始有升级,可以使用但不推荐 - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setTitle:image:imagePosition:imageRatio:imageTitleSpace:forItemIndex:"); diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index e1faa61..eca4bd1 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -264,6 +264,13 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)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类型"); From 6551281479bafd562f850e699955dccf13e2a266 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Thu, 1 Nov 2018 15:25:08 +0900 Subject: [PATCH 14/22] Add .DS_Store to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5b361d2..6cf576f 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,5 @@ fastlane/test_output # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode -iOSInjectionProject/ \ No newline at end of file +iOSInjectionProject/ +.DS_Store From 665c4237933ac9c8b266bdedfd68cbefdd2ea0a9 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Tue, 18 Dec 2018 12:36:57 +0900 Subject: [PATCH 15/22] Merge remote-tracking branch 'SPStore/master' --- .DS_Store | Bin 6148 -> 6148 bytes Example/.DS_Store | Bin 6148 -> 6148 bytes Example/SPPageMenu/.DS_Store | Bin 6148 -> 6148 bytes .../Assets.xcassets/asc.imageset/1@2x.png | Bin 0 -> 2919 bytes .../asc.imageset/Contents.json | 21 + Example/SPPageMenu/Base.lproj/Main.storyboard | 8 +- Example/SPPageMenu/Info.plist | 2 +- Example/SPPageMenu/ParentViewController.m | 43 +- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 95 ++- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 759 ++++++++++++------ README.md | 112 ++- SPPageMenu.podspec | 2 +- SPPageMenu/SPPageMenu.h | 95 ++- SPPageMenu/SPPageMenu.m | 671 +++++++++++----- 14 files changed, 1223 insertions(+), 585 deletions(-) create mode 100644 Example/SPPageMenu/Assets.xcassets/asc.imageset/1@2x.png create mode 100644 Example/SPPageMenu/Assets.xcassets/asc.imageset/Contents.json diff --git a/.DS_Store b/.DS_Store index f2802134735362e767e3efd608107cc77d22818b..29bbbf6d0617e723089b22082616344dade97ca4 100644 GIT binary patch delta 242 zcmZoMXffEJ$s%zwsURn_xWvHVIwKP^3o9Et2PY8la!z(+N%jXZ;{_zDt4+;Kbrg&Z zEoyZXs?E&}bQDaC&1!2oImA^BZ9NlmE32w&YU^e)fB_>Tgl6D}(lBb)i4D4&**X650|2e^FUO$LtNF+)-xfuvZ}hKwr;l+>ieqVUX=l8lhlJlCAmveZ2PBA>*P z)X7mSp6Ssb4FQQI89+sx$__aV3?Kk@nnZQAk*TSUf|02S!ez$hAeV7Ss=Ecnl{WRw on!RM{vgL;kgLN@5GD2tuK`0HQj!YJ0mDnuCc7$;=JI7ys0Qx~n_5c6? diff --git a/Example/.DS_Store b/Example/.DS_Store index f933140bf00967dbb03631c60f87319057188ee8..a88de7dcd980b4d3aee98aab747ad39f67ed7cd0 100644 GIT binary patch delta 73 zcmZoMXffE}#Kg+U$;rz(*_|m#hy%#vj2Doot~NC{)lo1uw3xh`X%3TI`(#;W9f5)j X!{Frn+yVv=U_QVA65M={*;xbtTz?S~ delta 73 zcmZoMXffE}#Kg+M$-&Dp*_|m#h!e=^95 WoSdIqzyJcw9~eM_n-4NOivR#wco7`{ diff --git a/Example/SPPageMenu/.DS_Store b/Example/SPPageMenu/.DS_Store index c7183fba50383490ea4f20e23785b19effa588ad..b679c16b8be466485ed0819f0db2ad5159f5e520 100644 GIT binary patch delta 61 zcmZoMXffDug>|weix3+p5b$zN4rECd1To_UB&w@T%}sR_j14U&A7yczyoFVC^CLD@ O#)%C&o7p-3@&f?W&=6z* delta 200 zcmZoMXffDuh1KnAQbA5;afyM!4Mrwr7FITP4o)sk9!@?^p4i}w{PN(E#FEltr^KRo z0h!>;l+>ieqVUX=l8lhlJlCAmveZ2PBA>*P)X7;ap6Ssb4FQQI89+sx$__aV3?RVC z!5J?gQC)3hYO14PWNK2Yqfl*bZlI%JVr*Vp%gMnZsqPjOSK8D!Yxa_*%a$KH4A#ZK d$Oxes1fevHIx=}9tHkDQY^sc#**X650{{*WHF^L5 diff --git a/Example/SPPageMenu/Assets.xcassets/asc.imageset/1@2x.png b/Example/SPPageMenu/Assets.xcassets/asc.imageset/1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..da2a885d2ec4eeecae8cd500d383c6fee2c3a02c GIT binary patch literal 2919 zcmai03pmsJ8y}fllT>taYo_B+$F8Vw*NmiX^%SIYJ~-l3C1iWA(2(f}t zTwK8f4vzswLoJ|ih_yNx494^5fjBqfmPL2@&I%GN5O8rYm{2H$3QXbj3?qlGOAiMFvtAV^p&fdI!^AS|~K zEf7Qm0=|IyzpmfHGI-404Eh!xhqX{RE|te;%DYOi6Y`*-?)CsM-GnvbWQ)3=^`@e-;lp@ z{et{ihA&kAQYX1a4&TyQl$ZE#@_yy|RnYm50=JdNl$VS;-xk&gJnWmZMSDDKzGuGb zp-&CEa4PRmYjrt=KfB#pJ(wzm0szXd9f&sW6t!_mNT@q$RZgw!mDt?du}gvh1{{V> z#+#Vr&Dz*25q^(#b@*UpTE3ss{YyMEMh~b+aV>LAR5DI5V#Xp)_|uGlQ%OoI;k%YA zy5(*jnAtb5DTlLrqL*FW$1#0r8Z*~qi zRkU~%q;`Kl5d^DkuSs;emUX67yy4i?)w|8qCamWR0&CL7q$s6M%3PzDdMe<;+a2fq zuvOtUVTBx(Bg)JOX3UON>6*RWD$=;ti%WBtW~o4Qpy@FWYsuMnv&I|BD>v7mO|Z2T z&>HyVBFzfvtlp;IjvL&I*9fgl4suesO)p`i-8Z&Bn7BkekPCg3rM68q`^9jP@6Qq2 z8RJjG9nw~9sk>8{zv3j$b(SAJuBTmY4Vl{RzoMM1l00W*oLyl*_w&Qt`!{bFoJpp% zrnz0XCPi0DqJ!#n`74VY#kleax?&1avTcy9ob}GGy8X#P#Lrjlme;3*F$`=Ydm}GM zi+&G6WVfh^+JMI4_Ny^TQv-MqsCEnTW~9=aTW#O}&>g}2_`X6k0nq`C#h6*X*aSKU zl{V^pH-bWRV(!789Xyiprmx9`e9)b-t@qlw}^s$O%AZD@~t$UiAu7!ZuBMBxamF z0j-H4P;G(M&zc`(+&KHV^5jHO;Ulez&hSbX!&TRYmL#FUyW``9Xy>0P1&!Y!lm zph8K?sru>CC3=48D0`lnInODyXKVCjafL)EOxH0e=m`VpsFJP_B;=boslGnSsbFlE|6UR+uOBL+U9fSLec+z~PL%cV&!B z*>Qd=zTGg%pjzC&2Ay~+Aeor1`tWRs@jx<$swpx@L? zB}I$}+k$(V9UqUmQl*2*s^^d$`Th4lyy;v~MO6)~^zzE7o=F^f@>F})^>tMLD?y^@ z=oK_BWBbc)XYGh)$@PSzpcBtM#c?4zgEIGAd&jF9^tXSX3oe^S_FsT#oPJdbmG@>th@;@1@;_3-ueET8qrh~rAyyM%8 z*EclWtBl+?yDI;r=Saa1+q0B~wV;}(TEEI(<#A=35=(z7EY|h0PA2s3zxYf~mAo|E z^ZsMWm;wgtyz$)OPKANNN276L7q1W9S~9h)Wq-Ztib1t%LHT}j_GfED7=q@g;J_c{lci|`@j*;t5KQn|)fABTOf6UGd zY;x)=(@Ih-BzTHua7U~>#cG5=5WH*3ZaG1LJ<6@E8pgb=_gHRFQr&8cY$;RiQfQmw z9dH}lTn$E2G`;-`>P3eer20EGUCO6RW$2dk?LLDVw??d_w?EutrNfRvTy)3Gw^eHqnlE&V|RQj!y8nR+FEk7>Tx#kIqCa%W%O z4)-(aI>5*)rU#m8@(L$k*nL=y+Lf<~9ND}YQ-%&h_IM>w8_gcyZd(0o_nG#jJ!1x^ z-Ht9}i->#TdPibGIHX^f+Yc)d(&N5u_YWS9>?F49)W|MxUan23$74Owqm9r>*KH))}5yH$#uUR4}LL=al5kfl@H@ Pf4>eSGVzkFf6RXYT!qR5 literal 0 HcmV?d00001 diff --git a/Example/SPPageMenu/Assets.xcassets/asc.imageset/Contents.json b/Example/SPPageMenu/Assets.xcassets/asc.imageset/Contents.json new file mode 100644 index 0000000..31bdacd --- /dev/null +++ b/Example/SPPageMenu/Assets.xcassets/asc.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "1@2x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/SPPageMenu/Base.lproj/Main.storyboard b/Example/SPPageMenu/Base.lproj/Main.storyboard index 87f028a..4795ff7 100644 --- a/Example/SPPageMenu/Base.lproj/Main.storyboard +++ b/Example/SPPageMenu/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - - + + - + @@ -34,7 +34,7 @@ - + diff --git a/Example/SPPageMenu/Info.plist b/Example/SPPageMenu/Info.plist index 4604850..022d3a0 100644 --- a/Example/SPPageMenu/Info.plist +++ b/Example/SPPageMenu/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.4.1 + 3.4.5 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/Example/SPPageMenu/ParentViewController.m b/Example/SPPageMenu/ParentViewController.m index a7e1fb6..ee42cfe 100644 --- a/Example/SPPageMenu/ParentViewController.m +++ b/Example/SPPageMenu/ParentViewController.m @@ -23,7 +23,7 @@ #define screenW [UIScreen mainScreen].bounds.size.width #define screenH [UIScreen mainScreen].bounds.size.height #define pageMenuH 40 -#define NaviH (screenH == 812 ? 88 : 64) // 812是iPhoneX的高度 +#define NaviH (screenH >= 812 ? 88 : 64) // 812是iPhoneX的高度 #define scrollViewHeight (screenH-NaviH-pageMenuH) @interface ParentViewController () @@ -49,7 +49,6 @@ - (void)test1 { pageMenu.bridgeScrollView = self.scrollView; [self.view addSubview:pageMenu]; _pageMenu = pageMenu; - } // 示例2:SPPageMenuTrackerStyleLineLongerThanItem,下划线比item略长,长度等于tem宽+间距 @@ -103,7 +102,6 @@ - (void)test4 { [pageMenu setItems:self.dataArr selectedItemIndex:0]; // 设置缩放 pageMenu.selectedItemZoomScale = 1.3; - pageMenu.trackerFollowingMode = SPPageMenuTrackerFollowingModeHalf; // 设置代理 pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 @@ -212,7 +210,7 @@ - (void)test9 { // 示例10:不可滑动的等宽排列,关键代码:pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollEqualWidths; - (void)test10 { - self.dataArr = @[@"生活",@"影视中心",@"交通"]; + self.dataArr = @[@"生活",@"影视中心",@"交通规则"]; // trackerStyle:跟踪器的样式 SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleLine]; @@ -232,7 +230,7 @@ - (void)test10 { // 示例11:不可滑动的自适应内容排列,关键代码:pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollAdaptContent; // 这种排列方式下,itemPadding属性无效,因为内部自动计算间距 - (void)test11 { - self.dataArr = @[@"生活",@"影视中心",@"交通"]; + self.dataArr = @[@"生活",@"音乐榜中榜",@"交通规则"]; // trackerStyle:跟踪器的样式 SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleLine]; @@ -240,6 +238,7 @@ - (void)test11 { [pageMenu setItems:self.dataArr selectedItemIndex:1]; // 不可滑动的自适应内容排列 pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollAdaptContent; + // 设置代理 pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 @@ -325,7 +324,8 @@ - (void)test16 { // 传递数组,默认选中第2个 [pageMenu setItems:self.dataArr selectedItemIndex:1]; // 同时设置图片和文字,如果只想要文字,image传nil,如果只想要图片,title传nil,imagePosition和ratio传0即可 - [pageMenu setFunctionButtonTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop imageRatio:0.5 imageTitleSpace:0 forState:UIControlStateNormal]; + //[pageMenu setFunctionButtonTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop imageRatio:0.5 imageTitleSpace:0 forState:UIControlStateNormal]; + [pageMenu setFunctionButtonWithItem:[SPPageMenuButtonItem itemWithTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop] forState:UIControlStateNormal]; [pageMenu setFunctionButtonTitleTextAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} forState:UIControlStateNormal]; pageMenu.showFunctionButton = YES; // 设置代理 @@ -355,14 +355,18 @@ - (void)test18 { self.dataArr = @[@"生活",@"影视中心",@"交通",@"电视剧",@"搞笑",@"综艺"]; SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleRect]; - // 传递数组,默认选中第3个 - [pageMenu setItems:self.dataArr selectedItemIndex:2]; + // 传递数组,默认选中第1个 + [pageMenu setItems:self.dataArr selectedItemIndex:0]; // 指定第1个item为图片 [pageMenu setImage:[UIImage imageNamed:@"Expression_1"] forItemAtIndex:0]; // 指定第2个item同时含有图片和文字,图片在上 - [pageMenu setTitle:@"哈哈" image:[UIImage imageNamed:@"Expression_2"] imagePosition:SPItemImagePositionTop imageRatio:0.5 imageTitleSpace:0 forItemIndex:1]; + SPPageMenuButtonItem *item1 = [SPPageMenuButtonItem itemWithTitle:@"害羞" image:[UIImage imageNamed:@"Expression_2"]]; + item1.imagePosition = SPItemImagePositionTop; + [pageMenu setItem:item1 forItemIndex:1]; // 指定第4个item同时含有图片和文字,图片在右 - [pageMenu setTitle:@"哈哈" image:[UIImage imageNamed:@"dog"] imagePosition:SPItemImagePositionRight imageRatio:0.4 imageTitleSpace:0 forItemIndex:3]; +// [pageMenu setTitle:@"可爱的小狗" image:[UIImage imageNamed:@"dog"] imagePosition:SPItemImagePositionDefault imageRatio:0.4 imageTitleSpace:0 forItemIndex:3]; + SPPageMenuButtonItem *item2 = [SPPageMenuButtonItem itemWithTitle:@"歌曲" image:[UIImage imageNamed:@"asc"] imagePosition:SPItemImagePositionRight]; + [pageMenu setItem:item2 forItemIndex:3]; pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 pageMenu.bridgeScrollView = self.scrollView; @@ -404,7 +408,7 @@ - (void)test20 { // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 pageMenu.bridgeScrollView = self.scrollView; - // 这里通过KVC的形式取出按钮数组,在通过下标获取指定的按钮。本框架没有特别提供返回指定按钮的方法,因为按钮是不能返回的,一旦返回,该按钮的属性就可以被外界轻松地任意修改,这是一个框架该考虑的安全问题。如果专门提供一个设置角标的方法,那么角标的样式又可以自定义,角标并非本框架的核心功能,所以没必要因为它将框架搞得3过于臃肿。 + // 这里通过KVC的形式取出按钮数组,再通过下标获取指定的按钮。本框架没有特别提供返回指定按钮的方法,因为按钮是不能返回的,一旦返回,该按钮的属性就可以被外界轻松地任意修改,这是一个框架该考虑的安全问题。如果专门提供一个设置角标的方法,那么角标的样式又可以自定义,角标并非本框架的核心功能,所以没必要因为它将框架搞得过于臃肿。 NSArray *buttons = [pageMenu valueForKey:@"_buttons"]; UIButton *button0 = [buttons objectAtIndex:0]; JSBadgeView *badgeView0 = [[JSBadgeView alloc] initWithParentView:button0.titleLabel alignment:JSBadgeViewAlignmentTopRight]; @@ -528,11 +532,14 @@ - (void)viewDidLoad { for (int i = 0; i < self.dataArr.count; i++) { if (controllerClassNames.count > i) { BaseViewController *baseVc = [[NSClassFromString(controllerClassNames[i]) alloc] init]; - NSString *text = [self.pageMenu titleForItemAtIndex:i]; - if (text.length) { - baseVc.text = text; - } else { + id object = [self.pageMenu objectForItemAtIndex:i]; + if ([object isKindOfClass:[NSString class]]) { + baseVc.text = object; + } else if ([object isKindOfClass:[UIImage class]]) { baseVc.text = @"图片"; + } else { + SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)object; + baseVc.text = item.title; } [self addChildViewController:baseVc]; // 控制器本来自带childViewControllers,但是遗憾的是该数组的元素顺序永远无法改变,只要是addChildViewController,都是添加到最后一个,而控制器不像数组那样,可以插入或删除任意位置,所以这里自己定义可变数组,以便插入(删除)(如果没有插入(删除)功能,直接用自带的childViewControllers即可) @@ -607,7 +614,7 @@ - (void)pageMenu:(SPPageMenu *)pageMenu functionButtonClicked:(UIButton *)functi #pragma mark - insert or remove -// object是插入的对象(NSString或UIImage),insertNumber是插入到第几个 +// object是插入的对象(NSString、UIImage或SPPageMenuButtonItem),insertNumber是插入到第几个 - (void)insertItemWithObject:(id)object toIndex:(NSInteger)insertNumber { if (insertNumber > self.myChildViewControllers.count) return; // 插入之前,先将新控制器之后的控制器view往后偏移 @@ -633,8 +640,10 @@ - (void)insertItemWithObject:(id)object toIndex:(NSInteger)insertNumber { // 要先添加控制器,再添加item,如果先添加item,会立即调代理方法,此时myChildViewControllers的个数还是0,在代理方法中retun了 if ([object isKindOfClass:[NSString class]]) { [self.pageMenu insertItemWithTitle:object atIndex:insertNumber animated:YES]; - } else { + } else if([object isKindOfClass:[UIImage class]]) { [self.pageMenu insertItemWithImage:object atIndex:insertNumber animated:YES]; + } else { + [self.pageMenu insertItem:object atIndex:insertNumber animated:YES]; } // 重新设置scrollView容量 diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index 9f3c1b0..241b96d 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -33,14 +33,14 @@ typedef NS_ENUM(NSInteger, SPPageMenuTrackerFollowingMode) { }; typedef NS_ENUM(NSInteger, SPItemImagePosition) { - SPItemImagePositionDefault, // 默认图片在左边 - SPItemImagePositionLeft, // 图片在左边 - SPItemImagePositionTop, // 图片在上面 - SPItemImagePositionRight, // 图片在右边 - SPItemImagePositionBottom // 图片在下面 + SPItemImagePositionDefault, // 默认图片在左侧 + SPItemImagePositionLeft, // 图片在文字左侧 + SPItemImagePositionRight, // 图片在文字右侧 + SPItemImagePositionTop, // 图片在文字上侧 + SPItemImagePositionBottom // 图片在文字下侧 }; -@class SPPageMenu; +@class SPPageMenu,SPPageMenuButtonItem; @protocol SPPageMenuDelegate @@ -62,7 +62,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { /** * 传递数据 * - * @param items 数组 (数组元素只能是NSString或UIImage类型) + * @param items 数组 (数组元素可以是NSString、UIImage类型、SPPageMenuButtonItem类型,其中SPPageMenuButtonItem相当于一个模型,可以同时设置图片和文字) * @param selectedItemIndex 默认选中item的下标 */ - (void)setItems:(nullable NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex; @@ -71,6 +71,12 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 +#if TARGET_INTERFACE_BUILDER +@property (nonatomic, readonly) IBInspectable NSInteger trackerStyle; // 该枚举属性支持storyBoard/xib,方便在storyBoard/xib中创建时直接设置 +#else +@property (nonatomic, readonly) SPPageMenuTrackerStyle trackerStyle; +#endif + // item之间的间距,默认30;当排列方式permutationWay为‘SPPageMenuPermutationWayNotScrollAdaptContent’时此属性无效,无效是合理的,不可能做到“不可滑动且自适应内容”然后间距又自定义,这2者相互制约; @property (nonatomic, assign) CGFloat itemPadding; @@ -101,9 +107,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 跟踪器的跟踪模式 @property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; -/** 代理 */ -@property (nonatomic, weak) id delegate; - // 分割线 @property (nonatomic, readonly) UIImageView *dividingLine; // 分割线,你可以拿到该对象设置一些自己想要的属性,如颜色、图片等,如果想要隐藏分割线,拿到该对象直接设置hidden为YES或设置alpha<0.01即可(eg:pageMenu.dividingLine.hidden = YES) @property (nonatomic) CGFloat dividingLineHeight; // 分割线的高度 @@ -114,10 +117,16 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 修改跟踪器样式 - (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle; +@property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO +@property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 + +@property (nonatomic, weak) id delegate; // 插入item,插入和删除操作时,如果itemIndex超过了了items的个数,则不做任何操作 - (void)insertItemWithTitle:(nullable NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItem:(nullable SPPageMenuButtonItem *)item atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; // 如果移除的正是当前选中的item(当前选中的item下标不为0),删除之后,选中的item会切换为上一个item - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated; - (void)removeAllItems; @@ -128,6 +137,11 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setImage:(nullable UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的图片 +- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex; // 同时为指定item设置标题和图片,其中参数item相当于一个模型,可以同时设置文字和图片 +- (nullable SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex; // 获取指定item + +- (id)objectForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item,该方法获取的item可能是NSString、UIImage或SPPageMenuButtonItem类型 + - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的宽度(如果width为0,item会根据内容自动计算width) - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的宽度 @@ -141,34 +155,8 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setBackgroundImage:(nullable UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics; - (nullable UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics; // 获取背景图片 -/** - 同时为指定item设置标题和图片 - - @param title 标题 - @param image 图片 - @param imagePosition 图片的位置,分上、左、下、右 - @param ratio 图片所占item的比例,图片在左右时默认0.5,图片在上下时默认2.0/3.0 - @param imageTitleSpace 图片与标题之间的间距,默认0 - @param itemIndex item的下标 - */ -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex; - - -@property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 -@property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 - -/** - * 同时为functionButton设置标题和图片 - * - * @param title 标题 - * @param image 图片 - * @param imagePosition 图片的位置,分上、左、下、右 - * @param ratio 图片所占item的比例,图片在左右时默认0.5,图片在上下时默认2.0/3.0 - * @param imageTitleSpace 图片与标题之间的间距,默认0 - * @param state 控件状态 - */ -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state; +// 同时为functionButton设置标题和图片 +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state; // 为functionButton配置相关属性,如设置字体、文字颜色等;在此,attributes中,只有NSFontAttributeName、NSForegroundColorAttributeName、NSBackgroundColorAttributeName有效 - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state; @@ -182,21 +170,40 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { } 3.如果外界设置了SPPageMenu的属性"bridgeScrollView",那么外界就可以不用在scrollViewDidScroll方法中调用这个方法来实现跟踪器时刻跟随外界scrollView的效果,内部会自动处理; 外界对SPPageMenu的属性"bridgeScrollView"赋值是实现此效果的最简便的操作 - 4.如果不想要此效果,可设置closeTrackerFollowingMode==YES */ - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; -// -------------- 以下方法和属性被废弃 -------------- +// -------------- 以下方法和属性被废弃,不再建议使用 -------------- // 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.4.0版本已纠正 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContentEdgeInsets:forItemAtIndex:"); // 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.4.0版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 @property (nonatomic, assign) BOOL closeTrackerFollowingMode NS_DEPRECATED_IOS(6_0, 6_0,"Use trackerFollowingMode instead"); -// 以下2个方法从3.0版本开始有升级,可以使用但不推荐 -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setTitle:image:imagePosition:imageRatio:imageTitleSpace:forItemIndex:"); -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use -setFunctionButtonTitle:image:imagePosition:imageRatio:imageTitleSpace:forState:"); +// 下面的方法均有升级,其中ratio参数已失效 +- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); +- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); +- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); +- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); +@end + + +// 这个类相当于模型,主要用于同时为某个按钮设置图片和文字时使用 +@interface SPPageMenuButtonItem : NSObject + +// 快速创建同时含有标题和图片的item,默认图片在左边,文字在右边 ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image; +// 快速创建同时含有标题和图片的item,imagePositiona参数为图片位置 ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition; + +@property (nonatomic, copy) NSString *title; +@property (nonatomic, strong) UIImage *image; +// 图片的位置 +@property (nonatomic, assign) SPItemImagePosition imagePosition; +// 图片与标题之间的间距,默认0.0 +@property (nonatomic, assign) CGFloat imageTitleSpace; + @end NS_ASSUME_NONNULL_END diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index 6d3d7c2..eef4300 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -45,159 +45,356 @@ - (void)setAlpha:(CGFloat)alpha { @end -@interface SPPageMenuItem : UIButton +@interface SPPageMenuButton : 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; +- (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition; + +@property (nonatomic) SPItemImagePosition imagePosition; // 图片位置 +@property (nonatomic, assign) CGFloat imageTitleSpace; // 图片和文字之间的间距 @end -@implementation SPPageMenuItem +@implementation SPPageMenuButton -- (instancetype)initWithImageRatio:(CGFloat)ratio { +- (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition { if (self = [super init]) { - _imageRatio = ratio; + self.imagePosition = imagePosition; } return self; } -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { +#pragma mark - system methods +- (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; + _imagePosition = SPItemImagePositionLeft; + _imageTitleSpace = 0.0; } -- (void)setHighlighted:(BOOL)highlighted {} - +// 下面这2个方法,我所知道的: +// 在第一次调用titleLabel和imageView的getter方法(懒加载)时,alloc init之前会调用一次(无论有无图片文字都会直接调),因此,在重写这2个方法时,在方法里面不要使用self.imageView和self.titleLabel,因为这2个控件是懒加载,如果在重写的这2个方法里是第一调用imageView和titleLabel的getter方法, 则会造成死循环 +// 在layoutsSubviews中如果文字或图片不为空时会调用, 测试方式:在重写的这两个方法里调用setNeedsLayout(layutSubviews),发现会造成死循环 +// 设置文字图片、改动文字和图片、设置对齐方式,设置内容区域等时会调用,其实设置这些属性,系统是调用了layoutSubviews从而间接的去调用imageRectForContentRect:和titleRectForContentRect: +// ... - (CGRect)imageRectForContentRect:(CGRect)contentRect { + // 先获取系统为我们计算好的rect,这样大小图片在左右时我们就不要自己去计算,我门要改变的,仅仅是origin + CGRect imageRect = [super imageRectForContentRect:contentRect]; + CGRect titleRect = [super titleRectForContentRect:contentRect]; if (!self.currentTitle) { // 如果没有文字,则图片占据整个button,空格算一个文字 - return [super imageRectForContentRect:contentRect]; + return imageRect; } 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; - return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); + case SPItemImagePositionLeft: + case SPItemImagePositionDefault: { // 图片在左 + imageRect = [self imageRectImageAtLeftForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } 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); + case SPItemImagePositionRight: { + imageRect = [self imageRectImageAtRightForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } 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); + case SPItemImagePositionTop: { + imageRect = [self imageRectImageAtTopForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } 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); + imageRect = [self imageRectImageAtBottomForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } break; - default: - break; } - return CGRectZero; + return imageRect; } - (CGRect)titleRectForContentRect:(CGRect)contentRect { + CGRect titleRect = [super titleRectForContentRect:contentRect]; + CGRect imageRect = [super imageRectForContentRect:contentRect]; if (!self.currentImage) { // 如果没有图片 - return [super titleRectForContentRect:contentRect]; + return titleRect; } 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); + case SPItemImagePositionLeft: + case SPItemImagePositionDefault: { + titleRect = [self titleRectImageAtLeftForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } 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); + case SPItemImagePositionRight: { + titleRect = [self titleRectImageAtRightForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } 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); + case SPItemImagePositionTop: { + titleRect = [self titleRectImageAtTopForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } 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); + titleRect = [self titleRectImageAtBottomForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } break; - default: - break; } - return CGRectZero; + return titleRect; + +} + +#pragma - private +// ----------------------------------------------------- left ----------------------------------------------------- + +- (CGRect)imageRectImageAtLeftForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + // imageView的x值向左偏移间距的一半,另一半由titleLabe分担,不用管会不会超出contentRect,我定的规则是允许超出,如果对此作出限制,那么必须要对图片或者文字宽高有所压缩,压缩只能由imageEdgeInsets决定,当图片的内容区域容不下时才产生宽度压缩 + imageOrigin.x = imageOrigin.x - _imageTitleSpace*0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; } -- (void)setImagePosition:(SPItemImagePosition)imagePosition { - _imagePosition = imagePosition; - [self setNeedsDisplay]; +- (CGRect)titleRectImageAtLeftForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + + titleOrigin.x = titleOrigin.x + _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; } -- (void)setImageRatio:(CGFloat)imageRatio { - _imageRatio = imageRatio; - [self setNeedsDisplay]; +// ----------------------------------------------------- right ----------------------------------------------------- + +- (CGRect)imageRectImageAtRightForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + CGSize titleSize = titleRect.size; + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + + CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; + if (imageSize.width >= imageSafeWidth) { + return imageRect; + } + // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView + if (imageSize.width + titleSize.width > imageSafeWidth) { + imageSize.width = imageSize.width - (imageSize.width + titleSize.width - imageSafeWidth); + } + // (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - (imageSize.width + titleSize.width))/2.0+titleSize.width指的是imageView在其有效区域内联合titleLabel整体居中时的x值,有效区域指的是contentRect内缩imageEdgeInsets后的区域 + imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - (imageSize.width + titleSize.width))/2.0 + titleSize.width + self.contentEdgeInsets.left + self.imageEdgeInsets.left + _imageTitleSpace * 0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; +} + +- (CGRect)titleRectImageAtRightForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + CGSize imageSize = imageRect.size; + + // (contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right - (imageSize.width + titleSize.width))/2.0的意思是titleLabel在其有效区域内联合imageView整体居中时的x值,有效区域指的是contentRect内缩titleEdgeInsets后的区域 + titleOrigin.x = (contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right - (imageSize.width + titleSize.width))/2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left - _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; +} + +// ----------------------------------------------------- top ----------------------------------------------------- + +- (CGRect)imageRectImageAtTopForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + CGSize titleSize = titleRect.size; + + CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; + + // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView + if (imageSize.width > imageSafeWidth) { + imageSize.width = imageSafeWidth; + } + imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - imageSize.width) / 2.0 + self.contentEdgeInsets.left + self.imageEdgeInsets.left; + + // 给图片高度作最大限制,超出限制对高度进行压缩,这样还可以保证titeLabel不会超出其有效区域 + CGFloat imageTitleLimitMaxH = contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom; + if (imageSize.height < imageTitleLimitMaxH) { + if (titleSize.height + self.currentImage.size.height > imageTitleLimitMaxH) { + CGFloat beyondValue = titleSize.height + self.currentImage.size.height - imageTitleLimitMaxH; + imageSize.height = imageSize.height - beyondValue; + } + // 之所以采用自己计算的结果,是因为当sizeToFit且titleLabel的numberOfLines > 0时,系统内部会按照2行计算 + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + } + // (imageSize.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的insets,计算时都是以图片+文字这个整体作为考虑对象 + imageOrigin.y = (contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + self.contentEdgeInsets.top + self.imageEdgeInsets.top - _imageTitleSpace * 0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; +} + +- (CGRect)titleRectImageAtTopForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + + CGSize imageSize = imageRect.size; + // 这个if语句的含义是:计算图片由于设置了contentEdgeInsets而被压缩的高度,设置imageEdgeInsets被压缩的高度不计算在内。这样做的目的是,当设置了contentEdgeInsets时,图片可能会被压缩,此时titleLabel的y值依赖于图片压缩后的高度,当设置了imageEdgeInsets时,图片也可能被压缩,此时titleLabel的y值依赖于图片压缩前的高度,这样以来,设置imageEdgeInsets就不会对titleLabel的y值产生影响 + if (self.currentImage.size.height + titleSize.height > contentRect.size.height) { + imageSize.height = self.currentImage.size.height - (self.currentImage.size.height + titleSize.height - contentRect.size.height); + } + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + // titleLabel的安全宽度,这里一定要改变宽度值,因为当外界设置了titleEdgeInsets值时,系统计算出来的所有值都是在”左图右文“的基础上进行的,这个基础上可能会导致titleLabel的宽度被压缩,所以我们在此自己重新计算 + CGFloat titleSafeWidth = contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right; + if (titleSize.width > titleSafeWidth) { + titleSize.width = titleSafeWidth; + } + titleOrigin.x = (titleSafeWidth - titleSize.width) / 2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left; + + if (titleSize.height > contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom) { + titleSize.height = contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom; + } + + // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的Insets,计算时都是以图片+文字这个整体作为考虑对象 + titleOrigin.y = (contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + imageSize.height + self.contentEdgeInsets.top + self.titleEdgeInsets.top + _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; +} + +// ----------------------------------------------------- bottom ----------------------------------------------------- + +- (CGRect)imageRectImageAtBottomForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + CGSize titleSize = titleRect.size; + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + + CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; + // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView + if (imageSize.width > imageSafeWidth) { + imageSize.width = imageSafeWidth; + } + + imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - imageSize.width) / 2.0 + self.contentEdgeInsets.left + self.imageEdgeInsets.left; + + // 给图片高度作最大限制,超出限制对高度进行压缩,这样还可以保证titeLabel不会超出其有效区域 + CGFloat imageTitleLimitMaxH = contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom; + if (imageSize.height < imageTitleLimitMaxH) { + if (titleSize.height + self.currentImage.size.height > imageTitleLimitMaxH) { + CGFloat beyondValue = titleSize.height + self.currentImage.size.height - imageTitleLimitMaxH; + imageSize.height = imageSize.height - beyondValue; + } + // 之所以采用自己计算的结果,是因为当sizeToFit且titleLabel的numberOfLines > 0时,系统内部会按照2行计算 + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + } + // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的insets,计算时都是以图片+文字这个整体作为考虑对象 + imageOrigin.y = (contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + titleSize.height + self.contentEdgeInsets.top + self.imageEdgeInsets.top + _imageTitleSpace * 0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; +} + +- (CGRect)titleRectImageAtBottomForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + + CGSize imageSize = imageRect.size; + // 这个if语句的含义是:计算图片由于设置了contentEdgeInsets而被压缩的高度,设置imageEdgeInsets被压缩的高度不计算在内。这样做的目的是,当设置了contentEdgeInsets时,图片可能会被压缩,此时titleLabel的y值依赖于图片压缩后的高度,当设置了imageEdgeInsets时,图片也可能被压缩,此时titleLabel的y值依赖于图片压缩前的高度,这样一来,设置imageEdgeInsets就不会对titleLabel的y值产生影响 + if (self.currentImage.size.height + titleSize.height > contentRect.size.height) { + imageSize.height = self.currentImage.size.height - (self.currentImage.size.height + titleSize.height - contentRect.size.height); + if (imageSize.height < 0) { + imageSize.height = 0; + } + } + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + // titleLabel的安全宽度,因为当外界设置了titleEdgeInsets值时,系统计算出来的所有值都是在”左图右文“的基础上进行的,这个基础上可能会导致titleLabel的宽度被压缩,所以我们在此自己重新计算 + CGFloat titleSafeWidth = contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right; + if (titleSize.width > titleSafeWidth) { + titleSize.width = titleSafeWidth; + } + + titleOrigin.x = (titleSafeWidth - titleSize.width) / 2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left; + + if (titleSize.height > contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom) { + titleSize.height = contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom; + } + + // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的Insets,计算时都是以图片+文字这个整体作为考虑对象 + titleOrigin.y = (contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + self.contentEdgeInsets.top + self.titleEdgeInsets.top - _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; +} + +// 自己计算titleLabel的大小 +- (CGSize)calculateTitleSizeForSystemTitleSize:(CGSize)titleSize { + CGSize myTitleSize = titleSize; + // 获取按钮里的titleLabel,之所以遍历获取而不直接调用self.titleLabel,是因为假如这里是第一次调用self.titleLabel,则会跟titleRectForContentRect: 方法造成死循环,titleLabel的getter方法中,alloc init之前调用了titleRectForContentRect: + UILabel *titleLabel = [self findTitleLabel]; + if (!titleLabel) { // 此时还没有创建titleLabel,先通过系统button的字体进行文字宽度计算 + CGFloat fontSize = [UIFont buttonFontSize]; // 按钮默认字体,18号 + // 说明外界使用了被废弃的font属性,被废弃但是依然生效 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (self.font.pointSize != [UIFont buttonFontSize]) { + fontSize = self.font.pointSize; + } +#pragma clang diagnostic pop + myTitleSize.height = ceil([self.currentTitle boundingRectWithSize:CGSizeMake(titleSize.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]} context:nil].size.height); + // 根据文字计算宽度,取上整,补齐误差,保证跟titleLabel.intrinsicContentSize.width一致 + myTitleSize.width = ceil([self.currentTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, titleSize.height) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]} context:nil].size.width); + } else { // 说明此时titeLabel已经产生,直接取titleLabel的内容宽度 + myTitleSize.width = titleLabel.intrinsicContentSize.width; + myTitleSize.height = titleLabel.intrinsicContentSize.height; + } + return myTitleSize; +} + +// 遍历获取按钮里面的titleLabel +- (UILabel *)findTitleLabel { + for (UIView *subView in self.subviews) { + if ([subView isKindOfClass:NSClassFromString(@"UIButtonLabel")]) { + UILabel *titleLabel = (UILabel *)subView; + return titleLabel; + } + } + return nil; +} + + + +#pragma mark - setter +// 以下所有setter方法中都调用了layoutSubviews, 其实是为了间接的调用imageRectForContentRect:和titleRectForContentRect:,不能直接调用imageRectForContentRect:和titleRectForContentRect:,因为按钮的子控件布局最终都是通过调用layoutSubviews而确定,如果直接调用这两个方法,那么只能保证我们能够获取的CGRect是对的,但并不会作用在titleLabel和imageView上 +- (void)setImagePosition:(SPItemImagePosition)imagePosition { + _imagePosition = imagePosition; + [self setNeedsLayout]; } - (void)setImageTitleSpace:(CGFloat)imageTitleSpace { _imageTitleSpace = imageTitleSpace; - [self setNeedsDisplay]; + [self setNeedsLayout]; +} + +- (void)setContentHorizontalAlignment:(UIControlContentHorizontalAlignment)contentHorizontalAlignment { + [super setContentHorizontalAlignment:contentHorizontalAlignment]; + [self setNeedsLayout]; } -- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets { - [super setContentEdgeInsets:contentEdgeInsets]; - [self setNeedsDisplay]; +// 垂直方向的排列方式在设置之前如果调用了titleLabel或imageView的getter方法,则设置后不会生效,点击一下按钮之后就生效了,这应该属于按钮的一个小bug,我们只要重写它的setter方法重新布局一次就好 +- (void)setContentVerticalAlignment:(UIControlContentVerticalAlignment)contentVerticalAlignment { + [super setContentVerticalAlignment:contentVerticalAlignment]; + [self setNeedsLayout]; } @end @@ -211,9 +408,9 @@ @interface SPPageMenu() @property (nonatomic, weak) UIImageView *backgroundImageView; @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; -@property (nonatomic, weak) SPPageMenuItem *functionButton; +@property (nonatomic, weak) SPPageMenuButton *functionButton; @property (nonatomic, strong) NSMutableArray *buttons; -@property (nonatomic, strong) SPPageMenuItem *selectedButton; +@property (nonatomic, strong) SPPageMenuButton *selectedButton; @property (nonatomic, strong) NSMutableDictionary *setupWidths; @property (nonatomic, assign) BOOL insert; // 起始偏移量,为了判断滑动方向 @@ -223,10 +420,12 @@ @interface SPPageMenu() @property (nonatomic, assign) CGFloat startR; @property (nonatomic, assign) CGFloat startG; @property (nonatomic, assign) CGFloat startB; +@property (nonatomic, assign) CGFloat startA; /// 完成颜色, 取值范围 0~1 @property (nonatomic, assign) CGFloat endR; @property (nonatomic, assign) CGFloat endG; @property (nonatomic, assign) CGFloat endB; +@property (nonatomic, assign) CGFloat endA; // 这个高度,是存储itemScrollView的高度 @property (nonatomic, assign) CGFloat itemScrollViewH; @@ -261,11 +460,11 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex 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) { + for (SPPageMenuButton *button in self.buttons) { [button removeFromSuperview]; } } @@ -273,16 +472,16 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex for (int i = 0; i < items.count; i++) { id object = items[i]; - NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]], @"items中的元素只能是NSString或UIImage类型"); + NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]] || [object isKindOfClass:[SPPageMenuButtonItem class]], @"items中的元素类型只能是NSString、UIImage或SPPageMenuButtonItem"); [self addButton:i object:object animated:NO]; } [self setNeedsLayout]; [self layoutIfNeeded]; - + if (self.buttons.count) { // 默认选中selectedItemIndex对应的按钮 - SPPageMenuItem *selectedButton = [self.buttons objectAtIndex:selectedItemIndex]; + SPPageMenuButton *selectedButton = [self.buttons objectAtIndex:selectedItemIndex]; [self buttonInPageMenuClicked:selectedButton]; // SPPageMenuTrackerStyleTextZoom和SPPageMenuTrackerStyleNothing样式跟tracker没有关联 @@ -318,10 +517,22 @@ - (void)insertItemWithImage:(UIImage *)image atIndex:(NSUInteger)itemIndex anima } } +- (void)insertItem:(SPPageMenuButtonItem *)item 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:item atIndex:itemIndex]; + self.items = objects.copy; + [self addButton:itemIndex object:item 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) { + for (SPPageMenuButton *button in self.buttons) { if (button.tag-tagBaseValue > itemIndex) { button.tag = button.tag - 1; } @@ -333,7 +544,7 @@ - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { self.items = objects.copy; } if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; if (button == self.selectedButton) { // 如果删除的正是选中的item,删除之后,选中的按钮切换为上一个item self.selectedItemIndex = itemIndex > 0 ? itemIndex-1 : itemIndex; } @@ -353,7 +564,7 @@ - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { } else { [self setNeedsLayout]; } - + } - (void)removeAllItems { @@ -361,25 +572,25 @@ - (void)removeAllItems { [objects removeAllObjects]; self.items = objects.copy; self.items = nil; - + for (int i = 0; i < self.buttons.count; i++) { - SPPageMenuItem *button = self.buttons[i]; + SPPageMenuButton *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]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setImage:nil forState:UIControlStateNormal]; [button setTitle:title forState:UIControlStateNormal]; @@ -391,16 +602,17 @@ - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { } - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; - return item.currentTitle; + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + NSAssert([object isKindOfClass:[NSString class]],@"itemIndex对应的item不是NSString类型,请仔细核对"); + return object; } return nil; } - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:nil forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; @@ -412,24 +624,59 @@ - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { } - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + NSAssert([object isKindOfClass:[UIImage class]],@"itemIndex对应的item不是UIImage类型,请仔细核对"); + return object; + } + return nil; +} + +- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; - return item.currentImage; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + [button setTitle:item.title forState:UIControlStateNormal]; + [button setImage:item.image forState:UIControlStateNormal]; + button.imagePosition = item.imagePosition; + button.imageTitleSpace = item.imageTitleSpace; + + if (item != nil) { + NSMutableArray *items = self.items.mutableCopy; + [items replaceObjectAtIndex:itemIndex withObject:item]; + self.items = items.copy; + } + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + +- (SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + NSAssert([object isKindOfClass:[SPPageMenuButtonItem class]],@"itemIndex对应的item不是SPPageMenuButtonItem类型,请仔细核对"); + return object; } return nil; } +- (id)objectForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + return object; + } + return nil; +} - (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setEnabled:enaled]; } } - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex { if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; return button.enabled; } return YES; @@ -447,7 +694,7 @@ - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { return setupWidth; } else { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; return button.bounds.size.width; } } @@ -456,21 +703,21 @@ - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *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]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; button.contentEdgeInsets = contentInset; } } - (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; return button.contentEdgeInsets; } return UIEdgeInsetsZero; @@ -501,13 +748,12 @@ - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadi - (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]; + SPPageMenuButton *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; @@ -522,7 +768,7 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } - + [self setNeedsLayout]; [self layoutIfNeeded]; } @@ -532,19 +778,23 @@ - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImag [self.functionButton setTitle:title forState:state]; [self.functionButton setImage:image forState:state]; self.functionButton.imagePosition = imagePosition; - self.functionButton.imageRatio = ratio; self.functionButton.imageTitleSpace = imageTitleSpace; } +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state { + [self.functionButton setTitle:item.title forState:state]; + [self.functionButton setImage:item.image forState:state]; + self.functionButton.imagePosition = item.imagePosition; + self.functionButton.imageTitleSpace = item.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]; + SPPageMenuButton *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; @@ -559,7 +809,7 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag [items replaceObjectAtIndex:itemIndex withObject:image]; self.items = items.copy; } - + [self setNeedsLayout]; [self layoutIfNeeded]; } @@ -568,7 +818,6 @@ - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImag [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 { @@ -584,33 +833,39 @@ - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes } - (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) { + for (SPPageMenuButton *button in self.buttons) { if (button.tag-tagBaseValue >= index) { button.tag = button.tag + 1; // 由于有新button的加入,新button后面的button的tag值得+1 } } - SPPageMenuItem *button = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; + SPPageMenuButton *button = [SPPageMenuButton buttonWithType:UIButtonTypeCustom]; [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; - button.titleLabel.font = _itemTitleFont; + button.titleLabel.font = _unSelectedItemTitleFont; // 此时必然还没有选中任何按钮,设置_unSelectedItemTitleFont就相是设置所有按钮的文字颜色,这里不能用_itemTitleFont,如果外界先设置_unSelectedItemTitleFont,再创建按钮,假如这里使用_itemTitleFont,那外界设置的_unSelectedItemTitleFont就不生效 [button addTarget:self action:@selector(buttonInPageMenuClicked:) forControlEvents:UIControlEventTouchUpInside]; button.tag = tagBaseValue + index; if ([object isKindOfClass:[NSString class]]) { [button setTitle:object forState:UIControlStateNormal]; - } else { + } else if ([object isKindOfClass:[UIImage class]]) { [button setImage:object forState:UIControlStateNormal]; + } else { + SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)object; + [button setTitle:item.title forState:UIControlStateNormal]; + [button setImage:item.image forState:UIControlStateNormal]; + button.imagePosition = item.imagePosition; + button.imageTitleSpace = item.imageTitleSpace; } if (self.insert) { if ([self haveOrNeedsTracker]) { @@ -630,12 +885,12 @@ - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { [self.itemScrollView insertSubview:button atIndex:index]; } [self.buttons insertObject:button atIndex:index]; - + // setNeedsLayout会标记为需要刷新,layoutIfNeeded只有在有标记的情况下才会立即调用layoutSubViews,当然标记为刷新并非只有调用setNeedsLayout,如frame改变,addSubView等都会标记为刷新 - + if (self.insert && animated) { // 是插入的新按钮,且需要动画 // 取出上一个按钮 - SPPageMenuItem *lastButton; + SPPageMenuButton *lastButton; if (index > 0) { lastButton = self.buttons[index-1]; } @@ -672,15 +927,14 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { } - (void)initialize { - - _itemPadding = 30; + _itemPadding = 30.0; _selectedItemTitleColor = [UIColor redColor]; _unSelectedItemTitleColor = [UIColor blackColor]; _selectedItemTitleFont = [UIFont systemFontOfSize:16]; _unSelectedItemTitleFont = [UIFont systemFontOfSize:16]; _itemTitleFont = [UIFont systemFontOfSize:16]; - _trackerHeight = 3; - _dividingLineHeight = 1 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 + _trackerHeight = 3.0; + _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; _showFunctionButton = NO; @@ -688,7 +942,7 @@ - (void)initialize { _functionButtonShadowColor = [UIColor blackColor]; _selectedItemZoomScale = 1; _needTextColorGradients = YES; - + [self setupSubViews]; } @@ -702,16 +956,16 @@ - (void)setupSubViews { }; [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; @@ -724,7 +978,7 @@ - (void)setupSubViews { [backgroundView addSubview:itemScrollView]; _itemScrollView = itemScrollView; - SPPageMenuItem *functionButton = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; + SPPageMenuButton *functionButton = [SPPageMenuButton buttonWithType:UIButtonTypeCustom]; functionButton.backgroundColor = [UIColor whiteColor]; [functionButton setTitle:@"+" forState:UIControlStateNormal]; [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; @@ -739,7 +993,7 @@ - (void)setupSubViews { } // 按钮点击方法 -- (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { +- (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue; NSInteger toIndex = sender.tag - tagBaseValue; // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的,必须用下划线,用self.会造成死循环 @@ -750,14 +1004,14 @@ - (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { [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还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 + for (SPPageMenuButton *button in self.buttons) { // 必须遍历将非选中按钮还原缩放,而不是仅仅只让上一个选中的按钮还原缩放。因为当用户快速滑动外界scrollView时,会频繁的调用-zoomForTitleWithProgress:fromButton:toButton:方法,有可能经过的某一个button还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 if (button != sender && !CGAffineTransformEqualToTransform(button.transform, CGAffineTransformIdentity)) { button.transform = CGAffineTransformIdentity; } @@ -775,32 +1029,13 @@ - (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { [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 { +- (void)moveItemScrollViewWithSelectedButton:(SPPageMenuButton *)selectedButton { if (CGRectEqualToRect(self.backgroundView.frame, CGRectZero)) { return; } @@ -808,7 +1043,7 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { 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 @@ -821,11 +1056,11 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { } [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES]; - + } // 移动跟踪器 -- (void)moveTrackerWithSelectedButton:(SPPageMenuItem *)selectedButton { +- (void)moveTrackerWithSelectedButton:(SPPageMenuButton *)selectedButton { [UIView animateWithDuration:0.25 animations:^{ [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; }]; @@ -841,7 +1076,7 @@ - (void)delegatePerformMethodWithFromIndex:(NSUInteger)fromIndex toIndex:(NSUInt } // 功能按钮的点击方法 -- (void)functionButtonClicked:(SPPageMenuItem *)sender { +- (void)functionButtonClicked:(SPPageMenuButton *)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:functionButtonClicked:)]) { [self.delegate pageMenu:self functionButtonClicked:sender]; } @@ -908,8 +1143,8 @@ - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { // 这个方法才开始移动跟踪器 [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; } else if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeHalf) { - SPPageMenuItem *fromButton; - SPPageMenuItem *toButton; + SPPageMenuButton *fromButton; + SPPageMenuButton *toButton; if (progress > 0.5) { if (toIndex >= 0 && toIndex < self.buttons.count) { toButton = self.buttons[toIndex]; @@ -941,12 +1176,12 @@ - (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex 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) { @@ -985,7 +1220,7 @@ - (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex if (_selectedItemZoomScale != 1) { [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; } - + } else if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || self.trackerStyle == SPPageMenuTrackerStyleNothing) { // 缩放文字 if (_selectedItemZoomScale != 1) { @@ -1020,48 +1255,45 @@ - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton 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]; - + CGFloat a = self.endA - self.startA; + UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:self.startA + a * fromProgress]; + UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:self.startA + a * toProgress]; + // 设置文字颜色渐变 [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; - } +- (NSArray *)getRGBForColor:(UIColor *)color { + CGFloat red = 0.0; + CGFloat green = 0.0; + CGFloat blue = 0.0; + CGFloat alpha = 0.0; + [color getRed:&red green:&green blue:&blue alpha:&alpha]; + return @[@(red), @(green), @(blue), @(alpha)]; } /// 开始颜色设置 - (void)setupStartColor:(UIColor *)color { - CGFloat components[3]; - [self getRGBComponents:components forColor:color]; - self.startR = components[0]; - self.startG = components[1]; - self.startB = components[2]; + NSArray *components = [self getRGBForColor:color]; + self.startR = [components[0] floatValue]; + self.startG = [components[1] floatValue]; + self.startB = [components[2] floatValue]; + self.startA = [components[3] floatValue]; } /// 结束颜色设置 - (void)setupEndColor:(UIColor *)color { - CGFloat components[3]; - [self getRGBComponents:components forColor:color]; - self.endR = components[0]; - self.endG = components[1]; - self.endB = components[2]; + NSArray *components = [self getRGBForColor:color]; + self.endR = [components[0] floatValue]; + self.endG = [components[1] floatValue]; + self.endB = [components[2] floatValue]; + self.endA = [components[3] floatValue]; } - (void)zoomForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton { @@ -1089,7 +1321,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N - (void)setBridgeScrollView:(UIScrollView *)bridgeScrollView { _bridgeScrollView = bridgeScrollView; if (bridgeScrollView) { - + [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil]; } else { NSLog(@"你传了一个空的scrollView"); @@ -1183,7 +1415,7 @@ - (void)setItemTitleFont:(UIFont *)itemTitleFont { _itemTitleFont = itemTitleFont; _selectedItemTitleFont = itemTitleFont; _unSelectedItemTitleFont = itemTitleFont; - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { button.titleLabel.font = itemTitleFont; } [self setNeedsLayout]; @@ -1194,7 +1426,7 @@ - (void)setItemTitleFont:(UIFont *)itemTitleFont { - (void)setUnSelectedItemTitleFont:(UIFont *)unSelectedItemTitleFont { _unSelectedItemTitleFont = unSelectedItemTitleFont; - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { if (button == _selectedButton) { continue; } @@ -1225,7 +1457,7 @@ - (void)setSelectedItemTitleColor:(UIColor *)selectedItemTitleColor { - (void)setUnSelectedItemTitleColor:(UIColor *)unSelectedItemTitleColor { _unSelectedItemTitleColor = unSelectedItemTitleColor; [self setupEndColor:unSelectedItemTitleColor]; - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { if (button == _selectedButton) { continue; // 跳过选中的那个button } @@ -1236,7 +1468,7 @@ - (void)setUnSelectedItemTitleColor:(UIColor *)unSelectedItemTitleColor { - (void)setSelectedItemIndex:(NSInteger)selectedItemIndex { _selectedItemIndex = selectedItemIndex; if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:selectedItemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:selectedItemIndex]; [self buttonInPageMenuClicked:button]; } } @@ -1245,7 +1477,7 @@ - (void)setDelegate:(id)delegate { if (delegate == _delegate) {return;} _delegate = delegate; if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:_selectedItemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:_selectedItemIndex]; [self delegatePerformMethodWithFromIndex:button.tag-tagBaseValue toIndex:button.tag-tagBaseValue]; [self moveItemScrollViewWithSelectedButton:button]; } @@ -1285,16 +1517,16 @@ - (NSArray *)items { } - (NSMutableArray *)buttons { - + if (!_buttons) { _buttons = [NSMutableArray array]; - + } return _buttons; } - (NSMutableDictionary *)setupWidths { - + if (!_setupWidths) { _setupWidths = [NSMutableDictionary dictionary]; } @@ -1302,7 +1534,7 @@ - (NSMutableDictionary *)setupWidths { } - (UIImageView *)tracker { - + if (!_tracker) { _tracker = [[UIImageView alloc] init]; _tracker.layer.cornerRadius = _trackerHeight * 0.5; @@ -1319,14 +1551,14 @@ - (NSUInteger)numberOfItems { - (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; @@ -1348,46 +1580,42 @@ - (void)layoutSubviews { CGFloat itemScrollViewW = self.showFunctionButton ? 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]; + SPPageMenuButton *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; + textW = ceil([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; + textW = ceil([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; - } + CGFloat imageW = button.currentImage.size.width; + CGFloat imageH = button.currentImage.size.height; + if (imageH > itemScrollViewH) { + imageH = itemScrollViewH; } if (button.currentTitle && !button.currentImage) { - contentW = textW; + contentW = textW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if(button.currentImage && !button.currentTitle) { - contentW = imageW; - } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft)) { - contentW = textW + imageW; + contentW = imageW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; + } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft || button.imagePosition == SPItemImagePositionDefault)) { + contentW = textW + imageW + button.imageTitleSpace+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) { - contentW = MAX(textW, imageW); + contentW = MAX(textW, imageW)+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } if (setupButtonW) { contentW_sum += setupButtonW; @@ -1399,7 +1627,7 @@ - (void)layoutSubviews { } CGFloat diff = itemScrollViewW - contentW_sum; - [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuItem *button, NSUInteger idx, BOOL * _Nonnull stop) { + [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuButton *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]; @@ -1422,10 +1650,14 @@ - (void)layoutSubviews { } else { button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); } - + } else { - self->_itemPadding = diff/self.buttons.count; buttonW = [buttonWidths[idx] floatValue]; + self->_itemPadding = diff/self.buttons.count; + if (self->_itemPadding < 0) { // 如果总内容长度大于pageMenu的长度,则对每个按钮宽度进行均等压缩 + buttonW = buttonW - fabs(diff) / self.buttons.count; + self->_itemPadding = 0; + } if (idx == 0) { button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); } else { @@ -1434,7 +1666,7 @@ - (void)layoutSubviews { } lastButtonMaxX = CGRectGetMaxX(button.frame); }]; - + // 如果selectedButton有缩放,走完上面代码selectedButton的frame会还原,这会导致文字显示不全问题,为了解决这个问题,这里将selectedButton的frame强制缩放 if (!CGAffineTransformEqualToTransform(self.selectedButton.transform, CGAffineTransformIdentity)) { CGRect selectedButtonRect = self.selectedButton.frame; @@ -1445,24 +1677,25 @@ - (void)layoutSubviews { } [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 { - +- (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)selectedButton { + CGFloat trackerX; CGFloat trackerY; CGFloat trackerW; CGFloat trackerH; - + CGFloat selectedButtonWidth = selectedButton.frame.size.width; - + switch (self.trackerStyle) { case SPPageMenuTrackerStyleLine: { @@ -1516,7 +1749,7 @@ - (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuItem *)selectedButto default: break; } - + CGPoint trackerCenter = self.tracker.center; trackerCenter.x = selectedButton.center.x; self.tracker.center = trackerCenter; @@ -1526,6 +1759,42 @@ - (void)dealloc { [self.bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; } + +@end + +@implementation SPPageMenuButtonItem + ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image { + SPPageMenuButtonItem *item = [[SPPageMenuButtonItem alloc] initWithTitle:title image:image imagePosition:SPItemImagePositionDefault]; + return item; +} + ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition { + SPPageMenuButtonItem *item = [[SPPageMenuButtonItem alloc] initWithTitle:title image:image imagePosition:imagePosition]; + return item; +} + +- (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition { + if (self = [super init]) { + self.title = title; + self.image = image; + self.imagePosition = imagePosition; + } + return self; +} + +- (instancetype)init { + if (self = [super init]) { + [self initialize]; + } + return self; +} + +- (void)initialize { + _imagePosition = SPItemImagePositionDefault; + _imageTitleSpace = 0.0; +} + @end #pragma clang diagnostic pop diff --git a/README.md b/README.md index df601a3..e53411e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # SPPageMenu + [![Build Status](http://img.shields.io/travis/SPStore/SPPageMenu.svg?style=flat)](https://travis-ci.org/SPStore/SPPageMenu) [![Pod Version](http://img.shields.io/cocoapods/v/SPPageMenu.svg?style=flat)](http://cocoadocs.org/docsets/SPPageMenu/) [![Pod Platform](http://img.shields.io/cocoapods/p/SPPageMenu.svg?style=flat)](http://cocoadocs.org/docsets/SPPageMenu/) @@ -8,13 +9,49 @@ ![codecov](https://img.shields.io/badge/codecov-88%25-orange.svg) # 目录 -* [如何安装](#如何安装) -* [部分功能演示图](#部分功能演示图) -* [重难点讲解](#重难点讲解) -* [使用者提问](#使用者提问) + +- [如何安装](#如何安装) +- [部分功能演示图](#部分功能演示图) +- [重难点讲解](#重难点讲解) +- [使用者提问](#使用者提问) ## 如何安装 -#### 版本3.4.1 + +#### 版本 3.4.5 + +``` +target 'MyApp' do + pod 'SPPageMenu', '~> 3.4.5' +end + +说明:3.4.5版本在3.4.4版本的基础上修复了先设置unSelectedItemTitleFont,再设置items文字显示不全问题 +``` + +#### 版本 3.4.4 + +``` +target 'MyApp' do + pod 'SPPageMenu', '~> 3.4.4' +end + +说明:3.4.4版本在3.4.2版本的基础上改动如下: +1、重构了内部自定义按钮 +2、解决了标题颜色的alpha值小于1时颜色渐变不准确问题 +3、新增SPPageMenuButtonItem模型,用于同时设置文字和图片 +``` + +#### 版本 3.4.2 + +``` +target 'MyApp' do + pod 'SPPageMenu', '~> 3.4.2' +end + +说明:3.4.2版本在3.4.1版本的基础上,trackStyle属性支持storyBoard/xib,方便在storyBoard/xib中创建时可以直接设置 +``` + +#### 版本 3.4.1 + ``` target 'MyApp' do  pod 'SPPageMenu', '~> 3.4.1' @@ -23,7 +60,8 @@ end 3.4.1版本在3.4.0版本的基础上修复了多次调用setItems:selectedItemIndex:方法引发的问题 ``` -#### 版本3.4.0 +#### 版本 3.4.0 + ``` target 'MyApp' do  pod 'SPPageMenu', '~> 3.4.0' @@ -37,7 +75,8 @@ end 5、优化代码 ``` -#### 版本3.0 +#### 版本 3.0 + ``` target 'MyApp' do  pod 'SPPageMenu', '~> 3.0' @@ -57,7 +96,8 @@ end 11、修复长按按钮然后滑动scrollView无法滑动问题 ``` -#### 版本2.5.5 +#### 版本 2.5.5 + ``` target 'MyApp' do  pod 'SPPageMenu', '~> 2.5.5' @@ -71,58 +111,69 @@ end 4、可以设置分割线高度 ``` -##### 版本2.5.3 +##### 版本 2.5.3 + ``` target 'MyApp' do pod 'SPPageMenu', '~> 2.5.3' end ``` + ## 部分功能演示图 -(友情提示:如果您的网络较慢,gif图可能会延迟加载,您可以先把宝贵的时间浏览其它信息) + +(友情提示:如果您的网络较慢,gif 图可能会延迟加载,您可以先把宝贵的时间浏览其它信息) ![image](https://github.com/SPStore/SPPageMenu/blob/master/3006981-889f087b55f3e57f.gif) + ## 重难点讲解 + ``` // 该属性是选中的按钮下标,大家可以通过这个属性判断选择了第几个按钮,如果改变其值,可以用于切换选中的按钮 -@property (nonatomic) NSInteger selectedItemIndex; +@property (nonatomic) NSInteger selectedItemIndex; ``` + ``` // 这个scrollView是外界传进来的scrollView,通常的案例都是在pageMenu的下方有若干个子控制器在切换,子控制器的切换由滑动 scrollView实现,使用者只需要把该scrollView传给bridgeScrollView,SPPageMenu框架内部会监听该scrollView的横向滚动,实 现了让跟踪器时刻跟随该scrollView滚动的效果。暂时不支持监听垂直方向的滚动,如果你的scrollVeiw要垂直滚动实现切换按钮,你 -不妨可以尝试在-scrollViewDidScroll:代理方法中设置selectedItemIndex的值 +不妨可以尝试自己在-scrollViewDidScroll:代理方法中设置selectedItemIndex的值 @property (nonatomic, strong) UIScrollView *bridgeScrollView; ``` + ``` -// 排列方式:支持3中排列方式;1、可滑动,按钮宽度根据内容自适应;2、不可滑动,按钮等宽;3、不可滑动,按钮宽度根据内容自 +// 排列方式:支持3种排列方式;1、可滑动,按钮宽度根据内容自适应;2、不可滑动,按钮等宽;3、不可滑动,按钮宽度根据内容自 适应。3种排列方式都有非常高的使用频率。第1种排列方式:SPPageMene的容量会根据按钮个数而定;第2种和第3种排列方式:SPPageMenu 的容量固定为SPPageMenu的宽度 -@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; +@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; ``` + ``` // 跟踪器跟踪模式;这个属性从3.4版本开始闪亮登场。该属性是个枚举,共3个:1、SPPageMenuTrackerFollowingModeAlways,这个 -枚举值的意思是让跟踪器时刻跟随外界scrollView(即bridgeScrollView)横向移; 2、SPPageMenuTrackerFollowingModeEnd,这个 +枚举值的意思是让跟踪器时刻跟随外界scrollView(即bridgeScrollView)横向移动; 2、SPPageMenuTrackerFollowingModeEnd,这个 枚举的意思是当外界scrollView滑动结束时,跟踪器才开始移动;相当于3.4版本之前的closeTrackerFollowingMode属性 3、 SPPageMenuTrackerFollowingModeHalf,这个枚举的意思是当外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动。 @property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; ``` + ``` // 内容的四周内边距(内容不包括分割线),默认UIEdgeInsetsZero;这个属性是个惊喜,往往能做到一些你意想不到的事情。假如你的 -SPPageMenu控件高度固定不变,想要设置跟踪器距离按钮之间的垂直间距变小,就可以设置该属性的top和bottom值,让SPPageMenu的 +SPPageMenu控件高度固定不变,想要设置跟踪器与按钮之间的垂直间距变小,就可以设置该属性的top和bottom值,让SPPageMenu的 内容在垂直方向上内缩 -@property (nonatomic, assign) UIEdgeInsets contentInset; +@property (nonatomic, assign) UIEdgeInsets contentInset; ``` + ``` // 该方法的效果和属性bridgeScrollView功能一致,如果外界想通过该方法实现跟踪器时刻跟随scrollView移动,可以在代理方法 -scrollViewDidScroll:中调用该方法,如果方法和属性同时实现,属性优先级更高 - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; ``` + ``` // 代理方法:若以下2个代理方法同时实现了,只会走第2个代理方法(第2个代理方法包含了第1个代理方法的功能) // 代理方法何时触发?代理方法有2种方式会触发,第一种是点击了SPPageMenu的按钮,这种方式无论点击的按钮是否同一个都会触发; @@ -133,18 +184,23 @@ SPPageMenu控件高度固定不变,想要设置跟踪器距离按钮之间的 - (void)pageMenu:(SPPageMenu *)pageMenu itemSelectedAtIndex:(NSInteger)index; - (void)pageMenu:(SPPageMenu *)pageMenu itemSelectedFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; ``` -### 想了解更多使用细节,大家可以把demo下载到本地,里面有非常多的示例以及详细的注释 + +### 想了解更多使用细节,大家可以把 demo 下载到本地,里面有非常多的示例以及详细的注释 # 使用者提问 -* **问**:当我设置排列方式为按钮等宽(即SPPageMenuPermutationWayNotScrollEqualWidths),为什么按钮文字显示不全?
- **答**:这是因为你的按钮个数较多或者文字较长,你可以通过设置itemPadding属性来调整按钮之间的间距,间距调小,每个按钮的宽度就会增大, - 如果itemPadding设置为0仍然显示不全,那就请选择其它排列方式。 - -* **问**:如何不通过点击按钮或者滑动外界scrollView来实现选中按钮的切换 ?
- **答**:你可以改变selectedItemIndex的值切换选中按钮 - -* **问**:如何在不改变pageMenu高度的情况下,让跟踪器和按钮之间的垂直间距变小 ?
- **答**:你可以通过设置contentInset的top和bottom值,让pageMenu的内容在垂直方向上往中间挤压 -[回到顶部](#目录) +- **问**:当我设置排列方式为按钮等宽(即 SPPageMenuPermutationWayNotScrollEqualWidths),为什么按钮文字显示不全?
+ **答**:这是因为你的按钮个数较多或者文字较长,你可以通过设置 itemPadding 属性来调整按钮之间的间距,间距调小,每个按钮的宽度就会增大, + 如果 itemPadding 设置为 0 仍然显示不全,那就请选择其它排列方式。 +- **问**:如何不通过点击按钮或者滑动外界 scrollView 来实现选中按钮的切换 ?
+ **答**:你可以改变 selectedItemIndex 的值切换选中按钮 + +- **问**:如何在不改变 pageMenu 高度的情况下,让跟踪器和按钮之间的垂直间距变小 ?
+ **答**:你可以通过设置 contentInset 的 top 和 bottom 值,让 pageMenu 的内容在垂直方向上往中间挤压 +- **问**:如何给按钮设置角标 ?
+ **答**:本框架并未单独提供设置角标的方法,因为设置角标是一个比较大的工程,如果提供角标,又得给角标提供各种属性设置,这将会把本框架搞的非常臃肿,这也 + 不是本框架的重点内容,但是也不是不可以设置,你可以通过 KVC 获取按钮的数组 buttons,然后通过该数组获取指定按钮,拿到该按钮就可以设置角标,具体 + 事例 demo 中有示范 + +[回到顶部](#目录) diff --git a/SPPageMenu.podspec b/SPPageMenu.podspec index b15b966..924a9c8 100644 --- a/SPPageMenu.podspec +++ b/SPPageMenu.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "SPPageMenu" - s.version = "3.4.1" + s.version = "3.4.5" s.summary = "分页菜单." # This description is used to generate tags and improve search results. diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index 9f3c1b0..241b96d 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -33,14 +33,14 @@ typedef NS_ENUM(NSInteger, SPPageMenuTrackerFollowingMode) { }; typedef NS_ENUM(NSInteger, SPItemImagePosition) { - SPItemImagePositionDefault, // 默认图片在左边 - SPItemImagePositionLeft, // 图片在左边 - SPItemImagePositionTop, // 图片在上面 - SPItemImagePositionRight, // 图片在右边 - SPItemImagePositionBottom // 图片在下面 + SPItemImagePositionDefault, // 默认图片在左侧 + SPItemImagePositionLeft, // 图片在文字左侧 + SPItemImagePositionRight, // 图片在文字右侧 + SPItemImagePositionTop, // 图片在文字上侧 + SPItemImagePositionBottom // 图片在文字下侧 }; -@class SPPageMenu; +@class SPPageMenu,SPPageMenuButtonItem; @protocol SPPageMenuDelegate @@ -62,7 +62,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { /** * 传递数据 * - * @param items 数组 (数组元素只能是NSString或UIImage类型) + * @param items 数组 (数组元素可以是NSString、UIImage类型、SPPageMenuButtonItem类型,其中SPPageMenuButtonItem相当于一个模型,可以同时设置图片和文字) * @param selectedItemIndex 默认选中item的下标 */ - (void)setItems:(nullable NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex; @@ -71,6 +71,12 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 +#if TARGET_INTERFACE_BUILDER +@property (nonatomic, readonly) IBInspectable NSInteger trackerStyle; // 该枚举属性支持storyBoard/xib,方便在storyBoard/xib中创建时直接设置 +#else +@property (nonatomic, readonly) SPPageMenuTrackerStyle trackerStyle; +#endif + // item之间的间距,默认30;当排列方式permutationWay为‘SPPageMenuPermutationWayNotScrollAdaptContent’时此属性无效,无效是合理的,不可能做到“不可滑动且自适应内容”然后间距又自定义,这2者相互制约; @property (nonatomic, assign) CGFloat itemPadding; @@ -101,9 +107,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 跟踪器的跟踪模式 @property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; -/** 代理 */ -@property (nonatomic, weak) id delegate; - // 分割线 @property (nonatomic, readonly) UIImageView *dividingLine; // 分割线,你可以拿到该对象设置一些自己想要的属性,如颜色、图片等,如果想要隐藏分割线,拿到该对象直接设置hidden为YES或设置alpha<0.01即可(eg:pageMenu.dividingLine.hidden = YES) @property (nonatomic) CGFloat dividingLineHeight; // 分割线的高度 @@ -114,10 +117,16 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { // 修改跟踪器样式 - (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle; +@property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO +@property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 +@property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 + +@property (nonatomic, weak) id delegate; // 插入item,插入和删除操作时,如果itemIndex超过了了items的个数,则不做任何操作 - (void)insertItemWithTitle:(nullable NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItem:(nullable SPPageMenuButtonItem *)item atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; // 如果移除的正是当前选中的item(当前选中的item下标不为0),删除之后,选中的item会切换为上一个item - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated; - (void)removeAllItems; @@ -128,6 +137,11 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setImage:(nullable UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的图片 +- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex; // 同时为指定item设置标题和图片,其中参数item相当于一个模型,可以同时设置文字和图片 +- (nullable SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex; // 获取指定item + +- (id)objectForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item,该方法获取的item可能是NSString、UIImage或SPPageMenuButtonItem类型 + - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的宽度(如果width为0,item会根据内容自动计算width) - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的宽度 @@ -141,34 +155,8 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setBackgroundImage:(nullable UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics; - (nullable UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics; // 获取背景图片 -/** - 同时为指定item设置标题和图片 - - @param title 标题 - @param image 图片 - @param imagePosition 图片的位置,分上、左、下、右 - @param ratio 图片所占item的比例,图片在左右时默认0.5,图片在上下时默认2.0/3.0 - @param imageTitleSpace 图片与标题之间的间距,默认0 - @param itemIndex item的下标 - */ -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex; - - -@property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 -@property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 - -/** - * 同时为functionButton设置标题和图片 - * - * @param title 标题 - * @param image 图片 - * @param imagePosition 图片的位置,分上、左、下、右 - * @param ratio 图片所占item的比例,图片在左右时默认0.5,图片在上下时默认2.0/3.0 - * @param imageTitleSpace 图片与标题之间的间距,默认0 - * @param state 控件状态 - */ -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state; +// 同时为functionButton设置标题和图片 +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state; // 为functionButton配置相关属性,如设置字体、文字颜色等;在此,attributes中,只有NSFontAttributeName、NSForegroundColorAttributeName、NSBackgroundColorAttributeName有效 - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state; @@ -182,21 +170,40 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { } 3.如果外界设置了SPPageMenu的属性"bridgeScrollView",那么外界就可以不用在scrollViewDidScroll方法中调用这个方法来实现跟踪器时刻跟随外界scrollView的效果,内部会自动处理; 外界对SPPageMenu的属性"bridgeScrollView"赋值是实现此效果的最简便的操作 - 4.如果不想要此效果,可设置closeTrackerFollowingMode==YES */ - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; -// -------------- 以下方法和属性被废弃 -------------- +// -------------- 以下方法和属性被废弃,不再建议使用 -------------- // 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.4.0版本已纠正 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContentEdgeInsets:forItemAtIndex:"); // 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.4.0版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 @property (nonatomic, assign) BOOL closeTrackerFollowingMode NS_DEPRECATED_IOS(6_0, 6_0,"Use trackerFollowingMode instead"); -// 以下2个方法从3.0版本开始有升级,可以使用但不推荐 -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setTitle:image:imagePosition:imageRatio:imageTitleSpace:forItemIndex:"); -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use -setFunctionButtonTitle:image:imagePosition:imageRatio:imageTitleSpace:forState:"); +// 下面的方法均有升级,其中ratio参数已失效 +- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); +- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); +- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); +- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); +@end + + +// 这个类相当于模型,主要用于同时为某个按钮设置图片和文字时使用 +@interface SPPageMenuButtonItem : NSObject + +// 快速创建同时含有标题和图片的item,默认图片在左边,文字在右边 ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image; +// 快速创建同时含有标题和图片的item,imagePositiona参数为图片位置 ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition; + +@property (nonatomic, copy) NSString *title; +@property (nonatomic, strong) UIImage *image; +// 图片的位置 +@property (nonatomic, assign) SPItemImagePosition imagePosition; +// 图片与标题之间的间距,默认0.0 +@property (nonatomic, assign) CGFloat imageTitleSpace; + @end NS_ASSUME_NONNULL_END diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index eca4bd1..eef4300 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -45,159 +45,356 @@ - (void)setAlpha:(CGFloat)alpha { @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 +@interface SPPageMenuButton : UIButton + +- (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition; + +@property (nonatomic) SPItemImagePosition imagePosition; // 图片位置 +@property (nonatomic, assign) CGFloat imageTitleSpace; // 图片和文字之间的间距 -@implementation SPPageMenuItem +@end +@implementation SPPageMenuButton -- (instancetype)initWithImageRatio:(CGFloat)ratio { +- (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition { if (self = [super init]) { - _imageRatio = ratio; + self.imagePosition = imagePosition; } return self; } -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { +#pragma mark - system methods +- (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; + _imagePosition = SPItemImagePositionLeft; + _imageTitleSpace = 0.0; } -- (void)setHighlighted:(BOOL)highlighted {} - +// 下面这2个方法,我所知道的: +// 在第一次调用titleLabel和imageView的getter方法(懒加载)时,alloc init之前会调用一次(无论有无图片文字都会直接调),因此,在重写这2个方法时,在方法里面不要使用self.imageView和self.titleLabel,因为这2个控件是懒加载,如果在重写的这2个方法里是第一调用imageView和titleLabel的getter方法, 则会造成死循环 +// 在layoutsSubviews中如果文字或图片不为空时会调用, 测试方式:在重写的这两个方法里调用setNeedsLayout(layutSubviews),发现会造成死循环 +// 设置文字图片、改动文字和图片、设置对齐方式,设置内容区域等时会调用,其实设置这些属性,系统是调用了layoutSubviews从而间接的去调用imageRectForContentRect:和titleRectForContentRect: +// ... - (CGRect)imageRectForContentRect:(CGRect)contentRect { + // 先获取系统为我们计算好的rect,这样大小图片在左右时我们就不要自己去计算,我门要改变的,仅仅是origin + CGRect imageRect = [super imageRectForContentRect:contentRect]; + CGRect titleRect = [super titleRectForContentRect:contentRect]; if (!self.currentTitle) { // 如果没有文字,则图片占据整个button,空格算一个文字 - return [super imageRectForContentRect:contentRect]; + return imageRect; } 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; - return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); + case SPItemImagePositionLeft: + case SPItemImagePositionDefault: { // 图片在左 + imageRect = [self imageRectImageAtLeftForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } 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); + case SPItemImagePositionRight: { + imageRect = [self imageRectImageAtRightForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } 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); + case SPItemImagePositionTop: { + imageRect = [self imageRectImageAtTopForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } 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); + imageRect = [self imageRectImageAtBottomForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; } break; - default: - break; } - return CGRectZero; + return imageRect; } - (CGRect)titleRectForContentRect:(CGRect)contentRect { + CGRect titleRect = [super titleRectForContentRect:contentRect]; + CGRect imageRect = [super imageRectForContentRect:contentRect]; if (!self.currentImage) { // 如果没有图片 - return [super titleRectForContentRect:contentRect]; + return titleRect; } 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); + case SPItemImagePositionLeft: + case SPItemImagePositionDefault: { + titleRect = [self titleRectImageAtLeftForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } 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); + case SPItemImagePositionRight: { + titleRect = [self titleRectImageAtRightForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } 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); + case SPItemImagePositionTop: { + titleRect = [self titleRectImageAtTopForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } 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); + titleRect = [self titleRectImageAtBottomForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; } break; - default: - break; } - return CGRectZero; + return titleRect; + +} + +#pragma - private +// ----------------------------------------------------- left ----------------------------------------------------- + +- (CGRect)imageRectImageAtLeftForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + // imageView的x值向左偏移间距的一半,另一半由titleLabe分担,不用管会不会超出contentRect,我定的规则是允许超出,如果对此作出限制,那么必须要对图片或者文字宽高有所压缩,压缩只能由imageEdgeInsets决定,当图片的内容区域容不下时才产生宽度压缩 + imageOrigin.x = imageOrigin.x - _imageTitleSpace*0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; } -- (void)setImagePosition:(SPItemImagePosition)imagePosition { - _imagePosition = imagePosition; - [self setNeedsDisplay]; +- (CGRect)titleRectImageAtLeftForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + + titleOrigin.x = titleOrigin.x + _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; } -- (void)setImageRatio:(CGFloat)imageRatio { - _imageRatio = imageRatio; - [self setNeedsDisplay]; +// ----------------------------------------------------- right ----------------------------------------------------- + +- (CGRect)imageRectImageAtRightForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + CGSize titleSize = titleRect.size; + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + + CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; + if (imageSize.width >= imageSafeWidth) { + return imageRect; + } + // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView + if (imageSize.width + titleSize.width > imageSafeWidth) { + imageSize.width = imageSize.width - (imageSize.width + titleSize.width - imageSafeWidth); + } + // (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - (imageSize.width + titleSize.width))/2.0+titleSize.width指的是imageView在其有效区域内联合titleLabel整体居中时的x值,有效区域指的是contentRect内缩imageEdgeInsets后的区域 + imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - (imageSize.width + titleSize.width))/2.0 + titleSize.width + self.contentEdgeInsets.left + self.imageEdgeInsets.left + _imageTitleSpace * 0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; +} + +- (CGRect)titleRectImageAtRightForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + CGSize imageSize = imageRect.size; + + // (contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right - (imageSize.width + titleSize.width))/2.0的意思是titleLabel在其有效区域内联合imageView整体居中时的x值,有效区域指的是contentRect内缩titleEdgeInsets后的区域 + titleOrigin.x = (contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right - (imageSize.width + titleSize.width))/2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left - _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; +} + +// ----------------------------------------------------- top ----------------------------------------------------- + +- (CGRect)imageRectImageAtTopForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + CGSize titleSize = titleRect.size; + + CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; + + // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView + if (imageSize.width > imageSafeWidth) { + imageSize.width = imageSafeWidth; + } + imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - imageSize.width) / 2.0 + self.contentEdgeInsets.left + self.imageEdgeInsets.left; + + // 给图片高度作最大限制,超出限制对高度进行压缩,这样还可以保证titeLabel不会超出其有效区域 + CGFloat imageTitleLimitMaxH = contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom; + if (imageSize.height < imageTitleLimitMaxH) { + if (titleSize.height + self.currentImage.size.height > imageTitleLimitMaxH) { + CGFloat beyondValue = titleSize.height + self.currentImage.size.height - imageTitleLimitMaxH; + imageSize.height = imageSize.height - beyondValue; + } + // 之所以采用自己计算的结果,是因为当sizeToFit且titleLabel的numberOfLines > 0时,系统内部会按照2行计算 + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + } + // (imageSize.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的insets,计算时都是以图片+文字这个整体作为考虑对象 + imageOrigin.y = (contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + self.contentEdgeInsets.top + self.imageEdgeInsets.top - _imageTitleSpace * 0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; +} + +- (CGRect)titleRectImageAtTopForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + + CGSize imageSize = imageRect.size; + // 这个if语句的含义是:计算图片由于设置了contentEdgeInsets而被压缩的高度,设置imageEdgeInsets被压缩的高度不计算在内。这样做的目的是,当设置了contentEdgeInsets时,图片可能会被压缩,此时titleLabel的y值依赖于图片压缩后的高度,当设置了imageEdgeInsets时,图片也可能被压缩,此时titleLabel的y值依赖于图片压缩前的高度,这样以来,设置imageEdgeInsets就不会对titleLabel的y值产生影响 + if (self.currentImage.size.height + titleSize.height > contentRect.size.height) { + imageSize.height = self.currentImage.size.height - (self.currentImage.size.height + titleSize.height - contentRect.size.height); + } + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + // titleLabel的安全宽度,这里一定要改变宽度值,因为当外界设置了titleEdgeInsets值时,系统计算出来的所有值都是在”左图右文“的基础上进行的,这个基础上可能会导致titleLabel的宽度被压缩,所以我们在此自己重新计算 + CGFloat titleSafeWidth = contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right; + if (titleSize.width > titleSafeWidth) { + titleSize.width = titleSafeWidth; + } + titleOrigin.x = (titleSafeWidth - titleSize.width) / 2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left; + + if (titleSize.height > contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom) { + titleSize.height = contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom; + } + + // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的Insets,计算时都是以图片+文字这个整体作为考虑对象 + titleOrigin.y = (contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + imageSize.height + self.contentEdgeInsets.top + self.titleEdgeInsets.top + _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; +} + +// ----------------------------------------------------- bottom ----------------------------------------------------- + +- (CGRect)imageRectImageAtBottomForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { + CGPoint imageOrigin = imageRect.origin; + CGSize imageSize = imageRect.size; + CGSize titleSize = titleRect.size; + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + + CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; + // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView + if (imageSize.width > imageSafeWidth) { + imageSize.width = imageSafeWidth; + } + + imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - imageSize.width) / 2.0 + self.contentEdgeInsets.left + self.imageEdgeInsets.left; + + // 给图片高度作最大限制,超出限制对高度进行压缩,这样还可以保证titeLabel不会超出其有效区域 + CGFloat imageTitleLimitMaxH = contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom; + if (imageSize.height < imageTitleLimitMaxH) { + if (titleSize.height + self.currentImage.size.height > imageTitleLimitMaxH) { + CGFloat beyondValue = titleSize.height + self.currentImage.size.height - imageTitleLimitMaxH; + imageSize.height = imageSize.height - beyondValue; + } + // 之所以采用自己计算的结果,是因为当sizeToFit且titleLabel的numberOfLines > 0时,系统内部会按照2行计算 + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + } + // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的insets,计算时都是以图片+文字这个整体作为考虑对象 + imageOrigin.y = (contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + titleSize.height + self.contentEdgeInsets.top + self.imageEdgeInsets.top + _imageTitleSpace * 0.5; + imageRect.size = imageSize; + imageRect.origin = imageOrigin; + return imageRect; +} + +- (CGRect)titleRectImageAtBottomForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { + CGPoint titleOrigin = titleRect.origin; + CGSize titleSize = titleRect.size; + + CGSize imageSize = imageRect.size; + // 这个if语句的含义是:计算图片由于设置了contentEdgeInsets而被压缩的高度,设置imageEdgeInsets被压缩的高度不计算在内。这样做的目的是,当设置了contentEdgeInsets时,图片可能会被压缩,此时titleLabel的y值依赖于图片压缩后的高度,当设置了imageEdgeInsets时,图片也可能被压缩,此时titleLabel的y值依赖于图片压缩前的高度,这样一来,设置imageEdgeInsets就不会对titleLabel的y值产生影响 + if (self.currentImage.size.height + titleSize.height > contentRect.size.height) { + imageSize.height = self.currentImage.size.height - (self.currentImage.size.height + titleSize.height - contentRect.size.height); + if (imageSize.height < 0) { + imageSize.height = 0; + } + } + + titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; + // titleLabel的安全宽度,因为当外界设置了titleEdgeInsets值时,系统计算出来的所有值都是在”左图右文“的基础上进行的,这个基础上可能会导致titleLabel的宽度被压缩,所以我们在此自己重新计算 + CGFloat titleSafeWidth = contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right; + if (titleSize.width > titleSafeWidth) { + titleSize.width = titleSafeWidth; + } + + titleOrigin.x = (titleSafeWidth - titleSize.width) / 2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left; + + if (titleSize.height > contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom) { + titleSize.height = contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom; + } + + // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的Insets,计算时都是以图片+文字这个整体作为考虑对象 + titleOrigin.y = (contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + self.contentEdgeInsets.top + self.titleEdgeInsets.top - _imageTitleSpace * 0.5; + titleRect.size = titleSize; + titleRect.origin = titleOrigin; + return titleRect; +} + +// 自己计算titleLabel的大小 +- (CGSize)calculateTitleSizeForSystemTitleSize:(CGSize)titleSize { + CGSize myTitleSize = titleSize; + // 获取按钮里的titleLabel,之所以遍历获取而不直接调用self.titleLabel,是因为假如这里是第一次调用self.titleLabel,则会跟titleRectForContentRect: 方法造成死循环,titleLabel的getter方法中,alloc init之前调用了titleRectForContentRect: + UILabel *titleLabel = [self findTitleLabel]; + if (!titleLabel) { // 此时还没有创建titleLabel,先通过系统button的字体进行文字宽度计算 + CGFloat fontSize = [UIFont buttonFontSize]; // 按钮默认字体,18号 + // 说明外界使用了被废弃的font属性,被废弃但是依然生效 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (self.font.pointSize != [UIFont buttonFontSize]) { + fontSize = self.font.pointSize; + } +#pragma clang diagnostic pop + myTitleSize.height = ceil([self.currentTitle boundingRectWithSize:CGSizeMake(titleSize.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]} context:nil].size.height); + // 根据文字计算宽度,取上整,补齐误差,保证跟titleLabel.intrinsicContentSize.width一致 + myTitleSize.width = ceil([self.currentTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, titleSize.height) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]} context:nil].size.width); + } else { // 说明此时titeLabel已经产生,直接取titleLabel的内容宽度 + myTitleSize.width = titleLabel.intrinsicContentSize.width; + myTitleSize.height = titleLabel.intrinsicContentSize.height; + } + return myTitleSize; +} + +// 遍历获取按钮里面的titleLabel +- (UILabel *)findTitleLabel { + for (UIView *subView in self.subviews) { + if ([subView isKindOfClass:NSClassFromString(@"UIButtonLabel")]) { + UILabel *titleLabel = (UILabel *)subView; + return titleLabel; + } + } + return nil; +} + + + +#pragma mark - setter +// 以下所有setter方法中都调用了layoutSubviews, 其实是为了间接的调用imageRectForContentRect:和titleRectForContentRect:,不能直接调用imageRectForContentRect:和titleRectForContentRect:,因为按钮的子控件布局最终都是通过调用layoutSubviews而确定,如果直接调用这两个方法,那么只能保证我们能够获取的CGRect是对的,但并不会作用在titleLabel和imageView上 +- (void)setImagePosition:(SPItemImagePosition)imagePosition { + _imagePosition = imagePosition; + [self setNeedsLayout]; } - (void)setImageTitleSpace:(CGFloat)imageTitleSpace { _imageTitleSpace = imageTitleSpace; - [self setNeedsDisplay]; + [self setNeedsLayout]; } -- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets { - [super setContentEdgeInsets:contentEdgeInsets]; - [self setNeedsDisplay]; +- (void)setContentHorizontalAlignment:(UIControlContentHorizontalAlignment)contentHorizontalAlignment { + [super setContentHorizontalAlignment:contentHorizontalAlignment]; + [self setNeedsLayout]; +} + +// 垂直方向的排列方式在设置之前如果调用了titleLabel或imageView的getter方法,则设置后不会生效,点击一下按钮之后就生效了,这应该属于按钮的一个小bug,我们只要重写它的setter方法重新布局一次就好 +- (void)setContentVerticalAlignment:(UIControlContentVerticalAlignment)contentVerticalAlignment { + [super setContentVerticalAlignment:contentVerticalAlignment]; + [self setNeedsLayout]; } @end @@ -211,9 +408,9 @@ @interface SPPageMenu() @property (nonatomic, weak) UIImageView *backgroundImageView; @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; -@property (nonatomic, weak) SPPageMenuItem *functionButton; +@property (nonatomic, weak) SPPageMenuButton *functionButton; @property (nonatomic, strong) NSMutableArray *buttons; -@property (nonatomic, strong) SPPageMenuItem *selectedButton; +@property (nonatomic, strong) SPPageMenuButton *selectedButton; @property (nonatomic, strong) NSMutableDictionary *setupWidths; @property (nonatomic, assign) BOOL insert; // 起始偏移量,为了判断滑动方向 @@ -223,10 +420,12 @@ @interface SPPageMenu() @property (nonatomic, assign) CGFloat startR; @property (nonatomic, assign) CGFloat startG; @property (nonatomic, assign) CGFloat startB; +@property (nonatomic, assign) CGFloat startA; /// 完成颜色, 取值范围 0~1 @property (nonatomic, assign) CGFloat endR; @property (nonatomic, assign) CGFloat endG; @property (nonatomic, assign) CGFloat endB; +@property (nonatomic, assign) CGFloat endA; // 这个高度,是存储itemScrollView的高度 @property (nonatomic, assign) CGFloat itemScrollViewH; @@ -265,7 +464,7 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex self.insert = NO; if (self.buttons.count) { - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { [button removeFromSuperview]; } } @@ -273,7 +472,7 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex for (int i = 0; i < items.count; i++) { id object = items[i]; - NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]], @"items中的元素只能是NSString或UIImage类型"); + NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]] || [object isKindOfClass:[SPPageMenuButtonItem class]], @"items中的元素类型只能是NSString、UIImage或SPPageMenuButtonItem"); [self addButton:i object:object animated:NO]; } @@ -282,7 +481,7 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex if (self.buttons.count) { // 默认选中selectedItemIndex对应的按钮 - SPPageMenuItem *selectedButton = [self.buttons objectAtIndex:selectedItemIndex]; + SPPageMenuButton *selectedButton = [self.buttons objectAtIndex:selectedItemIndex]; [self buttonInPageMenuClicked:selectedButton]; // SPPageMenuTrackerStyleTextZoom和SPPageMenuTrackerStyleNothing样式跟tracker没有关联 @@ -318,10 +517,22 @@ - (void)insertItemWithImage:(UIImage *)image atIndex:(NSUInteger)itemIndex anima } } +- (void)insertItem:(SPPageMenuButtonItem *)item 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:item atIndex:itemIndex]; + self.items = objects.copy; + [self addButton:itemIndex object:item 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) { + for (SPPageMenuButton *button in self.buttons) { if (button.tag-tagBaseValue > itemIndex) { button.tag = button.tag - 1; } @@ -333,7 +544,7 @@ - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { self.items = objects.copy; } if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; if (button == self.selectedButton) { // 如果删除的正是选中的item,删除之后,选中的按钮切换为上一个item self.selectedItemIndex = itemIndex > 0 ? itemIndex-1 : itemIndex; } @@ -363,7 +574,7 @@ - (void)removeAllItems { self.items = nil; for (int i = 0; i < self.buttons.count; i++) { - SPPageMenuItem *button = self.buttons[i]; + SPPageMenuButton *button = self.buttons[i]; [button removeFromSuperview]; } @@ -379,7 +590,7 @@ - (void)removeAllItems { - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setImage:nil forState:UIControlStateNormal]; [button setTitle:title forState:UIControlStateNormal]; @@ -391,16 +602,17 @@ - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { } - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; - return item.currentTitle; + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + NSAssert([object isKindOfClass:[NSString class]],@"itemIndex对应的item不是NSString类型,请仔细核对"); + return object; } return nil; } - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:nil forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; @@ -412,24 +624,59 @@ - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { } - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + NSAssert([object isKindOfClass:[UIImage class]],@"itemIndex对应的item不是UIImage类型,请仔细核对"); + return object; + } + return nil; +} + +- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; - return item.currentImage; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + [button setTitle:item.title forState:UIControlStateNormal]; + [button setImage:item.image forState:UIControlStateNormal]; + button.imagePosition = item.imagePosition; + button.imageTitleSpace = item.imageTitleSpace; + + if (item != nil) { + NSMutableArray *items = self.items.mutableCopy; + [items replaceObjectAtIndex:itemIndex withObject:item]; + self.items = items.copy; + } + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + +- (SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + NSAssert([object isKindOfClass:[SPPageMenuButtonItem class]],@"itemIndex对应的item不是SPPageMenuButtonItem类型,请仔细核对"); + return object; } return nil; } +- (id)objectForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id object = [self.items objectAtIndex:itemIndex]; + return object; + } + return nil; +} - (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setEnabled:enaled]; } } - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex { if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; return button.enabled; } return YES; @@ -447,7 +694,7 @@ - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { return setupWidth; } else { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; return button.bounds.size.width; } } @@ -456,21 +703,21 @@ - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *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]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; button.contentEdgeInsets = contentInset; } } - (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; return button.contentEdgeInsets; } return UIEdgeInsetsZero; @@ -501,11 +748,10 @@ - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadi - (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]; + SPPageMenuButton *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里只更新文字 @@ -532,19 +778,23 @@ - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImag [self.functionButton setTitle:title forState:state]; [self.functionButton setImage:image forState:state]; self.functionButton.imagePosition = imagePosition; - self.functionButton.imageRatio = ratio; self.functionButton.imageTitleSpace = imageTitleSpace; } +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state { + [self.functionButton setTitle:item.title forState:state]; + [self.functionButton setImage:item.image forState:state]; + self.functionButton.imagePosition = item.imagePosition; + self.functionButton.imageTitleSpace = item.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]; + SPPageMenuButton *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; @@ -568,7 +818,6 @@ - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImag [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 { @@ -597,20 +846,26 @@ - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView { - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { // 如果是插入,需要改变已有button的tag值 - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { if (button.tag-tagBaseValue >= index) { button.tag = button.tag + 1; // 由于有新button的加入,新button后面的button的tag值得+1 } } - SPPageMenuItem *button = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; + SPPageMenuButton *button = [SPPageMenuButton buttonWithType:UIButtonTypeCustom]; [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; - button.titleLabel.font = _itemTitleFont; + button.titleLabel.font = _unSelectedItemTitleFont; // 此时必然还没有选中任何按钮,设置_unSelectedItemTitleFont就相是设置所有按钮的文字颜色,这里不能用_itemTitleFont,如果外界先设置_unSelectedItemTitleFont,再创建按钮,假如这里使用_itemTitleFont,那外界设置的_unSelectedItemTitleFont就不生效 [button addTarget:self action:@selector(buttonInPageMenuClicked:) forControlEvents:UIControlEventTouchUpInside]; button.tag = tagBaseValue + index; if ([object isKindOfClass:[NSString class]]) { [button setTitle:object forState:UIControlStateNormal]; - } else { + } else if ([object isKindOfClass:[UIImage class]]) { [button setImage:object forState:UIControlStateNormal]; + } else { + SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)object; + [button setTitle:item.title forState:UIControlStateNormal]; + [button setImage:item.image forState:UIControlStateNormal]; + button.imagePosition = item.imagePosition; + button.imageTitleSpace = item.imageTitleSpace; } if (self.insert) { if ([self haveOrNeedsTracker]) { @@ -635,7 +890,7 @@ - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { if (self.insert && animated) { // 是插入的新按钮,且需要动画 // 取出上一个按钮 - SPPageMenuItem *lastButton; + SPPageMenuButton *lastButton; if (index > 0) { lastButton = self.buttons[index-1]; } @@ -672,15 +927,14 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { } - (void)initialize { - - _itemPadding = 30; + _itemPadding = 30.0; _selectedItemTitleColor = [UIColor redColor]; _unSelectedItemTitleColor = [UIColor blackColor]; _selectedItemTitleFont = [UIFont systemFontOfSize:16]; _unSelectedItemTitleFont = [UIFont systemFontOfSize:16]; _itemTitleFont = [UIFont systemFontOfSize:16]; - _trackerHeight = 3; - _dividingLineHeight = 1 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 + _trackerHeight = 3.0; + _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; _showFunctionButton = NO; @@ -723,8 +977,8 @@ - (void)setupSubViews { } [backgroundView addSubview:itemScrollView]; _itemScrollView = itemScrollView; - - SPPageMenuItem *functionButton = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; + + SPPageMenuButton *functionButton = [SPPageMenuButton buttonWithType:UIButtonTypeCustom]; functionButton.backgroundColor = [UIColor whiteColor]; [functionButton setTitle:@"+" forState:UIControlStateNormal]; [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; @@ -739,7 +993,7 @@ - (void)setupSubViews { } // 按钮点击方法 -- (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { +- (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue; NSInteger toIndex = sender.tag - tagBaseValue; // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的,必须用下划线,用self.会造成死循环 @@ -757,7 +1011,7 @@ - (void)buttonInPageMenuClicked:(SPPageMenuItem *)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还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 + for (SPPageMenuButton *button in self.buttons) { // 必须遍历将非选中按钮还原缩放,而不是仅仅只让上一个选中的按钮还原缩放。因为当用户快速滑动外界scrollView时,会频繁的调用-zoomForTitleWithProgress:fromButton:toButton:方法,有可能经过的某一个button还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 if (button != sender && !CGAffineTransformEqualToTransform(button.transform, CGAffineTransformIdentity)) { button.transform = CGAffineTransformIdentity; } @@ -775,32 +1029,13 @@ - (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { [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 { +- (void)moveItemScrollViewWithSelectedButton:(SPPageMenuButton *)selectedButton { if (CGRectEqualToRect(self.backgroundView.frame, CGRectZero)) { return; } @@ -825,7 +1060,7 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { } // 移动跟踪器 -- (void)moveTrackerWithSelectedButton:(SPPageMenuItem *)selectedButton { +- (void)moveTrackerWithSelectedButton:(SPPageMenuButton *)selectedButton { [UIView animateWithDuration:0.25 animations:^{ [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; }]; @@ -841,7 +1076,7 @@ - (void)delegatePerformMethodWithFromIndex:(NSUInteger)fromIndex toIndex:(NSUInt } // 功能按钮的点击方法 -- (void)functionButtonClicked:(SPPageMenuItem *)sender { +- (void)functionButtonClicked:(SPPageMenuButton *)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:functionButtonClicked:)]) { [self.delegate pageMenu:self functionButtonClicked:sender]; } @@ -908,8 +1143,8 @@ - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { // 这个方法才开始移动跟踪器 [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; } else if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeHalf) { - SPPageMenuItem *fromButton; - SPPageMenuItem *toButton; + SPPageMenuButton *fromButton; + SPPageMenuButton *toButton; if (progress > 0.5) { if (toIndex >= 0 && toIndex < self.buttons.count) { toButton = self.buttons[toIndex]; @@ -1024,8 +1259,9 @@ - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton 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]; + CGFloat a = self.endA - self.startA; + UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:self.startA + a * fromProgress]; + UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:self.startA + a * toProgress]; // 设置文字颜色渐变 [fromButton setTitleColor:fromColor forState:UIControlStateNormal]; @@ -1033,35 +1269,31 @@ - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton } // 获取颜色的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; - } +- (NSArray *)getRGBForColor:(UIColor *)color { + CGFloat red = 0.0; + CGFloat green = 0.0; + CGFloat blue = 0.0; + CGFloat alpha = 0.0; + [color getRed:&red green:&green blue:&blue alpha:&alpha]; + return @[@(red), @(green), @(blue), @(alpha)]; } /// 开始颜色设置 - (void)setupStartColor:(UIColor *)color { - CGFloat components[3]; - [self getRGBComponents:components forColor:color]; - self.startR = components[0]; - self.startG = components[1]; - self.startB = components[2]; + NSArray *components = [self getRGBForColor:color]; + self.startR = [components[0] floatValue]; + self.startG = [components[1] floatValue]; + self.startB = [components[2] floatValue]; + self.startA = [components[3] floatValue]; } /// 结束颜色设置 - (void)setupEndColor:(UIColor *)color { - CGFloat components[3]; - [self getRGBComponents:components forColor:color]; - self.endR = components[0]; - self.endG = components[1]; - self.endB = components[2]; + NSArray *components = [self getRGBForColor:color]; + self.endR = [components[0] floatValue]; + self.endG = [components[1] floatValue]; + self.endB = [components[2] floatValue]; + self.endA = [components[3] floatValue]; } - (void)zoomForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton { @@ -1183,7 +1415,7 @@ - (void)setItemTitleFont:(UIFont *)itemTitleFont { _itemTitleFont = itemTitleFont; _selectedItemTitleFont = itemTitleFont; _unSelectedItemTitleFont = itemTitleFont; - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { button.titleLabel.font = itemTitleFont; } [self setNeedsLayout]; @@ -1194,7 +1426,7 @@ - (void)setItemTitleFont:(UIFont *)itemTitleFont { - (void)setUnSelectedItemTitleFont:(UIFont *)unSelectedItemTitleFont { _unSelectedItemTitleFont = unSelectedItemTitleFont; - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { if (button == _selectedButton) { continue; } @@ -1225,7 +1457,7 @@ - (void)setSelectedItemTitleColor:(UIColor *)selectedItemTitleColor { - (void)setUnSelectedItemTitleColor:(UIColor *)unSelectedItemTitleColor { _unSelectedItemTitleColor = unSelectedItemTitleColor; [self setupEndColor:unSelectedItemTitleColor]; - for (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { if (button == _selectedButton) { continue; // 跳过选中的那个button } @@ -1236,7 +1468,7 @@ - (void)setUnSelectedItemTitleColor:(UIColor *)unSelectedItemTitleColor { - (void)setSelectedItemIndex:(NSInteger)selectedItemIndex { _selectedItemIndex = selectedItemIndex; if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:selectedItemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:selectedItemIndex]; [self buttonInPageMenuClicked:button]; } } @@ -1245,7 +1477,7 @@ - (void)setDelegate:(id)delegate { if (delegate == _delegate) {return;} _delegate = delegate; if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:_selectedItemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:_selectedItemIndex]; [self delegatePerformMethodWithFromIndex:button.tag-tagBaseValue toIndex:button.tag-tagBaseValue]; [self moveItemScrollViewWithSelectedButton:button]; } @@ -1360,34 +1592,30 @@ - (void)layoutSubviews { NSMutableArray *buttonWidths = [NSMutableArray array]; // 提前计算每个按钮的宽度,目的是为了计算间距 for (int i= 0 ; i < self.buttons.count; i++) { - SPPageMenuItem *button = self.buttons[i]; + SPPageMenuButton *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; + textW = ceil([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; + textW = ceil([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; - } + CGFloat imageW = button.currentImage.size.width; + CGFloat imageH = button.currentImage.size.height; + if (imageH > itemScrollViewH) { + imageH = itemScrollViewH; } if (button.currentTitle && !button.currentImage) { - contentW = textW; + contentW = textW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if(button.currentImage && !button.currentTitle) { - contentW = imageW; - } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft)) { - contentW = textW + imageW; + contentW = imageW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; + } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft || button.imagePosition == SPItemImagePositionDefault)) { + contentW = textW + imageW + button.imageTitleSpace+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) { - contentW = MAX(textW, imageW); + contentW = MAX(textW, imageW)+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } if (setupButtonW) { contentW_sum += setupButtonW; @@ -1398,8 +1626,8 @@ - (void)layoutSubviews { } } CGFloat diff = itemScrollViewW - contentW_sum; - - [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuItem *button, NSUInteger idx, BOOL * _Nonnull stop) { + + [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuButton *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]; @@ -1424,8 +1652,12 @@ - (void)layoutSubviews { } } else { - self->_itemPadding = diff/self.buttons.count; buttonW = [buttonWidths[idx] floatValue]; + self->_itemPadding = diff/self.buttons.count; + if (self->_itemPadding < 0) { // 如果总内容长度大于pageMenu的长度,则对每个按钮宽度进行均等压缩 + buttonW = buttonW - fabs(diff) / self.buttons.count; + self->_itemPadding = 0; + } if (idx == 0) { button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); } else { @@ -1452,9 +1684,10 @@ - (void)layoutSubviews { [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } + } -- (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuItem *)selectedButton { +- (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)selectedButton { CGFloat trackerX; CGFloat trackerY; @@ -1526,6 +1759,42 @@ - (void)dealloc { [self.bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; } + +@end + +@implementation SPPageMenuButtonItem + ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image { + SPPageMenuButtonItem *item = [[SPPageMenuButtonItem alloc] initWithTitle:title image:image imagePosition:SPItemImagePositionDefault]; + return item; +} + ++ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition { + SPPageMenuButtonItem *item = [[SPPageMenuButtonItem alloc] initWithTitle:title image:image imagePosition:imagePosition]; + return item; +} + +- (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition { + if (self = [super init]) { + self.title = title; + self.image = image; + self.imagePosition = imagePosition; + } + return self; +} + +- (instancetype)init { + if (self = [super init]) { + [self initialize]; + } + return self; +} + +- (void)initialize { + _imagePosition = SPItemImagePositionDefault; + _imageTitleSpace = 0.0; +} + @end #pragma clang diagnostic pop From 39b7855a6b1e7423f98bd656f539ca5b18407400 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Wed, 26 Dec 2018 19:14:47 +0900 Subject: [PATCH 16/22] Expose functionButton --- .DS_Store | Bin 6148 -> 6148 bytes Example/SPPageMenu/.DS_Store | Bin 6148 -> 6148 bytes Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 3 ++- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 1 - SPPageMenu/SPPageMenu.h | 3 ++- SPPageMenu/SPPageMenu.m | 1 - 6 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.DS_Store b/.DS_Store index 29bbbf6d0617e723089b22082616344dade97ca4..bdd4eb21af079c73e6e25a72d51750207d202288 100644 GIT binary patch delta 51 zcmZoMXfc@J&&awlU^g=(>tr65pPVVh$vH{+`8kY}KeHU=Fg7;TQ7|?!*?fRihiNlA H$6tN`uYnL) delta 39 vcmZoMXfc@J&&aYdU^g=(%VZvwpOY0>H&0&6;<;Id?KR`X2I @@ -120,6 +120,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO @property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 @property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 +@property (nonatomic, weak) SPPageMenuButton *functionButton; @property (nonatomic, weak) id delegate; diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index eef4300..ce799be 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -408,7 +408,6 @@ @interface SPPageMenu() @property (nonatomic, weak) UIImageView *backgroundImageView; @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; -@property (nonatomic, weak) SPPageMenuButton *functionButton; @property (nonatomic, strong) NSMutableArray *buttons; @property (nonatomic, strong) SPPageMenuButton *selectedButton; @property (nonatomic, strong) NSMutableDictionary *setupWidths; diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index 241b96d..d9a9279 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -40,7 +40,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { SPItemImagePositionBottom // 图片在文字下侧 }; -@class SPPageMenu,SPPageMenuButtonItem; +@class SPPageMenu,SPPageMenuButton,SPPageMenuButtonItem; @protocol SPPageMenuDelegate @@ -120,6 +120,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO @property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 @property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 +@property (nonatomic, weak) SPPageMenuButton *functionButton; @property (nonatomic, weak) id delegate; diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index eef4300..ce799be 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -408,7 +408,6 @@ @interface SPPageMenu() @property (nonatomic, weak) UIImageView *backgroundImageView; @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; -@property (nonatomic, weak) SPPageMenuButton *functionButton; @property (nonatomic, strong) NSMutableArray *buttons; @property (nonatomic, strong) SPPageMenuButton *selectedButton; @property (nonatomic, strong) NSMutableDictionary *setupWidths; From c12e5e006348106e292bf628e3c972f6e9faab80 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Wed, 26 Dec 2018 23:55:39 +0900 Subject: [PATCH 17/22] Expose SPPageMenuButton definition in .h --- Example/.DS_Store | Bin 6148 -> 6148 bytes Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 2 ++ Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 2 +- SPPageMenu/SPPageMenu.h | 2 ++ SPPageMenu/SPPageMenu.m | 2 +- 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Example/.DS_Store b/Example/.DS_Store index a88de7dcd980b4d3aee98aab747ad39f67ed7cd0..c38ade4a03e09e0660188749a4ad7b257cc5f013 100644 GIT binary patch delta 53 zcmZoMXffEZnTf;L*i=Ws*u-S=Zl*a*nx`hqGV2HwWEch~=jRqMfB^Fa29V(9gUrq% E0HTQwvj6}9 delta 53 zcmZoMXffEZnTf;H+*C)w*wA9~Zl*a*a_y64nRNsTG7N*0^K%OrK!EuG14wZ5L1t$W E0GWvnTmS$7 diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index d9a9279..5a4dad1 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -189,6 +189,8 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); @end +@interface SPPageMenuButton : UIButton +@end // 这个类相当于模型,主要用于同时为某个按钮设置图片和文字时使用 @interface SPPageMenuButtonItem : NSObject diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index ce799be..841e081 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -45,7 +45,7 @@ - (void)setAlpha:(CGFloat)alpha { @end -@interface SPPageMenuButton : UIButton +@interface SPPageMenuButton() - (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition; diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index d9a9279..5a4dad1 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -189,6 +189,8 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); @end +@interface SPPageMenuButton : UIButton +@end // 这个类相当于模型,主要用于同时为某个按钮设置图片和文字时使用 @interface SPPageMenuButtonItem : NSObject diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index ce799be..841e081 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -45,7 +45,7 @@ - (void)setAlpha:(CGFloat)alpha { @end -@interface SPPageMenuButton : UIButton +@interface SPPageMenuButton() - (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition; From be608e33bbbc98b5699e5f61817bbdf1c27c6c23 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Thu, 27 Dec 2018 12:34:45 +0900 Subject: [PATCH 18/22] Merge remote-tracking branch SPStore/master --- .DS_Store | Bin 6148 -> 0 bytes Example/.DS_Store | Bin 6148 -> 0 bytes .../UserInterfaceState.xcuserstate | Bin 65842 -> 71152 bytes Example/SPPageMenu/.DS_Store | Bin 6148 -> 0 bytes Example/SPPageMenu/Info.plist | 2 +- Example/SPPageMenu/ParentViewController.m | 68 +-- Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 77 ++-- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 411 ++++++++++++------ Example/SPPageMenu/ViewController.m | 4 +- README.md | 29 +- SPPageMenu.podspec | 2 +- SPPageMenu/SPPageMenu.h | 77 ++-- SPPageMenu/SPPageMenu.m | 411 ++++++++++++------ 13 files changed, 684 insertions(+), 397 deletions(-) delete mode 100644 .DS_Store delete mode 100644 Example/.DS_Store delete mode 100644 Example/SPPageMenu/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index bdd4eb21af079c73e6e25a72d51750207d202288..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}xR_5S}W?k1=vEkz+63I6y%0VzNO!crYuzw9WtXsvsEFCu&^Pi4 zd>v=nYUIb0#(?RN={N0kTINgI?f?L5^qmSo0RXb7gsD6>Glcx4Q?jH+(uho~(FGq| zdk}SPe`>ZK&=8XY5yBRoyJFuVwU(Ro6qvzG``d$>5%WrIHIh9_?WOFEMxhwM~ z9GI~c_oIf@yP)2Q72nv(J`F?v)3)q0&mA-i8~b4pTVCMyM6l<$IOX!(3!HG!2>XE( ziFHir5F0fM#o=(fzFp$g%I>JdhxK~7#B0@^(I}VQ+^XyywL13?!^hFnv*eZGH6&zR z;Rn2+F_ESdUHdlvQ3{!G-I~3?4}&&($eRZfPVi?z8(cU>4WKV?xw&DfS|yfhwI8!I zGua73kP|J7&wx5C+115QMQ5NH& zVImPdC`^VT%21|C3?{>IoU3_;!bGAB2d0V-rgmnkLSbTeT%U{Szzm7H)eL9`#u-?a z-2%P;hu_!#<3YNo8PE*;D+XBVz&vPRO6qN$N{-%Ij@m*cp?Qf!5rU0+j)g%_aS>Gr Z`ZyVghQdT5j-cod0YQUqGy{Liz&n}-pCbSO diff --git a/Example/.DS_Store b/Example/.DS_Store deleted file mode 100644 index c38ade4a03e09e0660188749a4ad7b257cc5f013..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!HN?>5UozKnkZrp3+}Nm9)utXiS9`l;(G8PER5(uWp`#LBqOuaLo(T@F*3iQ zf21dG{*JG@+hx~8h!+v93e~T=x~e<RJ)$-dHKB~P7Mcyh7oK0dm=$qmVvK)|i!7a$-R?J4t2cJ8Ty3@> zH(Ia4iCKhYRL*C^X!?SChfz6+z5Udf?A(sxXX$8h)ZTq)@-j;E(bOiV$p|4YpQm|Z z7DF@7lbM|-bOUywqxRl%+3(-$=>A}^>geVEez&6s{rjs`t9k2o@4@5a)3@)I?^ho_ zI))WqMk_Zx{(w&~Hg5JToaULy#~A0_-A#nhU3x<&WGKN4UeFZQ>ulT$Pt0jy7pUL) z@;kZ3RSCwF19p0~mO$9Kr*U4*^SqG{V3iW#BvG!gZ1W diff --git a/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/libo.xcuserdatad/UserInterfaceState.xcuserstate b/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/libo.xcuserdatad/UserInterfaceState.xcuserstate index 4d0c4e1a89364d4fc7ab28a0e892658a51700c9d..8816cdb314ff4a39ac495e5bae83b89e9ef1b810 100644 GIT binary patch delta 35976 zcma%k349IL`}fS8+4q@yZ|sEF*F+H1zVG|KMMw}rNMc{^JXBF#aM5bDBtk4*sA{WP zs`jO7tF?+!w5Yb)YJaVIpShDul>R@j$h~Lgoaa2>^PJ~AXMghEO!lV@?1BL6d*8Pu zG$yA4vG~Bk=B0zJsWwzwsvXsy>Ogg*I#I)@G-@=JNlm0CQPZgD6i_p%=cqZi z0kx1?LM^3Uqh6<0P%EkR)CTG;>TT+MY8&+l^(nQT+ChCz9iR?Uhp5BU7t~4WE9w+= znmR*Wq%KjHsc)%k)Q{91>UZis^$*R_D(wr>#pvR63|*P7Lf53@>H2h2x+C3*?nZZ~ zd((aBM0yxKoE|}^(j)0{bOxPCXVK&7Y4mh@1|6gwdJg?Ey?}m&ev@87uc60|J zd+5XT7xWSOOZq5%nm$9HrO(ml>2K*P^i}#h`Wk(k{*nHjzDNJVa7>VAG{(;qV`7-H zOckak6VKFVnlf#eZcJZhFq6a#VTLlv%qS*}8O@Ag(wPa&L}n5*nVG@>Gn09Nd68Mn zyv{6VRx#_DP0SYN1Li|!2eXUW&Fp6mFkdmJnA6M|<}C9ybAh?STxGsveq(-T?lFHb ze=`4L{t7aGGxrHY2w{mxBqEb2Qiv2LMMzOnjFcv2NKFz?YLVKc4oM(&Nj=htG$u_* z3(}IbC!I+b(u?#a{YeTLMbb$Y`G|Z%J|#QJ=VUM0M-Gw0 zo?^dcudr9ypV*(-U)a0s1CHWoj^PN$avaBVHs|B~T!4$>3UP(GB3wDHJXe9M$kpO% zb9K08Tyw4k*OF_+wdUGzZMmLYFRnK?luPDPxMAFIZUmRg1xIodxQW~(ZZbE8o61e& zrgQVS=ehaZ3*4*RGVV2Q4VTBQ<<@aqxDU7wxsSNr+#YT(w~yP;9pDafr?}JH8SX6i z9e0hp&fVa?=YHUBa<{lYxIel7@jNf^A}{d)K8i2I$M9wOa(ocy5uvD1QYaRU7>-{P-rSN6N0UT)=HW8bO&BPvJPqCNSTkIqD z75j<(#i3%dm?BOTCyA5ADdJRdnmApYAqGW91aY4Dyf|NcLwr*Vt`Jv>*LOsR_SBuQ)#<&R5~Udmrh70rLUw@(rM|U zbV<4_-Iacoev^Ke?n!@0e@emsNqxP{!{*+{FnTn zlo+L~QdOy@R99*!u}TA_q0&fctTa(tDD9OFN=K!W(o5;B^if7Cqm(pdv@%8srYmEW zaY}}gsZ3R-Dbtmgl?BQ|Wswq67As4XrOGSH3T35|t>h^0D_fKgln<4Ul g$~I+} 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 delta 31167 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%0D#Y_`cm^M2ev3 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 R0LH)nDOwefh{{WDp`78hc diff --git a/Example/SPPageMenu/.DS_Store b/Example/SPPageMenu/.DS_Store deleted file mode 100644 index eb329b9358b911f72e105556fc7b4b07488a7b1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKK~LK-6n=&doFGIGli--kZV0t%OHY$RR}LH|A!Ql|m>_AYHc}c_O+q(HQ4aeP z+dtTGKV!duzp~@rvkd~mn8YN6kY8ng&wlUO@%LoA1_0K{!#Y3}05}%HijCDhLUt-8 zS;cxHGkNmaHm$FfhoNfL`>fSxqU670bUkF^2Pl*WU)*At`h z)o^$1Bhl@KdDa8C{Q33AZ?|j@AGD8$rSWa>U>|?#3*O^bB#=Lt8VhCBdVj7@7B`d=&p6JoM0g#ze1r5 l!Af7pQlYDO1B(>2Iiw)E8ViNkgJS*&NE%FK7CFBundlePackageType APPL CFBundleShortVersionString - 3.4.5 + 3.5.0 CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/Example/SPPageMenu/ParentViewController.m b/Example/SPPageMenu/ParentViewController.m index ee42cfe..030d190 100644 --- a/Example/SPPageMenu/ParentViewController.m +++ b/Example/SPPageMenu/ParentViewController.m @@ -88,7 +88,8 @@ - (void)test3 { // 设置代理 pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 - pageMenu.bridgeScrollView = self.scrollView; [self.view addSubview:pageMenu]; + pageMenu.bridgeScrollView = self.scrollView; + [self.view addSubview:pageMenu]; _pageMenu = pageMenu; } @@ -218,7 +219,7 @@ - (void)test10 { [pageMenu setItems:self.dataArr selectedItemIndex:1]; // 不可滑动的等宽排列 pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollEqualWidths; - pageMenu.itemPadding = 0; + pageMenu.trackerWidth = 20; // 设置代理 pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 @@ -324,8 +325,7 @@ - (void)test16 { // 传递数组,默认选中第2个 [pageMenu setItems:self.dataArr selectedItemIndex:1]; // 同时设置图片和文字,如果只想要文字,image传nil,如果只想要图片,title传nil,imagePosition和ratio传0即可 - //[pageMenu setFunctionButtonTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop imageRatio:0.5 imageTitleSpace:0 forState:UIControlStateNormal]; - [pageMenu setFunctionButtonWithItem:[SPPageMenuButtonItem itemWithTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop] forState:UIControlStateNormal]; + [pageMenu setFunctionButtonContent:[SPPageMenuButtonItem itemWithTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop] forState:UIControlStateNormal]; [pageMenu setFunctionButtonTitleTextAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} forState:UIControlStateNormal]; pageMenu.showFunctionButton = YES; // 设置代理 @@ -362,11 +362,11 @@ - (void)test18 { // 指定第2个item同时含有图片和文字,图片在上 SPPageMenuButtonItem *item1 = [SPPageMenuButtonItem itemWithTitle:@"害羞" image:[UIImage imageNamed:@"Expression_2"]]; item1.imagePosition = SPItemImagePositionTop; - [pageMenu setItem:item1 forItemIndex:1]; + [pageMenu setItem:item1 forItemAtIndex:1]; // 指定第4个item同时含有图片和文字,图片在右 // [pageMenu setTitle:@"可爱的小狗" image:[UIImage imageNamed:@"dog"] imagePosition:SPItemImagePositionDefault imageRatio:0.4 imageTitleSpace:0 forItemIndex:3]; SPPageMenuButtonItem *item2 = [SPPageMenuButtonItem itemWithTitle:@"歌曲" image:[UIImage imageNamed:@"asc"] imagePosition:SPItemImagePositionRight]; - [pageMenu setItem:item2 forItemIndex:3]; + [pageMenu setItem:item2 forItemAtIndex:3]; pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 pageMenu.bridgeScrollView = self.scrollView; @@ -396,8 +396,39 @@ - (void)test19 { _pageMenu = pageMenu; } -// 示例20:给指定按钮加角标 +// 示例20:某个按钮上添加一个副标题 - (void)test20 { + self.dataArr = @[@"点菜",@"评论",@"商家",@"已购"]; + + SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleLineAttachment]; + // 传递数组,默认选中第2个 + [pageMenu setItems:self.dataArr selectedItemIndex:0]; + pageMenu.itemTitleFont = [UIFont boldSystemFontOfSize:17]; + pageMenu.selectedItemTitleColor = [UIColor blackColor]; + pageMenu.unSelectedItemTitleColor = [UIColor grayColor]; + pageMenu.trackerWidth = 20; + // 设置第一个按钮后面的自定义间距为60,增大第一个和第二个按钮之间的间距,腾出空间方便在第一个按钮的后面放置一个副标题 + [pageMenu setCustomSpacing:60 afterItemAtIndex:1]; + pageMenu.delegate = self; + pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollEqualWidths; + // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 + pageMenu.bridgeScrollView = self.scrollView; + [self.view addSubview:pageMenu]; + _pageMenu = pageMenu; + + // 获取第1个item上文字相对pageMenu的位置大小 + CGRect titleRect = [pageMenu titleRectRelativeToPageMenuForItemAtIndex:1]; + + UILabel *detailLabel = [[UILabel alloc] init]; + detailLabel.text = @"8384"; + detailLabel.font = [UIFont systemFontOfSize:11]; + detailLabel.textColor = [UIColor lightGrayColor]; + detailLabel.frame = CGRectMake(CGRectGetMaxX(titleRect),CGRectGetMaxY(titleRect)-16, 50, 16); + [pageMenu addComponentViewInScrollView:detailLabel]; +} + +// 示例21:给指定按钮加角标 +- (void)test21 { self.dataArr = @[@"生活",@"军事",@"水木年华",@"综艺"]; SPPageMenu *pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, NaviH, screenW, pageMenuH) trackerStyle:SPPageMenuTrackerStyleNothing]; @@ -433,25 +464,6 @@ - (void)test20 { _pageMenu = pageMenu; } -// 示例21:特别属性说明 -- (void)test21 { - self.dataArr = nil; - - NSString *text = @"本框架的bridgeScrollView属性是一个很重要但又容易忽略的属性,在外界的viewDidLoad中,每种示例都传了一个scrollView,即:“self.pageMenu.bridgeScrollView = self.scrollView”,这一传递,SPPageMenu内部会监听该scrollView的滚动状况,当该scrollView滚动的时候,就可以让跟踪器时刻跟随;如果你忘了或者不想设置这个属性,也可以在外界的scrollView的代理方法scrollViewDidScroll中调用接口“- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView”,这样也能实现跟踪器时刻跟随scrollView;如果不想让跟踪器时刻跟踪,而直到scrollView滑动结束才跟踪,在上面2种方式采取了任意一种的情况下,可以设置属性”pageMenu.closeTrackerFollowingMode = YES“"; - - UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, NaviH, screenW-20, screenH-NaviH)]; - label.numberOfLines = 0; - label.alpha = 0.6; - label.font = [UIFont systemFontOfSize:15]; - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text]; - NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; - paragraphStyle.firstLineHeadIndent = label.font.pointSize * 2; // 首行缩进2格 - [paragraphStyle setLineSpacing:6]; // 行间距 - [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [text length])]; - label.attributedText = attributedString; - [self.view addSubview:label]; -} - // ------------------------------------------------------------------------------------------------ - (void)viewDidLoad { [super viewDidLoad]; @@ -522,8 +534,6 @@ - (void)viewDidLoad { case 20: [self test21]; break; - default: - break; } [self.view addSubview:self.scrollView]; @@ -532,7 +542,7 @@ - (void)viewDidLoad { for (int i = 0; i < self.dataArr.count; i++) { if (controllerClassNames.count > i) { BaseViewController *baseVc = [[NSClassFromString(controllerClassNames[i]) alloc] init]; - id object = [self.pageMenu objectForItemAtIndex:i]; + id object = [self.pageMenu contentForItemAtIndex:i]; if ([object isKindOfClass:[NSString class]]) { baseVc.text = object; } else if ([object isKindOfClass:[UIImage class]]) { diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h index 5a4dad1..7cfb5bd 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h @@ -21,15 +21,15 @@ typedef NS_ENUM(NSInteger, SPPageMenuTrackerStyle) { }; typedef NS_ENUM(NSInteger, SPPageMenuPermutationWay) { - SPPageMenuPermutationWayScrollAdaptContent = 0, // 自适应内容,可以左右滑动 - SPPageMenuPermutationWayNotScrollEqualWidths, // 等宽排列,不可以滑动,整个内容被控制在pageMenu的范围之内,等宽是根据pageMenu的总宽度对每个item均分 - SPPageMenuPermutationWayNotScrollAdaptContent // 自适应内容,不可以滑动,整个内容被控制在pageMenu的范围之内,这种排列方式下,自动计算item之间的间距,itemPadding属性无效 + SPPageMenuPermutationWayScrollAdaptContent = 0, // 自适应内容,可以左右滑动 + SPPageMenuPermutationWayNotScrollEqualWidths, // 等宽排列,不可以滑动,整个内容被控制在pageMenu的范围之内,等宽是根据pageMenu的总宽度对每个按钮均分 + SPPageMenuPermutationWayNotScrollAdaptContent // 自适应内容,不可以滑动,整个内容被控制在pageMenu的范围之内,这种排列方式下,自动计算item之间的间距,itemPadding属性无效 }; typedef NS_ENUM(NSInteger, SPPageMenuTrackerFollowingMode) { SPPageMenuTrackerFollowingModeAlways = 0, // 外界scrollView拖动时,跟踪器时刻跟随外界scrollView移动 - SPPageMenuTrackerFollowingModeEnd, // 外界scrollVie拖动w结束后,跟踪器才开始移动 - SPPageMenuTrackerFollowingModeHalf // 外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动 + SPPageMenuTrackerFollowingModeEnd, // 外界scrollVie拖动结束后,跟踪器才开始移动 + SPPageMenuTrackerFollowingModeHalf // 外界scrollView拖动到一半时,跟踪器开始移动 }; typedef NS_ENUM(NSInteger, SPItemImagePosition) { @@ -68,7 +68,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setItems:(nullable NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex; @property (nonatomic) NSInteger selectedItemIndex; // 选中的item下标,改变其值可以用于切换选中的item - @property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 #if TARGET_INTERFACE_BUILDER @@ -77,8 +76,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, readonly) SPPageMenuTrackerStyle trackerStyle; #endif -// item之间的间距,默认30;当排列方式permutationWay为‘SPPageMenuPermutationWayNotScrollAdaptContent’时此属性无效,无效是合理的,不可能做到“不可滑动且自适应内容”然后间距又自定义,这2者相互制约; -@property (nonatomic, assign) CGFloat itemPadding; +@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; // 排列方式 + +@property (nonatomic, assign) CGFloat spacing; // item之间的间距 @property (nonatomic, strong) UIColor *selectedItemTitleColor; // 选中的item标题颜色 @property (nonatomic, strong) UIColor *unSelectedItemTitleColor; // 未选中的item标题颜色 @@ -87,30 +87,21 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonnull, nonatomic, strong) UIFont *selectedItemTitleFont; // 选中的item字体 @property (nonnull, nonatomic, strong) UIFont *unSelectedItemTitleFont; // 未选中的item字体 -// 外界的srollView,pageMenu会监听该scrollView的滚动状况,让跟踪器时刻跟随此scrollView滑动;所谓的滚动状况,是指手指拖拽滚动,非手指拖拽不算 +// 外界添加控制器view的srollView,pageMenu会监听该scrollView的滚动状况,让跟踪器时刻跟随此scrollView滑动;所谓的滚动状况,是指手指拖拽滚动,非手指拖拽不算 @property (nonatomic, strong) UIScrollView *bridgeScrollView; -@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; // 排列方式 - -@property (nonatomic, assign) UIEdgeInsets contentInset; // 内容的四周内边距(内容不包括分割线),默认UIEdgeInsetsZero - -@property(nonatomic) BOOL bounces; // 边界反弹效果,默认YES -@property(nonatomic) BOOL alwaysBounceHorizontal; // 水平方向上,当内容没有充满scrollView时,滑动scrollView是否有反弹效果,默认YES - - // 跟踪器 -@property (nonatomic, readonly) UIImageView *tracker; // 跟踪器,它是一个UIImageView类型,你可以拿到该对象去设置一些自己想要的属性,例如颜色,图片等,但是设置frame无效 +@property (nonatomic, readonly) UIImageView *tracker; // 跟踪器,它是一个UIImageView类型,你可以拿到该对象去设置一些自己想要的属性,例如颜色,图片等 @property (nonatomic, assign) CGFloat trackerWidth; // 跟踪器的宽度 -// 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。如果你想用默认高度,但是又不想要圆角半径,你可以设置trackerHeight为3,cornerRadius为0,这是去除默认半径的唯一办法 -- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; - -// 跟踪器的跟踪模式 -@property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; +- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; // 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。 +@property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; // 跟踪器的跟踪模式 // 分割线 @property (nonatomic, readonly) UIImageView *dividingLine; // 分割线,你可以拿到该对象设置一些自己想要的属性,如颜色、图片等,如果想要隐藏分割线,拿到该对象直接设置hidden为YES或设置alpha<0.01即可(eg:pageMenu.dividingLine.hidden = YES) @property (nonatomic) CGFloat dividingLineHeight; // 分割线的高度 +@property (nonatomic, assign) UIEdgeInsets contentInset; // 内容的四周内边距(内容不包括分割线),默认UIEdgeInsetsZero + // 选中的item缩放系数,默认为1,为1代表不缩放,[0,1)之间缩小,(1,+∞)之间放大,(-1,0)之间"倒立"缩小,(-∞,-1)之间"倒立"放大,为-1"倒立不缩放",如果依然使用了废弃的SPPageMenuTrackerStyleTextZoom样式,则缩放系数默认为1.3 @property (nonatomic) CGFloat selectedItemZoomScale; @property (nonatomic, assign) BOOL needTextColorGradients; // 是否需要文字渐变,默认为YES @@ -122,43 +113,55 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 @property (nonatomic, weak) SPPageMenuButton *functionButton; +@property (nonatomic) BOOL bounces; // 边界反弹效果,默认YES +@property (nonatomic) BOOL alwaysBounceHorizontal; // 水平方向上,当内容没有充满scrollView时,滑动scrollView是否有反弹效果,默认NO + @property (nonatomic, weak) id delegate; // 插入item,插入和删除操作时,如果itemIndex超过了了items的个数,则不做任何操作 -- (void)insertItemWithTitle:(nullable NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItem:(nullable SPPageMenuButtonItem *)item atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItemWithTitle:(nonnull NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItemWithImage:(nonnull UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItem:(nonnull SPPageMenuButtonItem *)item atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; // 如果移除的正是当前选中的item(当前选中的item下标不为0),删除之后,选中的item会切换为上一个item - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated; - (void)removeAllItems; -- (void)setTitle:(nullable NSString *)title forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的标题,设置后,仅会有文字 +- (void)setTitle:(nonnull NSString *)title forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的标题,设置后,仅会有文字 - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的标题 -- (void)setImage:(nullable UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 +- (void)setImage:(nonnull UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的图片 -- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex; // 同时为指定item设置标题和图片,其中参数item相当于一个模型,可以同时设置文字和图片 +- (void)setItem:(SPPageMenuButtonItem *)item forItemAtIndex:(NSUInteger)itemIndex; // 同时为指定item设置标题和图片 - (nullable SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex; // 获取指定item -- (id)objectForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item,该方法获取的item可能是NSString、UIImage或SPPageMenuButtonItem类型 +- (void)setContent:(id)content forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的内容,content可以是NSString、UIImage或SPPageMenuButtonItem类型 +- (id)contentForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的内容,该方法返回值可能是NSString、UIImage或SPPageMenuButtonItem类型 - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的宽度(如果width为0,item会根据内容自动计算width) - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的宽度 +- (void)setCustomSpacing:(CGFloat)spacing afterItemAtIndex:(NSUInteger)itemIndex; // 设置指定item后面的自定义间距 +- (CGFloat)customSpacingAfterItemAtIndex:(NSUInteger)itemIndex; // 获取指定item后面的自定义间距 + - (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的enabled状态 - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的enabled状态 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的四周内边距 - (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的四周内边距 -// 设置背景图片,barMetrics只有为UIBarMetricsDefault时才生效,如果外界传进来的backgroundImage调用过- resizableImageWithCapInsets:且参数capInsets不为UIEdgeInsetsZero,则直接用backgroundImage作为背景图; 否则内部会自动调用- resizableImageWithCapInsets:进行拉伸 +// 设置背景图片,barMetrics只有为UIBarMetricsDefault时才生效,如果外界传进来的backgroundImage调用过-resizableImageWithCapInsets:且参数capInsets不为UIEdgeInsetsZero,则直接用backgroundImage作为背景图; 否则内部会自动调用-resizableImageWithCapInsets:进行拉伸 - (void)setBackgroundImage:(nullable UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics; - (nullable UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics; // 获取背景图片 -// 同时为functionButton设置标题和图片 -- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state; +- (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 文字相对pageMenu位置和大小 +- (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 图片相对pageMenu位置和大小 +- (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 按钮相对pageMenu位置和大小 +- (void)addComponentViewInScrollView:(UIView *)componentView; // 在内置的scrollView上添加一个view + +// 设置功能按钮的内容,content可以是NSString、UIImage或SPPageMenuButtonItem类型 +- (void)setFunctionButtonContent:(id)content forState:(UIControlState)state; // 为functionButton配置相关属性,如设置字体、文字颜色等;在此,attributes中,只有NSFontAttributeName、NSForegroundColorAttributeName、NSBackgroundColorAttributeName有效 - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state; @@ -175,9 +178,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; - // -------------- 以下方法和属性被废弃,不再建议使用 -------------- +@property (nonatomic, assign) CGFloat itemPadding NS_DEPRECATED_IOS(6_0, 6_0, "Use spacing instead");; // 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.4.0版本已纠正 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContentEdgeInsets:forItemAtIndex:"); // 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.4.0版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 @@ -187,6 +190,10 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); +- (id)objectForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -contentForItemAtIndex:"); +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use -setFunctionButtonContent:forState:"); +- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem:forItemAtIndex:"); +- (void)setContent:(id)content forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContent:forItemAtIndex:"); @end @interface SPPageMenuButton : UIButton @@ -201,7 +208,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { + (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition; @property (nonatomic, copy) NSString *title; -@property (nonatomic, strong) UIImage *image; +@property (nonatomic, copy) UIImage *image; // 图片的位置 @property (nonatomic, assign) SPItemImagePosition imagePosition; // 图片与标题之间的间距,默认0.0 diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index 841e081..05885de 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -410,7 +410,8 @@ @interface SPPageMenu() @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; @property (nonatomic, strong) NSMutableArray *buttons; @property (nonatomic, strong) SPPageMenuButton *selectedButton; -@property (nonatomic, strong) NSMutableDictionary *setupWidths; +@property (nonatomic, strong) NSMutableDictionary *customWidths; +@property (nonatomic, strong) NSMutableDictionary *customSpacings; @property (nonatomic, assign) BOOL insert; // 起始偏移量,为了判断滑动方向 @property (nonatomic, assign) CGFloat beginOffsetX; @@ -428,6 +429,7 @@ @interface SPPageMenu() // 这个高度,是存储itemScrollView的高度 @property (nonatomic, assign) CGFloat itemScrollViewH; +@property (nonatomic, assign) BOOL forceUseSettingSpacing; @end #pragma clang diagnostic push @@ -487,7 +489,7 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex if ([self haveOrNeedsTracker]) { [self.itemScrollView insertSubview:self.tracker atIndex:0]; // 这里千万不能再去调用setNeedsLayout和layoutIfNeeded,因为如果外界在此之前对selectedButton进行了缩放,调用了layoutSubViews后会重新对selectedButton设置frame,先缩放再重设置frame会导致文字显示不全,所以我们直接跳过layoutSubViews调用resetSetupTrackerFrameWithSelectedButton:只设置tracker的frame - [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; + [self resetupTrackerFrameWithSelectedButton:selectedButton]; } } } @@ -563,7 +565,6 @@ - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { } else { [self setNeedsLayout]; } - } - (void)removeAllItems { @@ -588,6 +589,7 @@ - (void)removeAllItems { } - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { + if (title == nil) return; if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setImage:nil forState:UIControlStateNormal]; @@ -600,7 +602,7 @@ - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { [self setNeedsLayout]; } -- (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { +- (NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; NSAssert([object isKindOfClass:[NSString class]],@"itemIndex对应的item不是NSString类型,请仔细核对"); @@ -610,6 +612,7 @@ - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { } - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { + if (image == nil) return; if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:nil forState:UIControlStateNormal]; @@ -622,7 +625,7 @@ - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { [self setNeedsLayout]; } -- (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { +- (UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; NSAssert([object isKindOfClass:[UIImage class]],@"itemIndex对应的item不是UIImage类型,请仔细核对"); @@ -632,6 +635,7 @@ - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { } - (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex { + if (item == nil) return; if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:item.title forState:UIControlStateNormal]; @@ -649,6 +653,10 @@ - (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex } } +- (void)setItem:(SPPageMenuButtonItem *)item forItemAtIndex:(NSUInteger)itemIndex { + [self setItem:item forItemIndex:itemIndex]; +} + - (SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; @@ -658,6 +666,41 @@ - (SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex { return nil; } +- (void)setContent:(id)content forItemIndex:(NSUInteger)itemIndex { + if (content == nil) return; + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + if ([content isKindOfClass:[NSString class]]) { + [button setTitle:content forState:UIControlStateNormal]; + } else if ([content isKindOfClass:[UIImage class]]) { + [button setImage:content forState:UIControlStateNormal]; + } else if ([content isKindOfClass:[SPPageMenuButtonItem class]]) { + SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)content; + [button setTitle:item.title forState:UIControlStateNormal]; + [button setImage:item.image forState:UIControlStateNormal]; + button.imagePosition = item.imagePosition; + button.imageTitleSpace = item.imageTitleSpace; + } + NSMutableArray *items = self.items.mutableCopy; + [items replaceObjectAtIndex:itemIndex withObject:content]; + self.items = items.copy; + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + +- (void)setContent:(id)content forItemAtIndex:(NSUInteger)itemIndex { + [self setContent:content forItemIndex:itemIndex]; +} + +- (id)contentForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id content = [self.items objectAtIndex:itemIndex]; + return content; + } + return nil; +} + - (id)objectForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; @@ -683,14 +726,16 @@ - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex { - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - [self.setupWidths setObject:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; + [self.customWidths setValue:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; + [self setNeedsLayout]; + [self layoutIfNeeded]; } } - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { - CGFloat setupWidth = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; - if (setupWidth) { - return setupWidth; + CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; + if (customWidth) { + return customWidth; } else { if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; @@ -700,6 +745,23 @@ - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { return 0; } +- (void)setCustomSpacing:(CGFloat)spacing afterItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + [self.customSpacings setValue:@(spacing) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + +- (CGFloat)customSpacingAfterItemAtIndex:(NSUInteger)itemIndex { + if ([self.customSpacings.allKeys containsObject:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]) { + CGFloat customSpacing = [[self.customSpacings valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; + return customSpacing; + } else { + return CGFLOAT_MAX; + } +} + - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; @@ -711,6 +773,8 @@ - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forItemAtIndex:(NSUInteg if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; button.contentEdgeInsets = contentInset; + [self setNeedsLayout]; + [self layoutIfNeeded]; } } @@ -738,6 +802,37 @@ - (UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics { return self.backgroundImageView.image; } +- (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + CGRect titleRectAtPageMenu = [button.titleLabel convertRect:button.titleLabel.bounds toView:self]; + return titleRectAtPageMenu; + } + return CGRectZero; +} + +- (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + CGRect imageRectAtPageMenu = [button.imageView convertRect:button.imageView.bounds toView:self]; + return imageRectAtPageMenu; + } + return CGRectZero; +} + +- (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + CGRect buttonRectAtPageMenu = [button convertRect:button.bounds toView:self]; + return buttonRectAtPageMenu; + } + return CGRectZero; +} + +- (void)addComponentViewInScrollView:(UIView *)componentView { + [self.itemScrollView addSubview:componentView]; +} + - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius { _trackerHeight = trackerHeight; self.tracker.layer.cornerRadius = cornerRadius; @@ -745,14 +840,53 @@ - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadi [self layoutIfNeeded]; } -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex { +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state { + [self.functionButton setTitle:item.title forState:state]; + [self.functionButton setImage:item.image forState:state]; + self.functionButton.imagePosition = item.imagePosition; + self.functionButton.imageTitleSpace = item.imageTitleSpace; +} + +- (void)setFunctionButtonContent:(id)content forState:(UIControlState)state { + if ([content isKindOfClass:[NSString class]]) { + [self.functionButton setTitle:content forState:state]; + } else if ([content isKindOfClass:[UIImage class]]) { + [self.functionButton setImage:content forState:state]; + } else if ([content isKindOfClass:[SPPageMenuButtonItem class]]) { + SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)content; + [self.functionButton setTitle:item.title forState:state]; + [self.functionButton setImage:item.image forState:state]; + self.functionButton.imagePosition = item.imagePosition; + self.functionButton.imageTitleSpace = item.imageTitleSpace; + } +} + +- (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]; +} + +// 以下方法在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) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:title forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; button.imagePosition = imagePosition; - button.imageTitleSpace = imageTitleSpace; - + // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { NSMutableArray *items = self.items.mutableCopy; @@ -773,27 +907,14 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag } } -- (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.imageTitleSpace = imageTitleSpace; -} - -- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state { - [self.functionButton setTitle:item.title forState:state]; - [self.functionButton setImage:item.image forState:state]; - self.functionButton.imagePosition = item.imagePosition; - self.functionButton.imageTitleSpace = item.imageTitleSpace; -} - -// 以下2个方法在3.0版本上有升级,可以使用但不推荐 -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex { +- (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) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:title forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; button.imagePosition = imagePosition; + button.imageTitleSpace = imageTitleSpace; + // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { NSMutableArray *items = self.items.mutableCopy; @@ -808,42 +929,27 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag [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; } -- (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]; +- (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.imageTitleSpace = imageTitleSpace; } - #pragma mark - private - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { - // 如果是插入,需要改变已有button的tag值 for (SPPageMenuButton *button in self.buttons) { if (button.tag-tagBaseValue >= index) { @@ -885,8 +991,6 @@ - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { } [self.buttons insertObject:button atIndex:index]; - // setNeedsLayout会标记为需要刷新,layoutIfNeeded只有在有标记的情况下才会立即调用layoutSubViews,当然标记为刷新并非只有调用setNeedsLayout,如frame改变,addSubView等都会标记为刷新 - if (self.insert && animated) { // 是插入的新按钮,且需要动画 // 取出上一个按钮 SPPageMenuButton *lastButton; @@ -933,7 +1037,7 @@ - (void)initialize { _unSelectedItemTitleFont = [UIFont systemFontOfSize:16]; _itemTitleFont = [UIFont systemFontOfSize:16]; _trackerHeight = 3.0; - _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 + _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; _showFunctionButton = NO; @@ -941,12 +1045,11 @@ - (void)initialize { _functionButtonShadowColor = [UIColor blackColor]; _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 = [UIColor lightGrayColor]; __weak typeof(self) weakSelf = self; @@ -995,7 +1098,7 @@ - (void)setupSubViews { - (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue; NSInteger toIndex = sender.tag - tagBaseValue; - // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的,必须用下划线,用self.会造成死循环 + // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的 _selectedItemIndex = toIndex; // 如果sender是新的选中的按钮,则上一次的按钮颜色为非选中颜色,当前选中的颜色为选中颜色 if (self.selectedButton != sender) { @@ -1008,7 +1111,6 @@ - (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { [self moveItemScrollViewWithSelectedButton:sender]; if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || _selectedItemZoomScale != 1) { - if (labs(toIndex-fromIndex) >= 2) { // 该条件意思是当外界滑动scrollView连续的滑动了超过2页 for (SPPageMenuButton *button in self.buttons) { // 必须遍历将非选中按钮还原缩放,而不是仅仅只让上一个选中的按钮还原缩放。因为当用户快速滑动外界scrollView时,会频繁的调用-zoomForTitleWithProgress:fromButton:toButton:方法,有可能经过的某一个button还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 if (button != sender && !CGAffineTransformEqualToTransform(button.transform, CGAffineTransformIdentity)) { @@ -1030,7 +1132,6 @@ - (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { } } [self delegatePerformMethodWithFromIndex:fromIndex toIndex:toIndex]; - } // 点击button让itemScrollView发生偏移 @@ -1042,8 +1143,8 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuButton *)selectedButton CGPoint centerInPageMenu = [self.backgroundView convertPoint:selectedButton.center toView:self]; // CGRectGetMidX(self.backgroundView.frame)指的是屏幕水平中心位置,它的值是固定不变的 CGFloat offSetX = centerInPageMenu.x - CGRectGetMidX(self.backgroundView.frame); - - // itemScrollView的容量宽与自身宽之差(难点) + + // 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) { @@ -1053,15 +1154,13 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuButton *)selectedButton else if (offSetX > maxOffsetX){ offSetX = maxOffsetX; } - [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES]; - } // 移动跟踪器 - (void)moveTrackerWithSelectedButton:(SPPageMenuButton *)selectedButton { [UIView animateWithDuration:0.25 animations:^{ - [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; + [self resetupTrackerFrameWithSelectedButton:selectedButton]; }]; } @@ -1085,11 +1184,8 @@ - (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; - } + if (scrollView.contentOffset.x < 0 || scrollView.contentOffset.x > scrollView.contentSize.width-scrollView.bounds.size.width) {return;} // 当前偏移量 CGFloat currentOffSetX = scrollView.contentOffset.x; @@ -1121,13 +1217,10 @@ - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { 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进行赋值,直接在这里赋值可以让外界不用做此操作 @@ -1137,37 +1230,36 @@ - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { // 要return,点击了按钮,跟踪器自然会跟着被点击的按钮走 return; } - - if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeAlways) { - // 这个方法才开始移动跟踪器 - [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; - } else if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeHalf) { - SPPageMenuButton *fromButton; - SPPageMenuButton *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; + switch (self.trackerFollowingMode) { + case SPPageMenuTrackerFollowingModeAlways: + // 这个方法才开始移动跟踪器 + [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; + break; + case SPPageMenuTrackerFollowingModeHalf:{ + SPPageMenuButton *fromButton; + SPPageMenuButton *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 { + 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 - // 什么都不用做 + break; + default: + break; } - } // 这个方法才开始真正滑动跟踪器,上面都是做铺垫 @@ -1261,7 +1353,7 @@ - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton CGFloat a = self.endA - self.startA; UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:self.startA + a * fromProgress]; UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:self.startA + a * toProgress]; - + // 设置文字颜色渐变 [fromButton setTitleColor:fromColor forState:UIControlStateNormal]; [toButton setTitleColor:toColor forState:UIControlStateNormal]; @@ -1314,16 +1406,16 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N } } - #pragma mark - setter - (void)setBridgeScrollView:(UIScrollView *)bridgeScrollView { + if (bridgeScrollView == _bridgeScrollView) return; + if (_bridgeScrollView && bridgeScrollView != _bridgeScrollView) { + [_bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; + }; _bridgeScrollView = bridgeScrollView; if (bridgeScrollView) { - [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil]; - } else { - NSLog(@"你传了一个空的scrollView"); } } @@ -1404,12 +1496,18 @@ - (void)setFunctionButtonShadowOpacity:(CGFloat)functionButtonShadowOpacity { - (void)setItemPadding:(CGFloat)itemPadding { _itemPadding = itemPadding; + _forceUseSettingSpacing = YES; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } +- (void)setSpacing:(CGFloat)spacing { + _spacing = spacing; + self.itemPadding = spacing; +} + - (void)setItemTitleFont:(UIFont *)itemTitleFont { _itemTitleFont = itemTitleFont; _selectedItemTitleFont = itemTitleFont; @@ -1492,6 +1590,13 @@ - (void)setContentInset:(UIEdgeInsets)contentInset { - (void)setPermutationWay:(SPPageMenuPermutationWay)permutationWay { _permutationWay = permutationWay; + if (!_forceUseSettingSpacing) { + if (_permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { + _spacing = _itemPadding = 0; + } else { + _spacing = _itemPadding = 30; + } + } [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 @@ -1524,12 +1629,19 @@ - (NSMutableArray *)buttons { return _buttons; } -- (NSMutableDictionary *)setupWidths { +- (NSMutableDictionary *)customWidths { + + if (!_customWidths) { + _customWidths = [NSMutableDictionary dictionary]; + } + return _customWidths; +} - if (!_setupWidths) { - _setupWidths = [NSMutableDictionary dictionary]; +- (NSMutableDictionary *)customSpacings { + if (!_customSpacings) { + _customSpacings = [[NSMutableDictionary alloc] init]; } - return _setupWidths; + return _customSpacings; } - (UIImageView *)tracker { @@ -1573,7 +1685,6 @@ - (void)layoutSubviews { if (self.functionButtonShadowOpacity > 0) { self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath; } - CGFloat itemScrollViewX = 0; CGFloat itemScrollViewY = 0; CGFloat itemScrollViewW = self.showFunctionButton ? backgroundViewW-functionButtonW : backgroundViewW; @@ -1592,75 +1703,102 @@ - (void)layoutSubviews { // 提前计算每个按钮的宽度,目的是为了计算间距 for (int i= 0 ; i < self.buttons.count; i++) { SPPageMenuButton *button = self.buttons[i]; - CGFloat textW; - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; + CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; if (button == _selectedButton) { textW = ceil([button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_selectedItemTitleFont} context:nil].size.width); } else { textW = ceil([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 = button.currentImage.size.width; CGFloat imageH = button.currentImage.size.height; if (imageH > itemScrollViewH) { imageH = itemScrollViewH; } - if (button.currentTitle && !button.currentImage) { + if (button.currentTitle.length && !button.currentImage) { contentW = textW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if(button.currentImage && !button.currentTitle) { contentW = imageW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; - } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft || button.imagePosition == SPItemImagePositionDefault)) { + } else if (button.currentTitle.length && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft || button.imagePosition == SPItemImagePositionDefault)) { contentW = textW + imageW + button.imageTitleSpace+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) { contentW = MAX(textW, imageW)+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } - if (setupButtonW) { - contentW_sum += setupButtonW; - [buttonWidths addObject:@(setupButtonW)]; + if (customWidth) { + contentW_sum += customWidth; + [buttonWidths addObject:@(customWidth)]; } else { contentW_sum += contentW; [buttonWidths addObject:@(contentW)]; } } CGFloat diff = itemScrollViewW - contentW_sum; + if (self.permutationWay == SPPageMenuPermutationWayNotScrollAdaptContent && diff < 0) { + for (int i = 0; i < buttonWidths.count; i++) { + CGFloat buttonW = [buttonWidths[i] floatValue]; + buttonW -= fabs(diff)*buttonW/contentW_sum; + [buttonWidths replaceObjectAtIndex:i withObject:@(buttonW)]; + } + contentW_sum = [[buttonWidths valueForKeyPath:@"@sum.floatValue"] floatValue]; + } [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuButton *button, NSUInteger idx, BOOL * _Nonnull stop) { - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue]; + CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue]; + CGFloat customSpacing = 0.0; + if (idx > 0) { + NSString *key = [NSString stringWithFormat:@"%lu",(unsigned long)(idx-1)]; + if ([self.customSpacings.allKeys containsObject:key]) { + customSpacing = [[self.customSpacings valueForKey:key] floatValue]; + } else { + customSpacing = self->_itemPadding; + } + } + CGFloat totalCustomSpacing = [[self.customSpacings.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; + CGFloat totalSpacing = totalCustomSpacing + (self.buttons.count-self.customSpacings.count)*self->_itemPadding; 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); - + button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } else if (self.permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { // 求出外界设置的按钮宽度之和 - CGFloat totalSetupButtonW = [[self.setupWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; + CGFloat totalCustomWidth = [[self.customWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; // 如果该按钮外界设置了宽,则取外界设置的,如果外界没设置,则其余按钮等宽 - buttonW = setupButtonW ? setupButtonW : (itemScrollViewW-self->_itemPadding*(self.buttons.count)-totalSetupButtonW)/(self.buttons.count-self.setupWidths.count); + buttonW = customWidth ? customWidth : (itemScrollViewW-totalSpacing-totalCustomWidth)/(self.buttons.count-self.customWidths.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); + button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } else { buttonW = [buttonWidths[idx] floatValue]; - self->_itemPadding = diff/self.buttons.count; - if (self->_itemPadding < 0) { // 如果总内容长度大于pageMenu的长度,则对每个按钮宽度进行均等压缩 - buttonW = buttonW - fabs(diff) / self.buttons.count; - self->_itemPadding = 0; + if (_forceUseSettingSpacing) { // 如果强制使用外界设置的间距 + CGFloat paddingDiff = diff - totalSpacing; // 自动间距之和与外界设置的间距之和的差 + buttonW += paddingDiff * buttonW/contentW_sum; // 用上面计算出来的差值乘以原按钮宽度相对总按钮宽度的比例,得到的结果就是每个按钮宽度应该增减的值,这样可以保证各个按钮之间的宽度之比不变 + } else { // 否则使用自己计算的间距 + CGFloat autoPadding = diff/self.buttons.count; + if (autoPadding < 0) {autoPadding = 0.0;} + if (totalCustomSpacing > 0) { + CGFloat paddingDiff = totalCustomSpacing - autoPadding*self.customSpacings.count; + buttonW -= paddingDiff * buttonW/contentW_sum; + } + self->_itemPadding = autoPadding; + NSString *key = [NSString stringWithFormat:@"%lu",(unsigned long)(idx-1)]; + if (![self.customSpacings.allKeys containsObject:key]) { + customSpacing = self->_itemPadding; + } } + 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); + button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } lastButtonMaxX = CGRectGetMaxX(button.frame); @@ -1675,26 +1813,22 @@ - (void)layoutSubviews { self.selectedButton.frame = selectedButtonRect; } - [self resetSetupTrackerFrameWithSelectedButton:self.selectedButton]; - + [self resetupTrackerFrameWithSelectedButton:self.selectedButton]; + self.itemScrollView.contentSize = CGSizeMake(lastButtonMaxX+_itemPadding*0.5, 0); if (self.translatesAutoresizingMaskIntoConstraints == NO) { - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } } -- (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)selectedButton { - +- (void)resetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)selectedButton { CGFloat trackerX; CGFloat trackerY; CGFloat trackerW; CGFloat trackerH; - CGFloat selectedButtonWidth = selectedButton.frame.size.width; - switch (self.trackerStyle) { case SPPageMenuTrackerStyleLine: { @@ -1758,7 +1892,6 @@ - (void)dealloc { [self.bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; } - @end @implementation SPPageMenuButtonItem @@ -1782,18 +1915,6 @@ - (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image imagePosi return self; } -- (instancetype)init { - if (self = [super init]) { - [self initialize]; - } - return self; -} - -- (void)initialize { - _imagePosition = SPItemImagePositionDefault; - _imageTitleSpace = 0.0; -} - @end #pragma clang diagnostic pop diff --git a/Example/SPPageMenu/ViewController.m b/Example/SPPageMenu/ViewController.m index 0c644ac..173ae20 100644 --- a/Example/SPPageMenu/ViewController.m +++ b/Example/SPPageMenu/ViewController.m @@ -34,8 +34,8 @@ - (void)viewDidLoad { tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)]; tableView.sectionFooterHeight = CGFLOAT_MIN; - self.titles = @[@"跟踪器样式专区",@"排列方式专区",@"跟踪器跟踪模式专区",@"右侧功能按钮(插入和删除操作)",@"其余功能",@"特别属性"]; - self.dataSource = @[@[@"下划线与按钮等宽(默认)",@"下划线比按钮略长",@"下划线“依恋”样式",@"缩放",@"圆角矩形",@"圆角矩形(与pageMenu同时圆角)",@"矩形",@"无样式"],@[@"可滑动的自适应内容排列",@"不可滑动的等宽排列",@"不可滑动的自适应内容排列"],@[@"跟踪器时刻跟随外界scrollView移动",@"外界scrollVie拖动结束后,跟踪器才开始移动",@"外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动"],@[@"显示右侧功能按钮",@"给功能按钮设置图片和文字"],@[@"含有图片的按钮",@"指定按钮携带图片,或同时携带图片和文字,可以设置图片的位置",@"设置背景图片",@"角标"],@[@"bridgeScrollView属性"]]; + self.titles = @[@"跟踪器样式专区",@"排列方式专区",@"跟踪器跟踪模式专区",@"右侧功能按钮(插入和删除操作)",@"其它示例",@"特别属性"]; + self.dataSource = @[@[@"下划线与按钮等宽(默认)",@"下划线比按钮略长",@"下划线“依恋”样式",@"缩放",@"圆角矩形",@"圆角矩形(与pageMenu同时圆角)",@"矩形",@"无样式"],@[@"可滑动的自适应内容排列",@"不可滑动的等宽排列",@"不可滑动的自适应内容排列"],@[@"跟踪器时刻跟随外界scrollView移动",@"外界scrollVie拖动结束后,跟踪器才开始移动",@"外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动"],@[@"显示右侧功能按钮",@"给功能按钮设置图片和文字"],@[@"含有图片的按钮",@"指定按钮携带图片,或同时携带图片和文字,可以设置图片的位置",@"设置背景图片",@"某个按钮上添加一个副标题",@"角标"]]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { diff --git a/README.md b/README.md index e53411e..4936857 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,27 @@ ## 如何安装 +#### 版本 3.5.0 + +``` +target 'MyApp' do + pod 'SPPageMenu', '~> 3.5.0' +end + +说明:3.5.0版本在3.4.5版本的基础上改动如下: +1、新增7个API +* - (void)setContent:(id)content forItemAtIndex:(NSUInteger)itemIndex; +* - (void)setCustomSpacing:(CGFloat)spacing afterItemAtIndex:(NSUInteger)itemIndex; +* - (CGFloat)customSpacingAfterItemAtIndex:(NSUInteger)itemIndex; +* - (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; +* - (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; +* - (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; +* - (void)addComponentViewInScrollView:(UIView *)componentView; +2、在不可滑动自适应内容的排列方式下,设置间距依然生效 +3、修复设置指定item宽度和内间距失效问题 +4、修复多次对bridgeScrollView赋值上一个KCO观察者未移除问题 +``` + #### 版本 3.4.5 ``` @@ -54,7 +75,7 @@ end ``` target 'MyApp' do -  pod 'SPPageMenu', '~> 3.4.1' + pod 'SPPageMenu', '~> 3.4.1' end 3.4.1版本在3.4.0版本的基础上修复了多次调用setItems:selectedItemIndex:方法引发的问题 @@ -64,7 +85,7 @@ end ``` target 'MyApp' do -  pod 'SPPageMenu', '~> 3.4.0' + pod 'SPPageMenu', '~> 3.4.0' end 说明:3.4.0版本在3.0版本的基础上主要改动如下: @@ -79,7 +100,7 @@ end ``` target 'MyApp' do -  pod 'SPPageMenu', '~> 3.0' + pod 'SPPageMenu', '~> 3.0' end 说明:3.0版本在2.5.5版本的基础上主要改动如下: @@ -100,7 +121,7 @@ end ``` target 'MyApp' do -  pod 'SPPageMenu', '~> 2.5.5' + pod 'SPPageMenu', '~> 2.5.5' end 说明:2.5.5版本在2.5.3版本的基础上主要改动如下: diff --git a/SPPageMenu.podspec b/SPPageMenu.podspec index 924a9c8..70995fa 100644 --- a/SPPageMenu.podspec +++ b/SPPageMenu.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| # s.name = "SPPageMenu" - s.version = "3.4.5" + s.version = "3.5.0" s.summary = "分页菜单." # This description is used to generate tags and improve search results. diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index 5a4dad1..7cfb5bd 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -21,15 +21,15 @@ typedef NS_ENUM(NSInteger, SPPageMenuTrackerStyle) { }; typedef NS_ENUM(NSInteger, SPPageMenuPermutationWay) { - SPPageMenuPermutationWayScrollAdaptContent = 0, // 自适应内容,可以左右滑动 - SPPageMenuPermutationWayNotScrollEqualWidths, // 等宽排列,不可以滑动,整个内容被控制在pageMenu的范围之内,等宽是根据pageMenu的总宽度对每个item均分 - SPPageMenuPermutationWayNotScrollAdaptContent // 自适应内容,不可以滑动,整个内容被控制在pageMenu的范围之内,这种排列方式下,自动计算item之间的间距,itemPadding属性无效 + SPPageMenuPermutationWayScrollAdaptContent = 0, // 自适应内容,可以左右滑动 + SPPageMenuPermutationWayNotScrollEqualWidths, // 等宽排列,不可以滑动,整个内容被控制在pageMenu的范围之内,等宽是根据pageMenu的总宽度对每个按钮均分 + SPPageMenuPermutationWayNotScrollAdaptContent // 自适应内容,不可以滑动,整个内容被控制在pageMenu的范围之内,这种排列方式下,自动计算item之间的间距,itemPadding属性无效 }; typedef NS_ENUM(NSInteger, SPPageMenuTrackerFollowingMode) { SPPageMenuTrackerFollowingModeAlways = 0, // 外界scrollView拖动时,跟踪器时刻跟随外界scrollView移动 - SPPageMenuTrackerFollowingModeEnd, // 外界scrollVie拖动w结束后,跟踪器才开始移动 - SPPageMenuTrackerFollowingModeHalf // 外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动 + SPPageMenuTrackerFollowingModeEnd, // 外界scrollVie拖动结束后,跟踪器才开始移动 + SPPageMenuTrackerFollowingModeHalf // 外界scrollView拖动到一半时,跟踪器开始移动 }; typedef NS_ENUM(NSInteger, SPItemImagePosition) { @@ -68,7 +68,6 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setItems:(nullable NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex; @property (nonatomic) NSInteger selectedItemIndex; // 选中的item下标,改变其值可以用于切换选中的item - @property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 #if TARGET_INTERFACE_BUILDER @@ -77,8 +76,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, readonly) SPPageMenuTrackerStyle trackerStyle; #endif -// item之间的间距,默认30;当排列方式permutationWay为‘SPPageMenuPermutationWayNotScrollAdaptContent’时此属性无效,无效是合理的,不可能做到“不可滑动且自适应内容”然后间距又自定义,这2者相互制约; -@property (nonatomic, assign) CGFloat itemPadding; +@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; // 排列方式 + +@property (nonatomic, assign) CGFloat spacing; // item之间的间距 @property (nonatomic, strong) UIColor *selectedItemTitleColor; // 选中的item标题颜色 @property (nonatomic, strong) UIColor *unSelectedItemTitleColor; // 未选中的item标题颜色 @@ -87,30 +87,21 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonnull, nonatomic, strong) UIFont *selectedItemTitleFont; // 选中的item字体 @property (nonnull, nonatomic, strong) UIFont *unSelectedItemTitleFont; // 未选中的item字体 -// 外界的srollView,pageMenu会监听该scrollView的滚动状况,让跟踪器时刻跟随此scrollView滑动;所谓的滚动状况,是指手指拖拽滚动,非手指拖拽不算 +// 外界添加控制器view的srollView,pageMenu会监听该scrollView的滚动状况,让跟踪器时刻跟随此scrollView滑动;所谓的滚动状况,是指手指拖拽滚动,非手指拖拽不算 @property (nonatomic, strong) UIScrollView *bridgeScrollView; -@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; // 排列方式 - -@property (nonatomic, assign) UIEdgeInsets contentInset; // 内容的四周内边距(内容不包括分割线),默认UIEdgeInsetsZero - -@property(nonatomic) BOOL bounces; // 边界反弹效果,默认YES -@property(nonatomic) BOOL alwaysBounceHorizontal; // 水平方向上,当内容没有充满scrollView时,滑动scrollView是否有反弹效果,默认YES - - // 跟踪器 -@property (nonatomic, readonly) UIImageView *tracker; // 跟踪器,它是一个UIImageView类型,你可以拿到该对象去设置一些自己想要的属性,例如颜色,图片等,但是设置frame无效 +@property (nonatomic, readonly) UIImageView *tracker; // 跟踪器,它是一个UIImageView类型,你可以拿到该对象去设置一些自己想要的属性,例如颜色,图片等 @property (nonatomic, assign) CGFloat trackerWidth; // 跟踪器的宽度 -// 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。如果你想用默认高度,但是又不想要圆角半径,你可以设置trackerHeight为3,cornerRadius为0,这是去除默认半径的唯一办法 -- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; - -// 跟踪器的跟踪模式 -@property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; +- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; // 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。 +@property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; // 跟踪器的跟踪模式 // 分割线 @property (nonatomic, readonly) UIImageView *dividingLine; // 分割线,你可以拿到该对象设置一些自己想要的属性,如颜色、图片等,如果想要隐藏分割线,拿到该对象直接设置hidden为YES或设置alpha<0.01即可(eg:pageMenu.dividingLine.hidden = YES) @property (nonatomic) CGFloat dividingLineHeight; // 分割线的高度 +@property (nonatomic, assign) UIEdgeInsets contentInset; // 内容的四周内边距(内容不包括分割线),默认UIEdgeInsetsZero + // 选中的item缩放系数,默认为1,为1代表不缩放,[0,1)之间缩小,(1,+∞)之间放大,(-1,0)之间"倒立"缩小,(-∞,-1)之间"倒立"放大,为-1"倒立不缩放",如果依然使用了废弃的SPPageMenuTrackerStyleTextZoom样式,则缩放系数默认为1.3 @property (nonatomic) CGFloat selectedItemZoomScale; @property (nonatomic, assign) BOOL needTextColorGradients; // 是否需要文字渐变,默认为YES @@ -122,43 +113,55 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 @property (nonatomic, weak) SPPageMenuButton *functionButton; +@property (nonatomic) BOOL bounces; // 边界反弹效果,默认YES +@property (nonatomic) BOOL alwaysBounceHorizontal; // 水平方向上,当内容没有充满scrollView时,滑动scrollView是否有反弹效果,默认NO + @property (nonatomic, weak) id delegate; // 插入item,插入和删除操作时,如果itemIndex超过了了items的个数,则不做任何操作 -- (void)insertItemWithTitle:(nullable NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItemWithImage:(nullable UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItem:(nullable SPPageMenuButtonItem *)item atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItemWithTitle:(nonnull NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItemWithImage:(nonnull UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; +- (void)insertItem:(nonnull SPPageMenuButtonItem *)item atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; // 如果移除的正是当前选中的item(当前选中的item下标不为0),删除之后,选中的item会切换为上一个item - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated; - (void)removeAllItems; -- (void)setTitle:(nullable NSString *)title forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的标题,设置后,仅会有文字 +- (void)setTitle:(nonnull NSString *)title forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的标题,设置后,仅会有文字 - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的标题 -- (void)setImage:(nullable UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 +- (void)setImage:(nonnull UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的图片 -- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex; // 同时为指定item设置标题和图片,其中参数item相当于一个模型,可以同时设置文字和图片 +- (void)setItem:(SPPageMenuButtonItem *)item forItemAtIndex:(NSUInteger)itemIndex; // 同时为指定item设置标题和图片 - (nullable SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex; // 获取指定item -- (id)objectForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item,该方法获取的item可能是NSString、UIImage或SPPageMenuButtonItem类型 +- (void)setContent:(id)content forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的内容,content可以是NSString、UIImage或SPPageMenuButtonItem类型 +- (id)contentForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的内容,该方法返回值可能是NSString、UIImage或SPPageMenuButtonItem类型 - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的宽度(如果width为0,item会根据内容自动计算width) - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的宽度 +- (void)setCustomSpacing:(CGFloat)spacing afterItemAtIndex:(NSUInteger)itemIndex; // 设置指定item后面的自定义间距 +- (CGFloat)customSpacingAfterItemAtIndex:(NSUInteger)itemIndex; // 获取指定item后面的自定义间距 + - (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的enabled状态 - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的enabled状态 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的四周内边距 - (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的四周内边距 -// 设置背景图片,barMetrics只有为UIBarMetricsDefault时才生效,如果外界传进来的backgroundImage调用过- resizableImageWithCapInsets:且参数capInsets不为UIEdgeInsetsZero,则直接用backgroundImage作为背景图; 否则内部会自动调用- resizableImageWithCapInsets:进行拉伸 +// 设置背景图片,barMetrics只有为UIBarMetricsDefault时才生效,如果外界传进来的backgroundImage调用过-resizableImageWithCapInsets:且参数capInsets不为UIEdgeInsetsZero,则直接用backgroundImage作为背景图; 否则内部会自动调用-resizableImageWithCapInsets:进行拉伸 - (void)setBackgroundImage:(nullable UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics; - (nullable UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics; // 获取背景图片 -// 同时为functionButton设置标题和图片 -- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state; +- (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 文字相对pageMenu位置和大小 +- (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 图片相对pageMenu位置和大小 +- (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 按钮相对pageMenu位置和大小 +- (void)addComponentViewInScrollView:(UIView *)componentView; // 在内置的scrollView上添加一个view + +// 设置功能按钮的内容,content可以是NSString、UIImage或SPPageMenuButtonItem类型 +- (void)setFunctionButtonContent:(id)content forState:(UIControlState)state; // 为functionButton配置相关属性,如设置字体、文字颜色等;在此,attributes中,只有NSFontAttributeName、NSForegroundColorAttributeName、NSBackgroundColorAttributeName有效 - (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state; @@ -175,9 +178,9 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; - // -------------- 以下方法和属性被废弃,不再建议使用 -------------- +@property (nonatomic, assign) CGFloat itemPadding NS_DEPRECATED_IOS(6_0, 6_0, "Use spacing instead");; // 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.4.0版本已纠正 - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContentEdgeInsets:forItemAtIndex:"); // 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.4.0版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 @@ -187,6 +190,10 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); - (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); +- (id)objectForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -contentForItemAtIndex:"); +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use -setFunctionButtonContent:forState:"); +- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem:forItemAtIndex:"); +- (void)setContent:(id)content forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContent:forItemAtIndex:"); @end @interface SPPageMenuButton : UIButton @@ -201,7 +208,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { + (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition; @property (nonatomic, copy) NSString *title; -@property (nonatomic, strong) UIImage *image; +@property (nonatomic, copy) UIImage *image; // 图片的位置 @property (nonatomic, assign) SPItemImagePosition imagePosition; // 图片与标题之间的间距,默认0.0 diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index 841e081..05885de 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -410,7 +410,8 @@ @interface SPPageMenu() @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; @property (nonatomic, strong) NSMutableArray *buttons; @property (nonatomic, strong) SPPageMenuButton *selectedButton; -@property (nonatomic, strong) NSMutableDictionary *setupWidths; +@property (nonatomic, strong) NSMutableDictionary *customWidths; +@property (nonatomic, strong) NSMutableDictionary *customSpacings; @property (nonatomic, assign) BOOL insert; // 起始偏移量,为了判断滑动方向 @property (nonatomic, assign) CGFloat beginOffsetX; @@ -428,6 +429,7 @@ @interface SPPageMenu() // 这个高度,是存储itemScrollView的高度 @property (nonatomic, assign) CGFloat itemScrollViewH; +@property (nonatomic, assign) BOOL forceUseSettingSpacing; @end #pragma clang diagnostic push @@ -487,7 +489,7 @@ - (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex if ([self haveOrNeedsTracker]) { [self.itemScrollView insertSubview:self.tracker atIndex:0]; // 这里千万不能再去调用setNeedsLayout和layoutIfNeeded,因为如果外界在此之前对selectedButton进行了缩放,调用了layoutSubViews后会重新对selectedButton设置frame,先缩放再重设置frame会导致文字显示不全,所以我们直接跳过layoutSubViews调用resetSetupTrackerFrameWithSelectedButton:只设置tracker的frame - [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; + [self resetupTrackerFrameWithSelectedButton:selectedButton]; } } } @@ -563,7 +565,6 @@ - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { } else { [self setNeedsLayout]; } - } - (void)removeAllItems { @@ -588,6 +589,7 @@ - (void)removeAllItems { } - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { + if (title == nil) return; if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setImage:nil forState:UIControlStateNormal]; @@ -600,7 +602,7 @@ - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { [self setNeedsLayout]; } -- (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { +- (NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; NSAssert([object isKindOfClass:[NSString class]],@"itemIndex对应的item不是NSString类型,请仔细核对"); @@ -610,6 +612,7 @@ - (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { } - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { + if (image == nil) return; if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:nil forState:UIControlStateNormal]; @@ -622,7 +625,7 @@ - (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { [self setNeedsLayout]; } -- (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { +- (UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; NSAssert([object isKindOfClass:[UIImage class]],@"itemIndex对应的item不是UIImage类型,请仔细核对"); @@ -632,6 +635,7 @@ - (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { } - (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex { + if (item == nil) return; if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:item.title forState:UIControlStateNormal]; @@ -649,6 +653,10 @@ - (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex } } +- (void)setItem:(SPPageMenuButtonItem *)item forItemAtIndex:(NSUInteger)itemIndex { + [self setItem:item forItemIndex:itemIndex]; +} + - (SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; @@ -658,6 +666,41 @@ - (SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex { return nil; } +- (void)setContent:(id)content forItemIndex:(NSUInteger)itemIndex { + if (content == nil) return; + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + if ([content isKindOfClass:[NSString class]]) { + [button setTitle:content forState:UIControlStateNormal]; + } else if ([content isKindOfClass:[UIImage class]]) { + [button setImage:content forState:UIControlStateNormal]; + } else if ([content isKindOfClass:[SPPageMenuButtonItem class]]) { + SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)content; + [button setTitle:item.title forState:UIControlStateNormal]; + [button setImage:item.image forState:UIControlStateNormal]; + button.imagePosition = item.imagePosition; + button.imageTitleSpace = item.imageTitleSpace; + } + NSMutableArray *items = self.items.mutableCopy; + [items replaceObjectAtIndex:itemIndex withObject:content]; + self.items = items.copy; + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + +- (void)setContent:(id)content forItemAtIndex:(NSUInteger)itemIndex { + [self setContent:content forItemIndex:itemIndex]; +} + +- (id)contentForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.items.count) { + id content = [self.items objectAtIndex:itemIndex]; + return content; + } + return nil; +} + - (id)objectForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.items.count) { id object = [self.items objectAtIndex:itemIndex]; @@ -683,14 +726,16 @@ - (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex { - (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { - [self.setupWidths setObject:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; + [self.customWidths setValue:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; + [self setNeedsLayout]; + [self layoutIfNeeded]; } } - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { - CGFloat setupWidth = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; - if (setupWidth) { - return setupWidth; + CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; + if (customWidth) { + return customWidth; } else { if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; @@ -700,6 +745,23 @@ - (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { return 0; } +- (void)setCustomSpacing:(CGFloat)spacing afterItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + [self.customSpacings setValue:@(spacing) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; + [self setNeedsLayout]; + [self layoutIfNeeded]; + } +} + +- (CGFloat)customSpacingAfterItemAtIndex:(NSUInteger)itemIndex { + if ([self.customSpacings.allKeys containsObject:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]) { + CGFloat customSpacing = [[self.customSpacings valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; + return customSpacing; + } else { + return CGFLOAT_MAX; + } +} + - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex { if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; @@ -711,6 +773,8 @@ - (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forItemAtIndex:(NSUInteg if (itemIndex < self.buttons.count) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; button.contentEdgeInsets = contentInset; + [self setNeedsLayout]; + [self layoutIfNeeded]; } } @@ -738,6 +802,37 @@ - (UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics { return self.backgroundImageView.image; } +- (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + CGRect titleRectAtPageMenu = [button.titleLabel convertRect:button.titleLabel.bounds toView:self]; + return titleRectAtPageMenu; + } + return CGRectZero; +} + +- (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + CGRect imageRectAtPageMenu = [button.imageView convertRect:button.imageView.bounds toView:self]; + return imageRectAtPageMenu; + } + return CGRectZero; +} + +- (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { + if (itemIndex < self.buttons.count) { + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; + CGRect buttonRectAtPageMenu = [button convertRect:button.bounds toView:self]; + return buttonRectAtPageMenu; + } + return CGRectZero; +} + +- (void)addComponentViewInScrollView:(UIView *)componentView { + [self.itemScrollView addSubview:componentView]; +} + - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius { _trackerHeight = trackerHeight; self.tracker.layer.cornerRadius = cornerRadius; @@ -745,14 +840,53 @@ - (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadi [self layoutIfNeeded]; } -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex { +- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state { + [self.functionButton setTitle:item.title forState:state]; + [self.functionButton setImage:item.image forState:state]; + self.functionButton.imagePosition = item.imagePosition; + self.functionButton.imageTitleSpace = item.imageTitleSpace; +} + +- (void)setFunctionButtonContent:(id)content forState:(UIControlState)state { + if ([content isKindOfClass:[NSString class]]) { + [self.functionButton setTitle:content forState:state]; + } else if ([content isKindOfClass:[UIImage class]]) { + [self.functionButton setImage:content forState:state]; + } else if ([content isKindOfClass:[SPPageMenuButtonItem class]]) { + SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)content; + [self.functionButton setTitle:item.title forState:state]; + [self.functionButton setImage:item.image forState:state]; + self.functionButton.imagePosition = item.imagePosition; + self.functionButton.imageTitleSpace = item.imageTitleSpace; + } +} + +- (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]; +} + +// 以下方法在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) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:title forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; button.imagePosition = imagePosition; - button.imageTitleSpace = imageTitleSpace; - + // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { NSMutableArray *items = self.items.mutableCopy; @@ -773,27 +907,14 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag } } -- (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.imageTitleSpace = imageTitleSpace; -} - -- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state { - [self.functionButton setTitle:item.title forState:state]; - [self.functionButton setImage:item.image forState:state]; - self.functionButton.imagePosition = item.imagePosition; - self.functionButton.imageTitleSpace = item.imageTitleSpace; -} - -// 以下2个方法在3.0版本上有升级,可以使用但不推荐 -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex { +- (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) { SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:title forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; button.imagePosition = imagePosition; + button.imageTitleSpace = imageTitleSpace; + // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { NSMutableArray *items = self.items.mutableCopy; @@ -808,42 +929,27 @@ - (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imag [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; } -- (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]; +- (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.imageTitleSpace = imageTitleSpace; } - #pragma mark - private - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { - // 如果是插入,需要改变已有button的tag值 for (SPPageMenuButton *button in self.buttons) { if (button.tag-tagBaseValue >= index) { @@ -885,8 +991,6 @@ - (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { } [self.buttons insertObject:button atIndex:index]; - // setNeedsLayout会标记为需要刷新,layoutIfNeeded只有在有标记的情况下才会立即调用layoutSubViews,当然标记为刷新并非只有调用setNeedsLayout,如frame改变,addSubView等都会标记为刷新 - if (self.insert && animated) { // 是插入的新按钮,且需要动画 // 取出上一个按钮 SPPageMenuButton *lastButton; @@ -933,7 +1037,7 @@ - (void)initialize { _unSelectedItemTitleFont = [UIFont systemFontOfSize:16]; _itemTitleFont = [UIFont systemFontOfSize:16]; _trackerHeight = 3.0; - _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; // 适配屏幕分辨率 + _dividingLineHeight = 1.0 / [UIScreen mainScreen].scale; _contentInset = UIEdgeInsetsZero; _selectedItemIndex = 0; _showFunctionButton = NO; @@ -941,12 +1045,11 @@ - (void)initialize { _functionButtonShadowColor = [UIColor blackColor]; _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 = [UIColor lightGrayColor]; __weak typeof(self) weakSelf = self; @@ -995,7 +1098,7 @@ - (void)setupSubViews { - (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue; NSInteger toIndex = sender.tag - tagBaseValue; - // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的,必须用下划线,用self.会造成死循环 + // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的 _selectedItemIndex = toIndex; // 如果sender是新的选中的按钮,则上一次的按钮颜色为非选中颜色,当前选中的颜色为选中颜色 if (self.selectedButton != sender) { @@ -1008,7 +1111,6 @@ - (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { [self moveItemScrollViewWithSelectedButton:sender]; if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || _selectedItemZoomScale != 1) { - if (labs(toIndex-fromIndex) >= 2) { // 该条件意思是当外界滑动scrollView连续的滑动了超过2页 for (SPPageMenuButton *button in self.buttons) { // 必须遍历将非选中按钮还原缩放,而不是仅仅只让上一个选中的按钮还原缩放。因为当用户快速滑动外界scrollView时,会频繁的调用-zoomForTitleWithProgress:fromButton:toButton:方法,有可能经过的某一个button还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 if (button != sender && !CGAffineTransformEqualToTransform(button.transform, CGAffineTransformIdentity)) { @@ -1030,7 +1132,6 @@ - (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { } } [self delegatePerformMethodWithFromIndex:fromIndex toIndex:toIndex]; - } // 点击button让itemScrollView发生偏移 @@ -1042,8 +1143,8 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuButton *)selectedButton CGPoint centerInPageMenu = [self.backgroundView convertPoint:selectedButton.center toView:self]; // CGRectGetMidX(self.backgroundView.frame)指的是屏幕水平中心位置,它的值是固定不变的 CGFloat offSetX = centerInPageMenu.x - CGRectGetMidX(self.backgroundView.frame); - - // itemScrollView的容量宽与自身宽之差(难点) + + // 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) { @@ -1053,15 +1154,13 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuButton *)selectedButton else if (offSetX > maxOffsetX){ offSetX = maxOffsetX; } - [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES]; - } // 移动跟踪器 - (void)moveTrackerWithSelectedButton:(SPPageMenuButton *)selectedButton { [UIView animateWithDuration:0.25 animations:^{ - [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; + [self resetupTrackerFrameWithSelectedButton:selectedButton]; }]; } @@ -1085,11 +1184,8 @@ - (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; - } + if (scrollView.contentOffset.x < 0 || scrollView.contentOffset.x > scrollView.contentSize.width-scrollView.bounds.size.width) {return;} // 当前偏移量 CGFloat currentOffSetX = scrollView.contentOffset.x; @@ -1121,13 +1217,10 @@ - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { 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进行赋值,直接在这里赋值可以让外界不用做此操作 @@ -1137,37 +1230,36 @@ - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { // 要return,点击了按钮,跟踪器自然会跟着被点击的按钮走 return; } - - if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeAlways) { - // 这个方法才开始移动跟踪器 - [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; - } else if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeHalf) { - SPPageMenuButton *fromButton; - SPPageMenuButton *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; + switch (self.trackerFollowingMode) { + case SPPageMenuTrackerFollowingModeAlways: + // 这个方法才开始移动跟踪器 + [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; + break; + case SPPageMenuTrackerFollowingModeHalf:{ + SPPageMenuButton *fromButton; + SPPageMenuButton *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 { + 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 - // 什么都不用做 + break; + default: + break; } - } // 这个方法才开始真正滑动跟踪器,上面都是做铺垫 @@ -1261,7 +1353,7 @@ - (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton CGFloat a = self.endA - self.startA; UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:self.startA + a * fromProgress]; UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:self.startA + a * toProgress]; - + // 设置文字颜色渐变 [fromButton setTitleColor:fromColor forState:UIControlStateNormal]; [toButton setTitleColor:toColor forState:UIControlStateNormal]; @@ -1314,16 +1406,16 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N } } - #pragma mark - setter - (void)setBridgeScrollView:(UIScrollView *)bridgeScrollView { + if (bridgeScrollView == _bridgeScrollView) return; + if (_bridgeScrollView && bridgeScrollView != _bridgeScrollView) { + [_bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; + }; _bridgeScrollView = bridgeScrollView; if (bridgeScrollView) { - [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil]; - } else { - NSLog(@"你传了一个空的scrollView"); } } @@ -1404,12 +1496,18 @@ - (void)setFunctionButtonShadowOpacity:(CGFloat)functionButtonShadowOpacity { - (void)setItemPadding:(CGFloat)itemPadding { _itemPadding = itemPadding; + _forceUseSettingSpacing = YES; [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } +- (void)setSpacing:(CGFloat)spacing { + _spacing = spacing; + self.itemPadding = spacing; +} + - (void)setItemTitleFont:(UIFont *)itemTitleFont { _itemTitleFont = itemTitleFont; _selectedItemTitleFont = itemTitleFont; @@ -1492,6 +1590,13 @@ - (void)setContentInset:(UIEdgeInsets)contentInset { - (void)setPermutationWay:(SPPageMenuPermutationWay)permutationWay { _permutationWay = permutationWay; + if (!_forceUseSettingSpacing) { + if (_permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { + _spacing = _itemPadding = 0; + } else { + _spacing = _itemPadding = 30; + } + } [self setNeedsLayout]; [self layoutIfNeeded]; // 修正scrollView偏移 @@ -1524,12 +1629,19 @@ - (NSMutableArray *)buttons { return _buttons; } -- (NSMutableDictionary *)setupWidths { +- (NSMutableDictionary *)customWidths { + + if (!_customWidths) { + _customWidths = [NSMutableDictionary dictionary]; + } + return _customWidths; +} - if (!_setupWidths) { - _setupWidths = [NSMutableDictionary dictionary]; +- (NSMutableDictionary *)customSpacings { + if (!_customSpacings) { + _customSpacings = [[NSMutableDictionary alloc] init]; } - return _setupWidths; + return _customSpacings; } - (UIImageView *)tracker { @@ -1573,7 +1685,6 @@ - (void)layoutSubviews { if (self.functionButtonShadowOpacity > 0) { self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath; } - CGFloat itemScrollViewX = 0; CGFloat itemScrollViewY = 0; CGFloat itemScrollViewW = self.showFunctionButton ? backgroundViewW-functionButtonW : backgroundViewW; @@ -1592,75 +1703,102 @@ - (void)layoutSubviews { // 提前计算每个按钮的宽度,目的是为了计算间距 for (int i= 0 ; i < self.buttons.count; i++) { SPPageMenuButton *button = self.buttons[i]; - CGFloat textW; - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; + CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; if (button == _selectedButton) { textW = ceil([button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_selectedItemTitleFont} context:nil].size.width); } else { textW = ceil([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 = button.currentImage.size.width; CGFloat imageH = button.currentImage.size.height; if (imageH > itemScrollViewH) { imageH = itemScrollViewH; } - if (button.currentTitle && !button.currentImage) { + if (button.currentTitle.length && !button.currentImage) { contentW = textW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if(button.currentImage && !button.currentTitle) { contentW = imageW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; - } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft || button.imagePosition == SPItemImagePositionDefault)) { + } else if (button.currentTitle.length && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft || button.imagePosition == SPItemImagePositionDefault)) { contentW = textW + imageW + button.imageTitleSpace+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) { contentW = MAX(textW, imageW)+button.contentEdgeInsets.left+button.contentEdgeInsets.right; } - if (setupButtonW) { - contentW_sum += setupButtonW; - [buttonWidths addObject:@(setupButtonW)]; + if (customWidth) { + contentW_sum += customWidth; + [buttonWidths addObject:@(customWidth)]; } else { contentW_sum += contentW; [buttonWidths addObject:@(contentW)]; } } CGFloat diff = itemScrollViewW - contentW_sum; + if (self.permutationWay == SPPageMenuPermutationWayNotScrollAdaptContent && diff < 0) { + for (int i = 0; i < buttonWidths.count; i++) { + CGFloat buttonW = [buttonWidths[i] floatValue]; + buttonW -= fabs(diff)*buttonW/contentW_sum; + [buttonWidths replaceObjectAtIndex:i withObject:@(buttonW)]; + } + contentW_sum = [[buttonWidths valueForKeyPath:@"@sum.floatValue"] floatValue]; + } [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuButton *button, NSUInteger idx, BOOL * _Nonnull stop) { - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue]; + CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue]; + CGFloat customSpacing = 0.0; + if (idx > 0) { + NSString *key = [NSString stringWithFormat:@"%lu",(unsigned long)(idx-1)]; + if ([self.customSpacings.allKeys containsObject:key]) { + customSpacing = [[self.customSpacings valueForKey:key] floatValue]; + } else { + customSpacing = self->_itemPadding; + } + } + CGFloat totalCustomSpacing = [[self.customSpacings.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; + CGFloat totalSpacing = totalCustomSpacing + (self.buttons.count-self.customSpacings.count)*self->_itemPadding; 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); - + button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } else if (self.permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { // 求出外界设置的按钮宽度之和 - CGFloat totalSetupButtonW = [[self.setupWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; + CGFloat totalCustomWidth = [[self.customWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; // 如果该按钮外界设置了宽,则取外界设置的,如果外界没设置,则其余按钮等宽 - buttonW = setupButtonW ? setupButtonW : (itemScrollViewW-self->_itemPadding*(self.buttons.count)-totalSetupButtonW)/(self.buttons.count-self.setupWidths.count); + buttonW = customWidth ? customWidth : (itemScrollViewW-totalSpacing-totalCustomWidth)/(self.buttons.count-self.customWidths.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); + button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } else { buttonW = [buttonWidths[idx] floatValue]; - self->_itemPadding = diff/self.buttons.count; - if (self->_itemPadding < 0) { // 如果总内容长度大于pageMenu的长度,则对每个按钮宽度进行均等压缩 - buttonW = buttonW - fabs(diff) / self.buttons.count; - self->_itemPadding = 0; + if (_forceUseSettingSpacing) { // 如果强制使用外界设置的间距 + CGFloat paddingDiff = diff - totalSpacing; // 自动间距之和与外界设置的间距之和的差 + buttonW += paddingDiff * buttonW/contentW_sum; // 用上面计算出来的差值乘以原按钮宽度相对总按钮宽度的比例,得到的结果就是每个按钮宽度应该增减的值,这样可以保证各个按钮之间的宽度之比不变 + } else { // 否则使用自己计算的间距 + CGFloat autoPadding = diff/self.buttons.count; + if (autoPadding < 0) {autoPadding = 0.0;} + if (totalCustomSpacing > 0) { + CGFloat paddingDiff = totalCustomSpacing - autoPadding*self.customSpacings.count; + buttonW -= paddingDiff * buttonW/contentW_sum; + } + self->_itemPadding = autoPadding; + NSString *key = [NSString stringWithFormat:@"%lu",(unsigned long)(idx-1)]; + if (![self.customSpacings.allKeys containsObject:key]) { + customSpacing = self->_itemPadding; + } } + 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); + button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); } } lastButtonMaxX = CGRectGetMaxX(button.frame); @@ -1675,26 +1813,22 @@ - (void)layoutSubviews { self.selectedButton.frame = selectedButtonRect; } - [self resetSetupTrackerFrameWithSelectedButton:self.selectedButton]; - + [self resetupTrackerFrameWithSelectedButton:self.selectedButton]; + self.itemScrollView.contentSize = CGSizeMake(lastButtonMaxX+_itemPadding*0.5, 0); if (self.translatesAutoresizingMaskIntoConstraints == NO) { - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; } } -- (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)selectedButton { - +- (void)resetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)selectedButton { CGFloat trackerX; CGFloat trackerY; CGFloat trackerW; CGFloat trackerH; - CGFloat selectedButtonWidth = selectedButton.frame.size.width; - switch (self.trackerStyle) { case SPPageMenuTrackerStyleLine: { @@ -1758,7 +1892,6 @@ - (void)dealloc { [self.bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; } - @end @implementation SPPageMenuButtonItem @@ -1782,18 +1915,6 @@ - (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image imagePosi return self; } -- (instancetype)init { - if (self = [super init]) { - [self initialize]; - } - return self; -} - -- (void)initialize { - _imagePosition = SPItemImagePositionDefault; - _imageTitleSpace = 0.0; -} - @end #pragma clang diagnostic pop From fd70aaa8c2dd5ce3d44fa54407ff4ad04f79add9 Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Thu, 14 Feb 2019 12:30:26 +0900 Subject: [PATCH 19/22] Fix self warning --- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 2 +- SPPageMenu/SPPageMenu.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m index 05885de..f5016d3 100644 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m @@ -1778,7 +1778,7 @@ - (void)layoutSubviews { } else { buttonW = [buttonWidths[idx] floatValue]; - if (_forceUseSettingSpacing) { // 如果强制使用外界设置的间距 + if (self->_forceUseSettingSpacing) { // 如果强制使用外界设置的间距 CGFloat paddingDiff = diff - totalSpacing; // 自动间距之和与外界设置的间距之和的差 buttonW += paddingDiff * buttonW/contentW_sum; // 用上面计算出来的差值乘以原按钮宽度相对总按钮宽度的比例,得到的结果就是每个按钮宽度应该增减的值,这样可以保证各个按钮之间的宽度之比不变 } else { // 否则使用自己计算的间距 diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index 05885de..f5016d3 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -1778,7 +1778,7 @@ - (void)layoutSubviews { } else { buttonW = [buttonWidths[idx] floatValue]; - if (_forceUseSettingSpacing) { // 如果强制使用外界设置的间距 + if (self->_forceUseSettingSpacing) { // 如果强制使用外界设置的间距 CGFloat paddingDiff = diff - totalSpacing; // 自动间距之和与外界设置的间距之和的差 buttonW += paddingDiff * buttonW/contentW_sum; // 用上面计算出来的差值乘以原按钮宽度相对总按钮宽度的比例,得到的结果就是每个按钮宽度应该增减的值,这样可以保证各个按钮之间的宽度之比不变 } else { // 否则使用自己计算的间距 From 40f04765363bfabe29ea876315be2708fdb26aec Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Thu, 28 Mar 2019 14:19:28 +0900 Subject: [PATCH 20/22] Remove duplicated files --- Example/SPPageMenu.xcodeproj/project.pbxproj | 29 +- Example/SPPageMenu/SPPageMenu/.DS_Store | Bin 6148 -> 0 bytes Example/SPPageMenu/SPPageMenu/SPPageMenu.h | 222 -- Example/SPPageMenu/SPPageMenu/SPPageMenu.m | 1926 ------------------ 4 files changed, 15 insertions(+), 2162 deletions(-) delete mode 100644 Example/SPPageMenu/SPPageMenu/.DS_Store delete mode 100644 Example/SPPageMenu/SPPageMenu/SPPageMenu.h delete mode 100644 Example/SPPageMenu/SPPageMenu/SPPageMenu.m diff --git a/Example/SPPageMenu.xcodeproj/project.pbxproj b/Example/SPPageMenu.xcodeproj/project.pbxproj index c78f821..cc5da4f 100644 --- a/Example/SPPageMenu.xcodeproj/project.pbxproj +++ b/Example/SPPageMenu.xcodeproj/project.pbxproj @@ -25,9 +25,9 @@ 095E9C1E1FA6F8CC0097A889 /* SevenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 095E9C101FA6F8CC0097A889 /* SevenViewController.m */; }; 095E9C1F1FA6F8CC0097A889 /* SixViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 095E9C121FA6F8CC0097A889 /* SixViewController.m */; }; 095E9C211FA6F8CC0097A889 /* ThidViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 095E9C171FA6F8CC0097A889 /* ThidViewController.m */; }; - 095E9C291FA6F92D0097A889 /* SPPageMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 095E9C281FA6F92D0097A889 /* SPPageMenu.m */; }; 6A8808C1213D361D00C3553F /* mateor.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6A8808BF213D361C00C3553F /* mateor.jpg */; }; 6A9270122179BCF400831045 /* JSBadgeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A9270102179BCF400831045 /* JSBadgeView.m */; }; + 881633B7224A6B850004BE0C /* SPPageMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 881633B5224A6B850004BE0C /* SPPageMenu.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -84,11 +84,11 @@ 095E9C121FA6F8CC0097A889 /* SixViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SixViewController.m; sourceTree = ""; }; 095E9C161FA6F8CC0097A889 /* ThidViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThidViewController.h; sourceTree = ""; }; 095E9C171FA6F8CC0097A889 /* ThidViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThidViewController.m; sourceTree = ""; }; - 095E9C271FA6F92D0097A889 /* SPPageMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPageMenu.h; sourceTree = ""; }; - 095E9C281FA6F92D0097A889 /* SPPageMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPageMenu.m; sourceTree = ""; }; 6A8808BF213D361C00C3553F /* mateor.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = mateor.jpg; sourceTree = ""; }; 6A9270102179BCF400831045 /* JSBadgeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSBadgeView.m; sourceTree = ""; }; 6A9270112179BCF400831045 /* JSBadgeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBadgeView.h; sourceTree = ""; }; + 881633B5224A6B850004BE0C /* SPPageMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPageMenu.m; sourceTree = ""; }; + 881633B6224A6B850004BE0C /* SPPageMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPageMenu.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -139,7 +139,7 @@ 091A68741FA1CAC100DAA561 /* SPPageMenu */ = { isa = PBXGroup; children = ( - 095E9C261FA6F92D0097A889 /* SPPageMenu */, + 881633B4224A6B850004BE0C /* SPPageMenu */, 091A687B1FA1CAC100DAA561 /* ViewController.h */, 091A687C1FA1CAC100DAA561 /* ViewController.m */, 091229F71FA5F13000AEE295 /* ParentViewController.h */, @@ -209,22 +209,23 @@ path = ChildViewControllers; sourceTree = ""; }; - 095E9C261FA6F92D0097A889 /* SPPageMenu */ = { + 6A92700F2179BCF400831045 /* JSBadgeView */ = { isa = PBXGroup; children = ( - 095E9C271FA6F92D0097A889 /* SPPageMenu.h */, - 095E9C281FA6F92D0097A889 /* SPPageMenu.m */, + 6A9270112179BCF400831045 /* JSBadgeView.h */, + 6A9270102179BCF400831045 /* JSBadgeView.m */, ); - path = SPPageMenu; + path = JSBadgeView; sourceTree = ""; }; - 6A92700F2179BCF400831045 /* JSBadgeView */ = { + 881633B4224A6B850004BE0C /* SPPageMenu */ = { isa = PBXGroup; children = ( - 6A9270112179BCF400831045 /* JSBadgeView.h */, - 6A9270102179BCF400831045 /* JSBadgeView.m */, + 881633B5224A6B850004BE0C /* SPPageMenu.m */, + 881633B6224A6B850004BE0C /* SPPageMenu.h */, ); - path = JSBadgeView; + name = SPPageMenu; + path = ../../SPPageMenu; sourceTree = ""; }; DC317C5E213C0BB9000F4159 /* Images */ = { @@ -322,7 +323,7 @@ }; buildConfigurationList = 091A686D1FA1CAC100DAA561 /* Build configuration list for PBXProject "SPPageMenu" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -378,11 +379,11 @@ 095E9C191FA6F8CC0097A889 /* EightViewController.m in Sources */, 6A9270122179BCF400831045 /* JSBadgeView.m in Sources */, 095E9C181FA6F8CC0097A889 /* BaseViewController.m in Sources */, - 095E9C291FA6F92D0097A889 /* SPPageMenu.m in Sources */, 091229F91FA5F13000AEE295 /* ParentViewController.m in Sources */, 095E9C1E1FA6F8CC0097A889 /* SevenViewController.m in Sources */, 091A687D1FA1CAC100DAA561 /* ViewController.m in Sources */, 095E9C1A1FA6F8CC0097A889 /* FirstViewController.m in Sources */, + 881633B7224A6B850004BE0C /* SPPageMenu.m in Sources */, 091A687A1FA1CAC100DAA561 /* AppDelegate.m in Sources */, 095E9C1C1FA6F8CC0097A889 /* FourViewController.m in Sources */, 095E9C1F1FA6F8CC0097A889 /* SixViewController.m in Sources */, diff --git a/Example/SPPageMenu/SPPageMenu/.DS_Store b/Example/SPPageMenu/SPPageMenu/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, SPPageMenuTrackerStyle) { - SPPageMenuTrackerStyleLine = 0, // 下划线,默认与item等宽 - SPPageMenuTrackerStyleLineLongerThanItem, // 下划线,比item要长(长度为item的宽+间距) - SPPageMenuTrackerStyleLineAttachment, // 下划线“依恋”样式,此样式下默认宽度为字体的pointSize,你可以通过trackerWidth自定义宽度 - SPPageMenuTrackerStyleRoundedRect, // 圆角矩形 - SPPageMenuTrackerStyleRect, // 矩形 - SPPageMenuTrackerStyleTextZoom NS_ENUM_DEPRECATED_IOS(6_0, 6_0, "该枚举值已经被废弃,请用“selectedItemZoomScale”属性代替"), // 缩放(该枚举已经被废弃,用属性代替的目的是让其余样式可与缩放样式配套使用。如果你同时设置了该枚举和selectedItemZoomScale属性,selectedItemZoomScale优先级高于SPPageMenuTrackerStyleTextZoom - SPPageMenuTrackerStyleNothing // 什么样式都没有 -}; - -typedef NS_ENUM(NSInteger, SPPageMenuPermutationWay) { - SPPageMenuPermutationWayScrollAdaptContent = 0, // 自适应内容,可以左右滑动 - SPPageMenuPermutationWayNotScrollEqualWidths, // 等宽排列,不可以滑动,整个内容被控制在pageMenu的范围之内,等宽是根据pageMenu的总宽度对每个按钮均分 - SPPageMenuPermutationWayNotScrollAdaptContent // 自适应内容,不可以滑动,整个内容被控制在pageMenu的范围之内,这种排列方式下,自动计算item之间的间距,itemPadding属性无效 -}; - -typedef NS_ENUM(NSInteger, SPPageMenuTrackerFollowingMode) { - SPPageMenuTrackerFollowingModeAlways = 0, // 外界scrollView拖动时,跟踪器时刻跟随外界scrollView移动 - SPPageMenuTrackerFollowingModeEnd, // 外界scrollVie拖动结束后,跟踪器才开始移动 - SPPageMenuTrackerFollowingModeHalf // 外界scrollView拖动到一半时,跟踪器开始移动 -}; - -typedef NS_ENUM(NSInteger, SPItemImagePosition) { - SPItemImagePositionDefault, // 默认图片在左侧 - SPItemImagePositionLeft, // 图片在文字左侧 - SPItemImagePositionRight, // 图片在文字右侧 - SPItemImagePositionTop, // 图片在文字上侧 - SPItemImagePositionBottom // 图片在文字下侧 -}; - -@class SPPageMenu,SPPageMenuButton,SPPageMenuButtonItem; - -@protocol SPPageMenuDelegate - -@optional -// 若以下2个代理方法同时实现了,只会走第2个代理方法(第2个代理方法包含了第1个代理方法的功能) -- (void)pageMenu:(SPPageMenu *)pageMenu itemSelectedAtIndex:(NSInteger)index; -- (void)pageMenu:(SPPageMenu *)pageMenu itemSelectedFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; - -// 右侧的功能按钮被点击的代理方法 -- (void)pageMenu:(SPPageMenu *)pageMenu functionButtonClicked:(UIButton *)functionButton; -@end - -@interface SPPageMenu : UIView - -// 创建pagMenu -+ (instancetype)pageMenuWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle; -- (instancetype)initWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle; - -/** - * 传递数据 - * - * @param items 数组 (数组元素可以是NSString、UIImage类型、SPPageMenuButtonItem类型,其中SPPageMenuButtonItem相当于一个模型,可以同时设置图片和文字) - * @param selectedItemIndex 默认选中item的下标 - */ -- (void)setItems:(nullable NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex; - -@property (nonatomic) NSInteger selectedItemIndex; // 选中的item下标,改变其值可以用于切换选中的item -@property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 - -#if TARGET_INTERFACE_BUILDER -@property (nonatomic, readonly) IBInspectable NSInteger trackerStyle; // 该枚举属性支持storyBoard/xib,方便在storyBoard/xib中创建时直接设置 -#else -@property (nonatomic, readonly) SPPageMenuTrackerStyle trackerStyle; -#endif - -@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; // 排列方式 - -@property (nonatomic, assign) CGFloat spacing; // item之间的间距 - -@property (nonatomic, strong) UIColor *selectedItemTitleColor; // 选中的item标题颜色 -@property (nonatomic, strong) UIColor *unSelectedItemTitleColor; // 未选中的item标题颜色 - -@property (nonatomic, strong) UIFont *itemTitleFont; // 设置所有item标题字体,不区分选中的item和未选中的item -@property (nonnull, nonatomic, strong) UIFont *selectedItemTitleFont; // 选中的item字体 -@property (nonnull, nonatomic, strong) UIFont *unSelectedItemTitleFont; // 未选中的item字体 - -// 外界添加控制器view的srollView,pageMenu会监听该scrollView的滚动状况,让跟踪器时刻跟随此scrollView滑动;所谓的滚动状况,是指手指拖拽滚动,非手指拖拽不算 -@property (nonatomic, strong) UIScrollView *bridgeScrollView; - -// 跟踪器 -@property (nonatomic, readonly) UIImageView *tracker; // 跟踪器,它是一个UIImageView类型,你可以拿到该对象去设置一些自己想要的属性,例如颜色,图片等 -@property (nonatomic, assign) CGFloat trackerWidth; // 跟踪器的宽度 -- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; // 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。 -@property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; // 跟踪器的跟踪模式 - -// 分割线 -@property (nonatomic, readonly) UIImageView *dividingLine; // 分割线,你可以拿到该对象设置一些自己想要的属性,如颜色、图片等,如果想要隐藏分割线,拿到该对象直接设置hidden为YES或设置alpha<0.01即可(eg:pageMenu.dividingLine.hidden = YES) -@property (nonatomic) CGFloat dividingLineHeight; // 分割线的高度 - -@property (nonatomic, assign) UIEdgeInsets contentInset; // 内容的四周内边距(内容不包括分割线),默认UIEdgeInsetsZero - -// 选中的item缩放系数,默认为1,为1代表不缩放,[0,1)之间缩小,(1,+∞)之间放大,(-1,0)之间"倒立"缩小,(-∞,-1)之间"倒立"放大,为-1"倒立不缩放",如果依然使用了废弃的SPPageMenuTrackerStyleTextZoom样式,则缩放系数默认为1.3 -@property (nonatomic) CGFloat selectedItemZoomScale; -@property (nonatomic, assign) BOOL needTextColorGradients; // 是否需要文字渐变,默认为YES - -// 修改跟踪器样式 -- (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle; -@property (nonatomic, assign) BOOL showFunctionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat functionButtonShadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 -@property (nonatomic, strong) UIColor *functionButtonShadowColor; // 功能按钮左侧的阴影颜色,默认为黑色 -@property (nonatomic, weak) SPPageMenuButton *functionButton; - -@property (nonatomic) BOOL bounces; // 边界反弹效果,默认YES -@property (nonatomic) BOOL alwaysBounceHorizontal; // 水平方向上,当内容没有充满scrollView时,滑动scrollView是否有反弹效果,默认NO - -@property (nonatomic, weak) id delegate; - -// 插入item,插入和删除操作时,如果itemIndex超过了了items的个数,则不做任何操作 -- (void)insertItemWithTitle:(nonnull NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItemWithImage:(nonnull UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItem:(nonnull SPPageMenuButtonItem *)item atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -// 如果移除的正是当前选中的item(当前选中的item下标不为0),删除之后,选中的item会切换为上一个item -- (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)removeAllItems; - -- (void)setTitle:(nonnull NSString *)title forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的标题,设置后,仅会有文字 -- (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的标题 - -- (void)setImage:(nonnull UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 -- (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的图片 - -- (void)setItem:(SPPageMenuButtonItem *)item forItemAtIndex:(NSUInteger)itemIndex; // 同时为指定item设置标题和图片 -- (nullable SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex; // 获取指定item - -- (void)setContent:(id)content forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的内容,content可以是NSString、UIImage或SPPageMenuButtonItem类型 -- (id)contentForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的内容,该方法返回值可能是NSString、UIImage或SPPageMenuButtonItem类型 - -- (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的宽度(如果width为0,item会根据内容自动计算width) -- (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的宽度 - -- (void)setCustomSpacing:(CGFloat)spacing afterItemAtIndex:(NSUInteger)itemIndex; // 设置指定item后面的自定义间距 -- (CGFloat)customSpacingAfterItemAtIndex:(NSUInteger)itemIndex; // 获取指定item后面的自定义间距 - -- (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的enabled状态 -- (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的enabled状态 - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的四周内边距 -- (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的四周内边距 - -// 设置背景图片,barMetrics只有为UIBarMetricsDefault时才生效,如果外界传进来的backgroundImage调用过-resizableImageWithCapInsets:且参数capInsets不为UIEdgeInsetsZero,则直接用backgroundImage作为背景图; 否则内部会自动调用-resizableImageWithCapInsets:进行拉伸 -- (void)setBackgroundImage:(nullable UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics; -- (nullable UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics; // 获取背景图片 - -- (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 文字相对pageMenu位置和大小 -- (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 图片相对pageMenu位置和大小 -- (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 按钮相对pageMenu位置和大小 - -- (void)addComponentViewInScrollView:(UIView *)componentView; // 在内置的scrollView上添加一个view - -// 设置功能按钮的内容,content可以是NSString、UIImage或SPPageMenuButtonItem类型 -- (void)setFunctionButtonContent:(id)content forState:(UIControlState)state; -// 为functionButton配置相关属性,如设置字体、文字颜色等;在此,attributes中,只有NSFontAttributeName、NSForegroundColorAttributeName、NSBackgroundColorAttributeName有效 -- (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state; - - -/* 1.让跟踪器时刻跟随外界scrollView滑动,实现了让跟踪器的宽度逐渐适应item宽度的功能; - 2.这个方法用于外界的scrollViewDidScroll代理方法中,如 - - - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - [self.pageMenu moveTrackerFollowScrollView:scrollView]; - } - - 3.如果外界设置了SPPageMenu的属性"bridgeScrollView",那么外界就可以不用在scrollViewDidScroll方法中调用这个方法来实现跟踪器时刻跟随外界scrollView的效果,内部会自动处理; 外界对SPPageMenu的属性"bridgeScrollView"赋值是实现此效果的最简便的操作 - */ -- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; - - -// -------------- 以下方法和属性被废弃,不再建议使用 -------------- - -@property (nonatomic, assign) CGFloat itemPadding NS_DEPRECATED_IOS(6_0, 6_0, "Use spacing instead");; -// 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.4.0版本已纠正 -- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets forForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContentEdgeInsets:forItemAtIndex:"); -// 默认NO;关闭跟踪器的跟随效果,在外界传了scrollView进来或者调用了moveTrackerFollowScrollView的情况下,如果为YES,则当外界滑动scrollView时,跟踪器不会时刻跟随,只有滑动结束才会跟随; 3.4.0版本开始被废弃,但是依然能使用,使用后相当于设置了SPPageMenuTrackerFollowingModeEnd枚举值 -@property (nonatomic, assign) BOOL closeTrackerFollowingMode NS_DEPRECATED_IOS(6_0, 6_0,"Use trackerFollowingMode instead"); -// 下面的方法均有升级,其中ratio参数已失效 -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem: forItemIndex:"); -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use - setFunctionButtonWithItem:forState:"); -- (id)objectForItemAtIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -contentForItemAtIndex:"); -- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state NS_DEPRECATED_IOS(6_0, 6_0, "Use -setFunctionButtonContent:forState:"); -- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setItem:forItemAtIndex:"); -- (void)setContent:(id)content forItemIndex:(NSUInteger)itemIndex NS_DEPRECATED_IOS(6_0, 6_0, "Use -setContent:forItemAtIndex:"); -@end - -@interface SPPageMenuButton : UIButton -@end - -// 这个类相当于模型,主要用于同时为某个按钮设置图片和文字时使用 -@interface SPPageMenuButtonItem : NSObject - -// 快速创建同时含有标题和图片的item,默认图片在左边,文字在右边 -+ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image; -// 快速创建同时含有标题和图片的item,imagePositiona参数为图片位置 -+ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition; - -@property (nonatomic, copy) NSString *title; -@property (nonatomic, copy) UIImage *image; -// 图片的位置 -@property (nonatomic, assign) SPItemImagePosition imagePosition; -// 图片与标题之间的间距,默认0.0 -@property (nonatomic, assign) CGFloat imageTitleSpace; - -@end - -NS_ASSUME_NONNULL_END - - - diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m deleted file mode 100644 index f5016d3..0000000 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ /dev/null @@ -1,1926 +0,0 @@ -// -// 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 SPPageMenuButton() - -- (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition; - -@property (nonatomic) SPItemImagePosition imagePosition; // 图片位置 -@property (nonatomic, assign) CGFloat imageTitleSpace; // 图片和文字之间的间距 - -@end - -@implementation SPPageMenuButton - -- (instancetype)initWithImagePosition:(SPItemImagePosition)imagePosition { - if (self = [super init]) { - self.imagePosition = imagePosition; - } - return self; -} - -#pragma mark - system methods - -- (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 { - _imagePosition = SPItemImagePositionLeft; - _imageTitleSpace = 0.0; -} - -// 下面这2个方法,我所知道的: -// 在第一次调用titleLabel和imageView的getter方法(懒加载)时,alloc init之前会调用一次(无论有无图片文字都会直接调),因此,在重写这2个方法时,在方法里面不要使用self.imageView和self.titleLabel,因为这2个控件是懒加载,如果在重写的这2个方法里是第一调用imageView和titleLabel的getter方法, 则会造成死循环 -// 在layoutsSubviews中如果文字或图片不为空时会调用, 测试方式:在重写的这两个方法里调用setNeedsLayout(layutSubviews),发现会造成死循环 -// 设置文字图片、改动文字和图片、设置对齐方式,设置内容区域等时会调用,其实设置这些属性,系统是调用了layoutSubviews从而间接的去调用imageRectForContentRect:和titleRectForContentRect: -// ... -- (CGRect)imageRectForContentRect:(CGRect)contentRect { - // 先获取系统为我们计算好的rect,这样大小图片在左右时我们就不要自己去计算,我门要改变的,仅仅是origin - CGRect imageRect = [super imageRectForContentRect:contentRect]; - CGRect titleRect = [super titleRectForContentRect:contentRect]; - if (!self.currentTitle) { // 如果没有文字,则图片占据整个button,空格算一个文字 - return imageRect; - } - switch (self.imagePosition) { - case SPItemImagePositionLeft: - case SPItemImagePositionDefault: { // 图片在左 - imageRect = [self imageRectImageAtLeftForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; - } - break; - case SPItemImagePositionRight: { - imageRect = [self imageRectImageAtRightForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; - } - break; - case SPItemImagePositionTop: { - imageRect = [self imageRectImageAtTopForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; - } - break; - case SPItemImagePositionBottom: { - imageRect = [self imageRectImageAtBottomForContentRect:contentRect imageRect:imageRect titleRect:titleRect]; - } - break; - } - return imageRect; -} - -- (CGRect)titleRectForContentRect:(CGRect)contentRect { - CGRect titleRect = [super titleRectForContentRect:contentRect]; - CGRect imageRect = [super imageRectForContentRect:contentRect]; - if (!self.currentImage) { // 如果没有图片 - return titleRect; - } - switch (self.imagePosition) { - case SPItemImagePositionLeft: - case SPItemImagePositionDefault: { - titleRect = [self titleRectImageAtLeftForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; - } - break; - case SPItemImagePositionRight: { - titleRect = [self titleRectImageAtRightForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; - } - break; - case SPItemImagePositionTop: { - titleRect = [self titleRectImageAtTopForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; - } - break; - case SPItemImagePositionBottom: { - titleRect = [self titleRectImageAtBottomForContentRect:contentRect titleRect:titleRect imageRect:imageRect]; - } - break; - } - return titleRect; - -} - -#pragma - private - -// ----------------------------------------------------- left ----------------------------------------------------- - -- (CGRect)imageRectImageAtLeftForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { - CGPoint imageOrigin = imageRect.origin; - CGSize imageSize = imageRect.size; - // imageView的x值向左偏移间距的一半,另一半由titleLabe分担,不用管会不会超出contentRect,我定的规则是允许超出,如果对此作出限制,那么必须要对图片或者文字宽高有所压缩,压缩只能由imageEdgeInsets决定,当图片的内容区域容不下时才产生宽度压缩 - imageOrigin.x = imageOrigin.x - _imageTitleSpace*0.5; - imageRect.size = imageSize; - imageRect.origin = imageOrigin; - return imageRect; -} - -- (CGRect)titleRectImageAtLeftForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { - CGPoint titleOrigin = titleRect.origin; - CGSize titleSize = titleRect.size; - - titleOrigin.x = titleOrigin.x + _imageTitleSpace * 0.5; - titleRect.size = titleSize; - titleRect.origin = titleOrigin; - return titleRect; -} - -// ----------------------------------------------------- right ----------------------------------------------------- - -- (CGRect)imageRectImageAtRightForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { - - CGPoint imageOrigin = imageRect.origin; - CGSize imageSize = imageRect.size; - CGSize titleSize = titleRect.size; - - titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; - - CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; - if (imageSize.width >= imageSafeWidth) { - return imageRect; - } - // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView - if (imageSize.width + titleSize.width > imageSafeWidth) { - imageSize.width = imageSize.width - (imageSize.width + titleSize.width - imageSafeWidth); - } - // (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - (imageSize.width + titleSize.width))/2.0+titleSize.width指的是imageView在其有效区域内联合titleLabel整体居中时的x值,有效区域指的是contentRect内缩imageEdgeInsets后的区域 - imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - (imageSize.width + titleSize.width))/2.0 + titleSize.width + self.contentEdgeInsets.left + self.imageEdgeInsets.left + _imageTitleSpace * 0.5; - imageRect.size = imageSize; - imageRect.origin = imageOrigin; - return imageRect; -} - -- (CGRect)titleRectImageAtRightForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { - - CGPoint titleOrigin = titleRect.origin; - CGSize titleSize = titleRect.size; - CGSize imageSize = imageRect.size; - - // (contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right - (imageSize.width + titleSize.width))/2.0的意思是titleLabel在其有效区域内联合imageView整体居中时的x值,有效区域指的是contentRect内缩titleEdgeInsets后的区域 - titleOrigin.x = (contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right - (imageSize.width + titleSize.width))/2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left - _imageTitleSpace * 0.5; - titleRect.size = titleSize; - titleRect.origin = titleOrigin; - return titleRect; -} - -// ----------------------------------------------------- top ----------------------------------------------------- - -- (CGRect)imageRectImageAtTopForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { - CGPoint imageOrigin = imageRect.origin; - CGSize imageSize = imageRect.size; - CGSize titleSize = titleRect.size; - - CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; - - // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView - if (imageSize.width > imageSafeWidth) { - imageSize.width = imageSafeWidth; - } - imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - imageSize.width) / 2.0 + self.contentEdgeInsets.left + self.imageEdgeInsets.left; - - // 给图片高度作最大限制,超出限制对高度进行压缩,这样还可以保证titeLabel不会超出其有效区域 - CGFloat imageTitleLimitMaxH = contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom; - if (imageSize.height < imageTitleLimitMaxH) { - if (titleSize.height + self.currentImage.size.height > imageTitleLimitMaxH) { - CGFloat beyondValue = titleSize.height + self.currentImage.size.height - imageTitleLimitMaxH; - imageSize.height = imageSize.height - beyondValue; - } - // 之所以采用自己计算的结果,是因为当sizeToFit且titleLabel的numberOfLines > 0时,系统内部会按照2行计算 - titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; - } - // (imageSize.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的insets,计算时都是以图片+文字这个整体作为考虑对象 - imageOrigin.y = (contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + self.contentEdgeInsets.top + self.imageEdgeInsets.top - _imageTitleSpace * 0.5; - imageRect.size = imageSize; - imageRect.origin = imageOrigin; - return imageRect; -} - -- (CGRect)titleRectImageAtTopForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { - CGPoint titleOrigin = titleRect.origin; - CGSize titleSize = titleRect.size; - - CGSize imageSize = imageRect.size; - // 这个if语句的含义是:计算图片由于设置了contentEdgeInsets而被压缩的高度,设置imageEdgeInsets被压缩的高度不计算在内。这样做的目的是,当设置了contentEdgeInsets时,图片可能会被压缩,此时titleLabel的y值依赖于图片压缩后的高度,当设置了imageEdgeInsets时,图片也可能被压缩,此时titleLabel的y值依赖于图片压缩前的高度,这样以来,设置imageEdgeInsets就不会对titleLabel的y值产生影响 - if (self.currentImage.size.height + titleSize.height > contentRect.size.height) { - imageSize.height = self.currentImage.size.height - (self.currentImage.size.height + titleSize.height - contentRect.size.height); - } - - titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; - // titleLabel的安全宽度,这里一定要改变宽度值,因为当外界设置了titleEdgeInsets值时,系统计算出来的所有值都是在”左图右文“的基础上进行的,这个基础上可能会导致titleLabel的宽度被压缩,所以我们在此自己重新计算 - CGFloat titleSafeWidth = contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right; - if (titleSize.width > titleSafeWidth) { - titleSize.width = titleSafeWidth; - } - titleOrigin.x = (titleSafeWidth - titleSize.width) / 2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left; - - if (titleSize.height > contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom) { - titleSize.height = contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom; - } - - // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的Insets,计算时都是以图片+文字这个整体作为考虑对象 - titleOrigin.y = (contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + imageSize.height + self.contentEdgeInsets.top + self.titleEdgeInsets.top + _imageTitleSpace * 0.5; - titleRect.size = titleSize; - titleRect.origin = titleOrigin; - return titleRect; -} - -// ----------------------------------------------------- bottom ----------------------------------------------------- - -- (CGRect)imageRectImageAtBottomForContentRect:(CGRect)contentRect imageRect:(CGRect)imageRect titleRect:(CGRect)titleRect { - CGPoint imageOrigin = imageRect.origin; - CGSize imageSize = imageRect.size; - CGSize titleSize = titleRect.size; - - titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; - - CGFloat imageSafeWidth = contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right; - // 这里水平中心对齐,跟图片在右边时的中心对齐时差别在于:图片在右边时中心对齐考虑了titleLabel+imageView这个整体,而这里只单独考虑imageView - if (imageSize.width > imageSafeWidth) { - imageSize.width = imageSafeWidth; - } - - imageOrigin.x = (contentRect.size.width - self.imageEdgeInsets.left - self.imageEdgeInsets.right - imageSize.width) / 2.0 + self.contentEdgeInsets.left + self.imageEdgeInsets.left; - - // 给图片高度作最大限制,超出限制对高度进行压缩,这样还可以保证titeLabel不会超出其有效区域 - CGFloat imageTitleLimitMaxH = contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom; - if (imageSize.height < imageTitleLimitMaxH) { - if (titleSize.height + self.currentImage.size.height > imageTitleLimitMaxH) { - CGFloat beyondValue = titleSize.height + self.currentImage.size.height - imageTitleLimitMaxH; - imageSize.height = imageSize.height - beyondValue; - } - // 之所以采用自己计算的结果,是因为当sizeToFit且titleLabel的numberOfLines > 0时,系统内部会按照2行计算 - titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; - } - // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的insets,计算时都是以图片+文字这个整体作为考虑对象 - imageOrigin.y = (contentRect.size.height - self.imageEdgeInsets.top - self.imageEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + titleSize.height + self.contentEdgeInsets.top + self.imageEdgeInsets.top + _imageTitleSpace * 0.5; - imageRect.size = imageSize; - imageRect.origin = imageOrigin; - return imageRect; -} - -- (CGRect)titleRectImageAtBottomForContentRect:(CGRect)contentRect titleRect:(CGRect)titleRect imageRect:(CGRect)imageRect { - CGPoint titleOrigin = titleRect.origin; - CGSize titleSize = titleRect.size; - - CGSize imageSize = imageRect.size; - // 这个if语句的含义是:计算图片由于设置了contentEdgeInsets而被压缩的高度,设置imageEdgeInsets被压缩的高度不计算在内。这样做的目的是,当设置了contentEdgeInsets时,图片可能会被压缩,此时titleLabel的y值依赖于图片压缩后的高度,当设置了imageEdgeInsets时,图片也可能被压缩,此时titleLabel的y值依赖于图片压缩前的高度,这样一来,设置imageEdgeInsets就不会对titleLabel的y值产生影响 - if (self.currentImage.size.height + titleSize.height > contentRect.size.height) { - imageSize.height = self.currentImage.size.height - (self.currentImage.size.height + titleSize.height - contentRect.size.height); - if (imageSize.height < 0) { - imageSize.height = 0; - } - } - - titleSize = [self calculateTitleSizeForSystemTitleSize:titleSize]; - // titleLabel的安全宽度,因为当外界设置了titleEdgeInsets值时,系统计算出来的所有值都是在”左图右文“的基础上进行的,这个基础上可能会导致titleLabel的宽度被压缩,所以我们在此自己重新计算 - CGFloat titleSafeWidth = contentRect.size.width - self.titleEdgeInsets.left - self.titleEdgeInsets.right; - if (titleSize.width > titleSafeWidth) { - titleSize.width = titleSafeWidth; - } - - titleOrigin.x = (titleSafeWidth - titleSize.width) / 2.0 + self.contentEdgeInsets.left + self.titleEdgeInsets.left; - - if (titleSize.height > contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom) { - titleSize.height = contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom; - } - - // (self.currentImage.size.height + titleSize.height)这个整体高度很重要,这里相当于按照按钮原有规则进行对齐,即按钮的对齐方式不管是设置谁的Insets,计算时都是以图片+文字这个整体作为考虑对象 - titleOrigin.y = (contentRect.size.height - self.titleEdgeInsets.top - self.titleEdgeInsets.bottom - (imageSize.height + titleSize.height)) / 2.0 + self.contentEdgeInsets.top + self.titleEdgeInsets.top - _imageTitleSpace * 0.5; - titleRect.size = titleSize; - titleRect.origin = titleOrigin; - return titleRect; -} - -// 自己计算titleLabel的大小 -- (CGSize)calculateTitleSizeForSystemTitleSize:(CGSize)titleSize { - CGSize myTitleSize = titleSize; - // 获取按钮里的titleLabel,之所以遍历获取而不直接调用self.titleLabel,是因为假如这里是第一次调用self.titleLabel,则会跟titleRectForContentRect: 方法造成死循环,titleLabel的getter方法中,alloc init之前调用了titleRectForContentRect: - UILabel *titleLabel = [self findTitleLabel]; - if (!titleLabel) { // 此时还没有创建titleLabel,先通过系统button的字体进行文字宽度计算 - CGFloat fontSize = [UIFont buttonFontSize]; // 按钮默认字体,18号 - // 说明外界使用了被废弃的font属性,被废弃但是依然生效 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (self.font.pointSize != [UIFont buttonFontSize]) { - fontSize = self.font.pointSize; - } -#pragma clang diagnostic pop - myTitleSize.height = ceil([self.currentTitle boundingRectWithSize:CGSizeMake(titleSize.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]} context:nil].size.height); - // 根据文字计算宽度,取上整,补齐误差,保证跟titleLabel.intrinsicContentSize.width一致 - myTitleSize.width = ceil([self.currentTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, titleSize.height) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]} context:nil].size.width); - } else { // 说明此时titeLabel已经产生,直接取titleLabel的内容宽度 - myTitleSize.width = titleLabel.intrinsicContentSize.width; - myTitleSize.height = titleLabel.intrinsicContentSize.height; - } - return myTitleSize; -} - -// 遍历获取按钮里面的titleLabel -- (UILabel *)findTitleLabel { - for (UIView *subView in self.subviews) { - if ([subView isKindOfClass:NSClassFromString(@"UIButtonLabel")]) { - UILabel *titleLabel = (UILabel *)subView; - return titleLabel; - } - } - return nil; -} - - - -#pragma mark - setter -// 以下所有setter方法中都调用了layoutSubviews, 其实是为了间接的调用imageRectForContentRect:和titleRectForContentRect:,不能直接调用imageRectForContentRect:和titleRectForContentRect:,因为按钮的子控件布局最终都是通过调用layoutSubviews而确定,如果直接调用这两个方法,那么只能保证我们能够获取的CGRect是对的,但并不会作用在titleLabel和imageView上 -- (void)setImagePosition:(SPItemImagePosition)imagePosition { - _imagePosition = imagePosition; - [self setNeedsLayout]; -} - -- (void)setImageTitleSpace:(CGFloat)imageTitleSpace { - _imageTitleSpace = imageTitleSpace; - [self setNeedsLayout]; -} - -- (void)setContentHorizontalAlignment:(UIControlContentHorizontalAlignment)contentHorizontalAlignment { - [super setContentHorizontalAlignment:contentHorizontalAlignment]; - [self setNeedsLayout]; -} - -// 垂直方向的排列方式在设置之前如果调用了titleLabel或imageView的getter方法,则设置后不会生效,点击一下按钮之后就生效了,这应该属于按钮的一个小bug,我们只要重写它的setter方法重新布局一次就好 -- (void)setContentVerticalAlignment:(UIControlContentVerticalAlignment)contentVerticalAlignment { - [super setContentVerticalAlignment:contentVerticalAlignment]; - [self setNeedsLayout]; -} - -@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, strong) NSMutableArray *buttons; -@property (nonatomic, strong) SPPageMenuButton *selectedButton; -@property (nonatomic, strong) NSMutableDictionary *customWidths; -@property (nonatomic, strong) NSMutableDictionary *customSpacings; -@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; -@property (nonatomic, assign) CGFloat startA; -/// 完成颜色, 取值范围 0~1 -@property (nonatomic, assign) CGFloat endR; -@property (nonatomic, assign) CGFloat endG; -@property (nonatomic, assign) CGFloat endB; -@property (nonatomic, assign) CGFloat endA; - -// 这个高度,是存储itemScrollView的高度 -@property (nonatomic, assign) CGFloat itemScrollViewH; -@property (nonatomic, assign) BOOL forceUseSettingSpacing; -@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 (SPPageMenuButton *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]] || [object isKindOfClass:[SPPageMenuButtonItem class]], @"items中的元素类型只能是NSString、UIImage或SPPageMenuButtonItem"); - [self addButton:i object:object animated:NO]; - } - - [self setNeedsLayout]; - [self layoutIfNeeded]; - - if (self.buttons.count) { - // 默认选中selectedItemIndex对应的按钮 - SPPageMenuButton *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 resetupTrackerFrameWithSelectedButton: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)insertItem:(SPPageMenuButtonItem *)item 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:item atIndex:itemIndex]; - self.items = objects.copy; - [self addButton:itemIndex object:item 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 (SPPageMenuButton *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) { - SPPageMenuButton *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++) { - SPPageMenuButton *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 (title == nil) return; - if (itemIndex < self.buttons.count) { - SPPageMenuButton *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]; -} - -- (NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.items.count) { - id object = [self.items objectAtIndex:itemIndex]; - NSAssert([object isKindOfClass:[NSString class]],@"itemIndex对应的item不是NSString类型,请仔细核对"); - return object; - } - return nil; -} - -- (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { - if (image == nil) return; - if (itemIndex < self.buttons.count) { - SPPageMenuButton *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]; -} - -- (UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.items.count) { - id object = [self.items objectAtIndex:itemIndex]; - NSAssert([object isKindOfClass:[UIImage class]],@"itemIndex对应的item不是UIImage类型,请仔细核对"); - return object; - } - return nil; -} - -- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex { - if (item == nil) return; - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - [button setTitle:item.title forState:UIControlStateNormal]; - [button setImage:item.image forState:UIControlStateNormal]; - button.imagePosition = item.imagePosition; - button.imageTitleSpace = item.imageTitleSpace; - - if (item != nil) { - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:item]; - self.items = items.copy; - } - [self setNeedsLayout]; - [self layoutIfNeeded]; - } -} - -- (void)setItem:(SPPageMenuButtonItem *)item forItemAtIndex:(NSUInteger)itemIndex { - [self setItem:item forItemIndex:itemIndex]; -} - -- (SPPageMenuButtonItem *)itemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.items.count) { - id object = [self.items objectAtIndex:itemIndex]; - NSAssert([object isKindOfClass:[SPPageMenuButtonItem class]],@"itemIndex对应的item不是SPPageMenuButtonItem类型,请仔细核对"); - return object; - } - return nil; -} - -- (void)setContent:(id)content forItemIndex:(NSUInteger)itemIndex { - if (content == nil) return; - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - if ([content isKindOfClass:[NSString class]]) { - [button setTitle:content forState:UIControlStateNormal]; - } else if ([content isKindOfClass:[UIImage class]]) { - [button setImage:content forState:UIControlStateNormal]; - } else if ([content isKindOfClass:[SPPageMenuButtonItem class]]) { - SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)content; - [button setTitle:item.title forState:UIControlStateNormal]; - [button setImage:item.image forState:UIControlStateNormal]; - button.imagePosition = item.imagePosition; - button.imageTitleSpace = item.imageTitleSpace; - } - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:content]; - self.items = items.copy; - [self setNeedsLayout]; - [self layoutIfNeeded]; - } -} - -- (void)setContent:(id)content forItemAtIndex:(NSUInteger)itemIndex { - [self setContent:content forItemIndex:itemIndex]; -} - -- (id)contentForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.items.count) { - id content = [self.items objectAtIndex:itemIndex]; - return content; - } - return nil; -} - -- (id)objectForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.items.count) { - id object = [self.items objectAtIndex:itemIndex]; - return object; - } - return nil; -} - -- (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - [button setEnabled:enaled]; - } -} - -- (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex { - if (self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - return button.enabled; - } - return YES; -} - -- (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - [self.customWidths setValue:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; - [self setNeedsLayout]; - [self layoutIfNeeded]; - } -} - -- (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { - CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; - if (customWidth) { - return customWidth; - } else { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - return button.bounds.size.width; - } - } - return 0; -} - -- (void)setCustomSpacing:(CGFloat)spacing afterItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - [self.customSpacings setValue:@(spacing) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; - [self setNeedsLayout]; - [self layoutIfNeeded]; - } -} - -- (CGFloat)customSpacingAfterItemAtIndex:(NSUInteger)itemIndex { - if ([self.customSpacings.allKeys containsObject:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]) { - CGFloat customSpacing = [[self.customSpacings valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; - return customSpacing; - } else { - return CGFLOAT_MAX; - } -} - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - button.contentEdgeInsets = contentInset; - } -} - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - button.contentEdgeInsets = contentInset; - [self setNeedsLayout]; - [self layoutIfNeeded]; - } -} - -- (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *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; -} - -- (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - CGRect titleRectAtPageMenu = [button.titleLabel convertRect:button.titleLabel.bounds toView:self]; - return titleRectAtPageMenu; - } - return CGRectZero; -} - -- (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - CGRect imageRectAtPageMenu = [button.imageView convertRect:button.imageView.bounds toView:self]; - return imageRectAtPageMenu; - } - return CGRectZero; -} - -- (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - CGRect buttonRectAtPageMenu = [button convertRect:button.bounds toView:self]; - return buttonRectAtPageMenu; - } - return CGRectZero; -} - -- (void)addComponentViewInScrollView:(UIView *)componentView { - [self.itemScrollView addSubview:componentView]; -} - -- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius { - _trackerHeight = trackerHeight; - self.tracker.layer.cornerRadius = cornerRadius; - [self setNeedsLayout]; - [self layoutIfNeeded]; -} - -- (void)setFunctionButtonWithItem:(SPPageMenuButtonItem *)item forState:(UIControlState)state { - [self.functionButton setTitle:item.title forState:state]; - [self.functionButton setImage:item.image forState:state]; - self.functionButton.imagePosition = item.imagePosition; - self.functionButton.imageTitleSpace = item.imageTitleSpace; -} - -- (void)setFunctionButtonContent:(id)content forState:(UIControlState)state { - if ([content isKindOfClass:[NSString class]]) { - [self.functionButton setTitle:content forState:state]; - } else if ([content isKindOfClass:[UIImage class]]) { - [self.functionButton setImage:content forState:state]; - } else if ([content isKindOfClass:[SPPageMenuButtonItem class]]) { - SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)content; - [self.functionButton setTitle:item.title forState:state]; - [self.functionButton setImage:item.image forState:state]; - self.functionButton.imagePosition = item.imagePosition; - self.functionButton.imageTitleSpace = item.imageTitleSpace; - } -} - -- (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]; -} - -// 以下方法在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) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - [button setTitle:title forState:UIControlStateNormal]; - [button setImage:image forState:UIControlStateNormal]; - button.imagePosition = imagePosition; - - // 文字和图片只能替换其一,因为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)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) { - SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; - [button setTitle:title forState:UIControlStateNormal]; - [button setImage:image forState:UIControlStateNormal]; - button.imagePosition = imagePosition; - 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 forState:(UIControlState)state { - [self.functionButton setTitle:title forState:state]; - [self.functionButton setImage:image forState:state]; - self.functionButton.imagePosition = imagePosition; -} - -- (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.imageTitleSpace = imageTitleSpace; -} - -#pragma mark - private - -- (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { - // 如果是插入,需要改变已有button的tag值 - for (SPPageMenuButton *button in self.buttons) { - if (button.tag-tagBaseValue >= index) { - button.tag = button.tag + 1; // 由于有新button的加入,新button后面的button的tag值得+1 - } - } - SPPageMenuButton *button = [SPPageMenuButton buttonWithType:UIButtonTypeCustom]; - [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; - button.titleLabel.font = _unSelectedItemTitleFont; // 此时必然还没有选中任何按钮,设置_unSelectedItemTitleFont就相是设置所有按钮的文字颜色,这里不能用_itemTitleFont,如果外界先设置_unSelectedItemTitleFont,再创建按钮,假如这里使用_itemTitleFont,那外界设置的_unSelectedItemTitleFont就不生效 - [button addTarget:self action:@selector(buttonInPageMenuClicked:) forControlEvents:UIControlEventTouchUpInside]; - button.tag = tagBaseValue + index; - if ([object isKindOfClass:[NSString class]]) { - [button setTitle:object forState:UIControlStateNormal]; - } else if ([object isKindOfClass:[UIImage class]]) { - [button setImage:object forState:UIControlStateNormal]; - } else { - SPPageMenuButtonItem *item = (SPPageMenuButtonItem *)object; - [button setTitle:item.title forState:UIControlStateNormal]; - [button setImage:item.image forState:UIControlStateNormal]; - button.imagePosition = item.imagePosition; - button.imageTitleSpace = item.imageTitleSpace; - } - 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]; - } else { // 已经有跟踪器 - [self.itemScrollView insertSubview:button atIndex:index+1]; // +1是因为跟踪器 - } - } else { - [self.itemScrollView insertSubview:button atIndex:index]; - } - if (!self.buttons.count) { - [self buttonInPageMenuClicked:button]; - } - } else { - [self.itemScrollView insertSubview:button atIndex:index]; - } - [self.buttons insertObject:button atIndex:index]; - - if (self.insert && animated) { // 是插入的新按钮,且需要动画 - // 取出上一个按钮 - SPPageMenuButton *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; - _showFunctionButton = NO; - _functionButtonShadowOpacity = 0.5; - _functionButtonShadowColor = [UIColor blackColor]; - _selectedItemZoomScale = 1; - _needTextColorGradients = YES; - [self setupSubViews]; -} - -- (void)setupSubViews { - // 必须先添加分割线 - SPPageMenuLine *dividingLine = [[SPPageMenuLine alloc] init]; - dividingLine.backgroundColor = [UIColor lightGrayColor]; - __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; - - SPPageMenuButton *functionButton = [SPPageMenuButton buttonWithType:UIButtonTypeCustom]; - functionButton.backgroundColor = [UIColor whiteColor]; - [functionButton setTitle:@"+" forState:UIControlStateNormal]; - [functionButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; - [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - functionButton.layer.shadowColor = _functionButtonShadowColor.CGColor; - functionButton.layer.shadowOffset = CGSizeMake(0, 0); - functionButton.layer.shadowRadius = 2; - functionButton.layer.shadowOpacity = _functionButtonShadowOpacity; // 默认是0,为0的话不会显示阴影 - functionButton.hidden = !_showFunctionButton; - [backgroundView addSubview:functionButton]; - _functionButton = functionButton; -} - -// 按钮点击方法 -- (void)buttonInPageMenuClicked:(SPPageMenuButton *)sender { - NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue; - NSInteger toIndex = sender.tag - tagBaseValue; - // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的 - _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 (SPPageMenuButton *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]; - } - } - [self delegatePerformMethodWithFromIndex:fromIndex toIndex:toIndex]; -} - -// 点击button让itemScrollView发生偏移 -- (void)moveItemScrollViewWithSelectedButton:(SPPageMenuButton *)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:(SPPageMenuButton *)selectedButton { - [UIView animateWithDuration:0.25 animations:^{ - [self resetupTrackerFrameWithSelectedButton: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:(SPPageMenuButton *)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; - } - switch (self.trackerFollowingMode) { - case SPPageMenuTrackerFollowingModeAlways: - // 这个方法才开始移动跟踪器 - [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; - break; - case SPPageMenuTrackerFollowingModeHalf:{ - SPPageMenuButton *fromButton; - SPPageMenuButton *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; - } - } - } - } - break; - default: - break; - } -} - -// 这个方法才开始真正滑动跟踪器,上面都是做铺垫 -- (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; - CGFloat a = self.endA - self.startA; - UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:self.startA + a * fromProgress]; - UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:self.startA + a * toProgress]; - - // 设置文字颜色渐变 - [fromButton setTitleColor:fromColor forState:UIControlStateNormal]; - [toButton setTitleColor:toColor forState:UIControlStateNormal]; -} - -// 获取颜色的RGB值 -- (NSArray *)getRGBForColor:(UIColor *)color { - CGFloat red = 0.0; - CGFloat green = 0.0; - CGFloat blue = 0.0; - CGFloat alpha = 0.0; - [color getRed:&red green:&green blue:&blue alpha:&alpha]; - return @[@(red), @(green), @(blue), @(alpha)]; -} - -/// 开始颜色设置 -- (void)setupStartColor:(UIColor *)color { - NSArray *components = [self getRGBForColor:color]; - self.startR = [components[0] floatValue]; - self.startG = [components[1] floatValue]; - self.startB = [components[2] floatValue]; - self.startA = [components[3] floatValue]; -} - -/// 结束颜色设置 -- (void)setupEndColor:(UIColor *)color { - NSArray *components = [self getRGBForColor:color]; - self.endR = [components[0] floatValue]; - self.endG = [components[1] floatValue]; - self.endB = [components[2] floatValue]; - self.endA = [components[3] floatValue]; -} - -- (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 { - if (bridgeScrollView == _bridgeScrollView) return; - if (_bridgeScrollView && bridgeScrollView != _bridgeScrollView) { - [_bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; - }; - _bridgeScrollView = bridgeScrollView; - if (bridgeScrollView) { - [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil]; - } -} - -- (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)setShowFunctionButton:(BOOL)showFunctionButton { - _showFunctionButton = showFunctionButton; - self.functionButton.hidden = !showFunctionButton; - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setFunctionButtonShadowOpacity:(CGFloat)functionButtonShadowOpacity { - _functionButtonShadowOpacity = functionButtonShadowOpacity; - self.functionButton.layer.shadowOpacity = functionButtonShadowOpacity; -} - -- (void)setItemPadding:(CGFloat)itemPadding { - _itemPadding = itemPadding; - _forceUseSettingSpacing = YES; - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setSpacing:(CGFloat)spacing { - _spacing = spacing; - self.itemPadding = spacing; -} - -- (void)setItemTitleFont:(UIFont *)itemTitleFont { - _itemTitleFont = itemTitleFont; - _selectedItemTitleFont = itemTitleFont; - _unSelectedItemTitleFont = itemTitleFont; - for (SPPageMenuButton *button in self.buttons) { - button.titleLabel.font = itemTitleFont; - } - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setUnSelectedItemTitleFont:(UIFont *)unSelectedItemTitleFont { - _unSelectedItemTitleFont = unSelectedItemTitleFont; - for (SPPageMenuButton *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 (SPPageMenuButton *button in self.buttons) { - if (button == _selectedButton) { - continue; // 跳过选中的那个button - } - [button setTitleColor:unSelectedItemTitleColor forState:UIControlStateNormal]; - } -} - -- (void)setSelectedItemIndex:(NSInteger)selectedItemIndex { - _selectedItemIndex = selectedItemIndex; - if (self.buttons.count) { - SPPageMenuButton *button = [self.buttons objectAtIndex:selectedItemIndex]; - [self buttonInPageMenuClicked:button]; - } -} - -- (void)setDelegate:(id)delegate { - if (delegate == _delegate) {return;} - _delegate = delegate; - if (self.buttons.count) { - SPPageMenuButton *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; - if (!_forceUseSettingSpacing) { - if (_permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { - _spacing = _itemPadding = 0; - } else { - _spacing = _itemPadding = 30; - } - } - [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; -} - -- (NSMutableDictionary *)customWidths { - - if (!_customWidths) { - _customWidths = [NSMutableDictionary dictionary]; - } - return _customWidths; -} - -- (NSMutableDictionary *)customSpacings { - if (!_customSpacings) { - _customSpacings = [[NSMutableDictionary alloc] init]; - } - return _customSpacings; -} - -- (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.functionButtonShadowOpacity > 0) { - self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath; - } - CGFloat itemScrollViewX = 0; - CGFloat itemScrollViewY = 0; - CGFloat itemScrollViewW = self.showFunctionButton ? 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++) { - SPPageMenuButton *button = self.buttons[i]; - CGFloat textW; - CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; - if (button == _selectedButton) { - textW = ceil([button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_selectedItemTitleFont} context:nil].size.width); - } else { - textW = ceil([button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_unSelectedItemTitleFont} context:nil].size.width); - } - CGFloat imageW = button.currentImage.size.width; - CGFloat imageH = button.currentImage.size.height; - if (imageH > itemScrollViewH) { - imageH = itemScrollViewH; - } - if (button.currentTitle.length && !button.currentImage) { - contentW = textW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; - } else if(button.currentImage && !button.currentTitle) { - contentW = imageW+button.contentEdgeInsets.left+button.contentEdgeInsets.right; - } else if (button.currentTitle.length && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft || button.imagePosition == SPItemImagePositionDefault)) { - contentW = textW + imageW + button.imageTitleSpace+button.contentEdgeInsets.left+button.contentEdgeInsets.right; - } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) { - contentW = MAX(textW, imageW)+button.contentEdgeInsets.left+button.contentEdgeInsets.right; - } - if (customWidth) { - contentW_sum += customWidth; - [buttonWidths addObject:@(customWidth)]; - } else { - contentW_sum += contentW; - [buttonWidths addObject:@(contentW)]; - } - } - CGFloat diff = itemScrollViewW - contentW_sum; - if (self.permutationWay == SPPageMenuPermutationWayNotScrollAdaptContent && diff < 0) { - for (int i = 0; i < buttonWidths.count; i++) { - CGFloat buttonW = [buttonWidths[i] floatValue]; - buttonW -= fabs(diff)*buttonW/contentW_sum; - [buttonWidths replaceObjectAtIndex:i withObject:@(buttonW)]; - } - contentW_sum = [[buttonWidths valueForKeyPath:@"@sum.floatValue"] floatValue]; - } - - [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuButton *button, NSUInteger idx, BOOL * _Nonnull stop) { - CGFloat customWidth = [[self.customWidths valueForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue]; - CGFloat customSpacing = 0.0; - if (idx > 0) { - NSString *key = [NSString stringWithFormat:@"%lu",(unsigned long)(idx-1)]; - if ([self.customSpacings.allKeys containsObject:key]) { - customSpacing = [[self.customSpacings valueForKey:key] floatValue]; - } else { - customSpacing = self->_itemPadding; - } - } - CGFloat totalCustomSpacing = [[self.customSpacings.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; - CGFloat totalSpacing = totalCustomSpacing + (self.buttons.count-self.customSpacings.count)*self->_itemPadding; - 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(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } - } else if (self.permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { - // 求出外界设置的按钮宽度之和 - CGFloat totalCustomWidth = [[self.customWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; - // 如果该按钮外界设置了宽,则取外界设置的,如果外界没设置,则其余按钮等宽 - buttonW = customWidth ? customWidth : (itemScrollViewW-totalSpacing-totalCustomWidth)/(self.buttons.count-self.customWidths.count); - if (buttonW < 0) { // 按钮过多时,有可能会为负数 - buttonW = 0; - } - if (idx == 0) { - button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } else { - button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } - - } else { - buttonW = [buttonWidths[idx] floatValue]; - if (self->_forceUseSettingSpacing) { // 如果强制使用外界设置的间距 - CGFloat paddingDiff = diff - totalSpacing; // 自动间距之和与外界设置的间距之和的差 - buttonW += paddingDiff * buttonW/contentW_sum; // 用上面计算出来的差值乘以原按钮宽度相对总按钮宽度的比例,得到的结果就是每个按钮宽度应该增减的值,这样可以保证各个按钮之间的宽度之比不变 - } else { // 否则使用自己计算的间距 - CGFloat autoPadding = diff/self.buttons.count; - if (autoPadding < 0) {autoPadding = 0.0;} - if (totalCustomSpacing > 0) { - CGFloat paddingDiff = totalCustomSpacing - autoPadding*self.customSpacings.count; - buttonW -= paddingDiff * buttonW/contentW_sum; - } - self->_itemPadding = autoPadding; - NSString *key = [NSString stringWithFormat:@"%lu",(unsigned long)(idx-1)]; - if (![self.customSpacings.allKeys containsObject:key]) { - customSpacing = self->_itemPadding; - } - } - if (buttonW < 0) { buttonW = 0;} - if (idx == 0) { - button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } else { - button.frame = CGRectMake(customSpacing+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } - } - lastButtonMaxX = CGRectGetMaxX(button.frame); - }]; - - // 如果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 resetupTrackerFrameWithSelectedButton:self.selectedButton]; - - self.itemScrollView.contentSize = CGSizeMake(lastButtonMaxX+_itemPadding*0.5, 0); - - if (self.translatesAutoresizingMaskIntoConstraints == NO) { - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; - } - -} - -- (void)resetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)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 - -@implementation SPPageMenuButtonItem - -+ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image { - SPPageMenuButtonItem *item = [[SPPageMenuButtonItem alloc] initWithTitle:title image:image imagePosition:SPItemImagePositionDefault]; - return item; -} - -+ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition { - SPPageMenuButtonItem *item = [[SPPageMenuButtonItem alloc] initWithTitle:title image:image imagePosition:imagePosition]; - return item; -} - -- (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image imagePosition:(SPItemImagePosition)imagePosition { - if (self = [super init]) { - self.title = title; - self.image = image; - self.imagePosition = imagePosition; - } - return self; -} - -@end - -#pragma clang diagnostic pop - - - - - - From 66169a69cf26e51ac0a55a223dd3afb9a2c913dd Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Thu, 18 Apr 2019 09:26:18 +0900 Subject: [PATCH 21/22] Remove IBInspectable for trackerStyle --- SPPageMenu/SPPageMenu.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SPPageMenu/SPPageMenu.h b/SPPageMenu/SPPageMenu.h index 7cfb5bd..4c38369 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -70,11 +70,7 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { @property (nonatomic) NSInteger selectedItemIndex; // 选中的item下标,改变其值可以用于切换选中的item @property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 -#if TARGET_INTERFACE_BUILDER -@property (nonatomic, readonly) IBInspectable NSInteger trackerStyle; // 该枚举属性支持storyBoard/xib,方便在storyBoard/xib中创建时直接设置 -#else @property (nonatomic, readonly) SPPageMenuTrackerStyle trackerStyle; -#endif @property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; // 排列方式 From 8b16eeb292b92c3821b408324a25739cccdc9d4d Mon Sep 17 00:00:00 2001 From: Xiao Xiao Date: Fri, 15 May 2020 01:11:05 +0900 Subject: [PATCH 22/22] Fix outbound crash --- SPPageMenu/SPPageMenu.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index f5016d3..23ab2d7 100644 --- a/SPPageMenu/SPPageMenu.m +++ b/SPPageMenu/SPPageMenu.m @@ -1265,8 +1265,12 @@ - (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { // 这个方法才开始真正滑动跟踪器,上面都是做铺垫 - (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]; + if (self.buttons.count < 2) { + return; + } + + UIButton *fromButton = self.buttons[fromIndex < self.buttons.count ? fromIndex : self.buttons.count-1]; + UIButton *toButton = self.buttons[toIndex < self.buttons.count ? toIndex : self.buttons.count-1]; // 2个按钮之间的距离 CGFloat xDistance = toButton.center.x - fromButton.center.x;