diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index f280213..0000000 Binary files a/.DS_Store and /dev/null differ 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 diff --git a/Example/.DS_Store b/Example/.DS_Store deleted file mode 100644 index f933140..0000000 Binary files a/Example/.DS_Store and /dev/null differ diff --git a/Example/SPPageMenu.xcodeproj/project.pbxproj b/Example/SPPageMenu.xcodeproj/project.pbxproj index 4a182d4..cc5da4f 100644 --- a/Example/SPPageMenu.xcodeproj/project.pbxproj +++ b/Example/SPPageMenu.xcodeproj/project.pbxproj @@ -25,8 +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 */ @@ -83,9 +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 */ @@ -136,13 +139,14 @@ 091A68741FA1CAC100DAA561 /* SPPageMenu */ = { isa = PBXGroup; children = ( - 095E9C261FA6F92D0097A889 /* SPPageMenu */, + 881633B4224A6B850004BE0C /* SPPageMenu */, 091A687B1FA1CAC100DAA561 /* ViewController.h */, 091A687C1FA1CAC100DAA561 /* ViewController.m */, 091229F71FA5F13000AEE295 /* ParentViewController.h */, 091229F81FA5F13000AEE295 /* ParentViewController.m */, 095E9C021FA6F8CC0097A889 /* ChildViewControllers */, DC317C5E213C0BB9000F4159 /* Images */, + 6A92700F2179BCF400831045 /* JSBadgeView */, 091A687E1FA1CAC100DAA561 /* Main.storyboard */, 091A68811FA1CAC100DAA561 /* Assets.xcassets */, 091A68831FA1CAC100DAA561 /* LaunchScreen.storyboard */, @@ -205,13 +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 = ""; + }; + 881633B4224A6B850004BE0C /* SPPageMenu */ = { + isa = PBXGroup; + children = ( + 881633B5224A6B850004BE0C /* SPPageMenu.m */, + 881633B6224A6B850004BE0C /* SPPageMenu.h */, + ); + name = SPPageMenu; + path = ../../SPPageMenu; sourceTree = ""; }; DC317C5E213C0BB9000F4159 /* Images */ = { @@ -285,7 +299,7 @@ 091A686A1FA1CAC100DAA561 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0920; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = iDress; TargetAttributes = { 091A68711FA1CAC100DAA561 = { @@ -309,7 +323,7 @@ }; buildConfigurationList = 091A686D1FA1CAC100DAA561 /* Build configuration list for PBXProject "SPPageMenu" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -363,12 +377,13 @@ 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 */, 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 */, @@ -441,6 +456,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; @@ -448,6 +464,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; @@ -494,6 +511,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; @@ -501,6 +519,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.xcodeproj/project.xcworkspace/xcuserdata/devlop1.xcuserdatad/UserInterfaceState.xcuserstate b/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/devlop1.xcuserdatad/UserInterfaceState.xcuserstate index 1c3eae1..a9fe1ce 100644 Binary files a/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/devlop1.xcuserdatad/UserInterfaceState.xcuserstate and b/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/devlop1.xcuserdatad/UserInterfaceState.xcuserstate differ 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 4d0c4e1..8816cdb 100644 Binary files a/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/libo.xcuserdatad/UserInterfaceState.xcuserstate and b/Example/SPPageMenu.xcodeproj/project.xcworkspace/xcuserdata/libo.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Example/SPPageMenu/.DS_Store b/Example/SPPageMenu/.DS_Store deleted file mode 100644 index c7183fb..0000000 Binary files a/Example/SPPageMenu/.DS_Store and /dev/null differ 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 0000000..da2a885 Binary files /dev/null and b/Example/SPPageMenu/Assets.xcassets/asc.imageset/1@2x.png differ 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 878aa13..6b1a96a 100644 --- a/Example/SPPageMenu/Info.plist +++ b/Example/SPPageMenu/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.4.0 + 3.5.0 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 9db1223..030d190 100644 --- a/Example/SPPageMenu/ParentViewController.m +++ b/Example/SPPageMenu/ParentViewController.m @@ -18,10 +18,12 @@ #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 NaviH (screenH == 812 ? 88 : 64) // 812是iPhoneX的高度 +#define pageMenuH 40 +#define NaviH (screenH >= 812 ? 88 : 64) // 812是iPhoneX的高度 #define scrollViewHeight (screenH-NaviH-pageMenuH) @interface ParentViewController () @@ -47,7 +49,6 @@ - (void)test1 { pageMenu.bridgeScrollView = self.scrollView; [self.view addSubview:pageMenu]; _pageMenu = pageMenu; - } // 示例2:SPPageMenuTrackerStyleLineLongerThanItem,下划线比item略长,长度等于tem宽+间距 @@ -87,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; } @@ -101,7 +103,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移动的效果 @@ -210,7 +211,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]; @@ -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移动的效果 @@ -230,7 +231,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]; @@ -238,6 +239,7 @@ - (void)test11 { [pageMenu setItems:self.dataArr selectedItemIndex:1]; // 不可滑动的自适应内容排列 pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollAdaptContent; + // 设置代理 pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 @@ -305,7 +307,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移动的效果 @@ -323,9 +325,9 @@ - (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 setFunctionButtonContent:[SPPageMenuButtonItem itemWithTitle:@"更多" image:[UIImage imageNamed:@"Expression_1"] imagePosition:SPItemImagePositionTop] 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移动的效果 @@ -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]; @@ -354,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 forItemAtIndex: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 forItemAtIndex:3]; pageMenu.delegate = self; // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 pageMenu.bridgeScrollView = self.scrollView; @@ -391,23 +396,72 @@ - (void)test19 { _pageMenu = pageMenu; } -// 示例20:特别属性说明 +// 示例20:某个按钮上添加一个副标题 - (void)test20 { - self.dataArr = nil; + 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; - 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“"; + // 获取第1个item上文字相对pageMenu的位置大小 + CGRect titleRect = [pageMenu titleRectRelativeToPageMenuForItemAtIndex:1]; - 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]; + 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]; + // 传递数组,默认选中第2个 + [pageMenu setItems:self.dataArr selectedItemIndex:1]; + pageMenu.delegate = self; + pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollAdaptContent; + // 给pageMenu传递外界的大scrollView,内部监听self.scrollView的滚动,从而实现让跟踪器跟随self.scrollView移动的效果 + pageMenu.bridgeScrollView = self.scrollView; + + // 这里通过KVC的形式取出按钮数组,再通过下标获取指定的按钮。本框架没有特别提供返回指定按钮的方法,因为按钮是不能返回的,一旦返回,该按钮的属性就可以被外界轻松地任意修改,这是一个框架该考虑的安全问题。如果专门提供一个设置角标的方法,那么角标的样式又可以自定义,角标并非本框架的核心功能,所以没必要因为它将框架搞得过于臃肿。 + 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; } // ------------------------------------------------------------------------------------------------ @@ -474,7 +528,11 @@ - (void)viewDidLoad { case 18: [self test19]; break; - default: + case 19: + [self test20]; + break; + case 20: + [self test21]; break; } @@ -484,11 +542,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 contentForItemAtIndex: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即可) @@ -563,7 +624,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往后偏移 @@ -589,8 +650,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/.DS_Store b/Example/SPPageMenu/SPPageMenu/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/Example/SPPageMenu/SPPageMenu/.DS_Store and /dev/null differ diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h b/Example/SPPageMenu/SPPageMenu/SPPageMenu.h deleted file mode 100644 index c835bb8..0000000 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.h +++ /dev/null @@ -1,201 +0,0 @@ -// -// SPPageMenu.h -// SPPageMenu -// -// Created by 乐升平 on 17/10/26. https://github.com/SPStore/SPPageMenu -// Copyright © 2017年 iDress. All rights reserved. -// - -#import - -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的总宽度对每个item均分 - SPPageMenuPermutationWayNotScrollAdaptContent // 自适应内容,不可以滑动,整个内容被控制在pageMenu的范围之内,这种排列方式下,自动计算item之间的间距,itemPadding属性无效 -}; - -typedef NS_ENUM(NSInteger, SPPageMenuTrackerFollowingMode) { - SPPageMenuTrackerFollowingModeAlways = 0, // 外界scrollView拖动时,跟踪器时刻跟随外界scrollView移动 - SPPageMenuTrackerFollowingModeEnd, // 外界scrollVie拖动w结束后,跟踪器才开始移动 - SPPageMenuTrackerFollowingModeHalf // 外界scrollView拖动距离超过屏幕一半时,跟踪器开始移动 -}; - -typedef NS_ENUM(NSInteger, SPItemImagePosition) { - SPItemImagePositionDefault, // 默认图片在左边 - SPItemImagePositionLeft, // 图片在左边 - SPItemImagePositionTop, // 图片在上面 - SPItemImagePositionRight, // 图片在右边 - SPItemImagePositionBottom // 图片在下面 -}; - -@class SPPageMenu; - -@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类型) - * @param selectedItemIndex 默认选中item的下标 - */ -- (void)setItems:(nullable NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex; - -@property (nonatomic) NSInteger selectedItemIndex; // 选中的item下标,改变其值可以用于切换选中的item - -@property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 - -// item之间的间距,默认30;当排列方式permutationWay为‘SPPageMenuPermutationWayNotScrollAdaptContent’时此属性无效,无效是合理的,不可能做到“不可滑动且自适应内容”然后间距又自定义,这2者相互制约; -@property (nonatomic, assign) CGFloat itemPadding; - -@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字体 - -// 外界的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, assign) CGFloat trackerWidth; // 跟踪器的宽度 -// 设置跟踪器的高度和圆角半径,矩形和圆角矩形样式下半径参数无效。其余样式下:默认的高度为3,圆角半径为高度的一半。如果你想用默认高度,但是又不想要圆角半径,你可以设置trackerHeight为3,cornerRadius为0,这是去除默认半径的唯一办法 -- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius; - -// 跟踪器的跟踪模式 -@property (nonatomic, assign) SPPageMenuTrackerFollowingMode trackerFollowingMode; - - -// 分割线 -@property (nonatomic, readonly) UIImageView *dividingLine; // 分割线,你可以拿到该对象设置一些自己想要的属性,如颜色、图片等,如果想要隐藏分割线,拿到该对象直接设置hidden为YES或设置alpha<0.01即可(eg:pageMenu.dividingLine.hidden = YES) -@property (nonatomic) CGFloat dividingLineHeight; // 分割线的高度 - -// 选中的item缩放系数,默认为1,为1代表不缩放,[0,1)之间缩小,(1,+∞)之间放大,(-1,0)之间"倒立"缩小,(-∞,-1)之间"倒立"放大,为-1"倒立不缩放",如果依然使用了废弃的SPPageMenuTrackerStyleTextZoom样式,则缩放系数默认为1.3 -@property (nonatomic) CGFloat selectedItemZoomScale; -@property (nonatomic, assign) BOOL needTextColorGradients; // 是否需要文字渐变,默认为YES - -@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; -// 如果移除的正是当前选中的item(当前选中的item下标不为0),删除之后,选中的item会切换为上一个item -- (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)removeAllItems; - -- (void)setTitle:(nullable NSString *)title forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的标题,设置后,仅会有文字 -- (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的标题 - -- (void)setImage:(nullable UIImage *)image forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的图片,设置后,仅会有图片 -- (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex; // 获取指定item的图片 - -- (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex; // 设置指定item的宽度(如果width为0,item会根据内容自动计算width) -- (CGFloat)widthForItemAtIndex:(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; // 获取背景图片 - -/** - 同时为指定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 showFuntionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat funtionButtonshadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 - -/** - * 同时为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配置相关属性,如设置字体、文字颜色等;在此,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"赋值是实现此效果的最简便的操作 - 4.如果不想要此效果,可设置closeTrackerFollowingMode==YES - */ -- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; - - - -// -------------- 以下方法和属性被废弃 -------------- - -// 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.1版本已纠正 -- (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枚举值 -@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:"); -@end - -NS_ASSUME_NONNULL_END - - - diff --git a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m b/Example/SPPageMenu/SPPageMenu/SPPageMenu.m deleted file mode 100644 index 46b3655..0000000 --- a/Example/SPPageMenu/SPPageMenu/SPPageMenu.m +++ /dev/null @@ -1,1529 +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 SPPageMenuItem : UIButton - -- (instancetype)initWithImageRatio:(CGFloat)ratio; -// 图片的高度所占按钮的高度比例,注意要浮点数,如果传分数比如三分之二,要写2.0/3.0,不能写2/3 -@property (nonatomic, assign) CGFloat imageRatio; -// 图片的位置 -@property (nonatomic, assign) SPItemImagePosition imagePosition; -// 图片与标题之间的间距 -@property (nonatomic, assign) CGFloat imageTitleSpace; -@end - -@implementation SPPageMenuItem - - -- (instancetype)initWithImageRatio:(CGFloat)ratio { - if (self = [super init]) { - _imageRatio = ratio; - } - return self; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - - [self initialize]; - - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - if (self = [super initWithCoder:aDecoder]) { - - [self initialize]; - - } - return self; -} - -- (void)initialize { - _imageRatio = 0.5; - _imagePosition = SPItemImagePositionDefault; - - self.imageView.contentMode = UIViewContentModeScaleAspectFit; - self.titleLabel.textAlignment = NSTextAlignmentCenter; -} - -- (void)setHighlighted:(BOOL)highlighted {} - -- (CGRect)imageRectForContentRect:(CGRect)contentRect { - if (!self.currentTitle) { // 如果没有文字,则图片占据整个button,空格算一个文字 - return [super imageRectForContentRect:contentRect]; - } - switch (self.imagePosition) { - case SPItemImagePositionDefault: - case SPItemImagePositionLeft: { // 图片在左 - _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; - CGFloat imageW = (contentRect.size.width-_imageTitleSpace) * _imageRatio; - CGFloat imageH = contentRect.size.height; - return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); - } - break; - case SPItemImagePositionTop: { - _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; - CGFloat imageW = contentRect.size.width; - CGFloat imageH = (contentRect.size.height-_imageTitleSpace) * _imageRatio; - return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); - } - break; - case SPItemImagePositionRight: { - _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; - CGFloat imageW = (contentRect.size.width-_imageTitleSpace) * _imageRatio; - CGFloat imageH = contentRect.size.height; - CGFloat imageX = contentRect.size.width - imageW; - return CGRectMake(imageX+self.contentEdgeInsets.left, self.contentEdgeInsets.top, imageW, imageH); - } - break; - case SPItemImagePositionBottom: { - _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; - CGFloat imageW = contentRect.size.width; - CGFloat imageH = (contentRect.size.height - _imageTitleSpace) * _imageRatio; - CGFloat imageY = contentRect.size.height-imageH; - return CGRectMake(self.contentEdgeInsets.left, imageY+self.contentEdgeInsets.top, imageW, imageH); - } - break; - default: - break; - } - return CGRectZero; -} - -- (CGRect)titleRectForContentRect:(CGRect)contentRect { - if (!self.currentImage) { // 如果没有图片 - return [super titleRectForContentRect:contentRect]; - } - switch (self.imagePosition) { - case SPItemImagePositionDefault: - case SPItemImagePositionLeft: { - _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; - CGFloat titleX = (contentRect.size.width-_imageTitleSpace) * _imageRatio + _imageTitleSpace; - CGFloat titleW = contentRect.size.width - titleX; - CGFloat titleH = contentRect.size.height; - return CGRectMake(titleX+self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH); - } - break; - case SPItemImagePositionTop: { - _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; - CGFloat titleY = (contentRect.size.height-_imageTitleSpace) * _imageRatio + _imageTitleSpace; - CGFloat titleW = contentRect.size.width; - CGFloat titleH = contentRect.size.height - titleY; - return CGRectMake(self.contentEdgeInsets.left, titleY+self.contentEdgeInsets.top, titleW, titleH); - } - break; - case SPItemImagePositionRight: { - _imageRatio = _imageRatio == 0.0 ? 0.5 : _imageRatio; - CGFloat titleW = (contentRect.size.width - _imageTitleSpace) * (1-_imageRatio); - CGFloat titleH = contentRect.size.height; - return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH); - } - break; - case SPItemImagePositionBottom: { - _imageRatio = _imageRatio == 0.0 ? 2.0/3.0 : _imageRatio; - CGFloat titleW = contentRect.size.width; - CGFloat titleH = (contentRect.size.height-_imageTitleSpace) * (1 - _imageRatio); - return CGRectMake(self.contentEdgeInsets.left, self.contentEdgeInsets.top, titleW, titleH); - } - break; - default: - break; - } - return CGRectZero; - -} - -- (void)setImagePosition:(SPItemImagePosition)imagePosition { - _imagePosition = imagePosition; - [self setNeedsDisplay]; -} - -- (void)setImageRatio:(CGFloat)imageRatio { - _imageRatio = imageRatio; - [self setNeedsDisplay]; -} - -- (void)setImageTitleSpace:(CGFloat)imageTitleSpace { - _imageTitleSpace = imageTitleSpace; - [self setNeedsDisplay]; -} - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets { - [super setContentEdgeInsets:contentEdgeInsets]; - [self setNeedsDisplay]; -} - -@end - -@interface SPPageMenu() -@property (nonatomic, assign) SPPageMenuTrackerStyle trackerStyle; -@property (nonatomic, strong) NSArray *items; // 里面装的是字符串或者图片 -@property (nonatomic, strong) UIImageView *tracker; -@property (nonatomic, assign) CGFloat trackerHeight; -@property (nonatomic, weak) UIView *backgroundView; -@property (nonatomic, weak) UIImageView *backgroundImageView; -@property (nonatomic, strong) UIImageView *dividingLine; -@property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; -@property (nonatomic, weak) SPPageMenuItem *functionButton; -@property (nonatomic, strong) NSMutableArray *buttons; -@property (nonatomic, strong) SPPageMenuItem *selectedButton; -@property (nonatomic, strong) NSMutableDictionary *setupWidths; -@property (nonatomic, assign) BOOL insert; -// 起始偏移量,为了判断滑动方向 -@property (nonatomic, assign) CGFloat beginOffsetX; - -/// 开始颜色, 取值范围 0~1 -@property (nonatomic, assign) CGFloat startR; -@property (nonatomic, assign) CGFloat startG; -@property (nonatomic, assign) CGFloat startB; -/// 完成颜色, 取值范围 0~1 -@property (nonatomic, assign) CGFloat endR; -@property (nonatomic, assign) CGFloat endG; -@property (nonatomic, assign) CGFloat endB; - -// 这个高度,是存储itemScrollView的高度 -@property (nonatomic, assign) CGFloat itemScrollViewH; -@end - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -@implementation SPPageMenu - - -#pragma mark - public - -+ (instancetype)pageMenuWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle { - SPPageMenu *pageMenu = [[SPPageMenu alloc] initWithFrame:frame trackerStyle:trackerStyle]; - return pageMenu; -} - -- (instancetype)initWithFrame:(CGRect)frame trackerStyle:(SPPageMenuTrackerStyle)trackerStyle { - if (self = [super init]) { - self.frame = frame; - self.backgroundColor = [UIColor whiteColor]; - self.trackerStyle = trackerStyle; - [self setupStartColor:_selectedItemTitleColor]; - [self setupEndColor:_unSelectedItemTitleColor]; - } - return self; -} - -- (void)setItems:(NSArray *)items selectedItemIndex:(NSInteger)selectedItemIndex { - if (selectedItemIndex < 0) selectedItemIndex = 0; - NSAssert(selectedItemIndex <= items.count-1, @"selectedItemIndex 大于了 %ld",items.count-1); - _items = items.copy; - _selectedItemIndex = selectedItemIndex; - - self.insert = NO; - - for (int i = 0; i < items.count; i++) { - id object = items[i]; - NSAssert([object isKindOfClass:[NSString class]] || [object isKindOfClass:[UIImage class]], @"items中的元素只能是NSString或UIImage类型"); - [self addButton:i object:object animated:NO]; - } - - [self setNeedsLayout]; - [self layoutIfNeeded]; - - if (self.buttons.count) { - // 默认选中selectedItemIndex对应的按钮 - SPPageMenuItem *selectedButton = [self.buttons objectAtIndex:selectedItemIndex]; - [self buttonInPageMenuClicked:selectedButton]; - - // SPPageMenuTrackerStyleTextZoom和SPPageMenuTrackerStyleNothing样式跟tracker没有关联 - if ([self haveOrNeedsTracker]) { - [self.itemScrollView insertSubview:self.tracker atIndex:0]; - // 这里千万不能再去调用setNeedsLayout和layoutIfNeeded,因为如果外界在此之前对selectedButton进行了缩放,调用了layoutSubViews后会重新对selectedButton设置frame,先缩放再重设置frame会导致文字显示不全,所以我们直接跳过layoutSubViews调用resetSetupTrackerFrameWithSelectedButton:只设置tracker的frame - [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; - } - } -} - -- (void)insertItemWithTitle:(NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated { - self.insert = YES; - NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count); - NSMutableArray *titleArr = self.items.mutableCopy; - [titleArr insertObject:title atIndex:itemIndex]; - self.items = titleArr; - [self addButton:itemIndex object:title animated:animated]; - if (itemIndex <= self.selectedItemIndex) { - _selectedItemIndex += 1; - } -} - -- (void)insertItemWithImage:(UIImage *)image atIndex:(NSUInteger)itemIndex animated:(BOOL)animated { - self.insert = YES; - NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count); - NSMutableArray *objects = self.items.mutableCopy; - [objects insertObject:image atIndex:itemIndex]; - self.items = objects.copy; - [self addButton:itemIndex object:image animated:animated]; - if (itemIndex <= self.selectedItemIndex) { - _selectedItemIndex += 1; - } -} - -- (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { - NSAssert(itemIndex <= self.items.count, @"itemIndex超过了items的总个数“%ld”",self.items.count); - // 被删除的按钮之后的按钮需要修改tag值 - for (SPPageMenuItem *button in self.buttons) { - if (button.tag-tagBaseValue > itemIndex) { - button.tag = button.tag - 1; - } - } - if (self.items.count) { - NSMutableArray *objects = self.items.mutableCopy; - // 特别注意的是:不能先通过itemIndex取出对象,然后再将对象删除,因为这样会删除所有相同的对象 - [objects removeObjectAtIndex:itemIndex]; - self.items = objects.copy; - } - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - if (button == self.selectedButton) { // 如果删除的正是选中的item,删除之后,选中的按钮切换为上一个item - self.selectedItemIndex = itemIndex > 0 ? itemIndex-1 : itemIndex; - } - [self.buttons removeObjectAtIndex:itemIndex]; - [button removeFromSuperview]; - if (self.buttons.count == 0) { // 说明移除了所有 - [self.tracker removeFromSuperview]; - self.selectedButton = nil; - self.selectedItemIndex = 0; - } - } - if (animated) { - [UIView animateWithDuration:0.5 animations:^{ - [self setNeedsLayout]; - [self layoutIfNeeded]; - }]; - } else { - [self setNeedsLayout]; - } - -} - -- (void)removeAllItems { - NSMutableArray *objects = self.items.mutableCopy; - [objects removeAllObjects]; - self.items = objects.copy; - self.items = nil; - - for (int i = 0; i < self.buttons.count; i++) { - SPPageMenuItem *button = self.buttons[i]; - [button removeFromSuperview]; - } - - [self.buttons removeAllObjects]; - - [self.tracker removeFromSuperview]; - - self.selectedButton = nil; - self.selectedItemIndex = 0; - - [self setNeedsLayout]; -} - -- (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - [button setImage:nil forState:UIControlStateNormal]; - [button setTitle:title forState:UIControlStateNormal]; - - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:title]; - self.items = items.copy; - } - [self setNeedsLayout]; -} - -- (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; - return item.currentTitle; - } - return nil; -} - -- (void)setImage:(UIImage *)image forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - [button setTitle:nil forState:UIControlStateNormal]; - [button setImage:image forState:UIControlStateNormal]; - - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:image]; - self.items = items.copy; - } - [self setNeedsLayout]; -} - -- (nullable UIImage *)imageForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; - return item.currentImage; - } - return nil; -} - - -- (void)setEnabled:(BOOL)enaled forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - [button setEnabled:enaled]; - } -} - -- (BOOL)enabledForItemAtIndex:(NSUInteger)itemIndex { - if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - return button.enabled; - } - return YES; -} - -- (void)setWidth:(CGFloat)width forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - [self.setupWidths setObject:@(width) forKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]]; - } -} - -- (CGFloat)widthForItemAtIndex:(NSUInteger)itemIndex { - CGFloat setupWidth = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)itemIndex]] floatValue]; - if (setupWidth) { - return setupWidth; - } else { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - return button.bounds.size.width; - } - } - return 0; -} - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - button.contentEdgeInsets = contentInset; - } -} - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentInset forItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - button.contentEdgeInsets = contentInset; - } -} - -- (UIEdgeInsets)contentEdgeInsetsForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - return button.contentEdgeInsets; - } - return UIEdgeInsetsZero; -} - -- (void)setBackgroundImage:(UIImage *)backgroundImage barMetrics:(UIBarMetrics)barMetrics { - if (barMetrics == UIBarMetricsDefault) { - if (UIEdgeInsetsEqualToEdgeInsets(backgroundImage.capInsets, UIEdgeInsetsZero)) { - CGFloat imageWidth = CGImageGetWidth(backgroundImage.CGImage); - CGFloat imageHeight = CGImageGetHeight(backgroundImage.CGImage); - [self.backgroundImageView setImage:[backgroundImage resizableImageWithCapInsets:UIEdgeInsetsMake(imageHeight*0.5, imageWidth*0.5, imageHeight*0.5, imageWidth*0.5) resizingMode:backgroundImage.resizingMode]]; - } else { - [self.backgroundImageView setImage:backgroundImage]; - } - } -} - -- (UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics { - return self.backgroundImageView.image; -} - -- (void)setTrackerHeight:(CGFloat)trackerHeight cornerRadius:(CGFloat)cornerRadius { - _trackerHeight = trackerHeight; - self.tracker.layer.cornerRadius = cornerRadius; - [self setNeedsLayout]; - [self layoutIfNeeded]; -} - -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forItemIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - [button setTitle:title forState:UIControlStateNormal]; - [button setImage:image forState:UIControlStateNormal]; - button.imagePosition = imagePosition; - button.imageRatio = ratio; - button.imageTitleSpace = imageTitleSpace; - - // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 - if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:image]; - self.items = items.copy; - } else if (image == nil) { - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:title]; - self.items = items.copy; - } else { - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:image]; - self.items = items.copy; - } - - [self setNeedsLayout]; - [self layoutIfNeeded]; - } -} - -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state { - [self.functionButton setTitle:title forState:state]; - [self.functionButton setImage:image forState:state]; - self.functionButton.imagePosition = imagePosition; - self.functionButton.imageRatio = ratio; - self.functionButton.imageTitleSpace = imageTitleSpace; -} - -// 以下2个方法在3.0版本上有升级,可以使用但不推荐 -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; - [button setTitle:title forState:UIControlStateNormal]; - [button setImage:image forState:UIControlStateNormal]; - button.imagePosition = imagePosition; - button.imageRatio = ratio; - - // 文字和图片只能替换其一,因为items数组里不能同时装文字和图片。当文字和图片同时设置时,items里只更新文字 - if (title == nil || title.length == 0 || [title isKindOfClass:[NSNull class]]) { - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:image]; - self.items = items.copy; - } else if (image == nil) { - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:title]; - self.items = items.copy; - } else { - NSMutableArray *items = self.items.mutableCopy; - [items replaceObjectAtIndex:itemIndex withObject:image]; - self.items = items.copy; - } - - [self setNeedsLayout]; - [self layoutIfNeeded]; - } -} -- (void)setFunctionButtonTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forState:(UIControlState)state { - [self.functionButton setTitle:title forState:state]; - [self.functionButton setImage:image forState:state]; - self.functionButton.imagePosition = imagePosition; - self.functionButton.imageRatio = ratio; -} - -- (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state { - if (attributes[NSFontAttributeName]) { - self.functionButton.titleLabel.font = attributes[NSFontAttributeName]; - } - if (attributes[NSForegroundColorAttributeName]) { - [self.functionButton setTitleColor:attributes[NSForegroundColorAttributeName] forState:state]; - } - if (attributes[NSBackgroundColorAttributeName]) { - self.functionButton.backgroundColor = attributes[NSBackgroundColorAttributeName]; - } -} - -- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView { - - // 说明外界传进来了一个scrollView,如果外界传进来了,pageMenu会观察该scrollView的contentOffset自动处理跟踪器的跟踪 - if (self.bridgeScrollView == scrollView) { return; } - - [self prepareMoveTrackerFollowScrollView:scrollView]; -} - - -#pragma mark - private - -- (void)addButton:(NSInteger)index object:(id)object animated:(BOOL)animated { - - // 如果是插入,需要改变已有button的tag值 - for (SPPageMenuItem *button in self.buttons) { - if (button.tag-tagBaseValue >= index) { - button.tag = button.tag + 1; // 由于有新button的加入,新button后面的button的tag值得+1 - } - } - SPPageMenuItem *button = [SPPageMenuItem buttonWithType:UIButtonTypeCustom]; - [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; - button.titleLabel.font = _itemTitleFont; - [button addTarget:self action:@selector(buttonInPageMenuClicked:) forControlEvents:UIControlEventTouchUpInside]; - button.tag = tagBaseValue + index; - if ([object isKindOfClass:[NSString class]]) { - [button setTitle:object forState:UIControlStateNormal]; - } else { - [button setImage:object forState:UIControlStateNormal]; - } - if (self.insert) { - if ([self haveOrNeedsTracker]) { - if (self.buttons.count == 0) { // 如果是第一个插入,需要将跟踪器加上,第一个插入说明itemScrollView上没有任何子控件 - [self.itemScrollView insertSubview:self.tracker atIndex:0]; - [self.itemScrollView insertSubview:button atIndex:index+1]; - } 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]; - - // setNeedsLayout会标记为需要刷新,layoutIfNeeded只有在有标记的情况下才会立即调用layoutSubViews,当然标记为刷新并非只有调用setNeedsLayout,如frame改变,addSubView等都会标记为刷新 - - if (self.insert && animated) { // 是插入的新按钮,且需要动画 - // 取出上一个按钮 - SPPageMenuItem *lastButton; - if (index > 0) { - lastButton = self.buttons[index-1]; - } - // 先给初始的origin,按钮将会从这个origin开始动画 - button.frame = CGRectMake(CGRectGetMaxX(lastButton.frame)+_itemPadding*0.5, 0, 0, 0); - button.titleLabel.frame = button.bounds; - [UIView animateWithDuration:.5 animations:^{ - [self setNeedsLayout]; - [self layoutIfNeeded]; - }]; - } -} - -// 是否已经或者即将有跟踪器 -- (BOOL)haveOrNeedsTracker { - if (self.trackerStyle != SPPageMenuTrackerStyleTextZoom && self.trackerStyle != SPPageMenuTrackerStyleNothing) { - return YES; - } - return NO; -} - -- (instancetype)initWithFrame:(CGRect)frame { - if (self = [super initWithFrame:frame]) { - [self initialize]; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - if (self = [super initWithCoder:aDecoder]) { - [self initialize]; - } - return self; -} - -- (void)initialize { - - _itemPadding = 30; - _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; // 适配屏幕分辨率 - _contentInset = UIEdgeInsetsZero; - _selectedItemIndex = 0; - _showFuntionButton = NO; - _funtionButtonshadowOpacity = 0.5; - _selectedItemZoomScale = 1; - _needTextColorGradients = YES; - - [self setupSubViews]; -} - -- (void)setupSubViews { - // 必须先添加分割线,再添加backgroundView;假如先添加backgroundView,那也就意味着backgroundView是SPPageMenu的第一个子控件,而scrollView又是backgroundView的第一个子控件,当外界在由导航控制器管理的控制器中将SPPageMenu添加为第一个子控件时,控制器会不断的往下遍历第一个子控件的第一个子控件,直到找到为scrollView为止,一旦发现某子控件的第一个子控件为scrollView,会将scrollView的内容往下偏移64;这时控制器中必须设置self.automaticallyAdjustsScrollViewInsets = NO;为了避免这样做,这里将分割线作为第一个子控件 - SPPageMenuLine *dividingLine = [[SPPageMenuLine alloc] init]; - dividingLine.backgroundColor = [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; - - 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.shadowOffset = CGSizeMake(0, 0); - functionButton.layer.shadowRadius = 2; - functionButton.layer.shadowOpacity = _funtionButtonshadowOpacity; // 默认是0,为0的话不会显示阴影 - functionButton.hidden = !_showFuntionButton; - [backgroundView addSubview:functionButton]; - _functionButton = functionButton; -} - -// 按钮点击方法 -- (void)buttonInPageMenuClicked:(SPPageMenuItem *)sender { - NSInteger fromIndex = self.selectedButton ? self.selectedButton.tag-tagBaseValue : sender.tag - tagBaseValue; - NSInteger toIndex = sender.tag - tagBaseValue; - // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的,必须用下划线,用self.会造成死循环 - _selectedItemIndex = toIndex; - // 如果sender是新的选中的按钮,则上一次的按钮颜色为非选中颜色,当前选中的颜色为选中颜色 - if (self.selectedButton != sender) { - [self.selectedButton setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; - [sender setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal]; - self.selectedButton.titleLabel.font = _unSelectedItemTitleFont; - sender.titleLabel.font = _selectedItemTitleFont; - - // 让itemScrollView发生偏移 - [self moveItemScrollViewWithSelectedButton:sender]; - - if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || _selectedItemZoomScale != 1) { - - if (labs(toIndex-fromIndex) >= 2) { // 该条件意思是当外界滑动scrollView连续的滑动了超过2页 - for (SPPageMenuItem *button in self.buttons) { // 必须遍历将非选中按钮还原缩放,而不是仅仅只让上一个选中的按钮还原缩放。因为当用户快速滑动外界scrollView时,会频繁的调用-zoomForTitleWithProgress:fromButton:toButton:方法,有可能经过的某一个button还没彻底还原缩放就直接过去了,从而可能会导致该按钮文字会显示不全,所以在这里,将所有非选中的按钮还原缩放 - if (button != sender && !CGAffineTransformEqualToTransform(button.transform, CGAffineTransformIdentity)) { - button.transform = CGAffineTransformIdentity; - } - } - } else { - self.selectedButton.transform = CGAffineTransformIdentity; - } - sender.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, _selectedItemZoomScale); - } - if (fromIndex != toIndex) { // 如果相等,说明是第1次进来或者2次点了同一个,此时不需要动画 - [self moveTrackerWithSelectedButton:sender]; - } - self.selectedButton = sender; - if (_selectedItemTitleFont != _unSelectedItemTitleFont) { - [self setNeedsLayout]; - [self layoutIfNeeded]; - } - } else { // 如果选中的按钮没有发生变化,比如用户往左边滑scrollView,还没滑动结束又开始往右滑动,此时选中的按钮就没变。如果设置了颜色渐变,而且当未选中的颜色带了不等于1的alpha值,如果用户往一边滑动还未结束又往另一边滑,则未选中的按钮颜色不是很准确。这个else就是去除这种不准确现象 - // 获取RGB和Alpha - CGFloat red = 0.0; - CGFloat green = 0.0; - CGFloat blue = 0.0; - CGFloat alpha = 0.0; - [_unSelectedItemTitleColor getRed:&red green:&green blue:&blue alpha:&alpha]; - // 此时alpha已经获取到了 - if (alpha < 1) { // 因为相信alpha=1的情况还是占多数的,如果不做判断,apha=1时也for循环设置未选中按钮的颜色有点浪费.alpha=1时不会产生颜色不准确问题 - for (SPPageMenuItem *button in self.buttons) { - if (button == sender) { - [button setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal]; - } else { - [button setTitleColor:_unSelectedItemTitleColor forState:UIControlStateNormal]; - } - } - } else { - [sender setTitleColor:_selectedItemTitleColor forState:UIControlStateNormal]; - } - } - [self delegatePerformMethodWithFromIndex:fromIndex toIndex:toIndex]; - -} - -// 点击button让itemScrollView发生偏移 -- (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { - if (CGRectEqualToRect(self.backgroundView.frame, CGRectZero)) { - return; - } - // 转换点的坐标位置 - CGPoint centerInPageMenu = [self.backgroundView convertPoint:selectedButton.center toView:self]; - // CGRectGetMidX(self.backgroundView.frame)指的是屏幕水平中心位置,它的值是固定不变的 - CGFloat offSetX = centerInPageMenu.x - CGRectGetMidX(self.backgroundView.frame); - - // itemScrollView的容量宽与自身宽之差(难点) - CGFloat maxOffsetX = self.itemScrollView.contentSize.width - self.itemScrollView.frame.size.width; - // 如果选中的button中心x值小于或者等于itemScrollView的中心x值,或者itemScrollView的容量宽度小于itemScrollView本身,此时点击button时不发生任何偏移,置offSetX为0 - if (offSetX <= 0 || maxOffsetX <= 0) { - offSetX = 0; - } - // 如果offSetX大于maxOffsetX,说明itemScrollView已经滑到尽头,此时button也发生任何偏移了 - else if (offSetX > maxOffsetX){ - offSetX = maxOffsetX; - } - - [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES]; - -} - -// 移动跟踪器 -- (void)moveTrackerWithSelectedButton:(SPPageMenuItem *)selectedButton { - [UIView animateWithDuration:0.25 animations:^{ - [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; - }]; -} - -// 执行代理方法 -- (void)delegatePerformMethodWithFromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { - if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:itemSelectedFromIndex:toIndex:)]) { - [self.delegate pageMenu:self itemSelectedFromIndex:fromIndex toIndex:toIndex]; - } else if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:itemSelectedAtIndex:)]) { - [self.delegate pageMenu:self itemSelectedAtIndex:toIndex]; - } -} - -// 功能按钮的点击方法 -- (void)functionButtonClicked:(SPPageMenuItem *)sender { - if (self.delegate && [self.delegate respondsToSelector:@selector(pageMenu:functionButtonClicked:)]) { - [self.delegate pageMenu:self functionButtonClicked:sender]; - } -} - -- (void)prepareMoveTrackerFollowScrollView:(UIScrollView *)scrollView { - - // 这个if条件的意思是scrollView的滑动不是由手指拖拽产生 - if (!scrollView.isDragging && !scrollView.isDecelerating) {return;} - - // 当滑到边界时,继续通过scrollView的bouces效果滑动时,直接return - if (scrollView.contentOffset.x < 0 || scrollView.contentOffset.x > scrollView.contentSize.width-scrollView.bounds.size.width) { - return; - } - - // 当前偏移量 - CGFloat currentOffSetX = scrollView.contentOffset.x; - // 偏移进度 - CGFloat offsetProgress = currentOffSetX / scrollView.bounds.size.width; - CGFloat progress = offsetProgress - floor(offsetProgress); - - NSInteger fromIndex = 0; - NSInteger toIndex = 0; - // 初始值不要等于scrollView.contentOffset.x,因为第一次进入此方法时,scrollView.contentOffset.x的值已经有一点点偏移了,不是很准确 - _beginOffsetX = scrollView.bounds.size.width * self.selectedItemIndex; - - // 以下注释的“拖拽”一词很准确,不可说成滑动,例如:当手指向右拖拽,还未拖到一半时就松开手,接下来scrollView则会往回滑动,这个往回,就是向左滑动,这也是_beginOffsetX不可时刻纪录的原因,如果时刻纪录,那么往回(向左)滑动时会被视为“向左拖拽”,然而,这个往回却是由“向右拖拽”而导致的 - if (currentOffSetX - _beginOffsetX > 0) { // 向左拖拽了 - // 求商,获取上一个item的下标 - fromIndex = currentOffSetX / scrollView.bounds.size.width; - // 当前item的下标等于上一个item的下标加1 - toIndex = fromIndex + 1; - if (toIndex >= self.buttons.count) { - toIndex = fromIndex; - } - } else if (currentOffSetX - _beginOffsetX < 0) { // 向右拖拽了 - toIndex = currentOffSetX / scrollView.bounds.size.width; - fromIndex = toIndex + 1; - progress = 1.0 - progress; - - } else { - progress = 1.0; - fromIndex = self.selectedItemIndex; - toIndex = fromIndex; - } - - if (currentOffSetX == scrollView.bounds.size.width * fromIndex) {// 滚动停止了 - progress = 1.0; - toIndex = fromIndex; - } - - - // 如果滚动停止,直接通过点击按钮选中toIndex对应的item - if (currentOffSetX == scrollView.bounds.size.width*toIndex) { // 这里toIndex==fromIndex - // 这一次赋值起到2个作用,一是点击toIndex对应的按钮,走一遍代理方法,二是弥补跟踪器的结束跟踪,因为本方法是在scrollViewDidScroll中调用,可能离滚动结束还有一丁点的距离,本方法就不调了,最终导致外界还要在scrollView滚动结束的方法里self.selectedItemIndex进行赋值,直接在这里赋值可以让外界不用做此操作 - if (_selectedItemIndex != toIndex) { - self.selectedItemIndex = toIndex; - } - // 要return,点击了按钮,跟踪器自然会跟着被点击的按钮走 - return; - } - - if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeAlways) { - // 这个方法才开始移动跟踪器 - [self moveTrackerWithProgress:progress fromIndex:fromIndex toIndex:toIndex currentOffsetX:currentOffSetX beginOffsetX:_beginOffsetX]; - } else if (self.trackerFollowingMode == SPPageMenuTrackerFollowingModeHalf) { - SPPageMenuItem *fromButton; - SPPageMenuItem *toButton; - if (progress > 0.5) { - if (toIndex >= 0 && toIndex < self.buttons.count) { - toButton = self.buttons[toIndex]; - fromButton = self.buttons[fromIndex]; - - if (_selectedItemIndex != toIndex) { - self.selectedItemIndex = toIndex; - } - } - } else { - if (fromIndex >= 0 && fromIndex < self.buttons.count) { - toButton = self.buttons[fromIndex]; - fromButton = self.buttons[toIndex]; - - if (_selectedItemIndex != fromIndex) { - self.selectedItemIndex = fromIndex; - } - } - } - - } else { // self.trackerFollowingMode = SPPageMenuTrackerFollowingModeEnd - // 什么都不用做 - } - -} - -// 这个方法才开始真正滑动跟踪器,上面都是做铺垫 -- (void)moveTrackerWithProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex currentOffsetX:(CGFloat)currentOffsetX beginOffsetX:(CGFloat)beginOffsetX { - - UIButton *fromButton = self.buttons[fromIndex]; - UIButton *toButton = self.buttons[toIndex]; - - // 2个按钮之间的距离 - CGFloat xDistance = toButton.center.x - fromButton.center.x; - // 2个按钮宽度的差值 - CGFloat wDistance = toButton.frame.size.width - fromButton.frame.size.width; - - CGRect newFrame = self.tracker.frame; - CGPoint newCenter = self.tracker.center; - if (self.trackerStyle == SPPageMenuTrackerStyleLine) { - newCenter.x = fromButton.center.x + xDistance * progress; - newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress); - self.tracker.frame = newFrame; - self.tracker.center = newCenter; - if (_selectedItemZoomScale != 1) { - [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; - } - } else if (self.trackerStyle == SPPageMenuTrackerStyleLineAttachment) { - // 这种样式的计算比较复杂,有个很关键的技巧,就是参考progress分别为0、0.5、1时的临界值 - // 原先的x值 - CGFloat originX = fromButton.frame.origin.x+(fromButton.frame.size.width-(_trackerWidth ? _trackerWidth : fromButton.titleLabel.font.pointSize))*0.5; - // 原先的宽度 - CGFloat originW = _trackerWidth ? _trackerWidth : fromButton.titleLabel.font.pointSize; - if (currentOffsetX - _beginOffsetX >= 0) { // 向左拖拽了 - if (progress < 0.5) { - newFrame.origin.x = originX; // x值保持不变 - newFrame.size.width = originW + xDistance * progress * 2; - } else { - newFrame.origin.x = originX + xDistance * (progress-0.5) * 2; - newFrame.size.width = originW + xDistance - xDistance * (progress-0.5) * 2; - } - } else { // 向右拖拽了 - // 此时xDistance为负 - if (progress < 0.5) { - newFrame.origin.x = originX + xDistance * progress * 2; - newFrame.size.width = originW - xDistance * progress * 2; - } else { - newFrame.origin.x = originX + xDistance; - newFrame.size.width = originW - xDistance + xDistance * (progress-0.5) * 2; - } - } - self.tracker.frame = newFrame; - if (_selectedItemZoomScale != 1) { - [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; - } - - } else if (self.trackerStyle == SPPageMenuTrackerStyleTextZoom || self.trackerStyle == SPPageMenuTrackerStyleNothing) { - // 缩放文字 - if (_selectedItemZoomScale != 1) { - [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; - } - } else if (self.trackerStyle == SPPageMenuTrackerStyleRoundedRect) { - newCenter.x = fromButton.center.x + xDistance * progress; - newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress + _itemPadding); - self.tracker.frame = newFrame; - self.tracker.center = newCenter; - if (_selectedItemZoomScale != 1) { - [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; - } - } else { - newCenter.x = fromButton.center.x + xDistance * progress; - newFrame.size.width = _trackerWidth ? _trackerWidth : (fromButton.frame.size.width + wDistance * progress + _itemPadding); - self.tracker.frame = newFrame; - self.tracker.center = newCenter; - if (_selectedItemZoomScale != 1) { - [self zoomForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; - } - } - // 文字颜色渐变 - if (self.needTextColorGradients) { - [self colorGradientForTitleWithProgress:progress fromButton:fromButton toButton:toButton]; - } -} - -// 颜色渐变方法 -- (void)colorGradientForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton { - // 获取 targetProgress - CGFloat fromProgress = progress; - // 获取 originalProgress - CGFloat toProgress = 1 - fromProgress; - - CGFloat r = self.endR - self.startR; - CGFloat g = self.endG - self.startG; - CGFloat b = self.endB - self.startB; - UIColor *fromColor = [UIColor colorWithRed:self.startR + r * fromProgress green:self.startG + g * fromProgress blue:self.startB + b * fromProgress alpha:1]; - UIColor *toColor = [UIColor colorWithRed:self.startR + r * toProgress green:self.startG + g * toProgress blue:self.startB + b * toProgress alpha:1]; - - // 设置文字颜色渐变 - [fromButton setTitleColor:fromColor forState:UIControlStateNormal]; - [toButton setTitleColor:toColor forState:UIControlStateNormal]; -} - -// 获取颜色的RGB值 -- (void)getRGBComponents:(CGFloat [3])components forColor:(UIColor *)color { - CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); - unsigned char resultingPixel[4]; - CGContextRef context = CGBitmapContextCreate(&resultingPixel, 1, 1, 8, 4, rgbColorSpace, 1); - CGContextSetFillColorWithColor(context, [color CGColor]); - CGContextFillRect(context, CGRectMake(0, 0, 1, 1)); - CGContextRelease(context); - CGColorSpaceRelease(rgbColorSpace); - for (int component = 0; component < 3; component++) { - components[component] = resultingPixel[component] / 255.0f; - } -} - -/// 开始颜色设置 -- (void)setupStartColor:(UIColor *)color { - CGFloat components[3]; - [self getRGBComponents:components forColor:color]; - self.startR = components[0]; - self.startG = components[1]; - self.startB = components[2]; -} - -/// 结束颜色设置 -- (void)setupEndColor:(UIColor *)color { - CGFloat components[3]; - [self getRGBComponents:components forColor:color]; - self.endR = components[0]; - self.endG = components[1]; - self.endB = components[2]; -} - -- (void)zoomForTitleWithProgress:(CGFloat)progress fromButton:(UIButton *)fromButton toButton:(UIButton *)toButton { - CGFloat diff = _selectedItemZoomScale - 1; - fromButton.transform = CGAffineTransformMakeScale((1 - progress) * diff + 1, (1 - progress) * diff + 1); - toButton.transform = CGAffineTransformMakeScale(progress * diff + 1, progress * diff + 1); -} - -#pragma mark - KVO - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (object == self.bridgeScrollView) { - if ([keyPath isEqualToString:scrollViewContentOffset]) { - // 当scrolllView滚动时,让跟踪器跟随scrollView滑动 - [self prepareMoveTrackerFollowScrollView:self.bridgeScrollView]; - } - } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - - -#pragma mark - setter - -- (void)setBridgeScrollView:(UIScrollView *)bridgeScrollView { - _bridgeScrollView = bridgeScrollView; - if (bridgeScrollView) { - - [bridgeScrollView addObserver:self forKeyPath:scrollViewContentOffset options:NSKeyValueObservingOptionNew context:nil]; - } else { - NSLog(@"你传了一个空的scrollView"); - } -} - -- (void)setTrackerStyle:(SPPageMenuTrackerStyle)trackerStyle { - _trackerStyle = trackerStyle; - switch (trackerStyle) { - case SPPageMenuTrackerStyleLine: - case SPPageMenuTrackerStyleLineLongerThanItem: - case SPPageMenuTrackerStyleLineAttachment: - self.tracker.backgroundColor = _selectedItemTitleColor; - break; - case SPPageMenuTrackerStyleRoundedRect: - case SPPageMenuTrackerStyleRect: - self.tracker.backgroundColor = [UIColor redColor]; - _selectedItemTitleColor = [UIColor whiteColor]; - // _trackerHeight是默认有值的,所有样式都会按照事先询问_trackerHeight有没有值,如果有值则采用_trackerHeight,如果矩形或圆角矩形样式下也用_trackerHeight高度太小了,除非外界用户自己设置了_trackerHeight - _trackerHeight = 0; - break; - case SPPageMenuTrackerStyleTextZoom: - // 此样式下默认1.3 - self.selectedItemZoomScale = 1.3; - break; - default: - break; - } -} - -- (void)setBounces:(BOOL)bounces { - _bounces = bounces; - self.itemScrollView.bounces = bounces; -} - -- (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal { - _alwaysBounceHorizontal = alwaysBounceHorizontal; - self.itemScrollView.alwaysBounceHorizontal = alwaysBounceHorizontal; -} - -- (void)setTrackerWidth:(CGFloat)trackerWidth { - _trackerWidth = trackerWidth; - CGRect trackerRect = self.tracker.frame; - trackerRect.size.width = trackerWidth; - self.tracker.frame = trackerRect; - CGPoint trackerCenter = self.tracker.center; - trackerCenter.x = _selectedButton.center.x; - self.tracker.center = trackerCenter; -} - -- (void)setDividingLineHeight:(CGFloat)dividingLineHeight { - _dividingLineHeight = dividingLineHeight; - [self setNeedsLayout]; - [self layoutIfNeeded]; -} - -- (void)setSelectedItemZoomScale:(CGFloat)selectedItemZoomScale { - _selectedItemZoomScale = selectedItemZoomScale; - if (selectedItemZoomScale != 1) { - _selectedButton.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, _selectedItemZoomScale); - self.tracker.transform = CGAffineTransformMakeScale(_selectedItemZoomScale, 1); - } else { - _selectedButton.transform = CGAffineTransformIdentity; - self.tracker.transform = CGAffineTransformIdentity; - } -} - -- (void)setShowFuntionButton:(BOOL)showFuntionButton { - _showFuntionButton = showFuntionButton; - self.functionButton.hidden = !showFuntionButton; - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setFuntionButtonshadowOpacity:(CGFloat)funtionButtonshadowOpacity { - _funtionButtonshadowOpacity = funtionButtonshadowOpacity; - self.functionButton.layer.shadowOpacity = funtionButtonshadowOpacity; -} - -- (void)setItemPadding:(CGFloat)itemPadding { - _itemPadding = itemPadding; - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setItemTitleFont:(UIFont *)itemTitleFont { - _itemTitleFont = itemTitleFont; - _selectedItemTitleFont = itemTitleFont; - _unSelectedItemTitleFont = itemTitleFont; - for (SPPageMenuItem *button in self.buttons) { - button.titleLabel.font = itemTitleFont; - } - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setUnSelectedItemTitleFont:(UIFont *)unSelectedItemTitleFont { - _unSelectedItemTitleFont = unSelectedItemTitleFont; - for (SPPageMenuItem *button in self.buttons) { - if (button == _selectedButton) { - continue; - } - button.titleLabel.font = unSelectedItemTitleFont; - } - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setSelectedItemTitleFont:(UIFont *)selectedItemTitleFont { - _selectedItemTitleFont = selectedItemTitleFont; - self.selectedButton.titleLabel.font = selectedItemTitleFont; - - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setSelectedItemTitleColor:(UIColor *)selectedItemTitleColor { - _selectedItemTitleColor = selectedItemTitleColor; - [self setupStartColor:selectedItemTitleColor]; - [self.selectedButton setTitleColor:selectedItemTitleColor forState:UIControlStateNormal]; -} - -- (void)setUnSelectedItemTitleColor:(UIColor *)unSelectedItemTitleColor { - _unSelectedItemTitleColor = unSelectedItemTitleColor; - [self setupEndColor:unSelectedItemTitleColor]; - for (SPPageMenuItem *button in self.buttons) { - if (button == _selectedButton) { - continue; // 跳过选中的那个button - } - [button setTitleColor:unSelectedItemTitleColor forState:UIControlStateNormal]; - } -} - -- (void)setSelectedItemIndex:(NSInteger)selectedItemIndex { - _selectedItemIndex = selectedItemIndex; - if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:selectedItemIndex]; - [self buttonInPageMenuClicked:button]; - } -} - -- (void)setDelegate:(id)delegate { - if (delegate == _delegate) {return;} - _delegate = delegate; - if (self.buttons.count) { - SPPageMenuItem *button = [self.buttons objectAtIndex:_selectedItemIndex]; - [self delegatePerformMethodWithFromIndex:button.tag-tagBaseValue toIndex:button.tag-tagBaseValue]; - [self moveItemScrollViewWithSelectedButton:button]; - } -} - -- (void)setContentInset:(UIEdgeInsets)contentInset { - _contentInset = contentInset; - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setPermutationWay:(SPPageMenuPermutationWay)permutationWay { - _permutationWay = permutationWay; - [self setNeedsLayout]; - [self layoutIfNeeded]; - // 修正scrollView偏移 - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; -} - -- (void)setCloseTrackerFollowingMode:(BOOL)closeTrackerFollowingMode { - _closeTrackerFollowingMode = closeTrackerFollowingMode; - if (closeTrackerFollowingMode) { - self.trackerFollowingMode = SPPageMenuTrackerFollowingModeEnd; - } else { - self.trackerFollowingMode = SPPageMenuTrackerFollowingModeAlways; - } -} -#pragma mark - getter - -- (NSArray *)items { - if (!_items) { - _items = [NSMutableArray array]; - } - return _items; -} - -- (NSMutableArray *)buttons { - - if (!_buttons) { - _buttons = [NSMutableArray array]; - - } - return _buttons; -} - -- (NSMutableDictionary *)setupWidths { - - if (!_setupWidths) { - _setupWidths = [NSMutableDictionary dictionary]; - } - return _setupWidths; -} - -- (UIImageView *)tracker { - - if (!_tracker) { - _tracker = [[UIImageView alloc] init]; - _tracker.layer.cornerRadius = _trackerHeight * 0.5; - _tracker.layer.masksToBounds = YES; - } - return _tracker; -} - -- (NSUInteger)numberOfItems { - return self.items.count; -} - -#pragma mark - 布局 - -- (void)layoutSubviews { - [super layoutSubviews]; - - CGFloat backgroundViewX = self.bounds.origin.x+_contentInset.left; - CGFloat backgroundViewY = self.bounds.origin.y+_contentInset.top; - CGFloat backgroundViewW = self.bounds.size.width-(_contentInset.left+_contentInset.right); - CGFloat backgroundViewH = self.bounds.size.height-(_contentInset.top+_contentInset.bottom); - self.backgroundView.frame = CGRectMake(backgroundViewX, backgroundViewY, backgroundViewW, backgroundViewH); - self.backgroundImageView.frame = self.backgroundView.bounds; - - CGFloat dividingLineW = self.bounds.size.width; - CGFloat dividingLineH = (self.dividingLine.hidden || self.dividingLine.alpha < 0.01) ? 0 : _dividingLineHeight; - CGFloat dividingLineX = 0; - CGFloat dividingLineY = self.bounds.size.height-dividingLineH; - self.dividingLine.frame = CGRectMake(dividingLineX, dividingLineY, dividingLineW, dividingLineH); - - CGFloat functionButtonH = backgroundViewH-dividingLineH; - CGFloat functionButtonW = functionButtonH; - CGFloat functionButtonX = backgroundViewW-functionButtonW; - CGFloat functionButtonY = 0; - self.functionButton.frame = CGRectMake(functionButtonX, functionButtonY, functionButtonW, functionButtonH); - // 通过shadowPath设置功能按钮的单边阴影 - if (self.funtionButtonshadowOpacity > 0) { - self.functionButton.layer.shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 2.5, 2, functionButtonH-5)].CGPath; - } - - CGFloat itemScrollViewX = 0; - CGFloat itemScrollViewY = 0; - CGFloat itemScrollViewW = self.showFuntionButton ? backgroundViewW-functionButtonW : backgroundViewW; - CGFloat itemScrollViewH = backgroundViewH-dividingLineH; - self.itemScrollView.frame = CGRectMake(itemScrollViewX, itemScrollViewY, itemScrollViewW, itemScrollViewH); - - // 存储itemScrollViewH,目的是解决选中按钮缩放后高度变化了的问题,我们要让选中的按钮缩放之后,依然保持原始高度 - _itemScrollViewH = itemScrollViewH; - - __block CGFloat buttonW = 0.0; - __block CGFloat lastButtonMaxX = 0.0; - - CGFloat contentW = 0.0; // 内容宽 - CGFloat contentW_sum = 0.0; // 所有文字宽度之和 - NSMutableArray *buttonWidths = [NSMutableArray array]; - // 提前计算每个按钮的宽度,目的是为了计算间距 - for (int i= 0 ; i < self.buttons.count; i++) { - SPPageMenuItem *button = self.buttons[i]; - - CGFloat textW; - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%d",i]] floatValue]; - if (button == _selectedButton) { - textW = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_selectedItemTitleFont} context:nil].size.width; - } else { - textW = [button.titleLabel.text boundingRectWithSize:CGSizeMake(MAXFLOAT, itemScrollViewH) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_unSelectedItemTitleFont} context:nil].size.width; - } - // CGImageGetWidth获取的图片宽度是图片在@1x、@2x、@3x的位置上的实际宽度 - // button.currentImage.size.width获取的宽度永远是@1x位置上的宽度,比如一张图片在@3x上的位置为300,那么button.currentImage.size.width就为100 - CGFloat imageW = CGImageGetWidth(button.currentImage.CGImage); - CGFloat imageH = CGImageGetHeight(button.currentImage.CGImage); - CGFloat ratio = imageW / imageH; - if (ratio >= 1) { // 宽大于高 - if (imageH > itemScrollViewH) { // 按比例适应在button中 - imageH = itemScrollViewH; - imageW = imageH * ratio; - } - } - if (button.currentTitle && !button.currentImage) { - contentW = textW; - } else if(button.currentImage && !button.currentTitle) { - contentW = imageW; - } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionRight || button.imagePosition == SPItemImagePositionLeft)) { - contentW = textW + imageW; - } else if (button.currentTitle && button.currentImage && (button.imagePosition == SPItemImagePositionTop || button.imagePosition == SPItemImagePositionBottom)) { - contentW = MAX(textW, imageW); - } - if (setupButtonW) { - contentW_sum += setupButtonW; - [buttonWidths addObject:@(setupButtonW)]; - } else { - contentW_sum += contentW; - [buttonWidths addObject:@(contentW)]; - } - } - CGFloat diff = itemScrollViewW - contentW_sum; - - [self.buttons enumerateObjectsUsingBlock:^(SPPageMenuItem *button, NSUInteger idx, BOOL * _Nonnull stop) { - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] floatValue]; - if (self.permutationWay == SPPageMenuPermutationWayScrollAdaptContent) { - buttonW = [buttonWidths[idx] floatValue]; - if (idx == 0) { - button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } else { - button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); - - } - } else if (self.permutationWay == SPPageMenuPermutationWayNotScrollEqualWidths) { - // 求出外界设置的按钮宽度之和 - CGFloat totalSetupButtonW = [[self.setupWidths.allValues valueForKeyPath:@"@sum.floatValue"] floatValue]; - // 如果该按钮外界设置了宽,则取外界设置的,如果外界没设置,则其余按钮等宽 - buttonW = setupButtonW ? setupButtonW : (itemScrollViewW-self->_itemPadding*(self.buttons.count)-totalSetupButtonW)/(self.buttons.count-self.setupWidths.count); - if (buttonW < 0) { // 按钮过多时,有可能会为负数 - buttonW = 0; - } - if (idx == 0) { - button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } else { - button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } - - } else { - self->_itemPadding = diff/self.buttons.count; - buttonW = [buttonWidths[idx] floatValue]; - if (idx == 0) { - button.frame = CGRectMake(self->_itemPadding*0.5+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } else { - button.frame = CGRectMake(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); - } - } - lastButtonMaxX = CGRectGetMaxX(button.frame); - }]; - - // 如果selectedButton有缩放,走完上面代码selectedButton的frame会还原,这会导致文字显示不全问题,为了解决这个问题,这里将selectedButton的frame强制缩放 - if (!CGAffineTransformEqualToTransform(self.selectedButton.transform, CGAffineTransformIdentity)) { - CGRect selectedButtonRect = self.selectedButton.frame; - selectedButtonRect.origin.y = selectedButtonRect.origin.y-(selectedButtonRect.size.height*_selectedItemZoomScale - selectedButtonRect.size.height)/2; - selectedButtonRect.origin.x = selectedButtonRect.origin.x-((selectedButtonRect.size.width*_selectedItemZoomScale - selectedButtonRect.size.width)/2); - selectedButtonRect.size = CGSizeMake(selectedButtonRect.size.width * _selectedItemZoomScale, selectedButtonRect.size.height*_selectedItemZoomScale); - self.selectedButton.frame = selectedButtonRect; - } - - [self resetSetupTrackerFrameWithSelectedButton:self.selectedButton]; - - self.itemScrollView.contentSize = CGSizeMake(lastButtonMaxX+_itemPadding*0.5, 0); - - if (self.translatesAutoresizingMaskIntoConstraints == NO) { - - [self moveItemScrollViewWithSelectedButton:self.selectedButton]; - } -} - -- (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuItem *)selectedButton { - - CGFloat trackerX; - CGFloat trackerY; - CGFloat trackerW; - CGFloat trackerH; - - CGFloat selectedButtonWidth = selectedButton.frame.size.width; - - switch (self.trackerStyle) { - case SPPageMenuTrackerStyleLine: - { - trackerW = _trackerWidth ? _trackerWidth : selectedButtonWidth; - trackerH = _trackerHeight; - trackerX = selectedButton.frame.origin.x; - trackerY = self.itemScrollView.bounds.size.height - trackerH; - self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); - } - break; - case SPPageMenuTrackerStyleLineLongerThanItem: - { - trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+(selectedButtonWidth ? _itemPadding : 0)); - trackerH = _trackerHeight; - trackerX = selectedButton.frame.origin.x; - trackerY = self.itemScrollView.bounds.size.height - trackerH; - self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); - } - break; - case SPPageMenuTrackerStyleLineAttachment: - { - trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth ? selectedButton.titleLabel.font.pointSize : 0); // 没有自定义宽度就固定宽度为字体大小 - trackerH = _trackerHeight; - trackerX = selectedButton.frame.origin.x; - trackerY = self.itemScrollView.bounds.size.height - trackerH; - self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); - } - break; - case SPPageMenuTrackerStyleRect: - { - trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+(selectedButtonWidth ? _itemPadding : 0)); - trackerH = _trackerHeight ? _trackerHeight : (selectedButton.frame.size.height); - trackerX = selectedButton.frame.origin.x; - trackerY = (_itemScrollViewH-trackerH)*0.5; - self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); - self.tracker.layer.cornerRadius = 0; - - } - break; - case SPPageMenuTrackerStyleRoundedRect: - { - trackerH = _trackerHeight ? _trackerHeight : (_itemTitleFont.lineHeight+10); - trackerW = _trackerWidth ? _trackerWidth : (selectedButtonWidth+_itemPadding); - trackerX = selectedButton.frame.origin.x; - trackerY = (_itemScrollViewH-trackerH)*0.5; - self.tracker.frame = CGRectMake(trackerX, trackerY, trackerW, trackerH); - self.tracker.layer.cornerRadius = MIN(trackerW, trackerH)*0.5; - self.tracker.layer.masksToBounds = YES; - } - break; - default: - break; - } - - CGPoint trackerCenter = self.tracker.center; - trackerCenter.x = selectedButton.center.x; - self.tracker.center = trackerCenter; -} - -- (void)dealloc { - [self.bridgeScrollView removeObserver:self forKeyPath:scrollViewContentOffset]; -} - -@end - -#pragma clang diagnostic pop - - - - - - diff --git a/Example/SPPageMenu/ViewController.m b/Example/SPPageMenu/ViewController.m index b3b9577..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 392c830..4936857 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,16 +9,83 @@ ![codecov](https://img.shields.io/badge/codecov-88%25-orange.svg) # 目录 -* [如何安装](#如何安装) -* [部分功能演示图](#部分功能演示图) -* [重难点讲解](#重难点讲解) -* [使用者提问](#使用者提问) + +- [如何安装](#如何安装) +- [部分功能演示图](#部分功能演示图) +- [重难点讲解](#重难点讲解) +- [使用者提问](#使用者提问) ## 如何安装 -#### 版本3.4.0 + +#### 版本 3.5.0 + ``` target 'MyApp' do -  pod 'SPPageMenu', '~> 3.4.0' + 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 + +``` +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' +end + +3.4.1版本在3.4.0版本的基础上修复了多次调用setItems:selectedItemIndex:方法引发的问题 +``` + +#### 版本 3.4.0 + +``` +target 'MyApp' do + pod 'SPPageMenu', '~> 3.4.0' end 说明:3.4.0版本在3.0版本的基础上主要改动如下: @@ -28,10 +96,11 @@ end 5、优化代码 ``` -#### 版本3.0 +#### 版本 3.0 + ``` target 'MyApp' do -  pod 'SPPageMenu', '~> 3.0' + pod 'SPPageMenu', '~> 3.0' end 说明:3.0版本在2.5.5版本的基础上主要改动如下: @@ -48,10 +117,11 @@ end 11、修复长按按钮然后滑动scrollView无法滑动问题 ``` -#### 版本2.5.5 +#### 版本 2.5.5 + ``` target 'MyApp' do -  pod 'SPPageMenu', '~> 2.5.5' + pod 'SPPageMenu', '~> 2.5.5' end 说明:2.5.5版本在2.5.3版本的基础上主要改动如下: @@ -62,58 +132,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的按钮,这种方式无论点击的按钮是否同一个都会触发; @@ -124,18 +205,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 560d765..70995fa 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.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 c835bb8..4c38369 100644 --- a/SPPageMenu/SPPageMenu.h +++ b/SPPageMenu/SPPageMenu.h @@ -21,26 +21,26 @@ 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) { - SPItemImagePositionDefault, // 默认图片在左边 - SPItemImagePositionLeft, // 图片在左边 - SPItemImagePositionTop, // 图片在上面 - SPItemImagePositionRight, // 图片在右边 - SPItemImagePositionBottom // 图片在下面 + SPItemImagePositionDefault, // 默认图片在左侧 + SPItemImagePositionLeft, // 图片在文字左侧 + SPItemImagePositionRight, // 图片在文字右侧 + SPItemImagePositionTop, // 图片在文字上侧 + SPItemImagePositionBottom // 图片在文字下侧 }; -@class SPPageMenu; +@class SPPageMenu,SPPageMenuButton,SPPageMenuButtonItem; @protocol SPPageMenuDelegate @@ -62,17 +62,19 @@ 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; @property (nonatomic) NSInteger selectedItemIndex; // 选中的item下标,改变其值可以用于切换选中的item - @property(nonatomic,readonly) NSUInteger numberOfItems; // items的总个数 -// item之间的间距,默认30;当排列方式permutationWay为‘SPPageMenuPermutationWayNotScrollAdaptContent’时此属性无效,无效是合理的,不可能做到“不可滑动且自适应内容”然后间距又自定义,这2者相互制约; -@property (nonatomic, assign) CGFloat itemPadding; +@property (nonatomic, readonly) SPPageMenuTrackerStyle trackerStyle; + +@property (nonatomic, assign) SPPageMenuPermutationWay permutationWay; // 排列方式 + +@property (nonatomic, assign) CGFloat spacing; // item之间的间距 @property (nonatomic, strong) UIColor *selectedItemTitleColor; // 选中的item标题颜色 @property (nonatomic, strong) UIColor *unSelectedItemTitleColor; // 未选中的item标题颜色 @@ -81,91 +83,81 @@ 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 +// 修改跟踪器样式 +- (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:(nullable NSString *)title atIndex:(NSUInteger)itemIndex animated:(BOOL)animated; -- (void)insertItemWithImage:(nullable UIImage *)image 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 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:进行拉伸 +// 设置背景图片,barMetrics只有为UIBarMetricsDefault时才生效,如果外界传进来的backgroundImage调用过-resizableImageWithCapInsets:且参数capInsets不为UIEdgeInsetsZero,则直接用backgroundImage作为背景图; 否则内部会自动调用-resizableImageWithCapInsets:进行拉伸 - (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; - +- (CGRect)titleRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 文字相对pageMenu位置和大小 +- (CGRect)imageRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 图片相对pageMenu位置和大小 +- (CGRect)buttonRectRelativeToPageMenuForItemAtIndex:(NSUInteger)itemIndex; // 按钮相对pageMenu位置和大小 -@property (nonatomic, assign) BOOL showFuntionButton; // 是否显示功能按钮(功能按钮显示在最右侧),默认为NO -@property (nonatomic, assign) CGFloat funtionButtonshadowOpacity; // 功能按钮左侧的阴影透明度,如果设置小于等于0,则没有阴影 - -/** - * 同时为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; +- (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; @@ -178,21 +170,46 @@ typedef NS_ENUM(NSInteger, SPItemImagePosition) { } 3.如果外界设置了SPPageMenu的属性"bridgeScrollView",那么外界就可以不用在scrollViewDidScroll方法中调用这个方法来实现跟踪器时刻跟随外界scrollView的效果,内部会自动处理; 外界对SPPageMenu的属性"bridgeScrollView"赋值是实现此效果的最简便的操作 - 4.如果不想要此效果,可设置closeTrackerFollowingMode==YES */ - (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView; +// -------------- 以下方法和属性被废弃,不再建议使用 -------------- -// -------------- 以下方法和属性被废弃 -------------- - -// 设置指定item的四周内边距,3.0版本的时候不小心多写了一个for,3.1版本已纠正 +@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.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:"); -- (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:"); +- (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/SPPageMenu/SPPageMenu.m b/SPPageMenu/SPPageMenu.m index 46b3655..23ab2d7 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() + +- (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; +} + +// ----------------------------------------------------- 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; } -- (void)setImageRatio:(CGFloat)imageRatio { - _imageRatio = imageRatio; - [self setNeedsDisplay]; +// ----------------------------------------------------- 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,10 +408,10 @@ @interface SPPageMenu() @property (nonatomic, weak) UIImageView *backgroundImageView; @property (nonatomic, strong) UIImageView *dividingLine; @property (nonatomic, weak) SPPageMenuScrollView *itemScrollView; -@property (nonatomic, weak) SPPageMenuItem *functionButton; @property (nonatomic, strong) NSMutableArray *buttons; -@property (nonatomic, strong) SPPageMenuItem *selectedButton; -@property (nonatomic, strong) NSMutableDictionary *setupWidths; +@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; @@ -223,13 +420,16 @@ @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; +@property (nonatomic, assign) BOOL forceUseSettingSpacing; @end #pragma clang diagnostic push @@ -261,28 +461,35 @@ - (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 (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]], @"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没有关联 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]; } } } @@ -311,10 +518,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; } @@ -326,7 +545,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; } @@ -346,7 +565,6 @@ - (void)removeItemAtIndex:(NSUInteger)itemIndex animated:(BOOL)animated { } else { [self setNeedsLayout]; } - } - (void)removeAllItems { @@ -354,25 +572,26 @@ - (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 (title == nil) return; 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]; @@ -383,17 +602,19 @@ - (void)setTitle:(NSString *)title forItemAtIndex:(NSUInteger)itemIndex { [self setNeedsLayout]; } -- (nullable NSString *)titleForItemAtIndex:(NSUInteger)itemIndex { - if (itemIndex < self.buttons.count) { - SPPageMenuItem *item = [self.buttons objectAtIndex:itemIndex]; - return item.currentTitle; +- (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) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + SPPageMenuButton *button = [self.buttons objectAtIndex:itemIndex]; [button setTitle:nil forState:UIControlStateNormal]; [button setImage:image forState:UIControlStateNormal]; @@ -404,25 +625,100 @@ - (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类型,请仔细核对"); + return object; + } + return nil; +} + +- (void)setItem:(SPPageMenuButtonItem *)item forItemIndex:(NSUInteger)itemIndex { + if (item == nil) return; 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]; + } +} + +- (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) { - 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; @@ -430,40 +726,61 @@ - (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) { - SPPageMenuItem *button = [self.buttons objectAtIndex:itemIndex]; + 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) { - 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; + [self setNeedsLayout]; + [self layoutIfNeeded]; } } - (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; @@ -485,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; @@ -492,14 +840,52 @@ - (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) { - 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]]) { @@ -515,28 +901,19 @@ - (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 imageTitleSpace:(CGFloat)imageTitleSpace forState:(UIControlState)state { - [self.functionButton setTitle:title forState:state]; - [self.functionButton setImage:image forState:state]; - self.functionButton.imagePosition = imagePosition; - self.functionButton.imageRatio = ratio; - self.functionButton.imageTitleSpace = imageTitleSpace; -} - -// 以下2个方法在3.0版本上有升级,可以使用但不推荐 -- (void)setTitle:(nullable NSString *)title image:(nullable UIImage *)image imagePosition:(SPItemImagePosition)imagePosition imageRatio:(CGFloat)ratio forItemIndex:(NSUInteger)itemIndex { +- (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]]) { @@ -552,58 +929,48 @@ - (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; - self.functionButton.imageRatio = ratio; } -- (void)setFunctionButtonTitleTextAttributes:(nullable NSDictionary *)attributes forState:(UIControlState)state { - if (attributes[NSFontAttributeName]) { - self.functionButton.titleLabel.font = attributes[NSFontAttributeName]; - } - if (attributes[NSForegroundColorAttributeName]) { - [self.functionButton setTitleColor:attributes[NSForegroundColorAttributeName] forState:state]; - } - if (attributes[NSBackgroundColorAttributeName]) { - self.functionButton.backgroundColor = attributes[NSBackgroundColorAttributeName]; - } -} - -- (void)moveTrackerFollowScrollView:(UIScrollView *)scrollView { - - // 说明外界传进来了一个scrollView,如果外界传进来了,pageMenu会观察该scrollView的contentOffset自动处理跟踪器的跟踪 - if (self.bridgeScrollView == scrollView) { return; } - - [self prepareMoveTrackerFollowScrollView:scrollView]; +- (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 (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]) { @@ -623,12 +990,10 @@ - (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]; } @@ -665,27 +1030,26 @@ - (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; - _showFuntionButton = NO; - _funtionButtonshadowOpacity = 0.5; + _showFunctionButton = NO; + _functionButtonShadowOpacity = 0.5; + _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; @@ -694,16 +1058,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; @@ -716,25 +1080,25 @@ - (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]; [functionButton addTarget:self action:@selector(functionButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; - functionButton.layer.shadowColor = [UIColor blackColor].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; } // 按钮点击方法 -- (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.会造成死循环 + // 更新下item对应的下标,必须在代理之前,否则外界在代理方法中拿到的不是最新的 _selectedItemIndex = toIndex; // 如果sender是新的选中的按钮,则上一次的按钮颜色为非选中颜色,当前选中的颜色为选中颜色 if (self.selectedButton != sender) { @@ -742,14 +1106,13 @@ - (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 (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; } @@ -767,32 +1130,12 @@ - (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; } @@ -801,7 +1144,7 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { // 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) { @@ -811,15 +1154,13 @@ - (void)moveItemScrollViewWithSelectedButton:(SPPageMenuItem *)selectedButton { else if (offSetX > maxOffsetX){ offSetX = maxOffsetX; } - [self.itemScrollView setContentOffset:CGPointMake(offSetX, 0) animated:YES]; - } // 移动跟踪器 -- (void)moveTrackerWithSelectedButton:(SPPageMenuItem *)selectedButton { +- (void)moveTrackerWithSelectedButton:(SPPageMenuButton *)selectedButton { [UIView animateWithDuration:0.25 animations:^{ - [self resetSetupTrackerFrameWithSelectedButton:selectedButton]; + [self resetupTrackerFrameWithSelectedButton:selectedButton]; }]; } @@ -833,7 +1174,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]; } @@ -843,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; @@ -879,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进行赋值,直接在这里赋值可以让外界不用做此操作 @@ -895,50 +1230,53 @@ - (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) { - SPPageMenuItem *fromButton; - SPPageMenuItem *toButton; - if (progress > 0.5) { - if (toIndex >= 0 && toIndex < self.buttons.count) { - toButton = self.buttons[toIndex]; - fromButton = self.buttons[fromIndex]; - - if (_selectedItemIndex != toIndex) { - self.selectedItemIndex = toIndex; + 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; } - } // 这个方法才开始真正滑动跟踪器,上面都是做铺垫 - (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; // 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 +1315,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,12 +1350,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]; + 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]; @@ -1025,35 +1364,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 { @@ -1075,16 +1410,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"); } } @@ -1149,33 +1484,39 @@ - (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 { _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 (SPPageMenuItem *button in self.buttons) { + for (SPPageMenuButton *button in self.buttons) { button.titleLabel.font = itemTitleFont; } [self setNeedsLayout]; @@ -1186,7 +1527,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; } @@ -1217,7 +1558,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 } @@ -1228,7 +1569,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]; } } @@ -1237,7 +1578,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]; } @@ -1253,6 +1594,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偏移 @@ -1277,24 +1625,31 @@ - (NSArray *)items { } - (NSMutableArray *)buttons { - + if (!_buttons) { _buttons = [NSMutableArray array]; - + } return _buttons; } -- (NSMutableDictionary *)setupWidths { +- (NSMutableDictionary *)customWidths { - if (!_setupWidths) { - _setupWidths = [NSMutableDictionary dictionary]; + if (!_customWidths) { + _customWidths = [NSMutableDictionary dictionary]; + } + return _customWidths; +} + +- (NSMutableDictionary *)customSpacings { + if (!_customSpacings) { + _customSpacings = [[NSMutableDictionary alloc] init]; } - return _setupWidths; + return _customSpacings; } - (UIImageView *)tracker { - + if (!_tracker) { _tracker = [[UIImageView alloc] init]; _tracker.layer.cornerRadius = _trackerHeight * 0.5; @@ -1311,14 +1666,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,102 +1686,128 @@ - (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); - + // 存储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]; + CGFloat customWidth = [[self.customWidths valueForKey:[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; + if (button.currentTitle.length && !button.currentImage) { + 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.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); + 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:^(SPPageMenuItem *button, NSUInteger idx, BOOL * _Nonnull stop) { - CGFloat setupButtonW = [[self.setupWidths objectForKey:[NSString stringWithFormat:@"%lu",(unsigned long)idx]] 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(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 { - self->_itemPadding = diff/self.buttons.count; 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(self->_itemPadding+lastButtonMaxX, 0, buttonW, itemScrollViewH); + 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; @@ -1436,25 +1817,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:(SPPageMenuItem *)selectedButton { - +- (void)resetupTrackerFrameWithSelectedButton:(SPPageMenuButton *)selectedButton { CGFloat trackerX; CGFloat trackerY; CGFloat trackerW; CGFloat trackerH; - CGFloat selectedButtonWidth = selectedButton.frame.size.width; - switch (self.trackerStyle) { case SPPageMenuTrackerStyleLine: { @@ -1508,7 +1886,7 @@ - (void)resetSetupTrackerFrameWithSelectedButton:(SPPageMenuItem *)selectedButto default: break; } - + CGPoint trackerCenter = self.tracker.center; trackerCenter.x = selectedButton.center.x; self.tracker.center = trackerCenter; @@ -1520,6 +1898,29 @@ - (void)dealloc { @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