From e7dedcd42cbf0239bc2e7e6e59d82645fe1faa7e Mon Sep 17 00:00:00 2001 From: Santiago Fernandez Date: Fri, 22 Jan 2016 15:23:54 -0300 Subject: [PATCH] adds TwitterPagerTabStripViewController & example --- Example.xcodeproj/project.pbxproj | 4 + .../TwitterExampleViewController.swift | 54 +++ Example/Storyboard.storyboard | 9 +- Sources/FXPageControl.h | 106 +++++ Sources/FXPageControl.m | 433 ++++++++++++++++++ .../TwitterPagerTabStripViewController.swift | 225 +++++++++ XLPagerTabStrip.xcodeproj/project.pbxproj | 27 ++ XLPagerTabStrip/XLPagerTabStrip.h | 1 + 8 files changed, 855 insertions(+), 4 deletions(-) create mode 100644 Example/Example/TwitterExampleViewController.swift create mode 100755 Sources/FXPageControl.h create mode 100755 Sources/FXPageControl.m create mode 100644 Sources/TwitterPagerTabStripViewController.swift diff --git a/Example.xcodeproj/project.pbxproj b/Example.xcodeproj/project.pbxproj index e7c1232..7996fd1 100644 --- a/Example.xcodeproj/project.pbxproj +++ b/Example.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 287D0A7E1C4B7B55004566D6 /* XLPagerTabStrip.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 287D0A7A1C4B7B26004566D6 /* XLPagerTabStrip.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 28F828D01C4B714D00330CF4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F828CF1C4B714D00330CF4 /* AppDelegate.swift */; }; 28F828DA1C4B714D00330CF4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 28F828D81C4B714D00330CF4 /* LaunchScreen.storyboard */; }; + CB2125DE1C52A80E002DAF42 /* TwitterExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2125DD1C52A80E002DAF42 /* TwitterExampleViewController.swift */; }; CB3697BF1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB3697BE1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift */; }; CB71C6EB1C4EB964008EC806 /* SegmentedExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB71C6EA1C4EB964008EC806 /* SegmentedExampleViewController.swift */; }; CB71C6F31C4FDDCE008EC806 /* PostCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB71C6F21C4FDDCE008EC806 /* PostCell.swift */; }; @@ -81,6 +82,7 @@ 28F828DB1C4B714D00330CF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Example/Info.plist; sourceTree = ""; }; 28F828E01C4B714D00330CF4 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28F828E61C4B714D00330CF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Example/ExampleUITests/Info.plist; sourceTree = ""; }; + CB2125DD1C52A80E002DAF42 /* TwitterExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TwitterExampleViewController.swift; path = Example/TwitterExampleViewController.swift; sourceTree = ""; }; CB3697BE1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ButtonBarExampleViewController.swift; path = Example/Example/ButtonBarExampleViewController.swift; sourceTree = SOURCE_ROOT; }; CB71C6EA1C4EB964008EC806 /* SegmentedExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SegmentedExampleViewController.swift; path = Example/SegmentedExampleViewController.swift; sourceTree = ""; }; CB71C6F21C4FDDCE008EC806 /* PostCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PostCell.swift; path = Example/PostCell.swift; sourceTree = ""; }; @@ -178,6 +180,7 @@ CBA0A1FE1C50304500C5748C /* BarExampleViewController.swift */, CBA0A2031C5033B400C5748C /* ReloadExampleViewController.swift */, CBBD435E1C5274AE001A748E /* NavButtonBarExampleViewController.swift */, + CB2125DD1C52A80E002DAF42 /* TwitterExampleViewController.swift */, ); name = Demo; sourceTree = ""; @@ -325,6 +328,7 @@ CBA0A2041C5033B400C5748C /* ReloadExampleViewController.swift in Sources */, CB3697BF1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift in Sources */, CBA0A1FF1C50304500C5748C /* BarExampleViewController.swift in Sources */, + CB2125DE1C52A80E002DAF42 /* TwitterExampleViewController.swift in Sources */, CBBD435F1C5274AE001A748E /* NavButtonBarExampleViewController.swift in Sources */, CB86ED8A1C4D712200DA463B /* ChildExampleViewController.swift in Sources */, CB86ED8E1C4D7A1800DA463B /* TableChildExampleViewController.swift in Sources */, diff --git a/Example/Example/TwitterExampleViewController.swift b/Example/Example/TwitterExampleViewController.swift new file mode 100644 index 0000000..3d56150 --- /dev/null +++ b/Example/Example/TwitterExampleViewController.swift @@ -0,0 +1,54 @@ +// +// TwitterExampleViewController.swift +// Example +// +// Created by Santiago on 1/22/16. +// +// + +import Foundation +import XLPagerTabStrip + +public class TwitterExampleViewController: TwitterPagerTabStripViewController { + var isReload = false + + public override func viewDidLoad() { + super.viewDidLoad() + + pagerOptions = pagerOptions.union(.IsProgressiveIndicator) + pagerOptions = pagerOptions.union(.IsElasticIndicatorLimit) + } + + public override func childViewControllersForPagerTabStripViewController(pagerTabStripController: PagerTabStripViewController) -> [UIViewController] { + + let child_1 = TableChildExampleViewController(style: .Plain) + let child_2 = ChildExampleViewController() + let child_3 = TableChildExampleViewController(style: .Grouped) + let child_4 = ChildExampleViewController() + let child_5 = TableChildExampleViewController(style: .Plain) + let child_6 = ChildExampleViewController() + let child_7 = TableChildExampleViewController(style: .Grouped) + let child_8 = ChildExampleViewController() + + guard isReload else { + return [child_1, child_2, child_3, child_4, child_5, child_6, child_7, child_8] + } + + var childViewControllers = [child_1, child_2, child_3, child_4, child_6, child_7, child_8] as Array + let count = childViewControllers.count + + for (index, _) in childViewControllers.enumerate(){ + let nElements = count - index + let n = (Int(arc4random()) % nElements) + index + if n != index{ + swap(&childViewControllers[index], &childViewControllers[n]) + } + } + let nItems = 1 + (rand() % 8) + return Array(childViewControllers.prefix(Int(nItems))) + } + @IBAction func reloadTapped(sender: AnyObject) { + isReload = true + reloadPagerTabStripView() + } +} diff --git a/Example/Storyboard.storyboard b/Example/Storyboard.storyboard index 7be554d..6ed280e 100644 --- a/Example/Storyboard.storyboard +++ b/Example/Storyboard.storyboard @@ -232,6 +232,7 @@ + @@ -366,17 +367,17 @@ - + - + - + @@ -394,7 +395,7 @@ - + diff --git a/Sources/FXPageControl.h b/Sources/FXPageControl.h new file mode 100755 index 0000000..37a0438 --- /dev/null +++ b/Sources/FXPageControl.h @@ -0,0 +1,106 @@ +// +// FXPageControl.h +// +// Version 1.4 +// +// Created by Nick Lockwood on 07/01/2010. +// Copyright 2010 Charcoal Design +// +// Distributed under the permissive zlib License +// Get the latest version of FXPageControl from here: +// +// https://github.com/nicklockwood/FXPageControl +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" +#import + + +#import +#undef weak_delegate +#if __has_feature(objc_arc_weak) +#define weak_delegate weak +#else +#define weak_delegate unsafe_unretained +#endif + + +extern const CGPathRef FXPageControlDotShapeCircle; +extern const CGPathRef FXPageControlDotShapeSquare; +extern const CGPathRef FXPageControlDotShapeTriangle; + + +@protocol FXPageControlDelegate; + + +IB_DESIGNABLE @interface FXPageControl : UIControl + +- (void)setUp; +- (CGSize)sizeForNumberOfPages:(NSInteger)pageCount; +- (void)updateCurrentPageDisplay; + +@property (nonatomic, weak_delegate) IBOutlet id delegate; + +@property (nonatomic, assign) IBInspectable NSInteger currentPage; +@property (nonatomic, assign) IBInspectable NSInteger numberOfPages; +@property (nonatomic, assign) IBInspectable BOOL defersCurrentPageDisplay; +@property (nonatomic, assign) IBInspectable BOOL hidesForSinglePage; +@property (nonatomic, assign, getter = isWrapEnabled) IBInspectable BOOL wrapEnabled; +@property (nonatomic, assign, getter = isVertical) IBInspectable BOOL vertical; + +@property (nonatomic, strong) IBInspectable UIImage *dotImage; +@property (nonatomic, assign) IBInspectable CGPathRef dotShape; +@property (nonatomic, assign) IBInspectable CGFloat dotSize; +@property (nonatomic, strong) IBInspectable UIColor *dotColor; +@property (nonatomic, strong) IBInspectable UIColor *dotShadowColor; +@property (nonatomic, assign) IBInspectable CGFloat dotShadowBlur; +@property (nonatomic, assign) IBInspectable CGSize dotShadowOffset; + +@property (nonatomic, strong) IBInspectable UIImage *selectedDotImage; +@property (nonatomic, assign) IBInspectable CGPathRef selectedDotShape; +@property (nonatomic, assign) IBInspectable CGFloat selectedDotSize; +@property (nonatomic, strong) IBInspectable UIColor *selectedDotColor; +@property (nonatomic, strong) IBInspectable UIColor *selectedDotShadowColor; +@property (nonatomic, assign) IBInspectable CGFloat selectedDotShadowBlur; +@property (nonatomic, assign) IBInspectable CGSize selectedDotShadowOffset; + +@property (nonatomic, assign) IBInspectable CGFloat dotSpacing; + +@end + + +@protocol FXPageControlDelegate +@optional + +- (UIImage *)pageControl:(FXPageControl *)pageControl imageForDotAtIndex:(NSInteger)index; +- (CGPathRef)pageControl:(FXPageControl *)pageControl shapeForDotAtIndex:(NSInteger)index; +- (UIColor *)pageControl:(FXPageControl *)pageControl colorForDotAtIndex:(NSInteger)index; + +- (UIImage *)pageControl:(FXPageControl *)pageControl selectedImageForDotAtIndex:(NSInteger)index; +- (CGPathRef)pageControl:(FXPageControl *)pageControl selectedShapeForDotAtIndex:(NSInteger)index; +- (UIColor *)pageControl:(FXPageControl *)pageControl selectedColorForDotAtIndex:(NSInteger)index; + +@end + + +#pragma GCC diagnostic pop diff --git a/Sources/FXPageControl.m b/Sources/FXPageControl.m new file mode 100755 index 0000000..338eb19 --- /dev/null +++ b/Sources/FXPageControl.m @@ -0,0 +1,433 @@ +// +// FXPageControl.m +// +// Version 1.4 +// +// Created by Nick Lockwood on 07/01/2010. +// Copyright 2010 Charcoal Design +// +// Distributed under the permissive zlib License +// Get the latest version of FXPageControl from here: +// +// https://github.com/nicklockwood/FXPageControl +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#import "FXPageControl.h" + + +#pragma GCC diagnostic ignored "-Wgnu" +#pragma GCC diagnostic ignored "-Wreceiver-is-weak" +#pragma GCC diagnostic ignored "-Warc-repeated-use-of-weak" +#pragma GCC diagnostic ignored "-Wdirect-ivar-access" + + +#import +#if !__has_feature(objc_arc) +#error This class requires automatic reference counting +#endif + + +const CGPathRef FXPageControlDotShapeCircle = (const CGPathRef)1; +const CGPathRef FXPageControlDotShapeSquare = (const CGPathRef)2; +const CGPathRef FXPageControlDotShapeTriangle = (const CGPathRef)3; +#define LAST_SHAPE FXPageControlDotShapeTriangle + + +@implementation NSObject (FXPageControl) + +- (UIImage *)pageControl:(__unused FXPageControl *)pageControl imageForDotAtIndex:(__unused NSInteger)index { return nil; } +- (CGPathRef)pageControl:(__unused FXPageControl *)pageControl shapeForDotAtIndex:(__unused NSInteger)index { return NULL; } +- (UIColor *)pageControl:(__unused FXPageControl *)pageControl colorForDotAtIndex:(__unused NSInteger)index { return nil; } + +- (UIImage *)pageControl:(__unused FXPageControl *)pageControl selectedImageForDotAtIndex:(__unused NSInteger)index { return nil; } +- (CGPathRef)pageControl:(__unused FXPageControl *)pageControl selectedShapeForDotAtIndex:(__unused NSInteger)index { return NULL; } +- (UIColor *)pageControl:(__unused FXPageControl *)pageControl selectedColorForDotAtIndex:(__unused NSInteger)index { return nil; } + +@end + + +@implementation FXPageControl + +- (void)setUp +{ + //needs redrawing if bounds change + self.contentMode = UIViewContentModeRedraw; + + //set defaults + _dotSpacing = 10.0f; + _dotSize = 6.0f; + _dotShadowOffset = CGSizeMake(0, 1); + _selectedDotShadowOffset = CGSizeMake(0, 1); +} + +- (id)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) + { + [self setUp]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if ((self = [super initWithCoder:aDecoder])) + { + [self setUp]; + } + return self; +} + +- (void)dealloc +{ + if (_dotShape > LAST_SHAPE) CGPathRelease(_dotShape); + if (_selectedDotShape > LAST_SHAPE) CGPathRelease(_selectedDotShape); +} + +- (CGSize)sizeForNumberOfPages:(__unused NSInteger)pageCount +{ + CGFloat width = _dotSize + (_dotSize + _dotSpacing) * (_numberOfPages - 1); + return _vertical? CGSizeMake(_dotSize, width): CGSizeMake(width, _dotSize); +} + +- (void)updateCurrentPageDisplay +{ + [self setNeedsDisplay]; +} + +- (void)drawRect:(__unused CGRect)rect +{ + if (_numberOfPages > 1 || !_hidesForSinglePage) + { + CGContextRef context = UIGraphicsGetCurrentContext(); + CGSize size = [self sizeForNumberOfPages:_numberOfPages]; + if (_vertical) + { + CGContextTranslateCTM(context, self.frame.size.width / 2, (self.frame.size.height - size.height) / 2); + } + else + { + CGContextTranslateCTM(context, (self.frame.size.width - size.width) / 2, self.frame.size.height / 2); + } + + for (int i = 0; i < _numberOfPages; i++) + { + UIImage *dotImage = nil; + UIColor *dotColor = nil; + CGPathRef dotShape = NULL; + CGFloat dotSize = 0; + UIColor *dotShadowColor = nil; + CGSize dotShadowOffset = CGSizeZero; + CGFloat dotShadowBlur = 0; + + if (i == _currentPage) + { + [_selectedDotColor setFill]; + dotImage = [_delegate pageControl:self selectedImageForDotAtIndex:i] ?: _selectedDotImage; + dotShape = [_delegate pageControl:self selectedShapeForDotAtIndex:i] ?: _selectedDotShape ?: _dotShape; + dotColor = [_delegate pageControl:self selectedColorForDotAtIndex:i] ?: _selectedDotColor ?: [UIColor blackColor]; + dotShadowBlur = _selectedDotShadowBlur; + dotShadowColor = _selectedDotShadowColor; + dotShadowOffset = _selectedDotShadowOffset; + dotSize = _selectedDotSize ?: _dotSize; + } + else + { + [_dotColor setFill]; + dotImage = [_delegate pageControl:self imageForDotAtIndex:i] ?: _dotImage; + dotShape = [_delegate pageControl:self shapeForDotAtIndex:i] ?: _dotShape; + dotColor = [_delegate pageControl:self colorForDotAtIndex:i] ?: _dotColor; + if (!dotColor) + { + //fall back to selected dot color with reduced alpha + dotColor = [_delegate pageControl:self selectedColorForDotAtIndex:i] ?: _selectedDotColor ?: [UIColor blackColor]; + dotColor = [dotColor colorWithAlphaComponent:0.25f]; + } + dotShadowBlur = _dotShadowBlur; + dotShadowColor = _dotShadowColor; + dotShadowOffset = _dotShadowOffset; + dotSize = _dotSize; + } + + CGContextSaveGState(context); + CGFloat offset = (_dotSize + _dotSpacing) * i + _dotSize / 2; + CGContextTranslateCTM(context, _vertical? 0: offset, _vertical? offset: 0); + + if (dotShadowColor && ![dotShadowColor isEqual:[UIColor clearColor]]) + { + CGContextSetShadowWithColor(context, dotShadowOffset, dotShadowBlur, dotShadowColor.CGColor); + } + if (dotImage) + { + [dotImage drawInRect:CGRectMake(-dotImage.size.width / 2, -dotImage.size.height / 2, dotImage.size.width, dotImage.size.height)]; + } + else + { + [dotColor setFill]; + if (!dotShape || dotShape == FXPageControlDotShapeCircle) + { + CGContextFillEllipseInRect(context, CGRectMake(-dotSize / 2, -dotSize / 2, dotSize, dotSize)); + } + else if (dotShape == FXPageControlDotShapeSquare) + { + CGContextFillRect(context, CGRectMake(-dotSize / 2, -dotSize / 2, dotSize, dotSize)); + } + else if (dotShape == FXPageControlDotShapeTriangle) + { + CGContextBeginPath(context); + CGContextMoveToPoint(context, 0, -dotSize / 2); + CGContextAddLineToPoint(context, dotSize / 2, dotSize / 2); + CGContextAddLineToPoint(context, -dotSize / 2, dotSize / 2); + CGContextAddLineToPoint(context, 0, -dotSize / 2); + CGContextFillPath(context); + } + else + { + CGContextBeginPath(context); + CGContextAddPath(context, dotShape); + CGContextFillPath(context); + } + } + CGContextRestoreGState(context); + } + } +} + +- (NSInteger)clampedPageValue:(NSInteger)page +{ + if (_wrapEnabled) + { + return _numberOfPages? (page + _numberOfPages) % _numberOfPages: 0; + } + else + { + return MIN(MAX(0, page), _numberOfPages - 1); + } +} + +- (void)setDotImage:(UIImage *)dotImage +{ + if (_dotImage != dotImage) + { + _dotImage = dotImage; + [self setNeedsDisplay]; + } +} + +- (void)setDotShape:(CGPathRef)dotShape +{ + if (_dotShape != dotShape) + { + if (_dotShape > LAST_SHAPE) CGPathRelease(_dotShape); + _dotShape = dotShape; + if (_dotShape > LAST_SHAPE) CGPathRetain(_dotShape); + [self setNeedsDisplay]; + } +} + +- (void)setDotSize:(CGFloat)dotSize +{ + if (ABS(_dotSize - dotSize) > 0.001) + { + _dotSize = dotSize; + [self setNeedsDisplay]; + } +} + +- (void)setDotColor:(UIColor *)dotColor +{ + if (_dotColor != dotColor) + { + _dotColor = dotColor; + [self setNeedsDisplay]; + } +} + +- (void)setDotShadowColor:(UIColor *)dotColor +{ + if (_dotShadowColor != dotColor) + { + _dotShadowColor = dotColor; + [self setNeedsDisplay]; + } +} + +- (void)setDotShadowBlur:(CGFloat)dotShadowBlur +{ + if (ABS(_dotShadowBlur - dotShadowBlur) > 0.001) + { + _dotShadowBlur = dotShadowBlur; + [self setNeedsDisplay]; + } +} + +- (void)setDotShadowOffset:(CGSize)dotShadowOffset +{ + if (!CGSizeEqualToSize(_dotShadowOffset, dotShadowOffset)) + { + _dotShadowOffset = dotShadowOffset; + [self setNeedsDisplay]; + } +} + +- (void)setSelectedDotImage:(UIImage *)dotImage +{ + if (_selectedDotImage != dotImage) + { + _selectedDotImage = dotImage; + [self setNeedsDisplay]; + } +} + +- (void)setSelectedDotColor:(UIColor *)dotColor +{ + if (_selectedDotColor != dotColor) + { + _selectedDotColor = dotColor; + [self setNeedsDisplay]; + } +} + +- (void)setSelectedDotShape:(CGPathRef)dotShape +{ + if (_selectedDotShape != dotShape) + { + if (_selectedDotShape > LAST_SHAPE) CGPathRelease(_selectedDotShape); + _selectedDotShape = dotShape; + if (_selectedDotShape > LAST_SHAPE) CGPathRetain(_selectedDotShape); + [self setNeedsDisplay]; + } +} + +- (void)setSelectedDotSize:(CGFloat)dotSize +{ + if (ABS(_selectedDotSize - dotSize) > 0.001) + { + _selectedDotSize = dotSize; + [self setNeedsDisplay]; + } +} + +- (void)setSelectedDotShadowColor:(UIColor *)dotColor +{ + if (_selectedDotShadowColor != dotColor) + { + _selectedDotShadowColor = dotColor; + [self setNeedsDisplay]; + } +} + +- (void)setSelectedDotShadowBlur:(CGFloat)dotShadowBlur +{ + if (ABS(_selectedDotShadowBlur - dotShadowBlur) > 0.001) + { + _selectedDotShadowBlur = dotShadowBlur; + [self setNeedsDisplay]; + } +} + +- (void)setSelectedDotShadowOffset:(CGSize)dotShadowOffset +{ + if (!CGSizeEqualToSize(_selectedDotShadowOffset, dotShadowOffset)) + { + _selectedDotShadowOffset = dotShadowOffset; + [self setNeedsDisplay]; + } +} + +- (void)setDotSpacing:(CGFloat)dotSpacing +{ + if (ABS(_dotSpacing - dotSpacing) > 0.001) + { + _dotSpacing = dotSpacing; + [self setNeedsDisplay]; + } +} + +- (void)setDelegate:(id)delegate +{ + if (_delegate != delegate) + { + _delegate = delegate; + [self setNeedsDisplay]; + } +} + +- (void)setCurrentPage:(NSInteger)page +{ + _currentPage = [self clampedPageValue:page]; + [self setNeedsDisplay]; +} + +- (void)setNumberOfPages:(NSInteger)pages +{ + if (_numberOfPages != pages) + { + _numberOfPages = pages; + if (_currentPage >= pages) + { + _currentPage = pages - 1; + } + [self setNeedsDisplay]; + } +} + +- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event +{ + CGPoint point = [touch locationInView:self]; + BOOL forward = _vertical? (point.y > self.frame.size.height / 2): (point.x > self.frame.size.width / 2); + _currentPage = [self clampedPageValue:_currentPage + (forward? 1: -1)]; + if (!_defersCurrentPageDisplay) + { + [self setNeedsDisplay]; + } + [self sendActionsForControlEvents:UIControlEventValueChanged]; + [super endTrackingWithTouch:touch withEvent:event]; +} + +- (CGSize)sizeThatFits:(__unused CGSize)size +{ + CGSize dotSize = [self sizeForNumberOfPages:_numberOfPages]; + if (_selectedDotSize) + { + CGFloat width = (_selectedDotSize - _dotSize); + CGFloat height = MAX(36, MAX(_dotSize, _selectedDotSize)); + dotSize.width = _vertical? height: dotSize.width + width; + dotSize.height = _vertical? dotSize.height + width: height; + + } + if ((_dotShadowColor && ![_dotShadowColor isEqual:[UIColor clearColor]]) || + (_selectedDotShadowColor && ![_selectedDotShadowColor isEqual:[UIColor clearColor]])) + { + dotSize.width += MAX(_dotShadowOffset.width, _selectedDotShadowOffset.width) * 2; + dotSize.height += MAX(_dotShadowOffset.height, _selectedDotShadowOffset.height) * 2; + dotSize.width += MAX(_dotShadowBlur, _selectedDotShadowBlur) * 2; + dotSize.height += MAX(_dotShadowBlur, _selectedDotShadowBlur) * 2; + } + return dotSize; +} + +- (CGSize)intrinsicContentSize +{ + return [self sizeThatFits:self.bounds.size]; +} + +@end diff --git a/Sources/TwitterPagerTabStripViewController.swift b/Sources/TwitterPagerTabStripViewController.swift new file mode 100644 index 0000000..b0ce118 --- /dev/null +++ b/Sources/TwitterPagerTabStripViewController.swift @@ -0,0 +1,225 @@ +// TwitterPagerTabStripViewController.swift +// XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip ) +// +// Copyright (c) 2016 Xmartlabs ( http://xmartlabs.com ) +// +// +// 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 Foundation + +public class TwitterPagerTabStripViewController: PagerTabStripViewController, PagerTabStripViewControllerDataSource, PagerTabStripViewControllerIsProgressiveDelegate { + lazy var navigationView: UIView = { + let navigationView = UIView() + navigationView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] + return navigationView + }() + + lazy var navigationScrollView: UIScrollView = { [unowned self] in + let navigationScrollView = UIScrollView(frame: CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 44)) + navigationScrollView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] + navigationScrollView.bounces = true + navigationScrollView.scrollsToTop = false + navigationScrollView.delegate = self + navigationScrollView.showsVerticalScrollIndicator = false + navigationScrollView.showsHorizontalScrollIndicator = false + navigationScrollView.pagingEnabled = true + navigationScrollView.userInteractionEnabled = false + navigationScrollView.alwaysBounceHorizontal = true + navigationScrollView.alwaysBounceVertical = false + return navigationScrollView + }() + var navigationPageControl: FXPageControl = { + let navigationPageControl = FXPageControl() + navigationPageControl.backgroundColor = .clearColor() + navigationPageControl.dotSize = 3.8 + navigationPageControl.dotSpacing = 4.0 + navigationPageControl.dotColor = UIColor(white: 1, alpha: 0.4) + navigationPageControl.selectedDotColor = .whiteColor() + navigationPageControl.userInteractionEnabled = false + return navigationPageControl + }() + var navigationItemsViews = [UIView]() + + var landscapeTitleFont = UIFont.systemFontOfSize(14) + var portraitTitleFont = UIFont.systemFontOfSize(18) + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + delegate = self + datasource = self + } + + public override func viewDidLoad() { + super.viewDidLoad() + + if navigationView.superview == nil { + navigationItem.titleView = navigationView + } + + navigationView.addObserver(self, forKeyPath: "frame", options: [.New, .Old], context: nil) + navigationView.frame = CGRectMake(0, 0, CGRectGetWidth(navigationController!.navigationBar.frame), CGRectGetHeight(navigationController!.navigationBar.frame)) + + if navigationScrollView.superview == nil { + navigationView.addSubview(navigationScrollView) + } + if navigationPageControl.superview == nil { + navigationView.addSubview(navigationPageControl) + } + + reloadNavigationViewItems() + } + + public override func reloadPagerTabStripView() { + super.reloadPagerTabStripView() + guard isViewLoaded() else { return } + + reloadNavigationViewItems() + setNavigationViewItemsPosition() + } + + // MARK: - PagerTabStripViewControllerDelegate + + public func pagerTabStripViewController(pagerTabStripViewController: PagerTabStripViewController, updateIndicatorFromIndex fromIndex: Int, toIndex: Int) throws { + + } + + public func pagerTabStripViewController(pagerTabStripViewController: PagerTabStripViewController, updateIndicatorFromIndex fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) throws { + let distance = getDistanceValue() + var xOffset: CGFloat = 0 + + if fromIndex < toIndex { + xOffset = distance * CGFloat(fromIndex) + distance * progressPercentage + } + else if fromIndex > toIndex { + xOffset = distance * CGFloat(fromIndex) - distance * progressPercentage + } + else { + xOffset = distance * CGFloat(fromIndex) + } + + navigationScrollView.contentOffset = CGPointMake(xOffset, 0) + setAlphaWithOffset(xOffset) + navigationPageControl.currentPage = currentIndex + } + + public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { + guard object as! UIView == navigationView && keyPath! == "frame" && change![NSKeyValueChangeKindKey] as! UInt == NSKeyValueChange.Setting.rawValue else { return } + + let oldRect = change![NSKeyValueChangeOldKey]!.CGRectValue + let newRect = change![NSKeyValueChangeOldKey]!.CGRectValue + guard !CGRectEqualToRect(oldRect, newRect) else { return } + + navigationScrollView.frame = CGRectMake(0, 0, CGRectGetWidth(navigationView.frame), CGRectGetHeight(navigationScrollView.frame)) + setNavigationViewItemsPosition() + } + + deinit { + navigationView.removeObserver(self, forKeyPath: "frame") + } + + // MARK: - Helpers + + func reloadNavigationViewItems() { + for item in navigationItemsViews { + item.removeFromSuperview() + } + + navigationItemsViews.removeAll() + + for (index, item) in viewControllers.enumerate() { + let child = item as! PagerTabStripChildItem + let childHeader = child.childHeaderForPagerTabStripViewController(self) + + let navTitleLabel = createNewLabelWithText(childHeader.title) + navTitleLabel.alpha = currentIndex == index ? 1 : 0 + navTitleLabel.textColor = childHeader.color ?? .whiteColor() + navigationScrollView.addSubview(navTitleLabel) + navigationItemsViews.append(navTitleLabel) + } + } + + private func setNavigationViewItemsPosition() { + setNavigationViewItemsPosition(true) + } + + private func setNavigationViewItemsPosition(updateAlpha: Bool) { + let distance = getDistanceValue() + let isPortrait = UIDeviceOrientationIsPortrait(UIDevice.currentDevice().orientation) + let labelHeightSpace: CGFloat = isPortrait ? 34 : 25 + for (index, view) in navigationItemsViews.enumerate() { + let label = view as! UILabel + if updateAlpha { + label.alpha = currentIndex == index ? 1 : 0 + } + label.font = isPortrait ? portraitTitleFont : landscapeTitleFont + let viewSize = getLabelSize(label) + let originX = distance - viewSize.width/2 + CGFloat(index) * distance + let originY = (CGFloat(labelHeightSpace) - viewSize.height) / 2 + label.frame = CGRectMake(originX, originY + 2, viewSize.width, viewSize.height) + label.tag = index + } + + let xOffset = distance * CGFloat(currentIndex) + navigationScrollView.contentOffset = CGPointMake(xOffset, 0) + + navigationPageControl.numberOfPages = navigationItemsViews.count + navigationPageControl.currentPage = currentIndex + let viewSize = navigationPageControl.sizeForNumberOfPages(navigationItemsViews.count) + let originX = distance - viewSize.width/2 + navigationPageControl.frame = CGRectMake(originX, labelHeightSpace, viewSize.width, viewSize.height) + } + + private func setAlphaWithOffset(xOffset: CGFloat) { + let distance = getDistanceValue() + for (index, view) in navigationItemsViews.enumerate() { + var alpha: CGFloat = 0 + if xOffset < distance * CGFloat(index) { + alpha = (xOffset - distance * CGFloat(index - 1)) / distance + } + else { + alpha = 1 - ((xOffset - distance * CGFloat(index)) / distance) + } + view.alpha = alpha + } + } + + private func createNewLabelWithText(text :String) -> UILabel { + let label = UILabel() + label.text = text + label.font = landscapeTitleFont + label.textColor = .whiteColor() + label.alpha = 0 + return label + } + + private func getLabelSize(label: UILabel) -> CGSize { + return (label.text! as NSString).sizeWithAttributes([NSFontAttributeName : label.font]) + } + + private func getDistanceValue() -> CGFloat { + let middle = navigationController?.navigationBar .convertPoint(navigationController!.navigationBar.center, toView: navigationView) + return middle!.x + } + + public override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + setNavigationViewItemsPosition(false) + } +} diff --git a/XLPagerTabStrip.xcodeproj/project.pbxproj b/XLPagerTabStrip.xcodeproj/project.pbxproj index df739f4..f0bd9d5 100644 --- a/XLPagerTabStrip.xcodeproj/project.pbxproj +++ b/XLPagerTabStrip.xcodeproj/project.pbxproj @@ -19,9 +19,12 @@ CB0986C81C5158A000DF7087 /* ButtonBarViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0986C71C5158A000DF7087 /* ButtonBarViewCell.swift */; }; CB0986CA1C515D9A00DF7087 /* ButtonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB0986C91C515D9A00DF7087 /* ButtonCell.xib */; }; CB71C6EE1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB71C6ED1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift */; }; + CB7D614C1C529B9500A957BA /* FXPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = CB7D614A1C529B9500A957BA /* FXPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CB7D614D1C529B9500A957BA /* FXPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = CB7D614B1C529B9500A957BA /* FXPageControl.m */; }; CB86ED6B1C4D6E6C00DA463B /* PagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB86ED6A1C4D6E6C00DA463B /* PagerTabStripViewController.swift */; }; CBA0A1FC1C502DA300C5748C /* BarPagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA0A1FA1C502DA300C5748C /* BarPagerTabStripViewController.swift */; }; CBA0A1FD1C502DA300C5748C /* BarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA0A1FB1C502DA300C5748C /* BarView.swift */; }; + CBBD43621C527E80001A748E /* TwitterPagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBD43611C527E80001A748E /* TwitterPagerTabStripViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -51,9 +54,12 @@ CB0986C71C5158A000DF7087 /* ButtonBarViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonBarViewCell.swift; sourceTree = ""; }; CB0986C91C515D9A00DF7087 /* ButtonCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ButtonCell.xib; sourceTree = ""; }; CB71C6ED1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedPagerTabStripViewController.swift; sourceTree = ""; }; + CB7D614A1C529B9500A957BA /* FXPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FXPageControl.h; sourceTree = ""; }; + CB7D614B1C529B9500A957BA /* FXPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FXPageControl.m; sourceTree = ""; }; CB86ED6A1C4D6E6C00DA463B /* PagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagerTabStripViewController.swift; sourceTree = ""; }; CBA0A1FA1C502DA300C5748C /* BarPagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarPagerTabStripViewController.swift; sourceTree = ""; }; CBA0A1FB1C502DA300C5748C /* BarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarView.swift; sourceTree = ""; }; + CBBD43611C527E80001A748E /* TwitterPagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterPagerTabStripViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -78,6 +84,7 @@ 281BFDBE1C511B890090C26F /* Views */ = { isa = PBXGroup; children = ( + CB7D614E1C529BB600A957BA /* FXPageControl */, CB0986C91C515D9A00DF7087 /* ButtonCell.xib */, CBA0A1FB1C502DA300C5748C /* BarView.swift */, CB0986C51C51395E00DF7087 /* ButtonBarView.swift */, @@ -93,6 +100,7 @@ CB86ED6A1C4D6E6C00DA463B /* PagerTabStripViewController.swift */, CB71C6ED1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift */, CB0986C31C51391600DF7087 /* ButtonBarPagerTabStripViewController.swift */, + CBBD43611C527E80001A748E /* TwitterPagerTabStripViewController.swift */, ); name = Controllers; sourceTree = ""; @@ -149,6 +157,15 @@ path = Sources; sourceTree = ""; }; + CB7D614E1C529BB600A957BA /* FXPageControl */ = { + isa = PBXGroup; + children = ( + CB7D614A1C529B9500A957BA /* FXPageControl.h */, + CB7D614B1C529B9500A957BA /* FXPageControl.m */, + ); + name = FXPageControl; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -157,6 +174,7 @@ buildActionMask = 2147483647; files = ( 28F828811C494B2C00330CF4 /* XLPagerTabStrip.h in Headers */, + CB7D614C1C529B9500A957BA /* FXPageControl.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -258,7 +276,9 @@ buildActionMask = 2147483647; files = ( CB0986C61C51395E00DF7087 /* ButtonBarView.swift in Sources */, + CBBD43621C527E80001A748E /* TwitterPagerTabStripViewController.swift in Sources */, CBA0A1FD1C502DA300C5748C /* BarView.swift in Sources */, + CB7D614D1C529B9500A957BA /* FXPageControl.m in Sources */, 281BFDC31C511F120090C26F /* PagerTabStripOptions.swift in Sources */, CBA0A1FC1C502DA300C5748C /* BarPagerTabStripViewController.swift in Sources */, CB71C6EE1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift in Sources */, @@ -382,6 +402,7 @@ 28F828921C494B2C00330CF4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -392,12 +413,16 @@ PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.XLPagerTabStrip; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 28F828931C494B2C00330CF4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -408,6 +433,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.xmartlabs.XLPagerTabStrip; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; }; name = Release; }; diff --git a/XLPagerTabStrip/XLPagerTabStrip.h b/XLPagerTabStrip/XLPagerTabStrip.h index 1102832..e3df325 100644 --- a/XLPagerTabStrip/XLPagerTabStrip.h +++ b/XLPagerTabStrip/XLPagerTabStrip.h @@ -15,4 +15,5 @@ FOUNDATION_EXPORT const unsigned char XLPagerTabStripVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import +#import "FXPageControl.h"