├── .DS_Store ├── .gitignore ├── .ruby-version ├── Example ├── .DS_Store ├── SampleSporify.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ │ └── Package.resolved │ │ └── xcuserdata │ │ │ └── emre.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── daniel.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── emre.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── SampleSporify │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── MainTabBarController.swift │ ├── SampleChildViewController.swift │ ├── SampleChildViewController.xib │ ├── SceneDelegate.swift │ ├── SecondViewController.swift │ └── ViewController.swift ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Package.swift ├── README.md ├── StickyTabBarViewController.podspec ├── StickyTabBarViewController └── Classes │ ├── ExpandableViewController.swift │ ├── ExpandableViewController.xib │ └── StickyViewControllerSupportingTabBarController.swift └── pull_request_template.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emrepun/StickyTabBarViewController/192800d8a31fab77507a29baf14c05e2a15395af/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | *.xcuserstate 92 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.3 2 | -------------------------------------------------------------------------------- /Example/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emrepun/StickyTabBarViewController/192800d8a31fab77507a29baf14c05e2a15395af/Example/.DS_Store -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C349F51A2431528C0024BD03 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C349F5192431528C0024BD03 /* MainTabBarController.swift */; }; 11 | C3898088242569D100F5F786 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3898087242569D100F5F786 /* AppDelegate.swift */; }; 12 | C389808C242569D100F5F786 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C389808B242569D100F5F786 /* ViewController.swift */; }; 13 | C389808F242569D100F5F786 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C389808D242569D100F5F786 /* Main.storyboard */; }; 14 | C3898091242569D200F5F786 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C3898090242569D200F5F786 /* Assets.xcassets */; }; 15 | C3898094242569D200F5F786 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C3898092242569D200F5F786 /* LaunchScreen.storyboard */; }; 16 | C389809E24256A4E00F5F786 /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C389809D24256A4E00F5F786 /* SecondViewController.swift */; }; 17 | C38980A224256AD800F5F786 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38980A124256AD800F5F786 /* SceneDelegate.swift */; }; 18 | C38980AC24257D5700F5F786 /* SampleChildViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38980AA24257D5700F5F786 /* SampleChildViewController.swift */; }; 19 | C38980AD24257D5700F5F786 /* SampleChildViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C38980AB24257D5700F5F786 /* SampleChildViewController.xib */; }; 20 | FBCA824F293BE70E003B2BC6 /* StickyTabBarViewController in Frameworks */ = {isa = PBXBuildFile; productRef = FBCA824E293BE70E003B2BC6 /* StickyTabBarViewController */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | C349F5192431528C0024BD03 /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; 25 | C3898084242569D100F5F786 /* SampleSporify.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleSporify.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | C3898087242569D100F5F786 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | C389808B242569D100F5F786 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 28 | C389808E242569D100F5F786 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | C3898090242569D200F5F786 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | C3898093242569D200F5F786 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | C3898095242569D200F5F786 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | C389809D24256A4E00F5F786 /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 33 | C38980A124256AD800F5F786 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 34 | C38980AA24257D5700F5F786 /* SampleChildViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleChildViewController.swift; sourceTree = ""; }; 35 | C38980AB24257D5700F5F786 /* SampleChildViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SampleChildViewController.xib; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | C3898081242569D100F5F786 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | FBCA824F293BE70E003B2BC6 /* StickyTabBarViewController in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 60FEE64ADFEB759EB35BF190 /* Frameworks */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | ); 54 | name = Frameworks; 55 | sourceTree = ""; 56 | }; 57 | C389807B242569D100F5F786 = { 58 | isa = PBXGroup; 59 | children = ( 60 | C3898086242569D100F5F786 /* SampleSporify */, 61 | C3898085242569D100F5F786 /* Products */, 62 | 60FEE64ADFEB759EB35BF190 /* Frameworks */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | C3898085242569D100F5F786 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | C3898084242569D100F5F786 /* SampleSporify.app */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | C3898086242569D100F5F786 /* SampleSporify */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | C3898087242569D100F5F786 /* AppDelegate.swift */, 78 | C38980A124256AD800F5F786 /* SceneDelegate.swift */, 79 | C389808B242569D100F5F786 /* ViewController.swift */, 80 | C389809D24256A4E00F5F786 /* SecondViewController.swift */, 81 | C38980AA24257D5700F5F786 /* SampleChildViewController.swift */, 82 | C38980AB24257D5700F5F786 /* SampleChildViewController.xib */, 83 | C389808D242569D100F5F786 /* Main.storyboard */, 84 | C3898090242569D200F5F786 /* Assets.xcassets */, 85 | C3898092242569D200F5F786 /* LaunchScreen.storyboard */, 86 | C3898095242569D200F5F786 /* Info.plist */, 87 | C349F5192431528C0024BD03 /* MainTabBarController.swift */, 88 | ); 89 | path = SampleSporify; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | C3898083242569D100F5F786 /* SampleSporify */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = C3898098242569D200F5F786 /* Build configuration list for PBXNativeTarget "SampleSporify" */; 98 | buildPhases = ( 99 | C3898080242569D100F5F786 /* Sources */, 100 | C3898081242569D100F5F786 /* Frameworks */, 101 | C3898082242569D100F5F786 /* Resources */, 102 | ); 103 | buildRules = ( 104 | ); 105 | dependencies = ( 106 | ); 107 | name = SampleSporify; 108 | packageProductDependencies = ( 109 | FBCA824E293BE70E003B2BC6 /* StickyTabBarViewController */, 110 | ); 111 | productName = SampleSporify; 112 | productReference = C3898084242569D100F5F786 /* SampleSporify.app */; 113 | productType = "com.apple.product-type.application"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | C389807C242569D100F5F786 /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastSwiftUpdateCheck = 1130; 122 | LastUpgradeCheck = 1130; 123 | ORGANIZATIONNAME = "Emre Havan"; 124 | TargetAttributes = { 125 | C3898083242569D100F5F786 = { 126 | CreatedOnToolsVersion = 11.3; 127 | }; 128 | }; 129 | }; 130 | buildConfigurationList = C389807F242569D100F5F786 /* Build configuration list for PBXProject "SampleSporify" */; 131 | compatibilityVersion = "Xcode 9.3"; 132 | developmentRegion = en; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | Base, 137 | ); 138 | mainGroup = C389807B242569D100F5F786; 139 | packageReferences = ( 140 | FBCA824D293BE70E003B2BC6 /* XCRemoteSwiftPackageReference "StickyTabBarViewController" */, 141 | ); 142 | productRefGroup = C3898085242569D100F5F786 /* Products */; 143 | projectDirPath = ""; 144 | projectRoot = ""; 145 | targets = ( 146 | C3898083242569D100F5F786 /* SampleSporify */, 147 | ); 148 | }; 149 | /* End PBXProject section */ 150 | 151 | /* Begin PBXResourcesBuildPhase section */ 152 | C3898082242569D100F5F786 /* Resources */ = { 153 | isa = PBXResourcesBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | C3898094242569D200F5F786 /* LaunchScreen.storyboard in Resources */, 157 | C3898091242569D200F5F786 /* Assets.xcassets in Resources */, 158 | C389808F242569D100F5F786 /* Main.storyboard in Resources */, 159 | C38980AD24257D5700F5F786 /* SampleChildViewController.xib in Resources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | C3898080242569D100F5F786 /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | C38980A224256AD800F5F786 /* SceneDelegate.swift in Sources */, 171 | C389809E24256A4E00F5F786 /* SecondViewController.swift in Sources */, 172 | C349F51A2431528C0024BD03 /* MainTabBarController.swift in Sources */, 173 | C389808C242569D100F5F786 /* ViewController.swift in Sources */, 174 | C38980AC24257D5700F5F786 /* SampleChildViewController.swift in Sources */, 175 | C3898088242569D100F5F786 /* AppDelegate.swift in Sources */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXSourcesBuildPhase section */ 180 | 181 | /* Begin PBXVariantGroup section */ 182 | C389808D242569D100F5F786 /* Main.storyboard */ = { 183 | isa = PBXVariantGroup; 184 | children = ( 185 | C389808E242569D100F5F786 /* Base */, 186 | ); 187 | name = Main.storyboard; 188 | sourceTree = ""; 189 | }; 190 | C3898092242569D200F5F786 /* LaunchScreen.storyboard */ = { 191 | isa = PBXVariantGroup; 192 | children = ( 193 | C3898093242569D200F5F786 /* Base */, 194 | ); 195 | name = LaunchScreen.storyboard; 196 | sourceTree = ""; 197 | }; 198 | /* End PBXVariantGroup section */ 199 | 200 | /* Begin XCBuildConfiguration section */ 201 | C3898096242569D200F5F786 /* Debug */ = { 202 | isa = XCBuildConfiguration; 203 | buildSettings = { 204 | ALWAYS_SEARCH_USER_PATHS = NO; 205 | CLANG_ANALYZER_NONNULL = YES; 206 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 208 | CLANG_CXX_LIBRARY = "libc++"; 209 | CLANG_ENABLE_MODULES = YES; 210 | CLANG_ENABLE_OBJC_ARC = YES; 211 | CLANG_ENABLE_OBJC_WEAK = YES; 212 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 213 | CLANG_WARN_BOOL_CONVERSION = YES; 214 | CLANG_WARN_COMMA = YES; 215 | CLANG_WARN_CONSTANT_CONVERSION = YES; 216 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INFINITE_RECURSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 224 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 225 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 227 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 228 | CLANG_WARN_STRICT_PROTOTYPES = YES; 229 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 230 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 231 | CLANG_WARN_UNREACHABLE_CODE = YES; 232 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 233 | COPY_PHASE_STRIP = NO; 234 | DEBUG_INFORMATION_FORMAT = dwarf; 235 | ENABLE_STRICT_OBJC_MSGSEND = YES; 236 | ENABLE_TESTABILITY = YES; 237 | GCC_C_LANGUAGE_STANDARD = gnu11; 238 | GCC_DYNAMIC_NO_PIC = NO; 239 | GCC_NO_COMMON_BLOCKS = YES; 240 | GCC_OPTIMIZATION_LEVEL = 0; 241 | GCC_PREPROCESSOR_DEFINITIONS = ( 242 | "DEBUG=1", 243 | "$(inherited)", 244 | ); 245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 247 | GCC_WARN_UNDECLARED_SELECTOR = YES; 248 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 249 | GCC_WARN_UNUSED_FUNCTION = YES; 250 | GCC_WARN_UNUSED_VARIABLE = YES; 251 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 252 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 253 | MTL_FAST_MATH = YES; 254 | ONLY_ACTIVE_ARCH = YES; 255 | SDKROOT = iphoneos; 256 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 257 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 258 | }; 259 | name = Debug; 260 | }; 261 | C3898097242569D200F5F786 /* Release */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_ANALYZER_NONNULL = YES; 266 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_ENABLE_OBJC_WEAK = YES; 272 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_COMMA = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 277 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 278 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 279 | CLANG_WARN_EMPTY_BODY = YES; 280 | CLANG_WARN_ENUM_CONVERSION = YES; 281 | CLANG_WARN_INFINITE_RECURSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 285 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 287 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 288 | CLANG_WARN_STRICT_PROTOTYPES = YES; 289 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 290 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 295 | ENABLE_NS_ASSERTIONS = NO; 296 | ENABLE_STRICT_OBJC_MSGSEND = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu11; 298 | GCC_NO_COMMON_BLOCKS = YES; 299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 301 | GCC_WARN_UNDECLARED_SELECTOR = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 303 | GCC_WARN_UNUSED_FUNCTION = YES; 304 | GCC_WARN_UNUSED_VARIABLE = YES; 305 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 306 | MTL_ENABLE_DEBUG_INFO = NO; 307 | MTL_FAST_MATH = YES; 308 | SDKROOT = iphoneos; 309 | SWIFT_COMPILATION_MODE = wholemodule; 310 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 311 | VALIDATE_PRODUCT = YES; 312 | }; 313 | name = Release; 314 | }; 315 | C3898099242569D200F5F786 /* Debug */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 319 | CODE_SIGN_STYLE = Automatic; 320 | INFOPLIST_FILE = SampleSporify/Info.plist; 321 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 322 | LD_RUNPATH_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "@executable_path/Frameworks", 325 | ); 326 | PRODUCT_BUNDLE_IDENTIFIER = com.pun.SampleSporify; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | SWIFT_VERSION = 5.0; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | }; 331 | name = Debug; 332 | }; 333 | C389809A242569D200F5F786 /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 337 | CODE_SIGN_STYLE = Automatic; 338 | INFOPLIST_FILE = SampleSporify/Info.plist; 339 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 340 | LD_RUNPATH_SEARCH_PATHS = ( 341 | "$(inherited)", 342 | "@executable_path/Frameworks", 343 | ); 344 | PRODUCT_BUNDLE_IDENTIFIER = com.pun.SampleSporify; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SWIFT_VERSION = 5.0; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | }; 349 | name = Release; 350 | }; 351 | /* End XCBuildConfiguration section */ 352 | 353 | /* Begin XCConfigurationList section */ 354 | C389807F242569D100F5F786 /* Build configuration list for PBXProject "SampleSporify" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | C3898096242569D200F5F786 /* Debug */, 358 | C3898097242569D200F5F786 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | C3898098242569D200F5F786 /* Build configuration list for PBXNativeTarget "SampleSporify" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | C3898099242569D200F5F786 /* Debug */, 367 | C389809A242569D200F5F786 /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | /* End XCConfigurationList section */ 373 | 374 | /* Begin XCRemoteSwiftPackageReference section */ 375 | FBCA824D293BE70E003B2BC6 /* XCRemoteSwiftPackageReference "StickyTabBarViewController" */ = { 376 | isa = XCRemoteSwiftPackageReference; 377 | repositoryURL = "https://github.com/emrepun/StickyTabBarViewController.git"; 378 | requirement = { 379 | kind = exactVersion; 380 | version = 1.0.6; 381 | }; 382 | }; 383 | /* End XCRemoteSwiftPackageReference section */ 384 | 385 | /* Begin XCSwiftPackageProductDependency section */ 386 | FBCA824E293BE70E003B2BC6 /* StickyTabBarViewController */ = { 387 | isa = XCSwiftPackageProductDependency; 388 | package = FBCA824D293BE70E003B2BC6 /* XCRemoteSwiftPackageReference "StickyTabBarViewController" */; 389 | productName = StickyTabBarViewController; 390 | }; 391 | /* End XCSwiftPackageProductDependency section */ 392 | }; 393 | rootObject = C389807C242569D100F5F786 /* Project object */; 394 | } 395 | -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "stickytabbarviewcontroller", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/emrepun/StickyTabBarViewController.git", 7 | "state" : { 8 | "revision" : "d4695db804d081f7a41b28e1fffdf1f0182b2ba9", 9 | "version" : "1.0.6" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/project.xcworkspace/xcuserdata/emre.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emrepun/StickyTabBarViewController/192800d8a31fab77507a29baf14c05e2a15395af/Example/SampleSporify.xcodeproj/project.xcworkspace/xcuserdata/emre.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/xcuserdata/daniel.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SampleSporify.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/xcuserdata/emre.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Example/SampleSporify.xcodeproj/xcuserdata/emre.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SampleSporify.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/SampleSporify/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SampleSporify 4 | // 5 | // Created by Emre Havan on 20.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | @available(iOS 13.0, *) 24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 25 | // Called when a new scene session is being created. 26 | // Use this method to select a configuration to create the new scene with. 27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 28 | } 29 | 30 | @available(iOS 13.0, *) 31 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 32 | // Called when the user discards a scene session. 33 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 34 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 35 | } 36 | 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Example/SampleSporify/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/SampleSporify/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/SampleSporify/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example/SampleSporify/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 34 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /Example/SampleSporify/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Example/SampleSporify/MainTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarController.swift 3 | // SampleSporify 4 | // 5 | // Created by Emre Havan on 30.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StickyTabBarViewController 11 | 12 | class MainTabBarController: StickyViewControllerSupportingTabBarController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/SampleSporify/SampleChildViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleChildViewController.swift 3 | // SampleSporify 4 | // 5 | // Created by Emre Havan on 20.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StickyTabBarViewController 11 | 12 | class SampleChildViewController: UIViewController, Expandable { 13 | var minimisedView: UIView { 14 | return collapsedStateView 15 | } 16 | 17 | @IBOutlet weak var collapsedStateView: UIView! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | } 22 | 23 | @IBAction func shutDownTapped(_ sender: Any) { 24 | container?.removeCollapsableChild(animated: true) 25 | } 26 | @IBAction func collapseFromExpandedTapped(_ sender: Any) { 27 | container?.collapseChild() 28 | } 29 | @IBAction func updateMinimisedViewTapped(_ sender: Any) { 30 | collapsedStateView.backgroundColor = .red 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/SampleSporify/SampleChildViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 32 | 39 | 40 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 67 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Example/SampleSporify/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SampleSporify 4 | // 5 | // Created by Emre Havan on 20.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @available(iOS 13.0, *) 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | guard let _ = (scene as? UIWindowScene) else { return } 22 | } 23 | 24 | func sceneDidDisconnect(_ scene: UIScene) { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | func sceneDidBecomeActive(_ scene: UIScene) { 32 | // Called when the scene has moved from an inactive state to an active state. 33 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 34 | } 35 | 36 | func sceneWillResignActive(_ scene: UIScene) { 37 | // Called when the scene will move from an active state to an inactive state. 38 | // This may occur due to temporary interruptions (ex. an incoming phone call). 39 | } 40 | 41 | func sceneWillEnterForeground(_ scene: UIScene) { 42 | // Called as the scene transitions from the background to the foreground. 43 | // Use this method to undo the changes made on entering the background. 44 | } 45 | 46 | func sceneDidEnterBackground(_ scene: UIScene) { 47 | // Called as the scene transitions from the foreground to the background. 48 | // Use this method to save data, release shared resources, and store enough scene-specific state information 49 | // to restore the scene back to its current state. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Example/SampleSporify/SecondViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.swift 3 | // SampleSporify 4 | // 5 | // Created by Emre Havan on 20.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SecondViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/SampleSporify/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SampleSporify 4 | // 5 | // Created by Emre Havan on 20.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StickyTabBarViewController 11 | 12 | class ViewController: UIViewController { 13 | 14 | var tabController: StickyViewControllerSupportingTabBarController? { 15 | if let tabBarController = tabBarController as? StickyViewControllerSupportingTabBarController { 16 | return tabBarController 17 | } 18 | return nil 19 | } 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | // Do any additional setup after loading the view. 24 | } 25 | @IBAction func tapped(_ sender: Any) { 26 | let viewControllerToStick = SampleChildViewController() 27 | tabController?.configureCollapsableChild(viewControllerToStick, 28 | isFullScreenOnFirstAppearance: false) 29 | } 30 | 31 | @IBAction func removerTapped(_ sender: Any) { 32 | tabController?.removeCollapsableChild(animated: true) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # https://bundler.io 2 | source 'https://rubygems.org' 3 | 4 | gem 'cocoapods' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | algoliasearch (1.27.1) 11 | httpclient (~> 2.8, >= 2.8.3) 12 | json (>= 1.5.1) 13 | atomos (0.1.3) 14 | claide (1.0.3) 15 | cocoapods (1.9.1) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.9.1) 19 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 20 | cocoapods-downloader (>= 1.2.2, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.4.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (>= 2.3.0, < 3.0) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.6) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.4) 33 | xcodeproj (>= 1.14.0, < 2.0) 34 | cocoapods-core (1.9.1) 35 | activesupport (>= 4.0.2, < 6) 36 | algoliasearch (~> 1.0) 37 | concurrent-ruby (~> 1.1) 38 | fuzzy_match (~> 2.0.4) 39 | nap (~> 1.0) 40 | netrc (~> 0.11) 41 | typhoeus (~> 1.0) 42 | cocoapods-deintegrate (1.0.4) 43 | cocoapods-downloader (1.6.3) 44 | cocoapods-plugins (1.0.0) 45 | nap 46 | cocoapods-search (1.0.0) 47 | cocoapods-stats (1.1.0) 48 | cocoapods-trunk (1.4.1) 49 | nap (>= 0.8, < 2.0) 50 | netrc (~> 0.11) 51 | cocoapods-try (1.1.0) 52 | colored2 (3.1.2) 53 | concurrent-ruby (1.1.6) 54 | escape (0.0.4) 55 | ethon (0.12.0) 56 | ffi (>= 1.3.0) 57 | ffi (1.12.2) 58 | fourflusher (2.3.1) 59 | fuzzy_match (2.0.4) 60 | gh_inspector (1.1.3) 61 | httpclient (2.8.3) 62 | i18n (0.9.5) 63 | concurrent-ruby (~> 1.0) 64 | json (2.3.0) 65 | minitest (5.14.0) 66 | molinillo (0.6.6) 67 | nanaimo (0.2.6) 68 | nap (1.1.0) 69 | netrc (0.11.0) 70 | ruby-macho (1.4.0) 71 | thread_safe (0.3.6) 72 | typhoeus (1.3.1) 73 | ethon (>= 0.9.0) 74 | tzinfo (1.2.10) 75 | thread_safe (~> 0.1) 76 | xcodeproj (1.16.0) 77 | CFPropertyList (>= 2.3.3, < 4.0) 78 | atomos (~> 0.1.3) 79 | claide (>= 1.0.2, < 2.0) 80 | colored2 (~> 3.1) 81 | nanaimo (~> 0.2.6) 82 | 83 | PLATFORMS 84 | ruby 85 | 86 | DEPENDENCIES 87 | cocoapods 88 | 89 | BUNDLED WITH 90 | 2.0.2 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Emre Havan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let packageName = "StickyTabBarViewController" 5 | 6 | let package = Package(name: packageName, 7 | platforms: [.iOS(.v10)], 8 | products: [.library(name: packageName, 9 | targets: [packageName])], 10 | targets: [.target(name: packageName, 11 | path: "\(packageName)/Classes")], 12 | swiftLanguageVersions: [.v5]) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StickyTabBarViewController 2 | Sticky and Collapsible View Controller on top of tab bar 3 | 4 | ![](https://media.giphy.com/media/W519AMUoGGIDx8eHBE/giphy.gif) 5 | ![](https://media.giphy.com/media/eNvbHbouudojLUu1dZ/giphy.gif) 6 | 7 | ## Requirements: 8 | - iOS 10.0 9 | - Tab bar is visible as long as there is a sticky view controller allocated on top of it (any vc pushed at any point should not set ```hidesBottomBarWhenPushed``` to ```true```. 10 | 11 | ## Installation 12 | 13 | StickyTabBarViewController is available through [SPM](https://swift.org/package-manager/) and [CocoaPods](http://cocoapods.org). 14 | 15 | - SPM Installation: 16 | Add it as a new package dependency with https://github.com/emrepun/StickyTabBarViewController.git 17 | 18 | - CocoaPods Installation: 19 | 20 | Simply add the following line to your Podfile: 21 | 22 | ```ruby 23 | pod 'StickyTabBarViewController', '1.0.5' 24 | ``` 25 | 26 | ## Usage 27 | 28 | Subclass ```StickyViewControllerSupportingTabBarController``` from your tab bar controller. 29 | 30 | Configure animation duration or collapsed view height directly from your tabbar controller: 31 | 32 | From ```viewDidLoad```: 33 | 34 | ```swift 35 | import UIKit 36 | import StickyTabBarViewController 37 | 38 | class MainTabBarController: StickyViewControllerSupportingTabBarController { 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | collapsedHeight = 50.0 43 | animationDuration = 0.5 44 | } 45 | } 46 | 47 | ``` 48 | 49 | By overriding initialisers of the tabbar controller: 50 | 51 | ```swift 52 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 53 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 54 | // here if you are using xib 55 | collapsedHeight = 50.0 56 | animationDuration = 0.5 57 | } 58 | 59 | required init?(coder: NSCoder) { 60 | super.init(coder: coder) 61 | // configure also on required init (if you are using storyboard for example) 62 | collapsedHeight = 50.0 63 | animationDuration = 0.5 64 | } 65 | ``` 66 | 67 | Can also update it any time by accessing to tabBarController. 68 | 69 | ## Presented View Controller Configurations: 70 | 71 | Any view controller to have sticky behaviour must conform to ```Expandable``` and implement a ```minimisedView```. 72 | 73 | The implemented ```minimisedView``` should be ideally anchored on top of the view controller's view and its height (either by a direct height constraint or some other constraints) should be equal to the value of ```collapsedHeight```. You don't need to worry about hiding or showing it since it is handled by StickyTabBarViewController itself. 74 | 75 | ```swift 76 | var minimisedView: UIView { 77 | return UIView() // or return your outlet for minimised view. 78 | } 79 | ``` 80 | 81 | Collapse sticky view from the view controller that conforms to ```Expandable``` as following: 82 | 83 | ```swift 84 | container?.collapseChild() 85 | ``` 86 | 87 | Expand sticky view from the view controller that conforms to ```Expandable``` as following: 88 | 89 | ```swift 90 | container?.expandChild() 91 | ``` 92 | 93 | Remove sticky view from the view controller that conforms to ```Expandable``` as following: 94 | 95 | ```swift 96 | container?.removeCollapsableChild(animated:) 97 | ``` 98 | 99 | Configure a Sticky child ViewController as following: 100 | 101 | ```swift 102 | if let tabBarController = tabBarController as? StickyViewControllerSupportingTabBarController { 103 | let viewControllerToStick = SampleChildViewController() 104 | tabBarController.configureCollapsableChild(viewControllerToStick, 105 | isFullScreenOnFirstAppearance: true) 106 | } 107 | ``` 108 | 109 | ## Interaction with the presented sticky child view controller from anywhere with tabBarController access: 110 | 111 | Access tabBarController to interact with sticky child view controller: 112 | 113 | ```swift 114 | var tabController: StickyViewControllerSupportingTabBarController? { 115 | if let tabBarController = tabBarController as? StickyViewControllerSupportingTabBarController { 116 | return tabBarController 117 | } 118 | return nil 119 | } 120 | ``` 121 | 122 | Expand/collapse child view controller: 123 | 124 | ```swift 125 | tabController?.collapseChild() 126 | ``` 127 | 128 | ```swift 129 | tabController?.expandChild() 130 | ``` 131 | 132 | ## Pending Improvements: 133 | - It would be nice to have the ability to hide tab bar and status bar upon expanding, in parameterised way. 134 | - Right now it is not possible to configure or overwrite the implemented sticky VC, one must first remove it and then implement another if needed. Maybe implement overwriting if configure is called while there is already a view controller allocated? 135 | -------------------------------------------------------------------------------- /StickyTabBarViewController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint StickyTabBarViewController.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see https://guides.cocoapods.org/syntax/podspec.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | s.name = 'StickyTabBarViewController' 12 | s.version = '1.0.5' 13 | s.summary = 'A sticky and expandable view controller on top of TabBar' 14 | 15 | s.description = <<-DESC 16 | StickyTabBarViewController provides a UI component that is similar to Spotify music player, Youtube's now playing view 17 | It provides the functionality for an application using UITabBarController to have a sticky, expandable and customizable 18 | view on top of tab bar. 19 | DESC 20 | 21 | s.homepage = 'https://github.com/emrepun/StickyTabBarViewController' 22 | s.license = { :type => 'MIT', :file => 'LICENSE' } 23 | s.author = { "emrepun" => "emrehavan@hotmail.com" } 24 | 25 | s.ios.deployment_target = '10.0' 26 | s.swift_version = '5.0' 27 | 28 | s.source = { :git => 'https://github.com/emrepun/StickyTabBarViewController.git', :tag => s.version.to_s } 29 | 30 | s.source_files = 'StickyTabBarViewController/Classes/**/*' 31 | 32 | end 33 | -------------------------------------------------------------------------------- /StickyTabBarViewController/Classes/ExpandableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandableViewController.swift 3 | // StickyTabBarViewController 4 | // 5 | // Created by Emre Havan on 20.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol Expandable: UIViewController { 12 | var minimisedView: UIView { get } 13 | var container: StickyViewControllerSupporting? { get set } 14 | } 15 | 16 | public extension Expandable { 17 | weak var container: StickyViewControllerSupporting? { 18 | get { tabBarController as? StickyViewControllerSupporting } 19 | 20 | set { /* No steps needed */ } 21 | } 22 | } 23 | 24 | class ExpandableViewController: UIViewController { 25 | 26 | // MARK: - Internal properties 27 | 28 | var heightConstraint: NSLayoutConstraint! 29 | var isEnlarged = false 30 | 31 | weak var tabController: StickyViewControllerSupporting? 32 | 33 | let deviceHeight: CGFloat = UIScreen.main.bounds.height 34 | var collapsedHeight: CGFloat 35 | var animationDuration: TimeInterval 36 | 37 | // MARK: - Private properties 38 | 39 | private var minimisedView: UIView 40 | private let childVC: Expandable 41 | 42 | // MARK: - Animation properties 43 | 44 | lazy var isBeginningUpwards = !isEnlarged 45 | var runningAnimation: UIViewPropertyAnimator? 46 | var animationProgressWhenInterrupted: CGFloat = 0 47 | 48 | // MARK: - Initialisers 49 | 50 | init(withChildVC childVC: Expandable, 51 | collapsedHeight: CGFloat, 52 | animationDuration: TimeInterval) { 53 | self.childVC = childVC 54 | self.collapsedHeight = collapsedHeight 55 | self.animationDuration = animationDuration 56 | self.minimisedView = childVC.minimisedView 57 | super.init(nibName: nil, bundle: nil) 58 | } 59 | 60 | required init?(coder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | 64 | // MARK: - Lifecycle 65 | 66 | override public func viewDidLoad() { 67 | super.viewDidLoad() 68 | let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(enlargeWithTap)) 69 | minimisedView.addGestureRecognizer(gestureRecognizer) 70 | let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) 71 | view.addGestureRecognizer(panGestureRecognizer) 72 | view.clipsToBounds = true 73 | configureChildVC() 74 | } 75 | 76 | // MARK: - Internal API 77 | 78 | func expand() { 79 | animateTransitionIfNeeded(isEnlarging: true, duration: animationDuration) 80 | } 81 | 82 | func collapse() { 83 | animateTransitionIfNeeded(isEnlarging: false, duration: animationDuration) 84 | } 85 | 86 | // MARK: - Private API 87 | 88 | private func configureChildVC() { 89 | addChild(childVC) 90 | view.addSubview(childVC.view) 91 | childVC.view.frame = view.bounds 92 | childVC.didMove(toParent: self) 93 | } 94 | 95 | @objc private func enlargeWithTap(recognizer: UITapGestureRecognizer) { 96 | switch recognizer.state { 97 | case .ended: 98 | animateTransitionIfNeeded(isEnlarging: !isEnlarged, duration: animationDuration) 99 | default: 100 | break 101 | } 102 | } 103 | 104 | @objc private func handlePan(recognizer: UIPanGestureRecognizer) { 105 | switch recognizer.state { 106 | case .began: 107 | let velocity = recognizer.velocity(in: childVC.view) 108 | isBeginningUpwards = isDirectionUpwards(for: velocity) 109 | startInteractiveTransition(isEnlarging: !isEnlarged, duration: animationDuration) 110 | case .changed: 111 | let velocity = recognizer.velocity(in: childVC.view) 112 | isBeginningUpwards = isDirectionUpwards(for: velocity) 113 | let translation = recognizer.translation(in: childVC.view) 114 | var fractionComplete = translation.y / deviceHeight 115 | fractionComplete = isEnlarged ? fractionComplete : -fractionComplete 116 | if runningAnimation?.isReversed ?? false { 117 | fractionComplete = -fractionComplete 118 | } 119 | updateInteractiveTransition(fractionCompleted: fractionComplete) 120 | case .ended: 121 | continueInteractiveTransition(isReversed: shouldReverseAnimation()) 122 | default: 123 | break 124 | } 125 | } 126 | 127 | private func shouldReverseAnimation() -> Bool { 128 | if isEnlarged && !isBeginningUpwards { 129 | return true 130 | } else if !isEnlarged && isBeginningUpwards { 131 | return true 132 | } 133 | return false 134 | } 135 | 136 | private func isDirectionUpwards(for velocity: CGPoint) -> Bool { 137 | return velocity.y > 0 138 | } 139 | 140 | private func animateTransitionIfNeeded(isEnlarging: Bool, duration: TimeInterval) { 141 | guard 142 | runningAnimation == nil, 143 | let tabController = tabController, 144 | // Make sure we are not trying to animate to same state by checking if the child is already in the same 145 | // state of passed `isEnlarging` value. 146 | self.isEnlarged != isEnlarging else { 147 | return 148 | } 149 | 150 | runningAnimation = UIViewPropertyAnimator( 151 | duration: duration, 152 | dampingRatio: 1) { 153 | if isEnlarging { 154 | self.heightConstraint.constant = self.deviceHeight - tabController.tabBar.frame.height 155 | self.minimisedView.alpha = 0.0 156 | } else { 157 | self.heightConstraint.constant = self.collapsedHeight 158 | self.minimisedView.alpha = 1.0 159 | } 160 | self.view.setNeedsLayout() 161 | tabController.view.setNeedsLayout() 162 | self.view.layoutIfNeeded() 163 | tabController.view.layoutIfNeeded() 164 | } 165 | 166 | runningAnimation?.addCompletion { (position) in 167 | switch position { 168 | case .end: 169 | self.isEnlarged = !self.isEnlarged 170 | default: 171 | () 172 | } 173 | self.runningAnimation = nil 174 | } 175 | 176 | runningAnimation?.startAnimation() 177 | } 178 | 179 | private func startInteractiveTransition(isEnlarging: Bool, duration: TimeInterval) { 180 | animateTransitionIfNeeded(isEnlarging: isEnlarging, duration: duration) 181 | runningAnimation?.pauseAnimation() 182 | animationProgressWhenInterrupted = runningAnimation?.fractionComplete ?? 0.0 183 | } 184 | 185 | private func updateInteractiveTransition(fractionCompleted: CGFloat) { 186 | runningAnimation?.fractionComplete = fractionCompleted + animationProgressWhenInterrupted 187 | } 188 | 189 | private func continueInteractiveTransition(isReversed: Bool) { 190 | runningAnimation?.isReversed = isReversed 191 | runningAnimation?.continueAnimation(withTimingParameters: nil, durationFactor: 0) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /StickyTabBarViewController/Classes/ExpandableViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /StickyTabBarViewController/Classes/StickyViewControllerSupportingTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyViewControllerSupportingTabBarController.swift 3 | // StickyTabBarViewController 4 | // 5 | // Created by Emre Havan on 20.03.20. 6 | // Copyright © 2020 Emre Havan. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | public protocol StickyViewControllerSupporting: UITabBarController { 11 | var collapsedHeight: CGFloat { get set } 12 | var animationDuration: TimeInterval { get set } 13 | var childViewController: Expandable? { get } 14 | func configureCollapsableChild(_ childViewController: Expandable, isFullScreenOnFirstAppearance: Bool) 15 | func removeCollapsableChild(animated: Bool) 16 | func collapseChild() 17 | func expandChild() 18 | } 19 | 20 | open class StickyViewControllerSupportingTabBarController: UITabBarController, StickyViewControllerSupporting { 21 | 22 | // MARK: - Public properties 23 | public var collapsedHeight: CGFloat = 50.0 24 | public var animationDuration: TimeInterval = 0.5 25 | public var childViewController: Expandable? 26 | 27 | // MARK: - Private properties 28 | private var collapsableVCFlow: ExpandableViewController? 29 | 30 | // MARK: - Public API 31 | 32 | /// Prepares View Controller to be embedded as a child (wrapped in another internal View Controller) 33 | /// - Parameter childViewController: A View Controller conforming to `Expandable` protocol 34 | /// - Parameter isFullScreenOnFirstAppearance: A boolean to determine if child view controller should be presented 35 | /// full screen on first configuration 36 | final public func configureCollapsableChild(_ childViewController: Expandable, 37 | isFullScreenOnFirstAppearance: Bool) { 38 | guard collapsableVCFlow == nil else { 39 | return 40 | } 41 | childViewController.loadViewIfNeeded() 42 | childViewController.container = self 43 | self.childViewController = childViewController 44 | collapsableVCFlow = ExpandableViewController(withChildVC: childViewController, 45 | collapsedHeight: collapsedHeight, 46 | animationDuration: animationDuration) 47 | 48 | collapsableVCFlow!.tabController = self 49 | view.addSubview(collapsableVCFlow!.view) 50 | addChild(collapsableVCFlow!) 51 | collapsableVCFlow!.view.translatesAutoresizingMaskIntoConstraints = false 52 | collapsableVCFlow!.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 53 | collapsableVCFlow!.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 54 | 55 | collapsableVCFlow!.view.bottomAnchor.constraint(equalTo: tabBar.topAnchor).isActive = true 56 | let heightConstraint = collapsableVCFlow!.view.heightAnchor.constraint(equalToConstant: collapsedHeight) 57 | heightConstraint.isActive = true 58 | collapsableVCFlow!.heightConstraint = heightConstraint 59 | 60 | collapsableVCFlow!.didMove(toParent: self) 61 | 62 | if isFullScreenOnFirstAppearance { 63 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 64 | self.collapsableVCFlow!.expand() 65 | } 66 | } 67 | } 68 | 69 | /// Removes child View Controller from view hierarchy and parent 70 | /// - Parameter animated: Whether or not the removal should be animated 71 | final public func removeCollapsableChild(animated: Bool) { 72 | guard let collapsableVCFlow = collapsableVCFlow else { 73 | return 74 | } 75 | if animated { 76 | UIView.animate(withDuration: animationDuration, 77 | animations: { 78 | collapsableVCFlow.heightConstraint.constant = 0.0 79 | collapsableVCFlow.view.layoutIfNeeded() 80 | self.view.layoutIfNeeded() 81 | }) { (completed) in 82 | if completed { 83 | self.removeStickyViewController() 84 | } 85 | } 86 | } else { 87 | removeStickyViewController() 88 | } 89 | } 90 | 91 | /// Collapse already presented child 92 | final public func collapseChild() { 93 | guard let collapsableVCFlow = collapsableVCFlow else { 94 | return 95 | } 96 | collapsableVCFlow.collapse() 97 | } 98 | 99 | /// Expand already presented child 100 | final public func expandChild() { 101 | guard let collapsableVCFlow = collapsableVCFlow else { 102 | return 103 | } 104 | collapsableVCFlow.expand() 105 | } 106 | 107 | // MARK: - Private API 108 | 109 | private func removeStickyViewController() { 110 | guard let collapsableVCFlow = collapsableVCFlow else { 111 | return 112 | } 113 | collapsableVCFlow.view.removeFromSuperview() 114 | collapsableVCFlow.removeFromParent() 115 | self.collapsableVCFlow = nil 116 | self.childViewController = nil 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Definition of Done 5 | 6 | - [ ] Has changes to Public API (Requires README update) 7 | - [ ] Updated README 8 | --------------------------------------------------------------------------------