diff --git a/Example/SMPageControl-Bridging-Header.h b/Example/SMPageControl-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/Example/SMPageControl-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/Example/SMPageControl.xcodeproj/project.pbxproj b/Example/SMPageControl.xcodeproj/project.pbxproj index f690bd9..46c07fa 100644 --- a/Example/SMPageControl.xcodeproj/project.pbxproj +++ b/Example/SMPageControl.xcodeproj/project.pbxproj @@ -18,7 +18,6 @@ 3D8496D2162A4849001ECE62 /* currentSearchDot@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D8496D0162A4849001ECE62 /* currentSearchDot@2x.png */; }; 3D8496D3162A4849001ECE62 /* searchDot@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D8496D1162A4849001ECE62 /* searchDot@2x.png */; }; 3D8496D7162A4911001ECE62 /* retro_intro@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D8496D6162A4911001ECE62 /* retro_intro@2x.png */; }; - 3D8496DE162A73A5001ECE62 /* SMPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D8496DD162A73A5001ECE62 /* SMPageControl.m */; }; 3D9CDA921629EA9100B95512 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D9CDA911629EA9100B95512 /* UIKit.framework */; }; 3D9CDA941629EA9100B95512 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D9CDA931629EA9100B95512 /* Foundation.framework */; }; 3D9CDA961629EA9100B95512 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D9CDA951629EA9100B95512 /* CoreGraphics.framework */; }; @@ -31,6 +30,7 @@ 3D9CDAAB1629EA9100B95512 /* SMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D9CDAAA1629EA9100B95512 /* SMViewController.m */; }; 3D9CDAAE1629EA9100B95512 /* SMViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3D9CDAAC1629EA9100B95512 /* SMViewController_iPhone.xib */; }; 3D9CDAB11629EA9100B95512 /* SMViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3D9CDAAF1629EA9100B95512 /* SMViewController_iPad.xib */; }; + 4E84A19222D20A8F00008376 /* SMPageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E84A19122D20A8F00008376 /* SMPageControl.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -45,8 +45,6 @@ 3D8496D0162A4849001ECE62 /* currentSearchDot@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "currentSearchDot@2x.png"; sourceTree = ""; }; 3D8496D1162A4849001ECE62 /* searchDot@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "searchDot@2x.png"; sourceTree = ""; }; 3D8496D6162A4911001ECE62 /* retro_intro@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "retro_intro@2x.png"; sourceTree = ""; }; - 3D8496DC162A73A5001ECE62 /* SMPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SMPageControl.h; path = ../../SMPageControl.h; sourceTree = ""; }; - 3D8496DD162A73A5001ECE62 /* SMPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SMPageControl.m; path = ../../SMPageControl.m; sourceTree = ""; }; 3D9CDA8D1629EA9100B95512 /* SMPageControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SMPageControl.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3D9CDA911629EA9100B95512 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 3D9CDA931629EA9100B95512 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -64,6 +62,8 @@ 3D9CDAAA1629EA9100B95512 /* SMViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SMViewController.m; sourceTree = ""; }; 3D9CDAAD1629EA9100B95512 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/SMViewController_iPhone.xib; sourceTree = ""; }; 3D9CDAB01629EA9100B95512 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/SMViewController_iPad.xib; sourceTree = ""; }; + 4E84A19022D20A8F00008376 /* SMPageControl-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SMPageControl-Bridging-Header.h"; sourceTree = ""; }; + 4E84A19122D20A8F00008376 /* SMPageControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SMPageControl.swift; path = ../SMPageControl.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -84,8 +84,10 @@ isa = PBXGroup; children = ( 3D9CDA971629EA9100B95512 /* SMPageControl */, + 4E84A19122D20A8F00008376 /* SMPageControl.swift */, 3D9CDA901629EA9100B95512 /* Frameworks */, 3D9CDA8E1629EA9100B95512 /* Products */, + 4E84A19022D20A8F00008376 /* SMPageControl-Bridging-Header.h */, ); sourceTree = ""; }; @@ -117,8 +119,6 @@ 3D9CDAAC1629EA9100B95512 /* SMViewController_iPhone.xib */, 3D9CDAAF1629EA9100B95512 /* SMViewController_iPad.xib */, 3D9CDA981629EA9100B95512 /* Supporting Files */, - 3D8496DC162A73A5001ECE62 /* SMPageControl.h */, - 3D8496DD162A73A5001ECE62 /* SMPageControl.m */, ); path = SMPageControl; sourceTree = ""; @@ -177,12 +177,18 @@ CLASSPREFIX = SM; LastUpgradeCheck = 0450; ORGANIZATIONNAME = "Spaceman Labs"; + TargetAttributes = { + 3D9CDA8C1629EA9100B95512 = { + LastSwiftMigration = 1020; + }; + }; }; buildConfigurationList = 3D9CDA871629EA9100B95512 /* Build configuration list for PBXProject "SMPageControl" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 3D9CDA821629EA9100B95512; @@ -227,10 +233,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4E84A19222D20A8F00008376 /* SMPageControl.swift in Sources */, 3D9CDA9E1629EA9100B95512 /* main.m in Sources */, 3D9CDAA21629EA9100B95512 /* SMAppDelegate.m in Sources */, 3D9CDAAB1629EA9100B95512 /* SMViewController.m in Sources */, - 3D8496DE162A73A5001ECE62 /* SMPageControl.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -319,10 +325,16 @@ 3D9CDAB51629EA9100B95512 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SMPageControl/SMPageControl-Prefix.pch"; INFOPLIST_FILE = "SMPageControl/SMPageControl-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "SMPageControl-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = app; }; name = Debug; @@ -330,10 +342,15 @@ 3D9CDAB61629EA9100B95512 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SMPageControl/SMPageControl-Prefix.pch"; INFOPLIST_FILE = "SMPageControl/SMPageControl-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "SMPageControl-Bridging-Header.h"; + SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = app; }; name = Release; diff --git a/Example/SMPageControl/SMViewController.h b/Example/SMPageControl/SMViewController.h index a6a2a9d..103137d 100644 --- a/Example/SMPageControl/SMViewController.h +++ b/Example/SMPageControl/SMViewController.h @@ -7,18 +7,9 @@ // #import -#import "SMPageControl.h" @interface SMViewController : UIViewController @property (nonatomic, weak) IBOutlet UIPageControl *pageControl; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl1; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl2; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl3; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl4; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl5; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl6; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl7; -@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl8; @end diff --git a/Example/SMPageControl/SMViewController.m b/Example/SMPageControl/SMViewController.m index d61dcbf..c250ff2 100644 --- a/Example/SMPageControl/SMViewController.m +++ b/Example/SMPageControl/SMViewController.m @@ -7,9 +7,19 @@ // #import "SMViewController.h" +#import "SMPageControl-Swift.h" @interface SMViewController () +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl1; +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl2; +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl3; +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl4; +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl5; +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl6; +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl7; +@property (nonatomic, weak) IBOutlet SMPageControl *spacePageControl8; + @end @implementation SMViewController @@ -34,8 +44,8 @@ - (void)viewDidLoad self.spacePageControl2.indicatorMargin = 20.0f; self.spacePageControl2.indicatorDiameter = 10.0f; - self.spacePageControl3.alignment = SMPageControlAlignmentLeft; - self.spacePageControl4.alignment = SMPageControlAlignmentRight; + self.spacePageControl3.alignment = SMPageControlAlignmentAlignmentLeft; + self.spacePageControl4.alignment = SMPageControlAlignmentAlignmentRight; [self.spacePageControl5 setPageIndicatorImage:[UIImage imageNamed:@"pageDot"]]; [self.spacePageControl5 setCurrentPageIndicatorImage:[UIImage imageNamed:@"currentPageDot"]]; @@ -73,12 +83,12 @@ - (void)viewDidLoad - (void)pageControl:(id)sender { - NSLog(@"Current Page (UIPageControl) : %i", self.pageControl.currentPage); + NSLog(@"Current Page (UIPageControl) : %li", (long)self.pageControl.currentPage); } - (void)spacePageControl:(SMPageControl *)sender { - NSLog(@"Current Page (SMPageControl): %i", sender.currentPage); + NSLog(@"Current Page (SMPageControl): %li", (long)sender.currentPage); } @end diff --git a/Example/SMPageControl/en.lproj/SMViewController_iPad.xib b/Example/SMPageControl/en.lproj/SMViewController_iPad.xib index 246a19e..f3041d3 100644 --- a/Example/SMPageControl/en.lproj/SMViewController_iPad.xib +++ b/Example/SMPageControl/en.lproj/SMViewController_iPad.xib @@ -1,8 +1,12 @@ - - + + + + + - - + + + @@ -26,129 +30,128 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + - \ No newline at end of file + diff --git a/Example/SMPageControl/en.lproj/SMViewController_iPhone.xib b/Example/SMPageControl/en.lproj/SMViewController_iPhone.xib index e87b5ee..52d5ac3 100644 --- a/Example/SMPageControl/en.lproj/SMViewController_iPhone.xib +++ b/Example/SMPageControl/en.lproj/SMViewController_iPhone.xib @@ -1,8 +1,12 @@ - - + + + + + - - + + + @@ -21,149 +25,147 @@ - + - + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - + - + - + - + - - - + - \ No newline at end of file + diff --git a/SMPageControl.h b/SMPageControl.h deleted file mode 100755 index 530c404..0000000 --- a/SMPageControl.h +++ /dev/null @@ -1,76 +0,0 @@ -// -// SMPageControl.h -// SMPageControl -// -// Created by Jerry Jones on 10/13/12. -// Copyright (c) 2012 Spaceman Labs. All rights reserved. -// - -#import - -typedef NS_ENUM(NSUInteger, SMPageControlAlignment) { - SMPageControlAlignmentLeft = 1, - SMPageControlAlignmentCenter, - SMPageControlAlignmentRight -}; - -typedef NS_ENUM(NSUInteger, SMPageControlVerticalAlignment) { - SMPageControlVerticalAlignmentTop = 1, - SMPageControlVerticalAlignmentMiddle, - SMPageControlVerticalAlignmentBottom -}; - -typedef NS_ENUM(NSUInteger, SMPageControlTapBehavior) { - SMPageControlTapBehaviorStep = 1, - SMPageControlTapBehaviorJump -}; - -@interface SMPageControl : UIControl - -@property (nonatomic) NSInteger numberOfPages; -@property (nonatomic) NSInteger currentPage; -@property (nonatomic) CGFloat indicatorMargin UI_APPEARANCE_SELECTOR; // deafult is 10 -@property (nonatomic) CGFloat indicatorDiameter UI_APPEARANCE_SELECTOR; // deafult is 6 -@property (nonatomic) CGFloat minHeight UI_APPEARANCE_SELECTOR; // default is 36, cannot be less than indicatorDiameter -@property (nonatomic) SMPageControlAlignment alignment UI_APPEARANCE_SELECTOR; // deafult is Center -@property (nonatomic) SMPageControlVerticalAlignment verticalAlignment UI_APPEARANCE_SELECTOR; // deafult is Middle - -@property (nonatomic, strong) UIImage *pageIndicatorImage UI_APPEARANCE_SELECTOR; -@property (nonatomic, strong) UIImage *pageIndicatorMaskImage UI_APPEARANCE_SELECTOR; // ignored if pageIndicatorImage is set -@property (nonatomic, strong) UIColor *pageIndicatorTintColor UI_APPEARANCE_SELECTOR; // ignored if pageIndicatorImage is set -@property (nonatomic, strong) UIImage *currentPageIndicatorImage UI_APPEARANCE_SELECTOR; -@property (nonatomic, strong) UIColor *currentPageIndicatorTintColor UI_APPEARANCE_SELECTOR; // ignored if currentPageIndicatorImage is set - -@property (nonatomic) BOOL hidesForSinglePage; // hide the the indicator if there is only one page. default is NO -@property (nonatomic) BOOL defersCurrentPageDisplay; // if set, clicking to a new page won't update the currently displayed page until -updateCurrentPageDisplay is called. default is NO - -@property (nonatomic) SMPageControlTapBehavior tapBehavior; // SMPageControlTapBehaviorStep provides an increment/decrement behavior exactly like UIPageControl. SMPageControlTapBehaviorJump allows specific pages to be selected by tapping their respective indicator. Default is SMPageControlTapBehaviorStep - -- (void)updateCurrentPageDisplay; // update page display to match the currentPage. ignored if defersCurrentPageDisplay is NO. setting the page value directly will update immediately - -- (CGRect)rectForPageIndicator:(NSInteger)pageIndex; -- (CGSize)sizeForNumberOfPages:(NSInteger)pageCount; - -- (void)setImage:(UIImage *)image forPage:(NSInteger)pageIndex; -- (void)setCurrentImage:(UIImage *)image forPage:(NSInteger)pageIndex; -- (void)setImageMask:(UIImage *)image forPage:(NSInteger)pageIndex; - -- (UIImage *)imageForPage:(NSInteger)pageIndex; -- (UIImage *)currentImageForPage:(NSInteger)pageIndex; -- (UIImage *)imageMaskForPage:(NSInteger)pageIndex; - -- (void)updatePageNumberForScrollView:(UIScrollView *)scrollView; -- (void)setScrollViewContentOffsetForCurrentPage:(UIScrollView *)scrollView animated:(BOOL)animated; - -#pragma mark - UIAccessibility - -// SMPageControl mirrors UIPageControl's standard accessibility functionality by default. -// Basically, the accessibility label is set to "[current page index + 1] of [page count]". - -// SMPageControl extends UIPageControl's functionality by allowing you to name specific pages. This is especially useful when using -// the per-page indicator images, and allows you to provide more context to the user. - -- (void)setName:(NSString *)name forPage:(NSInteger)pageIndex; -- (NSString *)nameForPage:(NSInteger)pageIndex; - -@end diff --git a/SMPageControl.m b/SMPageControl.m deleted file mode 100755 index b0eb1fa..0000000 --- a/SMPageControl.m +++ /dev/null @@ -1,727 +0,0 @@ -// -// SMPageControl.m -// SMPageControl -// -// Created by Jerry Jones on 10/13/12. -// Copyright (c) 2012 Spaceman Labs. All rights reserved. -// - -#import "SMPageControl.h" - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - - -#define DEFAULT_INDICATOR_WIDTH 6.0f -#define DEFAULT_INDICATOR_MARGIN 10.0f -#define DEFAULT_MIN_HEIGHT 36.0f - -#define DEFAULT_INDICATOR_WIDTH_LARGE 7.0f -#define DEFAULT_INDICATOR_MARGIN_LARGE 9.0f -#define DEFAULT_MIN_HEIGHT_LARGE 36.0f - -typedef NS_ENUM(NSUInteger, SMPageControlImageType) { - SMPageControlImageTypeNormal = 1, - SMPageControlImageTypeCurrent, - SMPageControlImageTypeMask -}; - -typedef NS_ENUM(NSUInteger, SMPageControlStyleDefaults) { - SMPageControlDefaultStyleClassic = 0, - SMPageControlDefaultStyleModern -}; - -static SMPageControlStyleDefaults _defaultStyleForSystemVersion; - -@interface SMPageControl () -@property (strong, readonly, nonatomic) NSMutableDictionary *pageNames; -@property (strong, readonly, nonatomic) NSMutableDictionary *pageImages; -@property (strong, readonly, nonatomic) NSMutableDictionary *currentPageImages; -@property (strong, readonly, nonatomic) NSMutableDictionary *pageImageMasks; -@property (strong, readonly, nonatomic) NSMutableDictionary *cgImageMasks; -@property (strong, readwrite, nonatomic) NSArray *pageRects; - -// Page Control used for stealing page number localizations for accessibility labels -// I'm not sure I love this technique, but it's the best way to get exact translations for all the languages -// that Apple supports out of the box -@property (nonatomic, strong) UIPageControl *accessibilityPageControl; -@end - -@implementation SMPageControl -{ -@private - NSInteger _displayedPage; - CGFloat _measuredIndicatorWidth; - CGFloat _measuredIndicatorHeight; - CGImageRef _pageImageMask; -} - -@synthesize pageNames = _pageNames; -@synthesize pageImages = _pageImages; -@synthesize currentPageImages = _currentPageImages; -@synthesize pageImageMasks = _pageImageMasks; -@synthesize cgImageMasks = _cgImageMasks; - -+ (void)initialize -{ - NSString *reqSysVer = @"7.0"; - NSString *currSysVer = [[UIDevice currentDevice] systemVersion]; - if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending) { - _defaultStyleForSystemVersion = SMPageControlDefaultStyleModern; - } else { - _defaultStyleForSystemVersion = SMPageControlDefaultStyleClassic; - } -} - -- (void)_initialize -{ - _numberOfPages = 0; - _tapBehavior = SMPageControlTapBehaviorStep; - - self.backgroundColor = [UIColor clearColor]; - - // If the app wasn't linked against iOS 7 or newer, always use the classic style - // otherwise, use the style of the current OS. -#ifdef __IPHONE_7_0 - [self setStyleWithDefaults:_defaultStyleForSystemVersion]; -#else - [self setStyleWithDefaults:SMPageControlDefaultStyleClassic]; -#endif - - _alignment = SMPageControlAlignmentCenter; - _verticalAlignment = SMPageControlVerticalAlignmentMiddle; - - self.isAccessibilityElement = YES; - self.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently; - self.accessibilityPageControl = [[UIPageControl alloc] init]; - self.contentMode = UIViewContentModeRedraw; -} - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (nil == self) { - return nil; - } - - [self _initialize]; - return self; -} - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (nil == self) { - return nil; - } - - [self _initialize]; - return self; -} - -- (void)dealloc -{ - if (_pageImageMask) { - CGImageRelease(_pageImageMask); - } -} - -- (void)drawRect:(CGRect)rect -{ - CGContextRef context = UIGraphicsGetCurrentContext(); - [self _renderPages:context rect:rect]; -} - -- (void)_renderPages:(CGContextRef)context rect:(CGRect)rect -{ - NSMutableArray *pageRects = [NSMutableArray arrayWithCapacity:self.numberOfPages]; - - if (_numberOfPages < 2 && _hidesForSinglePage) { - return; - } - - CGFloat left = [self _leftOffset]; - - CGFloat xOffset = left; - CGFloat yOffset = 0.0f; - UIColor *fillColor = nil; - UIImage *image = nil; - CGImageRef maskingImage = nil; - CGSize maskSize = CGSizeZero; - - for (NSInteger i = 0; i < _numberOfPages; i++) { - NSNumber *indexNumber = @(i); - - if (i == _displayedPage) { - fillColor = _currentPageIndicatorTintColor ? _currentPageIndicatorTintColor : [UIColor whiteColor]; - image = _currentPageImages[indexNumber]; - if (nil == image) { - image = _currentPageIndicatorImage; - } - } else { - fillColor = _pageIndicatorTintColor ? _pageIndicatorTintColor : [[UIColor whiteColor] colorWithAlphaComponent:0.3f]; - image = _pageImages[indexNumber]; - if (nil == image) { - image = _pageIndicatorImage; - } - } - - // If no finished images have been set, try a masking image - if (nil == image) { - maskingImage = (__bridge CGImageRef)_cgImageMasks[indexNumber]; - UIImage *originalImage = _pageImageMasks[indexNumber]; - maskSize = originalImage.size; - - // If no per page mask is set, try for a global page mask! - if (nil == maskingImage) { - maskingImage = _pageImageMask; - maskSize = _pageIndicatorMaskImage.size; - } - } - - [fillColor set]; - CGRect indicatorRect; - if (image) { - yOffset = [self _topOffsetForHeight:image.size.height rect:rect]; - CGFloat centeredXOffset = xOffset + floorf((_measuredIndicatorWidth - image.size.width) / 2.0f); - [image drawAtPoint:CGPointMake(centeredXOffset, yOffset)]; - indicatorRect = CGRectMake(centeredXOffset, yOffset, image.size.width, image.size.height); - } else if (maskingImage) { - yOffset = [self _topOffsetForHeight:maskSize.height rect:rect]; - CGFloat centeredXOffset = xOffset + floorf((_measuredIndicatorWidth - maskSize.width) / 2.0f); - indicatorRect = CGRectMake(centeredXOffset, yOffset, maskSize.width, maskSize.height); - CGContextDrawImage(context, indicatorRect, maskingImage); - } else { - yOffset = [self _topOffsetForHeight:_indicatorDiameter rect:rect]; - CGFloat centeredXOffset = xOffset + floorf((_measuredIndicatorWidth - _indicatorDiameter) / 2.0f); - indicatorRect = CGRectMake(centeredXOffset, yOffset, _indicatorDiameter, _indicatorDiameter); - CGContextFillEllipseInRect(context, indicatorRect); - } - - [pageRects addObject:[NSValue valueWithCGRect:indicatorRect]]; - maskingImage = NULL; - xOffset += _measuredIndicatorWidth + _indicatorMargin; - } - - self.pageRects = pageRects; - -} - -- (CGFloat)_leftOffset -{ - CGRect rect = self.bounds; - CGSize size = [self sizeForNumberOfPages:self.numberOfPages]; - CGFloat left = 0.0f; - switch (_alignment) { - case SMPageControlAlignmentCenter: - left = ceilf(CGRectGetMidX(rect) - (size.width / 2.0f)); - break; - case SMPageControlAlignmentRight: - left = CGRectGetMaxX(rect) - size.width; - break; - default: - break; - } - - return left; -} - -- (CGFloat)_topOffsetForHeight:(CGFloat)height rect:(CGRect)rect -{ - CGFloat top = 0.0f; - switch (_verticalAlignment) { - case SMPageControlVerticalAlignmentMiddle: - top = CGRectGetMidY(rect) - (height / 2.0f); - break; - case SMPageControlVerticalAlignmentBottom: - top = CGRectGetMaxY(rect) - height; - break; - default: - break; - } - - return top; -} - -- (void)updateCurrentPageDisplay -{ - _displayedPage = _currentPage; - [self setNeedsDisplay]; -} - -- (CGSize)sizeForNumberOfPages:(NSInteger)pageCount -{ - CGFloat marginSpace = MAX(0, pageCount - 1) * _indicatorMargin; - CGFloat indicatorSpace = pageCount * _measuredIndicatorWidth; - CGSize size = CGSizeMake(marginSpace + indicatorSpace, _measuredIndicatorHeight); - return size; -} - -- (CGRect)rectForPageIndicator:(NSInteger)pageIndex -{ - if (pageIndex < 0 || pageIndex >= _numberOfPages) { - return CGRectZero; - } - - CGFloat left = [self _leftOffset]; - CGSize size = [self sizeForNumberOfPages:pageIndex + 1]; - CGRect rect = CGRectMake(left + size.width - _measuredIndicatorWidth, 0, _measuredIndicatorWidth, _measuredIndicatorWidth); - return rect; -} - -- (void)_setImage:(UIImage *)image forPage:(NSInteger)pageIndex type:(SMPageControlImageType)type -{ - if (pageIndex < 0 || pageIndex >= _numberOfPages) { - return; - } - - NSMutableDictionary *dictionary = nil; - switch (type) { - case SMPageControlImageTypeCurrent: - dictionary = self.currentPageImages; - break; - case SMPageControlImageTypeNormal: - dictionary = self.pageImages; - break; - case SMPageControlImageTypeMask: - dictionary = self.pageImageMasks; - break; - default: - break; - } - - if (image) { - dictionary[@(pageIndex)] = image; - } else { - [dictionary removeObjectForKey:@(pageIndex)]; - } -} - -- (void)setImage:(UIImage *)image forPage:(NSInteger)pageIndex -{ - [self _setImage:image forPage:pageIndex type:SMPageControlImageTypeNormal]; - [self _updateMeasuredIndicatorSizes]; -} - -- (void)setCurrentImage:(UIImage *)image forPage:(NSInteger)pageIndex -{ - [self _setImage:image forPage:pageIndex type:SMPageControlImageTypeCurrent];; - [self _updateMeasuredIndicatorSizes]; -} - -- (void)setImageMask:(UIImage *)image forPage:(NSInteger)pageIndex -{ - [self _setImage:image forPage:pageIndex type:SMPageControlImageTypeMask]; - - if (nil == image) { - [self.cgImageMasks removeObjectForKey:@(pageIndex)]; - return; - } - - CGImageRef maskImage = [self createMaskForImage:image]; - - if (maskImage) { - self.cgImageMasks[@(pageIndex)] = (__bridge id)maskImage; - CGImageRelease(maskImage); - [self _updateMeasuredIndicatorSizeWithSize:image.size]; - [self setNeedsDisplay]; - } -} - -- (id)_imageForPage:(NSInteger)pageIndex type:(SMPageControlImageType)type -{ - if (pageIndex < 0 || pageIndex >= _numberOfPages) { - return nil; - } - - NSDictionary *dictionary = nil; - switch (type) { - case SMPageControlImageTypeCurrent: - dictionary = _currentPageImages; - break; - case SMPageControlImageTypeNormal: - dictionary = _pageImages; - break; - case SMPageControlImageTypeMask: - dictionary = _pageImageMasks; - break; - default: - break; - } - - return dictionary[@(pageIndex)]; -} - -- (UIImage *)imageForPage:(NSInteger)pageIndex -{ - return [self _imageForPage:pageIndex type:SMPageControlImageTypeNormal]; -} - -- (UIImage *)currentImageForPage:(NSInteger)pageIndex -{ - return [self _imageForPage:pageIndex type:SMPageControlImageTypeCurrent]; -} - -- (UIImage *)imageMaskForPage:(NSInteger)pageIndex -{ - return [self _imageForPage:pageIndex type:SMPageControlImageTypeMask]; -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - CGSize sizeThatFits = [self sizeForNumberOfPages:self.numberOfPages]; - sizeThatFits.height = MAX(sizeThatFits.height, _minHeight); - return sizeThatFits; -} - -- (CGSize)intrinsicContentSize -{ - if (_numberOfPages < 1 || (_numberOfPages < 2 && _hidesForSinglePage)) { - return CGSizeMake(UIViewNoIntrinsicMetric, 0.0f); - } - CGSize intrinsicContentSize = CGSizeMake(UIViewNoIntrinsicMetric, MAX(_measuredIndicatorHeight, _minHeight)); - return intrinsicContentSize; -} - -- (void)updatePageNumberForScrollView:(UIScrollView *)scrollView -{ - NSInteger page = (int)floorf(scrollView.contentOffset.x / scrollView.bounds.size.width); - self.currentPage = page; -} - -- (void)setScrollViewContentOffsetForCurrentPage:(UIScrollView *)scrollView animated:(BOOL)animated -{ - CGPoint offset = scrollView.contentOffset; - offset.x = scrollView.bounds.size.width * self.currentPage; - [scrollView setContentOffset:offset animated:animated]; -} - -- (void)setStyleWithDefaults:(SMPageControlStyleDefaults)defaultStyle -{ - switch (defaultStyle) { - case SMPageControlDefaultStyleModern: - self.indicatorDiameter = DEFAULT_INDICATOR_WIDTH_LARGE; - self.indicatorMargin = DEFAULT_INDICATOR_MARGIN_LARGE; - self.pageIndicatorTintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.2f]; - self.minHeight = DEFAULT_MIN_HEIGHT_LARGE; - break; - case SMPageControlDefaultStyleClassic: - default: - self.indicatorDiameter = DEFAULT_INDICATOR_WIDTH; - self.indicatorMargin = DEFAULT_INDICATOR_MARGIN; - self.pageIndicatorTintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.3f]; - self.minHeight = DEFAULT_MIN_HEIGHT; - break; - } -} - -#pragma mark - - -- (CGImageRef)createMaskForImage:(UIImage *)image CF_RETURNS_RETAINED -{ - size_t pixelsWide = image.size.width * image.scale; - size_t pixelsHigh = image.size.height * image.scale; - size_t bitmapBytesPerRow = (pixelsWide * 1); - CGContextRef context = CGBitmapContextCreate(NULL, pixelsWide, pixelsHigh, CGImageGetBitsPerComponent(image.CGImage), bitmapBytesPerRow, NULL, (CGBitmapInfo)kCGImageAlphaOnly); - CGContextTranslateCTM(context, 0.f, pixelsHigh); - CGContextScaleCTM(context, 1.0f, -1.0f); - - CGContextDrawImage(context, CGRectMake(0, 0, pixelsWide, pixelsHigh), image.CGImage); - CGImageRef maskImage = CGBitmapContextCreateImage(context); - CGContextRelease(context); - - return maskImage; -} - -- (void)_updateMeasuredIndicatorSizeWithSize:(CGSize)size -{ - _measuredIndicatorWidth = MAX(_measuredIndicatorWidth, size.width); - _measuredIndicatorHeight = MAX(_measuredIndicatorHeight, size.height); -} - -- (void)_updateMeasuredIndicatorSizes -{ - _measuredIndicatorWidth = _indicatorDiameter; - _measuredIndicatorHeight = _indicatorDiameter; - - // If we're only using images, ignore the _indicatorDiameter - if ( (self.pageIndicatorImage || self.pageIndicatorMaskImage) && self.currentPageIndicatorImage ) - { - _measuredIndicatorWidth = 0; - _measuredIndicatorHeight = 0; - } - - if (self.pageIndicatorImage) { - [self _updateMeasuredIndicatorSizeWithSize:self.pageIndicatorImage.size]; - } - - if (self.currentPageIndicatorImage) { - [self _updateMeasuredIndicatorSizeWithSize:self.currentPageIndicatorImage.size]; - } - - if (self.pageIndicatorMaskImage) { - [self _updateMeasuredIndicatorSizeWithSize:self.pageIndicatorMaskImage.size]; - } - - if ([self respondsToSelector:@selector(invalidateIntrinsicContentSize)]) { - [self invalidateIntrinsicContentSize]; - } -} - - -#pragma mark - Tap Gesture - -// We're using touchesEnded: because we want to mimick UIPageControl as close as possible -// As of iOS 6, UIPageControl still (as far as we know) does not use a tap gesture recognizer. This means that actions like -// touching down, sliding around, and releasing, still results in the page incrementing or decrementing. -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - UITouch *touch = [touches anyObject]; - CGPoint point = [touch locationInView:self]; - - if (SMPageControlTapBehaviorJump == self.tapBehavior) { - - __block NSInteger tappedIndicatorIndex = NSNotFound; - - [self.pageRects enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) { - CGRect indicatorRect = [value CGRectValue]; - - if (CGRectContainsPoint(indicatorRect, point)) { - tappedIndicatorIndex = index; - *stop = YES; - } - }]; - - if (NSNotFound != tappedIndicatorIndex) { - [self setCurrentPage:tappedIndicatorIndex sendEvent:YES canDefer:YES]; - return; - } - } - - CGSize size = [self sizeForNumberOfPages:self.numberOfPages]; - CGFloat left = [self _leftOffset]; - CGFloat middle = left + (size.width / 2.0f); - if (point.x < middle) { - [self setCurrentPage:self.currentPage - 1 sendEvent:YES canDefer:YES]; - } else { - [self setCurrentPage:self.currentPage + 1 sendEvent:YES canDefer:YES]; - } - -} - -#pragma mark - Accessors - -- (void)setFrame:(CGRect)frame -{ - [super setFrame:frame]; - [self setNeedsDisplay]; -} - -- (void)setIndicatorDiameter:(CGFloat)indicatorDiameter -{ - if (indicatorDiameter == _indicatorDiameter) { - return; - } - - _indicatorDiameter = indicatorDiameter; - - // Absolute minimum height of the control is the indicator diameter - if (_minHeight < indicatorDiameter) { - self.minHeight = indicatorDiameter; - } - - [self _updateMeasuredIndicatorSizes]; - [self setNeedsDisplay]; -} - -- (void)setIndicatorMargin:(CGFloat)indicatorMargin -{ - if (indicatorMargin == _indicatorMargin) { - return; - } - - _indicatorMargin = indicatorMargin; - [self setNeedsDisplay]; -} - -- (void)setMinHeight:(CGFloat)minHeight -{ - if (minHeight == _minHeight) { - return; - } - - // Absolute minimum height of the control is the indicator diameter - if (minHeight < _indicatorDiameter) { - minHeight = _indicatorDiameter; - } - - _minHeight = minHeight; - if ([self respondsToSelector:@selector(invalidateIntrinsicContentSize)]) { - [self invalidateIntrinsicContentSize]; - } - [self setNeedsLayout]; -} - -- (void)setNumberOfPages:(NSInteger)numberOfPages -{ - if (numberOfPages == _numberOfPages) { - return; - } - - self.accessibilityPageControl.numberOfPages = numberOfPages; - - _numberOfPages = MAX(0, numberOfPages); - if ([self respondsToSelector:@selector(invalidateIntrinsicContentSize)]) { - [self invalidateIntrinsicContentSize]; - } - [self updateAccessibilityValue]; - [self setNeedsDisplay]; -} - -- (void)setCurrentPage:(NSInteger)currentPage -{ - [self setCurrentPage:currentPage sendEvent:NO canDefer:NO]; -} - -- (void)setCurrentPage:(NSInteger)currentPage sendEvent:(BOOL)sendEvent canDefer:(BOOL)defer -{ - _currentPage = MIN(MAX(0, currentPage), _numberOfPages - 1); - self.accessibilityPageControl.currentPage = self.currentPage; - - [self updateAccessibilityValue]; - - if (NO == self.defersCurrentPageDisplay || NO == defer) { - _displayedPage = _currentPage; - [self setNeedsDisplay]; - } - - if (sendEvent) { - [self sendActionsForControlEvents:UIControlEventValueChanged]; - } -} - -- (void)setCurrentPageIndicatorImage:(UIImage *)currentPageIndicatorImage -{ - if ([currentPageIndicatorImage isEqual:_currentPageIndicatorImage]) { - return; - } - - _currentPageIndicatorImage = currentPageIndicatorImage; - [self _updateMeasuredIndicatorSizes]; - [self setNeedsDisplay]; -} - -- (void)setPageIndicatorImage:(UIImage *)pageIndicatorImage -{ - if ([pageIndicatorImage isEqual:_pageIndicatorImage]) { - return; - } - - _pageIndicatorImage = pageIndicatorImage; - [self _updateMeasuredIndicatorSizes]; - [self setNeedsDisplay]; -} - -- (void)setPageIndicatorMaskImage:(UIImage *)pageIndicatorMaskImage -{ - if ([pageIndicatorMaskImage isEqual:_pageIndicatorMaskImage]) { - return; - } - - _pageIndicatorMaskImage = pageIndicatorMaskImage; - - if (_pageImageMask) { - CGImageRelease(_pageImageMask); - } - - _pageImageMask = [self createMaskForImage:_pageIndicatorMaskImage]; - - [self _updateMeasuredIndicatorSizes]; - [self setNeedsDisplay]; -} - -- (NSMutableDictionary *)pageNames -{ - if (nil != _pageNames) { - return _pageNames; - } - - _pageNames = [[NSMutableDictionary alloc] init]; - return _pageNames; -} - -- (NSMutableDictionary *)pageImages -{ - if (nil != _pageImages) { - return _pageImages; - } - - _pageImages = [[NSMutableDictionary alloc] init]; - return _pageImages; -} - -- (NSMutableDictionary *)currentPageImages -{ - if (nil != _currentPageImages) { - return _currentPageImages; - } - - _currentPageImages = [[NSMutableDictionary alloc] init]; - return _currentPageImages; -} - -- (NSMutableDictionary *)pageImageMasks -{ - if (nil != _pageImageMasks) { - return _pageImageMasks; - } - - _pageImageMasks = [[NSMutableDictionary alloc] init]; - return _pageImageMasks; -} - -- (NSMutableDictionary *)cgImageMasks -{ - if (nil != _cgImageMasks) { - return _cgImageMasks; - } - - _cgImageMasks = [[NSMutableDictionary alloc] init]; - return _cgImageMasks; -} - -#pragma mark - UIAccessibility - -- (void)setName:(NSString *)name forPage:(NSInteger)pageIndex -{ - if (pageIndex < 0 || pageIndex >= _numberOfPages) { - return; - } - - self.pageNames[@(pageIndex)] = name; - -} - -- (NSString *)nameForPage:(NSInteger)pageIndex -{ - if (pageIndex < 0 || pageIndex >= _numberOfPages) { - return nil; - } - - return self.pageNames[@(pageIndex)]; -} - -- (void)updateAccessibilityValue -{ - NSString *pageName = [self nameForPage:self.currentPage]; - NSString *accessibilityValue = self.accessibilityPageControl.accessibilityValue; - - if (pageName) { - self.accessibilityValue = [NSString stringWithFormat:@"%@ - %@", pageName, accessibilityValue]; - } else { - self.accessibilityValue = accessibilityValue; - } -} - -@end diff --git a/SMPageControl.swift b/SMPageControl.swift new file mode 100644 index 0000000..db30749 --- /dev/null +++ b/SMPageControl.swift @@ -0,0 +1,559 @@ +// +// SMPageControl.h +// SMPageControl +// +// Created by Jerry Jones on 10/13/12. +// Updated to Swift by Noor ul Ain Ali on 7/1/19. +// +// Copyright (c) 2012 Spaceman Labs. All rights reserved. +// + +import UIKit + +@objc enum SMPageControlAlignment: Int { + case alignmentLeft = 0 + case alignmentCenter + case alignmentRight +} + +@objc enum SMPageControlVerticalAlignment: Int { + case alignmentTop = 1 + case alignmentMiddle + case alignmentBottom +} + +@objc enum SMPageControlTap: Int { + case tapBehaviorStep = 1 + case tapBehaviorJump +} + +enum SMPageControlImageType: Int { + case typeNormal = 1 + case typeCurrent + case typeMask +} + +enum SMPageControlStyle: Int { + case defaultStyleClassic = 0 + case defaultStyleModern +} + +@objc class SMPageControl : UIControl { + + @objc var numberOfPages: NSInteger = 0 { + didSet { + self.accessibilityPageControl.numberOfPages = numberOfPages + if numberOfPages != 0 { + self.currentPage = 0 + } + if self.responds(to: #selector(UIView.invalidateIntrinsicContentSize)) { + self.invalidateIntrinsicContentSize() + } + self.updateAccessibilityValue() + self.setNeedsDisplay() + } + } + @objc var currentPage: NSInteger = -1 { + willSet(newValue) { + self.currentPage = min(max(0, newValue), numberOfPages - 1) + } + didSet { + self.setCurrentPage(currentPage, sendEvent: false, canDefer: false) + } + } + + @objc var indicatorMargin: CGFloat = 10.0 { + didSet { + self.setNeedsDisplay() + } + } + @objc var indicatorDiameter: CGFloat = 6.0 { + willSet(newValue) { + // Absolute minimum height of the control is the indicator diameter + if minHeight < indicatorDiameter { + self.minHeight = indicatorDiameter + } + + self.updateMeasuredIndicatorSizes() + self.setNeedsDisplay() + } + } + + @objc var minHeight: CGFloat = 36.0 { + willSet(newValue) { + if minHeight < indicatorDiameter { + minHeight = indicatorDiameter + } else { + minHeight = newValue + } + if self.responds(to: #selector(UIView.invalidateIntrinsicContentSize)) { + self.invalidateIntrinsicContentSize() + } + self.setNeedsLayout() + } + } + @objc var alignment: SMPageControlAlignment = .alignmentCenter + @objc var verticalAlignment: SMPageControlVerticalAlignment = .alignmentMiddle + @objc var pageIndicatorImage: UIImage? = nil { + didSet { + self.updateMeasuredIndicatorSizes() + self.setNeedsDisplay() + } + } + @objc var pageIndicatorMaskImage: UIImage? = nil { + didSet { + if let image = pageIndicatorMaskImage { + pageImageMask = self.createMaskForImage(image) + } + self.updateMeasuredIndicatorSizes() + self.setNeedsDisplay() + } + } + @objc var pageIndicatorTintColor: UIColor? = nil + @objc var currentPageIndicatorImage: UIImage? = nil { + didSet { + self.updateMeasuredIndicatorSizes() + self.setNeedsDisplay() + } + } + @objc var currentPageIndicatorTintColor: UIColor? = nil + @objc var hidesForSinglePage: Bool = false + @objc var tapBehavior: SMPageControlTap = .tapBehaviorStep + var pageNames = NSMutableDictionary() + var pageImages : [Int: UIImage] = [:] + var currentPageImages : [Int: UIImage] = [:] + var pageImageMasks : [Int: UIImage] = [:] + var cgImageMasks : [Int: CGImage] = [:] + var pageRects = NSMutableArray() + // Page Control used for stealing page number localizations for accessibility labels + var accessibilityPageControl: UIPageControl! + private var displayedPage: NSInteger? + private var measuredIndicatorWidth: CGFloat = 0.0 + private var measuredIndicatorHeight: CGFloat = 0.0 + private var pageImageMask: CGImage? + + private let DEFAULTINDICATORWIDTH: CGFloat = 6.0 + private let DEFAULTINDICATORMARGIN: CGFloat = 10.0 + private let DEFAULTMINHEIGHT: CGFloat = 36.0 + private let DEFAULTINDICATORWIDTHLARGE: CGFloat = 7.0 + private let DEFAULTINDICATORMARGINLARGE: CGFloat = 9.0 + private let DEFAULTMINHEIGHTLARGE: CGFloat = 36.0 + + @objc func rectForPageIndicator( pageIndex: NSInteger) -> CGRect { + if pageIndex < 0 || pageIndex >= numberOfPages { + return CGRect.zero + } + + let left: CGFloat = self.leftOffset() + let size: CGSize = self.sizeForNumberOfPages(pageIndex + 1) + let rect: CGRect = CGRect(x: left + size.width - measuredIndicatorWidth, y: 0.0, width: measuredIndicatorWidth, height: measuredIndicatorWidth) + return rect + } + + @objc func sizeForNumberOfPages(_ pageCount: NSInteger) -> CGSize { + let marginSpace: CGFloat = CGFloat(max(0, pageCount - 1)) * indicatorMargin + let indicatorSpace: CGFloat = CGFloat(pageCount) * measuredIndicatorWidth + let size: CGSize = CGSize(width: marginSpace + indicatorSpace, height: measuredIndicatorHeight) + + return size + } + + @objc func setImage(_ image: UIImage, forPage pageIndex: NSInteger) { + self.setImage(image, forPage:pageIndex, type: SMPageControlImageType.typeNormal) + self.updateMeasuredIndicatorSizes() + } + + @objc func setCurrentImage(_ image: UIImage?, forPage pageIndex: NSInteger) { + self.setImage(image, forPage:pageIndex, type: SMPageControlImageType.typeCurrent) + self.updateMeasuredIndicatorSizes() + } + + @objc func setImageMask(_ image: UIImage?, forPage pageIndex: NSInteger) { + self.setImage(image, forPage:pageIndex, type:SMPageControlImageType.typeMask) + if image == nil { + self.cgImageMasks[pageIndex] = nil + return + } + if let mImage = image, let maskImage: CGImage = self.createMaskForImage(mImage) { + self.cgImageMasks[pageIndex] = maskImage + self.updateMeasuredIndicatorSizeWithSize(mImage.size) + self.setNeedsDisplay() + } + } + + @objc func imageForPage(_ pageIndex: NSInteger) -> UIImage? { + return self.imageForPage(pageIndex, type: SMPageControlImageType.typeNormal) + } + + @objc func currentImageForPage(_ pageIndex: NSInteger) -> UIImage? { + return self.imageForPage(pageIndex, type: SMPageControlImageType.typeCurrent) + } + + @objc func imageMaskForPage(_ pageIndex: NSInteger) -> UIImage? { + return self.imageForPage(pageIndex, type: SMPageControlImageType.typeMask) + } + + @objc func updatePageNumberForScrollView(_ scrollView: UIScrollView) { + let page: Int = Int(scrollView.contentOffset.x / scrollView.bounds.size.width) + self.currentPage = page + } + + @objc func setScrollViewContentOffsetForCurrentPage(_ scrollView: UIScrollView, animated: Bool) { + var offset: CGPoint = scrollView.contentOffset + offset.x = scrollView.bounds.size.width * CGFloat(self.currentPage) + scrollView.setContentOffset(offset, animated:animated) + } + + // MARK: - UIAccessibility + + // SMPageControl mirrors UIPageControl's standard accessibility functionality by default. + // Basically, the accessibility label is set to "[current page index + 1] of [page count]". + + // SMPageControl extends UIPageControl's functionality by allowing you to name specific pages. This is especially useful when using + // the per-page indicator images, and allows you to provide more context to the user. + + @objc func setName(_ name: String, forPage pageIndex: NSInteger) { + if pageIndex < 0 || pageIndex >= numberOfPages { + return + } + self.pageNames[pageIndex] = name + } + + @objc func nameForPage(_ pageIndex: NSInteger) -> String? { + if pageIndex < 0 || pageIndex >= numberOfPages { + return nil + } + return self.pageNames.object(forKey: pageIndex) as? String + } + + override func awakeFromNib() { + super.awakeFromNib() + initialize() + } + + override init(frame:CGRect) { + super.init(frame:frame) + initialize() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initialize() + } + + override func draw(_ rect: CGRect) { + if let context = UIGraphicsGetCurrentContext() { + self.renderPages(context, rect: rect) + } + } + + // MARK: - Private + + private func initialize() { + tapBehavior = SMPageControlTap.tapBehaviorStep + self.backgroundColor = UIColor.clear + self.setStyleWithDefaults(defaultStyle: SMPageControlStyle.defaultStyleClassic) + alignment = SMPageControlAlignment.alignmentCenter + verticalAlignment = SMPageControlVerticalAlignment.alignmentMiddle + self.isAccessibilityElement = true + self.accessibilityTraits = UIAccessibilityTraits.updatesFrequently + self.accessibilityPageControl = UIPageControl() + self.contentMode = UIView.ContentMode.redraw + numberOfPages = 0 + } + + private func renderPages(_ context: CGContext, rect: CGRect) { + let pageRects: NSMutableArray = NSMutableArray(capacity: self.numberOfPages) + + if numberOfPages < 2 && hidesForSinglePage { + return + } + + let left: CGFloat = self.leftOffset() + + var xOffset: CGFloat = left + var yOffset: CGFloat = 0.0 + var fillColor: UIColor! = nil + var image: UIImage! = nil + var maskingImage: CGImage? = nil + var maskSize: CGSize = CGSize.zero + + for indexNumber in 0.. CGFloat { + let rect: CGRect = self.bounds + let size: CGSize = self.sizeForNumberOfPages(self.numberOfPages) + var left: CGFloat = 0.0 + switch alignment { + case .alignmentCenter: + left = CGFloat(ceilf(Float(rect.midX - (size.width / 2.0)))) + case .alignmentRight: + left = rect.maxX - size.width + default: + () + } + return left + } + + private func topOffsetForHeight(height:CGFloat, rect:CGRect) -> CGFloat { + var top:CGFloat = 0.0 + switch verticalAlignment { + case .alignmentMiddle: + top = rect.midY - (height / 2.0) + case .alignmentBottom: + top = rect.maxY - height + default: + () + } + return top + } + + func setImage(_ image: UIImage!, forPage pageIndex:Int, type:SMPageControlImageType) { + if pageIndex < 0 || pageIndex >= numberOfPages { + return + } + var dictionary: [Int: UIImage] = [:] + switch type { + case SMPageControlImageType.typeCurrent: + dictionary = self.currentPageImages + case SMPageControlImageType.typeNormal: + dictionary = self.pageImages + case SMPageControlImageType.typeMask: + dictionary = self.pageImageMasks + } + if let dictImage = image { + dictionary[pageIndex] = dictImage + } else { + dictionary[pageIndex] = nil + } + } + + func imageForPage(_ pageIndex:Int, type:SMPageControlImageType) -> UIImage? { + if pageIndex < 0 || pageIndex >= numberOfPages { + return nil + } + var dictionary: [Int: UIImage] = [:] + switch (type) { + case SMPageControlImageType.typeCurrent: + dictionary = currentPageImages + case SMPageControlImageType.typeNormal: + dictionary = pageImages + case SMPageControlImageType.typeMask: + dictionary = pageImageMasks + } + return dictionary[pageIndex] + } + + override func sizeThatFits(_ size:CGSize) -> CGSize { + var sizeThatFits:CGSize = self.sizeForNumberOfPages(self.numberOfPages) + sizeThatFits.height = max(sizeThatFits.height, minHeight) + return sizeThatFits + } + + func intrinsicContentSize() -> CGSize { + if numberOfPages < 1 || (numberOfPages < 2 && hidesForSinglePage) { + return CGSize(width: UIView.noIntrinsicMetric, height: 0.0) + } + let intrinsicContentSize:CGSize = CGSize(width: UIView.noIntrinsicMetric, height: max(measuredIndicatorHeight, minHeight)) + return intrinsicContentSize + } + + func setStyleWithDefaults(defaultStyle:SMPageControlStyle) { + switch (defaultStyle) { + case .defaultStyleModern: + self.indicatorDiameter = DEFAULTINDICATORWIDTHLARGE + self.indicatorMargin = DEFAULTINDICATORMARGINLARGE + self.pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.2) + self.minHeight = DEFAULTMINHEIGHTLARGE + default: + self.indicatorDiameter = DEFAULTINDICATORWIDTH + self.indicatorMargin = DEFAULTINDICATORMARGIN + self.pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.3) + self.minHeight = DEFAULTMINHEIGHT + } + } + + // MARK: - + + func createMaskForImage(_ image: UIImage) -> CGImage? { + let pixelsWide: size_t = size_t(image.size.width * image.scale) + let pixelsHigh: size_t = size_t(image.size.height * image.scale) + let bitmapBytesPerRow: size_t = (pixelsWide * 1) + let colorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB() + + if let cgImage = image.cgImage, let context = CGContext(data: nil, + width: pixelsWide, + height: pixelsHigh, + bitsPerComponent: cgImage.bitsPerComponent, + bytesPerRow: bitmapBytesPerRow, + space: colorSpace, + bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue) + { + context.translateBy(x: 0.0, y: CGFloat(pixelsHigh)) + context.scaleBy(x: 1.0, y: -1.0) + context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(pixelsWide), height: CGFloat(pixelsHigh))) + return context.makeImage() + } + return nil + } + + func updateMeasuredIndicatorSizeWithSize(_ size:CGSize) { + measuredIndicatorWidth = max(measuredIndicatorWidth, size.width) + measuredIndicatorHeight = max(measuredIndicatorHeight, size.height) + } + + func updateMeasuredIndicatorSizes() { + measuredIndicatorWidth = indicatorDiameter + measuredIndicatorHeight = indicatorDiameter + + // If we're only using images, ignore the indicatorDiameter + if (self.pageIndicatorImage != nil || self.pageIndicatorMaskImage != nil && self.currentPageIndicatorImage != nil) + { + measuredIndicatorWidth = 0 + measuredIndicatorHeight = 0 + } + + if (self.pageIndicatorImage != nil) { + self.updateMeasuredIndicatorSizeWithSize(self.pageIndicatorImage?.size ?? CGSize.zero) + } + + if (self.currentPageIndicatorImage != nil) { + self.updateMeasuredIndicatorSizeWithSize(self.currentPageIndicatorImage?.size ?? CGSize.zero) + } + + if (self.pageIndicatorMaskImage != nil) { + self.updateMeasuredIndicatorSizeWithSize(self.pageIndicatorMaskImage?.size ?? CGSize.zero) + } + + if self.responds(to: #selector(UIView.invalidateIntrinsicContentSize)) { + self.invalidateIntrinsicContentSize() + } + } + + + // MARK: - Tap Gesture + + // We're using touchesEnded: because we want to mimick UIPageControl as close as possible + // As of iOS 6, UIPageControl still (as far as we know) does not use a tap gesture recognizer. This means that actions like + // touching down, sliding around, and releasing, still results in the page incrementing or decrementing. + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + if let touch: UITouch = touches.first { + let point: CGPoint = touch.location(in: self) + + if SMPageControlTap.tapBehaviorJump == self.tapBehavior { + + var tappedIndicatorIndex:Int = NSNotFound + self.pageRects.enumerateObjects { (value, index, stop) in + if let rectValue = value as? NSValue { + let indicatorRect = rectValue.cgRectValue + if indicatorRect.contains(point) { + tappedIndicatorIndex = index + stop.pointee = true + } + } + } + if NSNotFound != tappedIndicatorIndex { + self.setCurrentPage(tappedIndicatorIndex, sendEvent: true, canDefer: true) + return + } + } + + let size: CGSize = self.sizeForNumberOfPages(self.numberOfPages) + let left: CGFloat = self.leftOffset() + let middle: CGFloat = left + (size.width / 2.0) + if point.x < middle { + self.currentPage = self.currentPage - 1 + self.setCurrentPage(self.currentPage, sendEvent: true, canDefer: true) + } else { + self.currentPage = self.currentPage + 1 + self.setCurrentPage(self.currentPage, sendEvent: true, canDefer: true) + } + } + } + + + // MARK: - Accessors + + func setFrame(frame:CGRect) { + super.frame = frame + self.setNeedsDisplay() + } + + func setCurrentPage(_ currentPage: Int, sendEvent: Bool, canDefer: Bool) { + self.accessibilityPageControl.currentPage = self.currentPage + self.updateAccessibilityValue() + if !canDefer { + displayedPage = self.currentPage + self.setNeedsDisplay() + } + if sendEvent { + self.sendActions(for: UIControl.Event.valueChanged) + } + } + + func updateAccessibilityValue() { + if let pageName = self.nameForPage(self.currentPage), let accessibilityValue = self.accessibilityPageControl.accessibilityValue { + self.accessibilityValue = String(format:"%@ - %@", pageName, accessibilityValue) + } else { + self.accessibilityValue = accessibilityValue + } + } + +}