├── Podfile
├── BKSlidingViewController.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── BKSlidingViewController.xccheckout
└── project.pbxproj
├── BKSlidingViewController.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── BKSlidingViewController.xccheckout
├── BKSlidingViewController
├── UIViewController+BKTag.h
├── UIViewController+BKTag.m
├── BKSlidingViewController.h
├── BKSlidingViewController_Extras.h
└── BKSlidingViewController.m
├── README.md
├── LICENSE
├── BKSlidingViewController.podspec
└── .gitignore
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 | platform :ios, '7.0'
3 |
4 | pod 'BKSlidingViewController', :path => '.'
5 |
--------------------------------------------------------------------------------
/BKSlidingViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BKSlidingViewController.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/BKSlidingViewController/UIViewController+BKTag.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014-present 650 Industries.
2 |
3 | @import UIKit;
4 |
5 | /**
6 | Attach `NSInteger` tags to `UIViewController` classes.
7 | */
8 | @interface UIViewController (BKTag)
9 |
10 | /**
11 | An integer that you can use to identify view controllers in your application.
12 |
13 | @discussion The default value is 0. You can set the value of this tag and use that value to identify the view controller later.
14 | */
15 | @property (nonatomic, assign, setter=bk_setTag:, getter=bk_tag) NSInteger bk_tag;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BKSlidingViewController
2 |
3 | A side-scrolling view controller container class with a minimal, UITabBarController-like API.
4 |
5 | ## Installation
6 |
7 | 1. Add BKSlidingViewController to your Podfile.
8 | 2. In your terminal, run `pod install`.
9 |
10 | ## Usage
11 |
12 | 1. Add `#import ` to your application delegate class.
13 | 2. In your `application:didFinishLaunchingWithOptions:` method, create and configure an instance of `BKSlidingViewController` with your desired view controllers and initial selected index.
14 | 3. Set the `rootViewController` property on your `UIWindow` to your instance of `BKSlidingViewController`.
15 |
--------------------------------------------------------------------------------
/BKSlidingViewController/UIViewController+BKTag.m:
--------------------------------------------------------------------------------
1 | // Copyright 2014-present 650 Industries.
2 |
3 | #import "UIViewController+BKTag.h"
4 |
5 | @import ObjectiveC;
6 |
7 | static const void *UIViewControllerBKTagKey = "UIViewControllerBKTagKey";
8 |
9 | @implementation UIViewController (BKTag)
10 |
11 | - (void)bk_setTag:(NSInteger)tag
12 | {
13 | NSNumber *value = [NSNumber numberWithInteger:tag];
14 | objc_setAssociatedObject(self, UIViewControllerBKTagKey, value, OBJC_ASSOCIATION_COPY_NONATOMIC);
15 | }
16 |
17 | - (NSInteger)bk_tag
18 | {
19 | NSNumber *value = objc_getAssociatedObject(self, UIViewControllerBKTagKey);
20 | if (value) {
21 | return [value integerValue];
22 | } else {
23 | return 0;
24 | }
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 650 Industries, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/BKSlidingViewController.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "BKSlidingViewController"
3 | s.version = "1.1.0"
4 | s.summary = "A side-scrolling view controller container class with a minimal, UITabBarController-like API."
5 | s.description = <<-DESC
6 | UIPageViewController seems somewhat rigidly designed for paginated content, and is very efficient at lazily instantiating many view controllers with a particular pattern (before/after) and specific spatial metaphors. While it can do many things well, customization is ultimately limited by permissible alterations of the UIPageViewController class.
7 |
8 | BKSlidingViewController is an open-source substitute for applications desiring view controllers laid out horizontally, presented with a UITabBarController-like API.
9 | DESC
10 | s.homepage = "https://github.com/Basket/BKSlidingViewController"
11 | s.license = 'MIT'
12 | s.author = { "Andrew Toulouse" => "andrew@atoulou.se" }
13 | s.source = { :git => "https://github.com/Basket/BKSlidingViewController.git", :tag => s.version.to_s }
14 |
15 | s.platform = :ios, '7.0'
16 | s.requires_arc = true
17 |
18 | s.dependency 'BKDeltaCalculator', '~> 1.0'
19 |
20 | s.source_files = 'BKSlidingViewController/*.{h,m}'
21 | s.frameworks = 'UIKit'
22 | end
23 |
--------------------------------------------------------------------------------
/BKSlidingViewController/BKSlidingViewController.h:
--------------------------------------------------------------------------------
1 | // Copyright 2014-present 650 Industries.
2 |
3 | @import UIKit;
4 |
5 | /**
6 | `BKSlidingViewController` is a container view controller designed to slide view controllers in a scroll view, and to do
7 | so with an API mimicking that of `UITabBarController`.
8 |
9 | `BKSlidingViewController` methods may be called from within UIView animation blocks.
10 | */
11 | @interface BKSlidingViewController : UIViewController
12 |
13 | // These are designed to respect their animation context's duration. Call them from an animation
14 | // block if so desired.
15 |
16 | /**
17 | The view controllers displayed by the sliding view controller.
18 | */
19 | @property (nonatomic, copy) NSArray *viewControllers;
20 |
21 | /**
22 | The view controller being shown by the sliding view controller.
23 | */
24 | @property (nonatomic, weak) UIViewController *selectedViewController;
25 |
26 | /**
27 | The index of the view controller being shown by the sliding view controller.
28 | */
29 | @property (nonatomic, assign) NSInteger selectedIndex;
30 |
31 | /**
32 | The view controller in the sliding view controller assigned the given tag.
33 |
34 | @param tag The tag for the desired view controller.
35 | @return the first UIViewController whose tag matches.
36 | */
37 | - (UIViewController *)viewControllerWithTag:(NSInteger)tag;
38 |
39 | /**
40 | The index of the view controller in the sliding view controller assigned the given tag.
41 |
42 | @param tag The tag for the desired view controller index.
43 | @return the first index whose UIViewController's tag matches.
44 | */
45 | - (NSInteger)indexWithTag:(NSInteger)tag;
46 |
47 | @end
48 |
--------------------------------------------------------------------------------
/BKSlidingViewController.xcworkspace/xcshareddata/BKSlidingViewController.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | ED84C76A-DE3B-4E6C-A10C-6FDDF0B32AD0
9 | IDESourceControlProjectName
10 | BKSlidingViewController
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
14 | github.com:Basket/BKSlidingViewController.git
15 |
16 | IDESourceControlProjectPath
17 | BKSlidingViewController.xcworkspace
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
21 | ..
22 |
23 | IDESourceControlProjectURL
24 | github.com:Basket/BKSlidingViewController.git
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
36 | IDESourceControlWCCName
37 | BKSlidingViewController
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/BKSlidingViewController/BKSlidingViewController_Extras.h:
--------------------------------------------------------------------------------
1 | // Copyright 2015-present Andy Toulouse.
2 |
3 | #import "BKSlidingViewController.h"
4 |
5 | @protocol BKSlidingViewControllerDelegate;
6 |
7 | /**
8 | Additional functionality for `BKSlidingViewController` beyond its thin `UITabBarController`-like interface.
9 | */
10 | @interface BKSlidingViewController ()
11 |
12 | /**
13 | Return the amount the sliding view controller has scrolled in the range [0.0, 1.0], with the percentages are defined as:
14 |
15 | 0% The first view controller is centered.
16 | 50% If there is only one view controller, it is centered.
17 | 100% The last view controller is centered.
18 | */
19 | @property (nonatomic, assign, readonly) CGFloat percentScrolled;
20 |
21 | /**
22 | The delegate for the sliding view controller.
23 | */
24 | @property (nonatomic, weak) id delegate;
25 |
26 | @end
27 |
28 | /**
29 | The delegate protocol for the sliding view controller.
30 | */
31 | @protocol BKSlidingViewControllerDelegate
32 |
33 | @optional
34 |
35 | /**
36 | Called when the sliding view controller will begin dragging. Analogous to the corresponding scrollView method.
37 | */
38 | - (void)slidingViewControllerWillBeginDragging:(BKSlidingViewController *)slidingViewController;
39 |
40 | /**
41 | Called when the sliding view controller scrolls. Analogous to the corresponding scrollView method.
42 | */
43 | - (void)slidingViewControllerDidScroll:(BKSlidingViewController *)slidingViewController;
44 |
45 | /**
46 | Called when the sliding view controller ends decelerating. Analogous to the corresponding scrollView method.
47 | */
48 | - (void)slidingViewControllerDidEndDecelerating:(BKSlidingViewController *)slidingViewController;
49 | @end
--------------------------------------------------------------------------------
/BKSlidingViewController.xcodeproj/project.xcworkspace/xcshareddata/BKSlidingViewController.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | 9DF40F86-4F48-4E8F-BCEF-F8617E91F6CE
9 | IDESourceControlProjectName
10 | BKSlidingViewController
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
14 | github.com:Basket/BKSlidingViewController.git
15 |
16 | IDESourceControlProjectPath
17 | BKSlidingViewController.xcodeproj
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
21 | ../..
22 |
23 | IDESourceControlProjectURL
24 | github.com:Basket/BKSlidingViewController.git
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | 1B08D3D30C4A91BCCEA949ABBC700BC102BC97AD
36 | IDESourceControlWCCName
37 | BKSlidingViewController
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #########################
2 | # .gitignore file for Xcode4 / OS X Source projects
3 | #
4 | # Version 2.0
5 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects
6 | #
7 | # 2013 updates:
8 | # - fixed the broken "save personal Schemes"
9 | #
10 | # NB: if you are storing "built" products, this WILL NOT WORK,
11 | # and you should use a different .gitignore (or none at all)
12 | # This file is for SOURCE projects, where there are many extra
13 | # files that we want to exclude
14 | #
15 | #########################
16 |
17 | #####
18 | # OS X temporary files that should never be committed
19 |
20 | .DS_Store
21 | *.swp
22 | *.lock
23 | profile
24 |
25 |
26 | ####
27 | # Xcode temporary files that should never be committed
28 | #
29 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this...
30 |
31 | *~.nib
32 |
33 |
34 | ####
35 | # Xcode build files -
36 | #
37 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData"
38 |
39 | DerivedData/
40 |
41 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build"
42 |
43 | build/
44 |
45 |
46 | #####
47 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups)
48 | #
49 | # This is complicated:
50 | #
51 | # SOMETIMES you need to put this file in version control.
52 | # Apple designed it poorly - if you use "custom executables", they are
53 | # saved in this file.
54 | # 99% of projects do NOT use those, so they do NOT want to version control this file.
55 | # ..but if you're in the 1%, comment out the line "*.pbxuser"
56 |
57 | *.pbxuser
58 | *.mode1v3
59 | *.mode2v3
60 | *.perspectivev3
61 | # NB: also, whitelist the default ones, some projects need to use these
62 | !default.pbxuser
63 | !default.mode1v3
64 | !default.mode2v3
65 | !default.perspectivev3
66 |
67 |
68 | ####
69 | # Xcode 4 - semi-personal settings
70 | #
71 | #
72 | # OPTION 1: ---------------------------------
73 | # throw away ALL personal settings (including custom schemes!
74 | # - unless they are "shared")
75 | #
76 | # NB: this is exclusive with OPTION 2 below
77 | xcuserdata
78 |
79 | # OPTION 2: ---------------------------------
80 | # get rid of ALL personal settings, but KEEP SOME OF THEM
81 | # - NB: you must manually uncomment the bits you want to keep
82 | #
83 | # NB: this is exclusive with OPTION 1 above
84 | #
85 | #xcuserdata/**/*
86 |
87 | # (requires option 2 above): Personal Schemes
88 | #
89 | #!xcuserdata/**/xcschemes/*
90 |
91 | ####
92 | # XCode 4 workspaces - more detailed
93 | #
94 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :)
95 | #
96 | # Workspace layout is quite spammy. For reference:
97 | #
98 | # /(root)/
99 | # /(project-name).xcodeproj/
100 | # project.pbxproj
101 | # /project.xcworkspace/
102 | # contents.xcworkspacedata
103 | # /xcuserdata/
104 | # /(your name)/xcuserdatad/
105 | # UserInterfaceState.xcuserstate
106 | # /xcsshareddata/
107 | # /xcschemes/
108 | # (shared scheme name).xcscheme
109 | # /xcuserdata/
110 | # /(your name)/xcuserdatad/
111 | # (private scheme).xcscheme
112 | # xcschememanagement.plist
113 | #
114 | #
115 |
116 | ####
117 | # Xcode 4 - Deprecated classes
118 | #
119 | # Allegedly, if you manually "deprecate" your classes, they get moved here.
120 | #
121 | # We're using source-control, so this is a "feature" that we do not want!
122 |
123 | *.moved-aside
124 |
125 |
126 | ####
127 | # UNKNOWN: recommended by others, but I can't discover what these files are
128 | #
129 | # ...none. Everything is now explained.
130 |
131 | ####
132 | # CocoaPods
133 | #
134 | # Include Podfile and Podfile.lock but ignore the pods themselves
135 |
136 | Pods/
137 |
--------------------------------------------------------------------------------
/BKSlidingViewController.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 6B1C596A1988429A00A9FC48 /* UIViewController+BKTag.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 6BEE0A2C1987100100D73B41 /* UIViewController+BKTag.h */; };
11 | 6BEE0A1519870F5700D73B41 /* BKSlidingViewController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 6BEE0A1419870F5700D73B41 /* BKSlidingViewController.h */; };
12 | 6BEE0A1719870F5700D73B41 /* BKSlidingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE0A1619870F5700D73B41 /* BKSlidingViewController.m */; };
13 | 6BEE0A2F1987100100D73B41 /* UIViewController+BKTag.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BEE0A2B1987100100D73B41 /* UIViewController+BKTag.m */; };
14 | 99F09A4D684D3A42BD66C3F8 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 49D185DA94EBEB7EEC1ABA50 /* libPods.a */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXCopyFilesBuildPhase section */
18 | 6BEE0A0F19870F5700D73B41 /* CopyFiles */ = {
19 | isa = PBXCopyFilesBuildPhase;
20 | buildActionMask = 2147483647;
21 | dstPath = "include/$(PRODUCT_NAME)";
22 | dstSubfolderSpec = 16;
23 | files = (
24 | 6BEE0A1519870F5700D73B41 /* BKSlidingViewController.h in CopyFiles */,
25 | 6B1C596A1988429A00A9FC48 /* UIViewController+BKTag.h in CopyFiles */,
26 | );
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 2203431D931E412C4EAE5910 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; };
33 | 49D185DA94EBEB7EEC1ABA50 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
34 | 6BEE0A1119870F5700D73B41 /* libBKSlidingViewController.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBKSlidingViewController.a; sourceTree = BUILT_PRODUCTS_DIR; };
35 | 6BEE0A1419870F5700D73B41 /* BKSlidingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BKSlidingViewController.h; sourceTree = ""; };
36 | 6BEE0A1619870F5700D73B41 /* BKSlidingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BKSlidingViewController.m; sourceTree = ""; };
37 | 6BEE0A2B1987100100D73B41 /* UIViewController+BKTag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+BKTag.m"; sourceTree = ""; };
38 | 6BEE0A2C1987100100D73B41 /* UIViewController+BKTag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+BKTag.h"; sourceTree = ""; };
39 | D6D49356F45D34F97B1E885A /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; };
40 | /* End PBXFileReference section */
41 |
42 | /* Begin PBXFrameworksBuildPhase section */
43 | 6BEE0A0E19870F5700D73B41 /* Frameworks */ = {
44 | isa = PBXFrameworksBuildPhase;
45 | buildActionMask = 2147483647;
46 | files = (
47 | 99F09A4D684D3A42BD66C3F8 /* libPods.a in Frameworks */,
48 | );
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | /* End PBXFrameworksBuildPhase section */
52 |
53 | /* Begin PBXGroup section */
54 | 18B88C314925A5946098028A /* Pods */ = {
55 | isa = PBXGroup;
56 | children = (
57 | D6D49356F45D34F97B1E885A /* Pods.debug.xcconfig */,
58 | 2203431D931E412C4EAE5910 /* Pods.release.xcconfig */,
59 | );
60 | name = Pods;
61 | sourceTree = "";
62 | };
63 | 6BEE0A0819870F5700D73B41 = {
64 | isa = PBXGroup;
65 | children = (
66 | 6BEE0A1319870F5700D73B41 /* BKSlidingViewController */,
67 | 6BEE0A1219870F5700D73B41 /* Products */,
68 | 18B88C314925A5946098028A /* Pods */,
69 | 7A56D345A85686D265F48F26 /* Frameworks */,
70 | );
71 | sourceTree = "";
72 | };
73 | 6BEE0A1219870F5700D73B41 /* Products */ = {
74 | isa = PBXGroup;
75 | children = (
76 | 6BEE0A1119870F5700D73B41 /* libBKSlidingViewController.a */,
77 | );
78 | name = Products;
79 | sourceTree = "";
80 | };
81 | 6BEE0A1319870F5700D73B41 /* BKSlidingViewController */ = {
82 | isa = PBXGroup;
83 | children = (
84 | 6BEE0A1419870F5700D73B41 /* BKSlidingViewController.h */,
85 | 6BEE0A1619870F5700D73B41 /* BKSlidingViewController.m */,
86 | 6BEE0A2C1987100100D73B41 /* UIViewController+BKTag.h */,
87 | 6BEE0A2B1987100100D73B41 /* UIViewController+BKTag.m */,
88 | );
89 | path = BKSlidingViewController;
90 | sourceTree = "";
91 | };
92 | 7A56D345A85686D265F48F26 /* Frameworks */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 49D185DA94EBEB7EEC1ABA50 /* libPods.a */,
96 | );
97 | name = Frameworks;
98 | sourceTree = "";
99 | };
100 | /* End PBXGroup section */
101 |
102 | /* Begin PBXNativeTarget section */
103 | 6BEE0A1019870F5700D73B41 /* BKSlidingViewController */ = {
104 | isa = PBXNativeTarget;
105 | buildConfigurationList = 6BEE0A2519870F5700D73B41 /* Build configuration list for PBXNativeTarget "BKSlidingViewController" */;
106 | buildPhases = (
107 | 850533F88D5B73D818D4C5A2 /* Check Pods Manifest.lock */,
108 | 6BEE0A0D19870F5700D73B41 /* Sources */,
109 | 6BEE0A0E19870F5700D73B41 /* Frameworks */,
110 | 6BEE0A0F19870F5700D73B41 /* CopyFiles */,
111 | BBB21D433F36FBB84D7A38CD /* Copy Pods Resources */,
112 | );
113 | buildRules = (
114 | );
115 | dependencies = (
116 | );
117 | name = BKSlidingViewController;
118 | productName = BKSlidingViewController;
119 | productReference = 6BEE0A1119870F5700D73B41 /* libBKSlidingViewController.a */;
120 | productType = "com.apple.product-type.library.static";
121 | };
122 | /* End PBXNativeTarget section */
123 |
124 | /* Begin PBXProject section */
125 | 6BEE0A0919870F5700D73B41 /* Project object */ = {
126 | isa = PBXProject;
127 | attributes = {
128 | LastUpgradeCheck = 0600;
129 | ORGANIZATIONNAME = "650 Industries, Inc.";
130 | TargetAttributes = {
131 | 6BEE0A1019870F5700D73B41 = {
132 | CreatedOnToolsVersion = 6.0;
133 | };
134 | };
135 | };
136 | buildConfigurationList = 6BEE0A0C19870F5700D73B41 /* Build configuration list for PBXProject "BKSlidingViewController" */;
137 | compatibilityVersion = "Xcode 3.2";
138 | developmentRegion = English;
139 | hasScannedForEncodings = 0;
140 | knownRegions = (
141 | en,
142 | );
143 | mainGroup = 6BEE0A0819870F5700D73B41;
144 | productRefGroup = 6BEE0A1219870F5700D73B41 /* Products */;
145 | projectDirPath = "";
146 | projectRoot = "";
147 | targets = (
148 | 6BEE0A1019870F5700D73B41 /* BKSlidingViewController */,
149 | );
150 | };
151 | /* End PBXProject section */
152 |
153 | /* Begin PBXShellScriptBuildPhase section */
154 | 850533F88D5B73D818D4C5A2 /* Check Pods Manifest.lock */ = {
155 | isa = PBXShellScriptBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | );
159 | inputPaths = (
160 | );
161 | name = "Check Pods Manifest.lock";
162 | outputPaths = (
163 | );
164 | runOnlyForDeploymentPostprocessing = 0;
165 | shellPath = /bin/sh;
166 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
167 | showEnvVarsInLog = 0;
168 | };
169 | BBB21D433F36FBB84D7A38CD /* Copy Pods Resources */ = {
170 | isa = PBXShellScriptBuildPhase;
171 | buildActionMask = 2147483647;
172 | files = (
173 | );
174 | inputPaths = (
175 | );
176 | name = "Copy Pods Resources";
177 | outputPaths = (
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | shellPath = /bin/sh;
181 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
182 | showEnvVarsInLog = 0;
183 | };
184 | /* End PBXShellScriptBuildPhase section */
185 |
186 | /* Begin PBXSourcesBuildPhase section */
187 | 6BEE0A0D19870F5700D73B41 /* Sources */ = {
188 | isa = PBXSourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | 6BEE0A1719870F5700D73B41 /* BKSlidingViewController.m in Sources */,
192 | 6BEE0A2F1987100100D73B41 /* UIViewController+BKTag.m in Sources */,
193 | );
194 | runOnlyForDeploymentPostprocessing = 0;
195 | };
196 | /* End PBXSourcesBuildPhase section */
197 |
198 | /* Begin XCBuildConfiguration section */
199 | 6BEE0A2319870F5700D73B41 /* Debug */ = {
200 | isa = XCBuildConfiguration;
201 | buildSettings = {
202 | ALWAYS_SEARCH_USER_PATHS = NO;
203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
204 | CLANG_CXX_LIBRARY = "libc++";
205 | CLANG_ENABLE_MODULES = YES;
206 | CLANG_ENABLE_OBJC_ARC = YES;
207 | CLANG_WARN_BOOL_CONVERSION = YES;
208 | CLANG_WARN_CONSTANT_CONVERSION = YES;
209 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
210 | CLANG_WARN_EMPTY_BODY = YES;
211 | CLANG_WARN_ENUM_CONVERSION = YES;
212 | CLANG_WARN_INT_CONVERSION = YES;
213 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
214 | CLANG_WARN_UNREACHABLE_CODE = YES;
215 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
216 | COPY_PHASE_STRIP = NO;
217 | ENABLE_STRICT_OBJC_MSGSEND = YES;
218 | GCC_C_LANGUAGE_STANDARD = gnu99;
219 | GCC_DYNAMIC_NO_PIC = NO;
220 | GCC_OPTIMIZATION_LEVEL = 0;
221 | GCC_PREPROCESSOR_DEFINITIONS = (
222 | "DEBUG=1",
223 | "$(inherited)",
224 | );
225 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
226 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
227 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
228 | GCC_WARN_UNDECLARED_SELECTOR = YES;
229 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
230 | GCC_WARN_UNUSED_FUNCTION = YES;
231 | GCC_WARN_UNUSED_VARIABLE = YES;
232 | IPHONEOS_DEPLOYMENT_TARGET = 7.0;
233 | MTL_ENABLE_DEBUG_INFO = YES;
234 | ONLY_ACTIVE_ARCH = YES;
235 | SDKROOT = iphoneos;
236 | };
237 | name = Debug;
238 | };
239 | 6BEE0A2419870F5700D73B41 /* Release */ = {
240 | isa = XCBuildConfiguration;
241 | buildSettings = {
242 | ALWAYS_SEARCH_USER_PATHS = NO;
243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
244 | CLANG_CXX_LIBRARY = "libc++";
245 | CLANG_ENABLE_MODULES = YES;
246 | CLANG_ENABLE_OBJC_ARC = YES;
247 | CLANG_WARN_BOOL_CONVERSION = YES;
248 | CLANG_WARN_CONSTANT_CONVERSION = YES;
249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
250 | CLANG_WARN_EMPTY_BODY = YES;
251 | CLANG_WARN_ENUM_CONVERSION = YES;
252 | CLANG_WARN_INT_CONVERSION = YES;
253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
254 | CLANG_WARN_UNREACHABLE_CODE = YES;
255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
256 | COPY_PHASE_STRIP = YES;
257 | ENABLE_NS_ASSERTIONS = NO;
258 | ENABLE_STRICT_OBJC_MSGSEND = YES;
259 | GCC_C_LANGUAGE_STANDARD = gnu99;
260 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
261 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
262 | GCC_WARN_UNDECLARED_SELECTOR = YES;
263 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
264 | GCC_WARN_UNUSED_FUNCTION = YES;
265 | GCC_WARN_UNUSED_VARIABLE = YES;
266 | IPHONEOS_DEPLOYMENT_TARGET = 7.0;
267 | MTL_ENABLE_DEBUG_INFO = NO;
268 | SDKROOT = iphoneos;
269 | VALIDATE_PRODUCT = YES;
270 | };
271 | name = Release;
272 | };
273 | 6BEE0A2619870F5700D73B41 /* Debug */ = {
274 | isa = XCBuildConfiguration;
275 | baseConfigurationReference = D6D49356F45D34F97B1E885A /* Pods.debug.xcconfig */;
276 | buildSettings = {
277 | PRODUCT_NAME = "$(TARGET_NAME)";
278 | SKIP_INSTALL = YES;
279 | };
280 | name = Debug;
281 | };
282 | 6BEE0A2719870F5700D73B41 /* Release */ = {
283 | isa = XCBuildConfiguration;
284 | baseConfigurationReference = 2203431D931E412C4EAE5910 /* Pods.release.xcconfig */;
285 | buildSettings = {
286 | PRODUCT_NAME = "$(TARGET_NAME)";
287 | SKIP_INSTALL = YES;
288 | };
289 | name = Release;
290 | };
291 | /* End XCBuildConfiguration section */
292 |
293 | /* Begin XCConfigurationList section */
294 | 6BEE0A0C19870F5700D73B41 /* Build configuration list for PBXProject "BKSlidingViewController" */ = {
295 | isa = XCConfigurationList;
296 | buildConfigurations = (
297 | 6BEE0A2319870F5700D73B41 /* Debug */,
298 | 6BEE0A2419870F5700D73B41 /* Release */,
299 | );
300 | defaultConfigurationIsVisible = 0;
301 | defaultConfigurationName = Release;
302 | };
303 | 6BEE0A2519870F5700D73B41 /* Build configuration list for PBXNativeTarget "BKSlidingViewController" */ = {
304 | isa = XCConfigurationList;
305 | buildConfigurations = (
306 | 6BEE0A2619870F5700D73B41 /* Debug */,
307 | 6BEE0A2719870F5700D73B41 /* Release */,
308 | );
309 | defaultConfigurationIsVisible = 0;
310 | defaultConfigurationName = Release;
311 | };
312 | /* End XCConfigurationList section */
313 | };
314 | rootObject = 6BEE0A0919870F5700D73B41 /* Project object */;
315 | }
316 |
--------------------------------------------------------------------------------
/BKSlidingViewController/BKSlidingViewController.m:
--------------------------------------------------------------------------------
1 | // Copyright 2014-present 650 Industries.
2 | // Copyright 2015-present Andy Toulouse.
3 |
4 | #import "BKSlidingViewController.h"
5 | #import "BKSlidingViewController_Extras.h"
6 |
7 | #import
8 | #import
9 |
10 | #import "UIViewController+BKTag.h"
11 |
12 | typedef NS_ENUM(NSUInteger, BKSlidingViewControllerVisibility) {
13 | BKSlidingViewControllerVisibilityUnchanged,
14 | BKSlidingViewControllerVisibilityShouldShow,
15 | BKSlidingViewControllerVisibilityShouldHide,
16 | };
17 |
18 | @interface BKSlidingViewController () {
19 | BOOL _needsSelectedIndexUpdate;
20 | }
21 |
22 | - (void)setNeedsSelectedIndexUpdate;
23 | - (BOOL)needsSelectedIndexUpdate;
24 | - (void)updateSelectedIndexIfNeeded;
25 |
26 | @end
27 |
28 | @implementation BKSlidingViewController {
29 | UIScrollView *_scrollView;
30 |
31 | NSMutableArray *_viewControllers;
32 | NSMutableArray *_viewControllerParentViews;
33 | NSMapTable *_viewControllersVisible;
34 | NSMapTable *_viewControllersShouldChangeVisibility;
35 |
36 | CGFloat _interPageSpacing;
37 | }
38 |
39 |
40 | @synthesize viewControllers = _viewControllers;
41 |
42 | - (void)_initializeInstance
43 | {
44 | _interPageSpacing = 1;
45 | _viewControllers = [NSMutableArray array];
46 | _viewControllerParentViews = [NSMutableArray array];
47 | _viewControllersVisible = [NSMapTable weakToStrongObjectsMapTable];
48 | _viewControllersShouldChangeVisibility = [NSMapTable weakToStrongObjectsMapTable];
49 | }
50 |
51 | - (instancetype)initWithCoder:(NSCoder *)aDecoder
52 | {
53 | if (self = [super initWithCoder:aDecoder]) {
54 | [self _initializeInstance];
55 | }
56 | return self;
57 | }
58 |
59 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
60 | {
61 |
62 | if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
63 | [self _initializeInstance];
64 | }
65 | return self;
66 | }
67 |
68 | - (void)viewDidLoad
69 | {
70 | [super viewDidLoad];
71 | _scrollView = [[UIScrollView alloc] init];
72 | _scrollView.bounces = NO;
73 | _scrollView.delaysContentTouches = NO;
74 | _scrollView.delegate = self;
75 | _scrollView.pagingEnabled = YES;
76 | _scrollView.showsHorizontalScrollIndicator = NO;
77 | _scrollView.scrollsToTop = NO;
78 | [self.view addSubview:_scrollView];
79 | }
80 |
81 | - (void)viewWillLayoutSubviews
82 | {
83 | CGRect scrollViewRect = self.view.bounds;
84 | scrollViewRect.size.width += _interPageSpacing;
85 | _scrollView.frame = scrollViewRect;
86 |
87 | [_viewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
88 | UIView *parentView = _viewControllerParentViews[idx];
89 |
90 | CGRect baseRect = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));
91 | CGRect offsetRect = CGRectOffset(baseRect, idx * CGRectGetWidth(_scrollView.bounds), 0);
92 | parentView.frame = offsetRect;
93 | ((UIViewController *)obj).view.frame = parentView.bounds;
94 | }];
95 |
96 | }
97 |
98 | - (BOOL)shouldAutomaticallyForwardAppearanceMethods
99 | {
100 | return NO;
101 | }
102 |
103 | #pragma mark - View controller selection
104 |
105 | - (void)setViewControllers:(NSArray *)viewControllers
106 | {
107 | // Skip no-ops
108 | if (_viewControllers == viewControllers || [_viewControllers isEqualToArray:viewControllers]) {
109 | return;
110 | }
111 |
112 | UIViewController *maximallyVisibleVC = [self _maximallyVisibleViewController];
113 |
114 | // [[[ Calculate viewcontroller differences and sync up the parent views.
115 | BKDelta *differences = [[BKDeltaCalculator defaultCalculator] deltaFromOldArray:_viewControllers toNewArray:viewControllers];
116 | NSIndexSet *removedIndices = differences.removedIndices;
117 | NSIndexSet *addedIndices = differences.addedIndices;
118 |
119 | NSArray *removedViewControllers = [_viewControllers objectsAtIndexes:removedIndices];
120 | NSArray *addedViewControllers = [viewControllers objectsAtIndexes:addedIndices];
121 |
122 | NSArray *removedParentViews = [_viewControllerParentViews objectsAtIndexes:removedIndices];
123 | [_viewControllerParentViews removeObjectsAtIndexes:removedIndices];
124 | [_viewControllers removeObjectsAtIndexes:removedIndices];
125 |
126 | [addedIndices enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
127 | UIViewController *addedVC = viewControllers[idx];
128 | UIView *parentView = [[UIView alloc] init];
129 | [parentView addSubview:addedVC.view];
130 |
131 | [_viewControllers insertObject:addedVC atIndex:idx];
132 | [_viewControllerParentViews insertObject:parentView atIndex:idx];
133 | }];
134 | NSArray *addedParentViews = [_viewControllerParentViews objectsAtIndexes:addedIndices];
135 |
136 | for (UIViewController *addedVC in addedViewControllers) {
137 | [self _setViewController:addedVC visible:NO];
138 | }
139 | for (UIViewController *removedVC in removedViewControllers) {
140 | [self _setViewController:removedVC visible:NO];
141 | }
142 | // ]]]
143 |
144 | // Reconciliation logic: view controller selection
145 | UIViewController *targetViewController;
146 | if ([_viewControllers containsObject:_selectedViewController]) {
147 | targetViewController = _selectedViewController;
148 | } else if (_selectedIndex < _viewControllers.count) {
149 | targetViewController = _viewControllers[_selectedIndex];
150 | } else {
151 | targetViewController = [_viewControllers lastObject];
152 | }
153 |
154 | [UIView animateWithDuration:0 animations:^{ // Inherit animation context if any
155 | [self.view layoutIfNeeded];
156 |
157 | for (UIViewController *viewController in removedViewControllers) {
158 | [viewController willMoveToParentViewController:nil];
159 | }
160 | [addedViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
161 | [self addChildViewController:(UIViewController *)obj];
162 | UIView *parentView = addedParentViews[idx];
163 | [_scrollView addSubview:parentView];
164 | [self.view setNeedsLayout];
165 | }];
166 |
167 | _scrollView.contentSize = CGSizeMake(_viewControllers.count * CGRectGetWidth(_scrollView.bounds), CGRectGetHeight(_scrollView.bounds));
168 | [self.view layoutIfNeeded];
169 |
170 | // Force reselection of the view controller
171 | [self _setSelectedViewController:targetViewController];
172 | [self setNeedsSelectedIndexUpdate];
173 | [self updateSelectedIndexIfNeeded];
174 | [self _scrollViewportFromViewController:maximallyVisibleVC toViewController:targetViewController];
175 | } completion:^(BOOL finished) {
176 | [removedViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
177 | UIView *parentView = removedParentViews[idx];
178 | [parentView removeFromSuperview];
179 | [(UIViewController *)obj removeFromParentViewController];
180 | }];
181 | for (UIViewController *viewController in addedViewControllers) {
182 | [viewController didMoveToParentViewController:self];
183 | }
184 | }];
185 | }
186 |
187 | - (void)setSelectedViewController:(UIViewController *)selectedViewController
188 | {
189 | if (_selectedViewController != selectedViewController) {
190 | UIViewController *previousSelectedVC = _selectedViewController;
191 | [self _setSelectedViewController:selectedViewController];
192 | [self _scrollViewportFromViewController:previousSelectedVC toViewController:_selectedViewController];
193 | }
194 | }
195 |
196 | // Doesn't set the viewport
197 | - (void)_setSelectedViewController:(UIViewController *)selectedViewController
198 | {
199 | if (_selectedViewController != selectedViewController) {
200 | _selectedViewController = selectedViewController;
201 | [self setNeedsSelectedIndexUpdate];
202 | }
203 | }
204 |
205 | - (void)setSelectedIndex:(NSInteger)selectedIndex
206 | {
207 | if (_selectedIndex != selectedIndex) {
208 | NSAssert(selectedIndex < _viewControllers.count, @"Cannot select an index which is out of range");
209 | UIViewController *previousSelectedVC = _selectedViewController;
210 | [self _setSelectedViewController:_viewControllers[selectedIndex]];
211 | [self _scrollViewportFromViewController:previousSelectedVC toViewController:_selectedViewController];
212 | }
213 | }
214 |
215 | #pragma mark - Selection update neediness
216 |
217 | - (void)setNeedsSelectedIndexUpdate
218 | {
219 | _needsSelectedIndexUpdate = YES;
220 | }
221 |
222 | - (BOOL)needsSelectedIndexUpdate
223 | {
224 | return _needsSelectedIndexUpdate;
225 | }
226 |
227 | - (void)updateSelectedIndexIfNeeded
228 | {
229 | if (_needsSelectedIndexUpdate) {
230 | NSUInteger newSelectedIndex = [_viewControllers indexOfObject:_selectedViewController];
231 | NSAssert(newSelectedIndex != NSNotFound, @"Cannot select a view controller which is not present");
232 |
233 | if (_selectedIndex != newSelectedIndex) {
234 | [self willChangeValueForKey:@"selectedIndex"];
235 | _selectedIndex = newSelectedIndex;
236 | [self didChangeValueForKey:@"selectedIndex"];
237 | }
238 | [self setNeedsStatusBarAppearanceUpdate];
239 |
240 | _needsSelectedIndexUpdate = NO;
241 | }
242 | }
243 |
244 | #pragma mark - Tagging
245 |
246 | - (UIViewController *)viewControllerWithTag:(NSInteger)tag
247 | {
248 | for (UIViewController *viewController in self.viewControllers) {
249 | if (viewController.bk_tag == tag) {
250 | return viewController;
251 | }
252 | }
253 | return nil;
254 | }
255 |
256 | - (NSInteger)indexWithTag:(NSInteger)tag
257 | {
258 | for (NSUInteger i = 0; i < self.viewControllers.count; i++) {
259 | UIViewController *viewController = self.viewControllers[i];
260 | if (viewController.bk_tag == tag) {
261 | return i;
262 | }
263 | }
264 | return NSNotFound;
265 | }
266 |
267 | #pragma mark - Child view controller status bar methods
268 |
269 | - (UIViewController *)childViewControllerForStatusBarStyle
270 | {
271 | return _selectedViewController;
272 | }
273 |
274 | - (UIViewController *)childViewControllerForStatusBarHidden
275 | {
276 | return _selectedViewController;
277 | }
278 |
279 | #pragma mark -
280 |
281 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
282 | {
283 | if ([_delegate respondsToSelector:@selector(slidingViewControllerWillBeginDragging:)]) {
284 | [_delegate slidingViewControllerWillBeginDragging:self];
285 | }
286 | }
287 |
288 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView
289 | {
290 | CGFloat scrollViewWidth = CGRectGetWidth(scrollView.bounds);
291 | CGRect visibleRect = [self _viewport];
292 |
293 | CGFloat middleOffset = CGRectGetMidX(visibleRect);
294 | CGFloat approximateMiddleIndex = middleOffset / scrollViewWidth;
295 | NSInteger middleIndex = (NSInteger)floor(approximateMiddleIndex);
296 |
297 | NSUInteger minIndex = middleIndex > 0 ? middleIndex - 1 : 0;
298 | NSUInteger maxIndex = middleIndex < (_viewControllers.count - 1) ? middleIndex + 1 : _viewControllers.count - 1;
299 | NSRange validIndexRange = NSMakeRange(minIndex, maxIndex - minIndex + 1);
300 | NSIndexSet *validIndicesToCheck = [NSIndexSet indexSetWithIndexesInRange:validIndexRange];
301 | [_viewControllers enumerateObjectsAtIndexes:validIndicesToCheck
302 | options:0
303 | usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
304 | UIViewController *vc = obj;
305 | UIView *parentView = _viewControllerParentViews[idx];
306 |
307 | BOOL intersects = CGRectIntersectsRect(visibleRect, ((CALayer *)parentView.layer.presentationLayer).frame);
308 | if (intersects) {
309 | [self _setViewController:vc shouldChangeVisibility:BKSlidingViewControllerVisibilityShouldShow];
310 | [self _updateAppearanceForViewControllerIfNeeded:vc animated:YES];
311 | }
312 | }];
313 |
314 | if ([_delegate respondsToSelector:@selector(slidingViewControllerDidScroll:)]) {
315 | [_delegate slidingViewControllerDidScroll:self];
316 | }
317 | }
318 |
319 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
320 | {
321 | UIViewController *maximallyVisibleVC = [self _maximallyVisibleViewController];
322 | for (UIViewController *viewController in _viewControllers) {
323 | if (maximallyVisibleVC == viewController) {
324 | continue;
325 | }
326 | [self _setViewController:viewController shouldChangeVisibility:BKSlidingViewControllerVisibilityShouldHide];
327 | [self _updateAppearanceForViewControllerIfNeeded:viewController animated:NO];
328 | }
329 |
330 | [self _setSelectedViewController:maximallyVisibleVC];
331 | [self updateSelectedIndexIfNeeded];
332 |
333 | if ([_delegate respondsToSelector:@selector(slidingViewControllerDidEndDecelerating:)]) {
334 | [_delegate slidingViewControllerDidEndDecelerating:self];
335 | }
336 | }
337 |
338 | #pragma mark - Extra method
339 |
340 | - (CGFloat)percentScrolled
341 | {
342 | CGFloat screenWidth = CGRectGetWidth(_scrollView.bounds);
343 | CGFloat totalWidth = _scrollView.contentSize.width + _scrollView.contentInset.left + _scrollView.contentInset.right;
344 | CGFloat workingWidth = totalWidth - screenWidth;
345 |
346 | CGFloat insetAdjustedXOffset = _scrollView.contentInset.left + _scrollView.contentOffset.x;
347 | CGFloat percent;
348 | if (workingWidth == 0) {
349 | percent = 0.5;
350 | } else {
351 | percent = insetAdjustedXOffset / workingWidth;
352 | }
353 | return percent;
354 | }
355 |
356 | #pragma mark - Rotation (NOTE: currently disabled)
357 |
358 | // Disabled for now - rotation isn't production-ready.
359 | - (BOOL)shouldAutorotate
360 | {
361 | return NO;
362 | }
363 |
364 | #if defined(__IPHONE_8_0)
365 | // iOS 8
366 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator
367 | {
368 | [coordinator animateAlongsideTransition:^(id context) {
369 | CGFloat screenWidth = CGRectGetWidth(_scrollView.bounds);
370 | _scrollView.contentOffset = CGPointMake(self.selectedIndex * screenWidth, 0);
371 | _scrollView.contentSize = CGSizeMake(_viewControllers.count * CGRectGetWidth(_scrollView.bounds), CGRectGetHeight(_scrollView.bounds));
372 | } completion:nil];
373 |
374 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
375 | }
376 | #endif
377 |
378 | // iOS <= 7
379 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
380 | {
381 | CGFloat screenWidth = CGRectGetWidth(_scrollView.bounds);
382 | _scrollView.contentOffset = CGPointMake(self.selectedIndex * screenWidth, 0);
383 | _scrollView.contentSize = CGSizeMake(_viewControllers.count * CGRectGetWidth(_scrollView.bounds), CGRectGetHeight(_scrollView.bounds));
384 | [self.view layoutIfNeeded];
385 |
386 | [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
387 | }
388 |
389 | #pragma mark - Helpers
390 |
391 | - (UIViewController *)_maximallyVisibleViewController
392 | {
393 | // Find visible view controllers
394 | CGRect visibleRect = [self _viewport];
395 | NSArray *visibleViewControllers = [self _viewControllersVisibleInRect:visibleRect];
396 |
397 | __block CGFloat maximumArea = -1;
398 | __block UIViewController *maximallyVisibleVC = nil;
399 | [visibleViewControllers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
400 | UIViewController *visibleVC = obj;
401 | UIView *parentView = _viewControllerParentViews[idx];
402 | CGRect intersection = CGRectIntersection(visibleRect, parentView.frame);
403 | CGFloat area = intersection.size.width * intersection.size.height;
404 | if (area > maximumArea) {
405 | maximumArea = area;
406 | maximallyVisibleVC = visibleVC;
407 | }
408 | }];
409 | return maximallyVisibleVC;
410 | }
411 |
412 | - (CGRect)_viewport
413 | {
414 | CALayer *visibleLayer = _scrollView.layer.presentationLayer;
415 | CGRect visibleRect = visibleLayer.bounds;
416 | return visibleRect;
417 | }
418 |
419 | - (NSArray *)_viewControllersVisibleInRect:(CGRect)rect
420 | {
421 | NSMutableArray *visibleViewControllers = [NSMutableArray array];
422 | [_viewControllerParentViews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
423 | UIViewController *visibleVC = _viewControllers[idx];
424 | UIView *parentView = obj;
425 | if (CGRectIntersectsRect(rect, parentView.frame)) {
426 | [visibleViewControllers addObject:visibleVC];
427 | }
428 | }];
429 | return visibleViewControllers;
430 | }
431 |
432 | - (void)_scrollViewportFromViewController:(UIViewController *)fromViewController
433 | toViewController:(UIViewController *)toViewController
434 | {
435 | CGRect targetRect = toViewController.view.superview.frame;
436 | CGFloat zoomScale = CGRectGetWidth(toViewController.view.superview.frame) / fmax(CGRectGetWidth(self.view.bounds), 1); // fmax to avoid div-by-0
437 | CGRect transformedRect = CGRectApplyAffineTransform(targetRect, CGAffineTransformMakeScale(zoomScale, zoomScale));
438 | _scrollView.contentOffset = transformedRect.origin;
439 | _scrollView.zoomScale = zoomScale;
440 |
441 | // Magic to figure out if our scrollview is animating its contentOffset change. Must happen _after_ the change,
442 | // because it works by detecting the added animation. NOTE: 0.001 seems to be the usual animation duration for
443 | // 'instant' animations. 0.25 is CATransaction's default animationDuration (for implicit animations).
444 | CAAnimation *animation = [_scrollView.layer animationForKey:@"bounds"];
445 | // if someone disabled actions and there are _no_ animations, nil < 0.001
446 | BOOL isAnimating = animation.duration > 0.001;
447 |
448 | [self _setViewController:fromViewController shouldChangeVisibility:BKSlidingViewControllerVisibilityShouldHide];
449 | [self _setViewController:toViewController shouldChangeVisibility:BKSlidingViewControllerVisibilityShouldShow];
450 |
451 | [self updateSelectedIndexIfNeeded];
452 |
453 | [self _updateAppearanceForViewControllerIfNeeded:fromViewController animated:isAnimating];
454 | [self _updateAppearanceForViewControllerIfNeeded:toViewController animated:isAnimating];
455 | }
456 |
457 | - (void)_updateAppearanceForViewControllerIfNeeded:(UIViewController *)viewController animated:(BOOL)animated
458 | {
459 | BOOL needed = [self _appearanceTransitionNeededForViewController:viewController];
460 | if (!needed) {
461 | [self _setViewController:viewController shouldChangeVisibility:BKSlidingViewControllerVisibilityUnchanged];
462 | return;
463 | }
464 |
465 | BOOL isAppearing = [self _shouldChangeVisibilityForViewController:viewController] == BKSlidingViewControllerVisibilityShouldShow;
466 | [viewController beginAppearanceTransition:isAppearing animated:animated];
467 |
468 | void (^animationBlock)(void) = ^{
469 | if (isAppearing) {
470 | viewController.view.hidden = NO;
471 | }
472 | [self _setViewController:viewController visible:isAppearing];
473 | };
474 | void (^completionBlock)(void) = ^{
475 | [viewController endAppearanceTransition];
476 | [self _setViewController:viewController shouldChangeVisibility:BKSlidingViewControllerVisibilityUnchanged];
477 | };
478 |
479 | if (!animated) {
480 | animationBlock();
481 | completionBlock();
482 | } else {
483 | [UIView animateWithDuration:0
484 | animations:animationBlock
485 | completion:^(BOOL finished) {
486 | completionBlock();
487 | }];
488 | }
489 | }
490 |
491 | - (BOOL)_appearanceTransitionNeededForViewController:(UIViewController *)viewController
492 | {
493 | BOOL isVisible = [self _visibilityForViewController:viewController];
494 | BKSlidingViewControllerVisibility desiredVisibility = [self _shouldChangeVisibilityForViewController:viewController];
495 | switch (desiredVisibility) {
496 | case BKSlidingViewControllerVisibilityUnchanged:
497 | return NO;
498 | case BKSlidingViewControllerVisibilityShouldShow:
499 | return !isVisible;
500 | case BKSlidingViewControllerVisibilityShouldHide:
501 | return isVisible;
502 | }
503 | }
504 |
505 | - (BOOL)_visibilityForViewController:(UIViewController *)viewController
506 | {
507 | return [(NSNumber *)[_viewControllersVisible objectForKey:viewController] boolValue];
508 | }
509 |
510 | - (void)_setViewController:(UIViewController *)viewController visible:(BOOL)visible
511 | {
512 | [_viewControllersVisible setObject:@(visible) forKey:viewController];
513 | }
514 |
515 | - (BKSlidingViewControllerVisibility)_shouldChangeVisibilityForViewController:(UIViewController *)viewController
516 | {
517 | return [(NSNumber *)[_viewControllersShouldChangeVisibility objectForKey:viewController] unsignedIntegerValue];
518 | }
519 |
520 | - (void)_setViewController:(UIViewController *)viewController shouldChangeVisibility:(BKSlidingViewControllerVisibility)change
521 | {
522 | [_viewControllersShouldChangeVisibility setObject:@(change) forKey:viewController];
523 | }
524 |
525 | @end
--------------------------------------------------------------------------------