├── 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 --------------------------------------------------------------------------------