-
Notifications
You must be signed in to change notification settings - Fork 156
/
RKSwipeBetweenViewControllers.m
284 lines (219 loc) · 11.7 KB
/
RKSwipeBetweenViewControllers.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
//
// RKSwipeBetweenViewControllers.m
// RKSwipeBetweenViewControllers
//
// Created by Richard Kim on 7/24/14.
// Copyright (c) 2014 Richard Kim. All rights reserved.
//
// @cwRichardKim for regular updates
#import "RKSwipeBetweenViewControllers.h"
//%%% customizeable button attributes
CGFloat X_BUFFER = 0.0; //%%% the number of pixels on either side of the segment
CGFloat Y_BUFFER = 14.0; //%%% number of pixels on top of the segment
CGFloat HEIGHT = 30.0; //%%% height of the segment
//%%% customizeable selector bar attributes (the black bar under the buttons)
CGFloat BOUNCE_BUFFER = 10.0; //%%% adds bounce to the selection bar when you scroll
CGFloat ANIMATION_SPEED = 0.2; //%%% the number of seconds it takes to complete the animation
CGFloat SELECTOR_Y_BUFFER = 40.0; //%%% the y-value of the bar that shows what page you are on (0 is the top)
CGFloat SELECTOR_HEIGHT = 4.0; //%%% thickness of the selector bar
CGFloat X_OFFSET = 8.0; //%%% for some reason there's a little bit of a glitchy offset. I'm going to look for a better workaround in the future
@interface RKSwipeBetweenViewControllers ()
@property (nonatomic) UIScrollView *pageScrollView;
@property (nonatomic) NSInteger currentPageIndex;
@property (nonatomic) BOOL isPageScrollingFlag; //%%% prevents scrolling / segment tap crash
@property (nonatomic) BOOL hasAppearedFlag; //%%% prevents reloading (maintains state)
@end
@implementation RKSwipeBetweenViewControllers
@synthesize viewControllerArray;
@synthesize selectionBar;
@synthesize pageController;
@synthesize navigationView;
@synthesize buttonText;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationBar.barTintColor = [UIColor colorWithRed:0.01 green:0.05 blue:0.06 alpha:1]; //%%% bartint
self.navigationBar.translucent = NO;
viewControllerArray = [[NSMutableArray alloc]init];
self.currentPageIndex = 0;
self.isPageScrollingFlag = NO;
self.hasAppearedFlag = NO;
}
#pragma mark Customizables
//%%% color of the status bar
-(UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
// return UIStatusBarStyleDefault;
}
//%%% sets up the tabs using a loop. You can take apart the loop to customize individual buttons, but remember to tag the buttons. (button.tag=0 and the second button.tag=1, etc)
-(void)setupSegmentButtons {
navigationView = [[UIView alloc]initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.navigationBar.frame.size.height)];
NSInteger numControllers = [viewControllerArray count];
if (!buttonText) {
buttonText = [[NSArray alloc]initWithObjects: @"first",@"second",@"third",@"fourth",@"etc",@"etc",@"etc",@"etc",nil]; //%%%buttontitle
}
for (int i = 0; i<numControllers; i++) {
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(X_BUFFER+i*(self.view.frame.size.width-2*X_BUFFER)/numControllers-X_OFFSET, Y_BUFFER, (self.view.frame.size.width-2*X_BUFFER)/numControllers, HEIGHT)];
[navigationView addSubview:button];
button.tag = i; //%%% IMPORTANT: if you make your own custom buttons, you have to tag them appropriately
button.backgroundColor = [UIColor colorWithRed:0.03 green:0.07 blue:0.08 alpha:1];//%%% buttoncolors
[button addTarget:self action:@selector(tapSegmentButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:[buttonText objectAtIndex:i] forState:UIControlStateNormal]; //%%%buttontitle
}
pageController.navigationController.navigationBar.topItem.titleView = navigationView;
//%%% example custom buttons example:
/*
NSInteger width = (self.view.frame.size.width-(2*X_BUFFER))/3;
UIButton *leftButton = [[UIButton alloc]initWithFrame:CGRectMake(X_BUFFER, Y_BUFFER, width, HEIGHT)];
UIButton *middleButton = [[UIButton alloc]initWithFrame:CGRectMake(X_BUFFER+width, Y_BUFFER, width, HEIGHT)];
UIButton *rightButton = [[UIButton alloc]initWithFrame:CGRectMake(X_BUFFER+2*width, Y_BUFFER, width, HEIGHT)];
[self.navigationBar addSubview:leftButton];
[self.navigationBar addSubview:middleButton];
[self.navigationBar addSubview:rightButton];
leftButton.tag = 0;
middleButton.tag = 1;
rightButton.tag = 2;
leftButton.backgroundColor = [UIColor colorWithRed:0.03 green:0.07 blue:0.08 alpha:1];
middleButton.backgroundColor = [UIColor colorWithRed:0.03 green:0.07 blue:0.08 alpha:1];
rightButton.backgroundColor = [UIColor colorWithRed:0.03 green:0.07 blue:0.08 alpha:1];
[leftButton addTarget:self action:@selector(tapSegmentButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[middleButton addTarget:self action:@selector(tapSegmentButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[rightButton addTarget:self action:@selector(tapSegmentButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[leftButton setTitle:@"left" forState:UIControlStateNormal];
[middleButton setTitle:@"middle" forState:UIControlStateNormal];
[rightButton setTitle:@"right" forState:UIControlStateNormal];
*/
[self setupSelector];
}
//%%% sets up the selection bar under the buttons on the navigation bar
-(void)setupSelector {
selectionBar = [[UIView alloc]initWithFrame:CGRectMake(X_BUFFER-X_OFFSET, SELECTOR_Y_BUFFER,(self.view.frame.size.width-2*X_BUFFER)/[viewControllerArray count], SELECTOR_HEIGHT)];
selectionBar.backgroundColor = [UIColor greenColor]; //%%% sbcolor
selectionBar.alpha = 0.8; //%%% sbalpha
[navigationView addSubview:selectionBar];
}
//generally, this shouldn't be changed unless you know what you're changing
#pragma mark Setup
-(void)viewWillAppear:(BOOL)animated {
if (!self.hasAppearedFlag) {
[self setupPageViewController];
[self setupSegmentButtons];
self.hasAppearedFlag = YES;
}
}
//%%% generic setup stuff for a pageview controller. Sets up the scrolling style and delegate for the controller
-(void)setupPageViewController {
pageController = (UIPageViewController*)self.topViewController;
pageController.delegate = self;
pageController.dataSource = self;
[pageController setViewControllers:@[[viewControllerArray objectAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
[self syncScrollView];
}
//%%% this allows us to get information back from the scrollview, namely the coordinate information that we can link to the selection bar.
-(void)syncScrollView {
for (UIView* view in pageController.view.subviews){
if([view isKindOfClass:[UIScrollView class]]) {
self.pageScrollView = (UIScrollView *)view;
self.pageScrollView.delegate = self;
}
}
}
//%%% methods called when you tap a button or scroll through the pages
// generally shouldn't touch this unless you know what you're doing or
// have a particular performance thing in mind
#pragma mark Movement
//%%% when you tap one of the buttons, it shows that page,
//but it also has to animate the other pages to make it feel like you're crossing a 2d expansion,
//so there's a loop that shows every view controller in the array up to the one you selected
//eg: if you're on page 1 and you click tab 3, then it shows you page 2 and then page 3
-(void)tapSegmentButtonAction:(UIButton *)button {
if (!self.isPageScrollingFlag) {
NSInteger tempIndex = self.currentPageIndex;
__weak typeof(self) weakSelf = self;
//%%% check to see if you're going left -> right or right -> left
if (button.tag > tempIndex) {
//%%% scroll through all the objects between the two points
for (int i = (int)tempIndex+1; i<=button.tag; i++) {
[pageController setViewControllers:@[[viewControllerArray objectAtIndex:i]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL complete){
//%%% if the action finishes scrolling (i.e. the user doesn't stop it in the middle),
//then it updates the page that it's currently on
if (complete) {
[weakSelf updateCurrentPageIndex:i];
}
}];
}
}
//%%% this is the same thing but for going right -> left
else if (button.tag < tempIndex) {
for (int i = (int)tempIndex-1; i >= button.tag; i--) {
[pageController setViewControllers:@[[viewControllerArray objectAtIndex:i]] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:^(BOOL complete){
if (complete) {
[weakSelf updateCurrentPageIndex:i];
}
}];
}
}
}
}
//%%% makes sure the nav bar is always aware of what page you're on
//in reference to the array of view controllers you gave
-(void)updateCurrentPageIndex:(int)newIndex {
self.currentPageIndex = newIndex;
}
//%%% method is called when any of the pages moves.
//It extracts the xcoordinate from the center point and instructs the selection bar to move accordingly
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat xFromCenter = self.view.frame.size.width-scrollView.contentOffset.x; //%%% positive for right swipe, negative for left
//%%% checks to see what page you are on and adjusts the xCoor accordingly.
//i.e. if you're on the second page, it makes sure that the bar starts from the frame.origin.x of the
//second tab instead of the beginning
NSInteger xCoor = X_BUFFER+selectionBar.frame.size.width*self.currentPageIndex-X_OFFSET;
selectionBar.frame = CGRectMake(xCoor-xFromCenter/[viewControllerArray count], selectionBar.frame.origin.y, selectionBar.frame.size.width, selectionBar.frame.size.height);
}
//%%% the delegate functions for UIPageViewController.
//Pretty standard, but generally, don't touch this.
#pragma mark UIPageViewController Delegate Functions
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - Page View Controller Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSInteger index = [viewControllerArray indexOfObject:viewController];
if ((index == NSNotFound) || (index == 0)) {
return nil;
}
index--;
return [viewControllerArray objectAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSInteger index = [viewControllerArray indexOfObject:viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [viewControllerArray count]) {
return nil;
}
return [viewControllerArray objectAtIndex:index];
}
-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if (completed) {
self.currentPageIndex = [viewControllerArray indexOfObject:[pageViewController.viewControllers lastObject]];
}
}
#pragma mark - Scroll View Delegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.isPageScrollingFlag = YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.isPageScrollingFlag = NO;
}
@end