├── .gitignore ├── CONTRIBUTING.md ├── Demo └── TabbyDemo │ ├── Podfile │ ├── Podfile.lock │ ├── README.md │ ├── TabbyDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── TabbyDemo.xcscheme │ ├── TabbyDemo.xcworkspace │ └── contents.xcworkspacedata │ └── TabbyDemo │ ├── AppDelegate.swift │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Controllers │ ├── FirstController.swift │ ├── GeneralController.swift │ ├── SecondController.swift │ └── ThirdController.swift │ ├── Info.plist │ └── Resources │ └── Assets.xcassets │ ├── AppIcon.appiconset │ └── Contents.json │ ├── Contents.json │ ├── Controllers │ ├── Contents.json │ └── chef.imageset │ │ ├── Chef Hat.png │ │ ├── Chef Hat@2x.png │ │ ├── Chef Hat@3x.png │ │ └── Contents.json │ └── Tabs │ ├── Contents.json │ ├── cow.imageset │ ├── Contents.json │ ├── Cow.png │ ├── Cow@2x.png │ └── Cow@3x.png │ ├── donut.imageset │ ├── Contents.json │ ├── Donut.png │ ├── Donut@2x.png │ └── Donut@3x.png │ └── fish.imageset │ ├── Contents.json │ ├── Fish.png │ ├── Fish@2x.png │ └── Fish@3x.png ├── LICENSE.md ├── README.md ├── Resources └── Info.plist ├── Sources ├── Cells │ └── TabbyCell.swift ├── Extensions │ ├── UIView+Shadow.swift │ └── UIViewController+Constraint.swift ├── Items │ └── TabbyBarItem.swift ├── Layout │ └── TabbyLayout.swift ├── Library │ ├── Behavior.swift │ ├── Constant.swift │ └── Constraints.swift ├── TabbyController.swift └── Views │ ├── TabbyBadge.swift │ └── TabbyBar.swift ├── Tabby.podspec ├── Tabby.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Tabby.xcscheme ├── TabbyTests ├── Info.plist └── TabbyTests.swift └── Web └── sample.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # Xcode 11 | # 12 | build/ 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | *.xccheckout 23 | *.moved-aside 24 | DerivedData 25 | *.hmap 26 | *.ipa 27 | *.xcuserstate 28 | 29 | # CocoaPods 30 | Pods 31 | 32 | # Carthage 33 | Carthage 34 | 35 | # SPM 36 | .build/ 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | GitHub Issues is for reporting bugs, discussing features and general feedback in **Tabby**. Be sure to check our [documentation](http://cocoadocs.org/docsets/Tabby), [FAQ](https://github.com/hyperoslo/Tabby/wiki/FAQ) and [past issues](https://github.com/hyperoslo/Tabby/issues?state=closed) before opening any new issues. 2 | 3 | If you are posting about a crash in your application, a stack trace is helpful, but additional context, in the form of code and explanation, is necessary to be of any use. 4 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '9.0' 4 | 5 | target 'TabbyDemo' do 6 | pod 'Tabby', path: '../../' 7 | end 8 | 9 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Tabby (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - Tabby (from `../../`) 6 | 7 | EXTERNAL SOURCES: 8 | Tabby: 9 | :path: ../../ 10 | 11 | SPEC CHECKSUMS: 12 | Tabby: d3fea59ddf2049d736057f6883ee7e78bf30ef84 13 | 14 | PODFILE CHECKSUM: 4aa57ba1c751435ccd574f358e2b9fea5594ee43 15 | 16 | COCOAPODS: 1.3.1 17 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/README.md: -------------------------------------------------------------------------------- 1 | # Tabby demo 2 | 3 | This is the demo for **Tabby**, here's how it works! 4 | 5 | // TODO: Add the images. 6 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1C711C24DFA29252CE823784 /* Pods_TabbyDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56FCA001056EB91FC430DDCA /* Pods_TabbyDemo.framework */; }; 11 | 298F6CAD1CE4A9BE00F2558F /* GeneralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298F6CAC1CE4A9BE00F2558F /* GeneralController.swift */; }; 12 | 29E9FD311CE31E2C003F9384 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E9FD301CE31E2C003F9384 /* AppDelegate.swift */; }; 13 | 29E9FD341CE31E41003F9384 /* FirstController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E9FD331CE31E41003F9384 /* FirstController.swift */; }; 14 | 29E9FD361CE31E4D003F9384 /* SecondController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E9FD351CE31E4D003F9384 /* SecondController.swift */; }; 15 | 29E9FD381CE31E55003F9384 /* ThirdController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E9FD371CE31E55003F9384 /* ThirdController.swift */; }; 16 | D5C7F74E1C3BC9CE008CDDBA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D5C7F74C1C3BC9CE008CDDBA /* LaunchScreen.storyboard */; }; 17 | D5C7F75B1C3BCA1E008CDDBA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5C7F7571C3BCA1E008CDDBA /* Assets.xcassets */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 298F6CAC1CE4A9BE00F2558F /* GeneralController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralController.swift; sourceTree = ""; }; 22 | 29E9FD301CE31E2C003F9384 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 29E9FD331CE31E41003F9384 /* FirstController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstController.swift; sourceTree = ""; }; 24 | 29E9FD351CE31E4D003F9384 /* SecondController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondController.swift; sourceTree = ""; }; 25 | 29E9FD371CE31E55003F9384 /* ThirdController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThirdController.swift; sourceTree = ""; }; 26 | 3288F9407B8A4A8901883929 /* Pods-TabbyDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TabbyDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TabbyDemo/Pods-TabbyDemo.debug.xcconfig"; sourceTree = ""; }; 27 | 56FCA001056EB91FC430DDCA /* Pods_TabbyDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TabbyDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 9CFB8E7A6DC9EFB2FBDE529C /* Pods-TabbyDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TabbyDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-TabbyDemo/Pods-TabbyDemo.release.xcconfig"; sourceTree = ""; }; 29 | D5C7F7401C3BC9CE008CDDBA /* TabbyDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TabbyDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | D5C7F74D1C3BC9CE008CDDBA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | D5C7F74F1C3BC9CE008CDDBA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | D5C7F7571C3BCA1E008CDDBA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | D5C7F73D1C3BC9CE008CDDBA /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | 1C711C24DFA29252CE823784 /* Pods_TabbyDemo.framework in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 1F7639D0D38EB9290BB75FD9 /* Frameworks */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | 56FCA001056EB91FC430DDCA /* Pods_TabbyDemo.framework */, 51 | ); 52 | name = Frameworks; 53 | sourceTree = ""; 54 | }; 55 | 29E9FD321CE31E41003F9384 /* Controllers */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 29E9FD331CE31E41003F9384 /* FirstController.swift */, 59 | 29E9FD351CE31E4D003F9384 /* SecondController.swift */, 60 | 29E9FD371CE31E55003F9384 /* ThirdController.swift */, 61 | 298F6CAC1CE4A9BE00F2558F /* GeneralController.swift */, 62 | ); 63 | path = Controllers; 64 | sourceTree = ""; 65 | }; 66 | 6D7B5C6DF09B3EC0735FBC73 /* Pods */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 3288F9407B8A4A8901883929 /* Pods-TabbyDemo.debug.xcconfig */, 70 | 9CFB8E7A6DC9EFB2FBDE529C /* Pods-TabbyDemo.release.xcconfig */, 71 | ); 72 | name = Pods; 73 | sourceTree = ""; 74 | }; 75 | D5C7F7371C3BC9CE008CDDBA = { 76 | isa = PBXGroup; 77 | children = ( 78 | D5C7F7421C3BC9CE008CDDBA /* TabbyDemo */, 79 | D5C7F7411C3BC9CE008CDDBA /* Products */, 80 | 6D7B5C6DF09B3EC0735FBC73 /* Pods */, 81 | 1F7639D0D38EB9290BB75FD9 /* Frameworks */, 82 | ); 83 | sourceTree = ""; 84 | }; 85 | D5C7F7411C3BC9CE008CDDBA /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | D5C7F7401C3BC9CE008CDDBA /* TabbyDemo.app */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | D5C7F7421C3BC9CE008CDDBA /* TabbyDemo */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 29E9FD301CE31E2C003F9384 /* AppDelegate.swift */, 97 | 29E9FD321CE31E41003F9384 /* Controllers */, 98 | D5C7F7561C3BCA1E008CDDBA /* Resources */, 99 | D5C7F7551C3BC9EA008CDDBA /* Supporting Files */, 100 | ); 101 | path = TabbyDemo; 102 | sourceTree = ""; 103 | }; 104 | D5C7F7551C3BC9EA008CDDBA /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | D5C7F74C1C3BC9CE008CDDBA /* LaunchScreen.storyboard */, 108 | D5C7F74F1C3BC9CE008CDDBA /* Info.plist */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | D5C7F7561C3BCA1E008CDDBA /* Resources */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | D5C7F7571C3BCA1E008CDDBA /* Assets.xcassets */, 117 | ); 118 | path = Resources; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | D5C7F73F1C3BC9CE008CDDBA /* TabbyDemo */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = D5C7F7521C3BC9CE008CDDBA /* Build configuration list for PBXNativeTarget "TabbyDemo" */; 127 | buildPhases = ( 128 | 5A73BA6C5F05AA1883D4E88A /* [CP] Check Pods Manifest.lock */, 129 | D5C7F73C1C3BC9CE008CDDBA /* Sources */, 130 | D5C7F73D1C3BC9CE008CDDBA /* Frameworks */, 131 | D5C7F73E1C3BC9CE008CDDBA /* Resources */, 132 | BB0D9BC8BF14AC894175D3ED /* [CP] Embed Pods Frameworks */, 133 | 93CFC8638D5F9BCAE475619B /* [CP] Copy Pods Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = TabbyDemo; 140 | productName = TabbyDemo; 141 | productReference = D5C7F7401C3BC9CE008CDDBA /* TabbyDemo.app */; 142 | productType = "com.apple.product-type.application"; 143 | }; 144 | /* End PBXNativeTarget section */ 145 | 146 | /* Begin PBXProject section */ 147 | D5C7F7381C3BC9CE008CDDBA /* Project object */ = { 148 | isa = PBXProject; 149 | attributes = { 150 | LastSwiftUpdateCheck = 0810; 151 | LastUpgradeCheck = 0910; 152 | ORGANIZATIONNAME = "Hyper Interaktiv AS"; 153 | TargetAttributes = { 154 | D5C7F73F1C3BC9CE008CDDBA = { 155 | CreatedOnToolsVersion = 7.2; 156 | LastSwiftMigration = 0800; 157 | }; 158 | }; 159 | }; 160 | buildConfigurationList = D5C7F73B1C3BC9CE008CDDBA /* Build configuration list for PBXProject "TabbyDemo" */; 161 | compatibilityVersion = "Xcode 3.2"; 162 | developmentRegion = English; 163 | hasScannedForEncodings = 0; 164 | knownRegions = ( 165 | en, 166 | Base, 167 | ); 168 | mainGroup = D5C7F7371C3BC9CE008CDDBA; 169 | productRefGroup = D5C7F7411C3BC9CE008CDDBA /* Products */; 170 | projectDirPath = ""; 171 | projectRoot = ""; 172 | targets = ( 173 | D5C7F73F1C3BC9CE008CDDBA /* TabbyDemo */, 174 | ); 175 | }; 176 | /* End PBXProject section */ 177 | 178 | /* Begin PBXResourcesBuildPhase section */ 179 | D5C7F73E1C3BC9CE008CDDBA /* Resources */ = { 180 | isa = PBXResourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | D5C7F75B1C3BCA1E008CDDBA /* Assets.xcassets in Resources */, 184 | D5C7F74E1C3BC9CE008CDDBA /* LaunchScreen.storyboard in Resources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXResourcesBuildPhase section */ 189 | 190 | /* Begin PBXShellScriptBuildPhase section */ 191 | 5A73BA6C5F05AA1883D4E88A /* [CP] Check Pods Manifest.lock */ = { 192 | isa = PBXShellScriptBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | inputPaths = ( 197 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 198 | "${PODS_ROOT}/Manifest.lock", 199 | ); 200 | name = "[CP] Check Pods Manifest.lock"; 201 | outputPaths = ( 202 | "$(DERIVED_FILE_DIR)/Pods-TabbyDemo-checkManifestLockResult.txt", 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 207 | showEnvVarsInLog = 0; 208 | }; 209 | 93CFC8638D5F9BCAE475619B /* [CP] Copy Pods Resources */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputPaths = ( 215 | ); 216 | name = "[CP] Copy Pods Resources"; 217 | outputPaths = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | shellPath = /bin/sh; 221 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-TabbyDemo/Pods-TabbyDemo-resources.sh\"\n"; 222 | showEnvVarsInLog = 0; 223 | }; 224 | BB0D9BC8BF14AC894175D3ED /* [CP] Embed Pods Frameworks */ = { 225 | isa = PBXShellScriptBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | ); 229 | inputPaths = ( 230 | "${SRCROOT}/Pods/Target Support Files/Pods-TabbyDemo/Pods-TabbyDemo-frameworks.sh", 231 | "${BUILT_PRODUCTS_DIR}/Tabby/Tabby.framework", 232 | ); 233 | name = "[CP] Embed Pods Frameworks"; 234 | outputPaths = ( 235 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Tabby.framework", 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-TabbyDemo/Pods-TabbyDemo-frameworks.sh\"\n"; 240 | showEnvVarsInLog = 0; 241 | }; 242 | /* End PBXShellScriptBuildPhase section */ 243 | 244 | /* Begin PBXSourcesBuildPhase section */ 245 | D5C7F73C1C3BC9CE008CDDBA /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | 29E9FD361CE31E4D003F9384 /* SecondController.swift in Sources */, 250 | 29E9FD341CE31E41003F9384 /* FirstController.swift in Sources */, 251 | 29E9FD311CE31E2C003F9384 /* AppDelegate.swift in Sources */, 252 | 298F6CAD1CE4A9BE00F2558F /* GeneralController.swift in Sources */, 253 | 29E9FD381CE31E55003F9384 /* ThirdController.swift in Sources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXSourcesBuildPhase section */ 258 | 259 | /* Begin PBXVariantGroup section */ 260 | D5C7F74C1C3BC9CE008CDDBA /* LaunchScreen.storyboard */ = { 261 | isa = PBXVariantGroup; 262 | children = ( 263 | D5C7F74D1C3BC9CE008CDDBA /* Base */, 264 | ); 265 | name = LaunchScreen.storyboard; 266 | sourceTree = ""; 267 | }; 268 | /* End PBXVariantGroup section */ 269 | 270 | /* Begin XCBuildConfiguration section */ 271 | D5C7F7501C3BC9CE008CDDBA /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 280 | CLANG_WARN_BOOL_CONVERSION = YES; 281 | CLANG_WARN_COMMA = YES; 282 | CLANG_WARN_CONSTANT_CONVERSION = YES; 283 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 284 | CLANG_WARN_EMPTY_BODY = YES; 285 | CLANG_WARN_ENUM_CONVERSION = YES; 286 | CLANG_WARN_INFINITE_RECURSION = YES; 287 | CLANG_WARN_INT_CONVERSION = YES; 288 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 292 | CLANG_WARN_STRICT_PROTOTYPES = YES; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = dwarf; 299 | ENABLE_STRICT_OBJC_MSGSEND = YES; 300 | ENABLE_TESTABILITY = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu99; 302 | GCC_DYNAMIC_NO_PIC = NO; 303 | GCC_NO_COMMON_BLOCKS = YES; 304 | GCC_OPTIMIZATION_LEVEL = 0; 305 | GCC_PREPROCESSOR_DEFINITIONS = ( 306 | "DEBUG=1", 307 | "$(inherited)", 308 | ); 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 311 | GCC_WARN_UNDECLARED_SELECTOR = YES; 312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 313 | GCC_WARN_UNUSED_FUNCTION = YES; 314 | GCC_WARN_UNUSED_VARIABLE = YES; 315 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 316 | MTL_ENABLE_DEBUG_INFO = YES; 317 | ONLY_ACTIVE_ARCH = YES; 318 | SDKROOT = iphoneos; 319 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 320 | SWIFT_VERSION = 4.0; 321 | }; 322 | name = Debug; 323 | }; 324 | D5C7F7511C3BC9CE008CDDBA /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 329 | CLANG_CXX_LIBRARY = "libc++"; 330 | CLANG_ENABLE_MODULES = YES; 331 | CLANG_ENABLE_OBJC_ARC = YES; 332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_COMMA = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INFINITE_RECURSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 350 | COPY_PHASE_STRIP = NO; 351 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 352 | ENABLE_NS_ASSERTIONS = NO; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu99; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 363 | MTL_ENABLE_DEBUG_INFO = NO; 364 | SDKROOT = iphoneos; 365 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 366 | SWIFT_VERSION = 4.0; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Release; 370 | }; 371 | D5C7F7531C3BC9CE008CDDBA /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | baseConfigurationReference = 3288F9407B8A4A8901883929 /* Pods-TabbyDemo.debug.xcconfig */; 374 | buildSettings = { 375 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 376 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 377 | CLANG_ENABLE_MODULES = YES; 378 | INFOPLIST_FILE = TabbyDemo/Info.plist; 379 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 380 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.TabbyDemo; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 383 | }; 384 | name = Debug; 385 | }; 386 | D5C7F7541C3BC9CE008CDDBA /* Release */ = { 387 | isa = XCBuildConfiguration; 388 | baseConfigurationReference = 9CFB8E7A6DC9EFB2FBDE529C /* Pods-TabbyDemo.release.xcconfig */; 389 | buildSettings = { 390 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 391 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 392 | CLANG_ENABLE_MODULES = YES; 393 | INFOPLIST_FILE = TabbyDemo/Info.plist; 394 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 395 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.TabbyDemo; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | }; 398 | name = Release; 399 | }; 400 | /* End XCBuildConfiguration section */ 401 | 402 | /* Begin XCConfigurationList section */ 403 | D5C7F73B1C3BC9CE008CDDBA /* Build configuration list for PBXProject "TabbyDemo" */ = { 404 | isa = XCConfigurationList; 405 | buildConfigurations = ( 406 | D5C7F7501C3BC9CE008CDDBA /* Debug */, 407 | D5C7F7511C3BC9CE008CDDBA /* Release */, 408 | ); 409 | defaultConfigurationIsVisible = 0; 410 | defaultConfigurationName = Release; 411 | }; 412 | D5C7F7521C3BC9CE008CDDBA /* Build configuration list for PBXNativeTarget "TabbyDemo" */ = { 413 | isa = XCConfigurationList; 414 | buildConfigurations = ( 415 | D5C7F7531C3BC9CE008CDDBA /* Debug */, 416 | D5C7F7541C3BC9CE008CDDBA /* Release */, 417 | ); 418 | defaultConfigurationIsVisible = 0; 419 | defaultConfigurationName = Release; 420 | }; 421 | /* End XCConfigurationList section */ 422 | }; 423 | rootObject = D5C7F7381C3BC9CE008CDDBA /* Project object */; 424 | } 425 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo.xcodeproj/xcshareddata/xcschemes/TabbyDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | 85 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Tabby 3 | 4 | class MainController: TabbyController { 5 | override func viewDidAppear(_ animated: Bool) { 6 | super.viewDidAppear(animated) 7 | 8 | setIndex = 1 9 | } 10 | } 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | lazy var items: [TabbyBarItem] = [ 16 | TabbyBarItem(controller: self.firstNavigation, image: "cow"), 17 | TabbyBarItem(controller: self.secondController, image: "donut"), 18 | TabbyBarItem(controller: self.thirdNavigation, image: "fish") 19 | ] 20 | 21 | lazy var mainController: MainController = { [unowned self] in 22 | let controller = MainController(items: self.items) 23 | 24 | controller.delegate = self 25 | controller.translucent = false 26 | 27 | return controller 28 | }() 29 | 30 | lazy var firstNavigation: UINavigationController = UINavigationController(rootViewController: self.firstController) 31 | lazy var thirdNavigation: UINavigationController = UINavigationController(rootViewController: self.thirdController) 32 | 33 | lazy var firstController: FirstController = FirstController() 34 | lazy var secondController: SecondController = SecondController() 35 | lazy var thirdController: ThirdController = ThirdController() 36 | 37 | var window: UIWindow? 38 | 39 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 40 | 41 | Tabby.Constant.Color.background = UIColor.white 42 | Tabby.Constant.Color.selected = UIColor(red:0.22, green:0.81, blue:0.99, alpha:1.00) 43 | 44 | Tabby.Constant.Behavior.labelVisibility = .activeVisible 45 | 46 | window = UIWindow(frame: UIScreen.main.bounds) 47 | window?.rootViewController = mainController 48 | window?.makeKeyAndVisible() 49 | 50 | return true 51 | } 52 | } 53 | 54 | extension AppDelegate: TabbyDelegate { 55 | 56 | func tabbyDidPress(_ item: TabbyBarItem) { 57 | // Do your awesome transformations! 58 | 59 | if items.index(of: item) == 1 { 60 | mainController.barHidden = true 61 | } 62 | 63 | let when = DispatchTime.now() + 2 64 | DispatchQueue.main.asyncAfter(deadline: when) { 65 | self.mainController.barHidden = false 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/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 | 27 | 28 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Controllers/FirstController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Tabby 3 | 4 | class FirstController: GeneralController { 5 | 6 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 7 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 8 | 9 | title = "Cows".uppercased() 10 | } 11 | 12 | required init?(coder aDecoder: NSCoder) { 13 | fatalError("init(coder:) has not been implemented") 14 | } 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | view.backgroundColor = .orange 20 | titleLabel.text = "Here be cows" 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Controllers/GeneralController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class GeneralController: UIViewController { 4 | 5 | lazy var imageView: UIImageView = { 6 | let imageView = UIImageView() 7 | imageView.contentMode = .scaleAspectFit 8 | imageView.image = UIImage(named: "chef") 9 | 10 | return imageView 11 | }() 12 | 13 | lazy var titleLabel: UILabel = { 14 | let label = UILabel() 15 | label.numberOfLines = 0 16 | label.font = UIFont.boldSystemFont(ofSize: 18) 17 | label.textColor = UIColor.lightGray 18 | label.textAlignment = .center 19 | 20 | return label 21 | }() 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | view.backgroundColor = UIColor.white 27 | 28 | setupConstraints() 29 | } 30 | 31 | // MARK: - Constraint methods 32 | 33 | func setupConstraints() { 34 | view.addSubview(titleLabel) 35 | titleLabel.translatesAutoresizingMaskIntoConstraints = false 36 | 37 | view.addSubview(imageView) 38 | imageView.translatesAutoresizingMaskIntoConstraints = false 39 | 40 | constraint(titleLabel, attributes: [.centerX, .centerY]) 41 | view.addConstraints([ 42 | NSLayoutConstraint( 43 | item: imageView, attribute: .bottom, 44 | relatedBy: .equal, toItem: titleLabel, 45 | attribute: .top, multiplier: 1, constant: -20), 46 | 47 | NSLayoutConstraint( 48 | item: titleLabel, attribute: .width, 49 | relatedBy: .equal, toItem: view, 50 | attribute: .width, multiplier: 1, constant: -200) 51 | ]) 52 | 53 | constraint(imageView, attributes: [.centerX]) 54 | view.addConstraints([ 55 | NSLayoutConstraint( 56 | item: imageView, attribute: .width, 57 | relatedBy: .equal, toItem: nil, 58 | attribute: .notAnAttribute, multiplier: 1, constant: 100), 59 | 60 | NSLayoutConstraint( 61 | item: imageView, attribute: .height, 62 | relatedBy: .equal, toItem: nil, 63 | attribute: .height, multiplier: 1, constant: 100), 64 | ]) 65 | } 66 | 67 | // MARK: - Helper methods 68 | 69 | func constraint(_ subview: UIView, attributes: [NSLayoutAttribute]) { 70 | for attribute in attributes { 71 | view.addConstraint( 72 | NSLayoutConstraint( 73 | item: subview, attribute: attribute, 74 | relatedBy: .equal, toItem: view, 75 | attribute: attribute, multiplier: 1, constant: 0) 76 | ) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Controllers/SecondController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SecondController: GeneralController { 4 | 5 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 6 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 7 | 8 | title = "Donut".uppercased() 9 | } 10 | 11 | required init?(coder aDecoder: NSCoder) { 12 | fatalError("init(coder:) has not been implemented") 13 | } 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | view.backgroundColor = .brown 19 | titleLabel.text = "Who doesn't like some donuts?" 20 | } 21 | 22 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 23 | super.touchesBegan(touches, with: event) 24 | 25 | let controller = UIAlertController(title: "Alert", message: "This is an alert", preferredStyle: .alert) 26 | let action = UIAlertAction(title: "Action", style: .default, handler: nil) 27 | 28 | controller.addAction(action) 29 | 30 | present(controller, animated: true, completion: nil) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Controllers/ThirdController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ThirdController: GeneralController { 4 | 5 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 6 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 7 | 8 | title = "Fish".uppercased() 9 | } 10 | 11 | required init?(coder aDecoder: NSCoder) { 12 | fatalError("init(coder:) has not been implemented") 13 | } 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | view.backgroundColor = .magenta 19 | titleLabel.text = "Fish... Am I right?" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/chef.imageset/Chef Hat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/chef.imageset/Chef Hat.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/chef.imageset/Chef Hat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/chef.imageset/Chef Hat@2x.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/chef.imageset/Chef Hat@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/chef.imageset/Chef Hat@3x.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Controllers/chef.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Chef Hat.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Chef Hat@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Chef Hat@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/cow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Cow.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Cow@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Cow@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/cow.imageset/Cow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/cow.imageset/Cow.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/cow.imageset/Cow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/cow.imageset/Cow@2x.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/cow.imageset/Cow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/cow.imageset/Cow@3x.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/donut.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Donut.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Donut@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Donut@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/donut.imageset/Donut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/donut.imageset/Donut.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/donut.imageset/Donut@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/donut.imageset/Donut@2x.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/donut.imageset/Donut@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/donut.imageset/Donut@3x.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/fish.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Fish.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Fish@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Fish@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/fish.imageset/Fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/fish.imageset/Fish.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/fish.imageset/Fish@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/fish.imageset/Fish@2x.png -------------------------------------------------------------------------------- /Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/fish.imageset/Fish@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Demo/TabbyDemo/TabbyDemo/Resources/Assets.xcassets/Tabs/fish.imageset/Fish@3x.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the **MIT** license 2 | 3 | > Copyright (c) 2015 Hyper Interaktiv AS 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tabby 2 | 3 | ⚠️ DEPRECATED, NO LONGER MAINTAINED 4 | 5 | `// TODO: Add the image here.` 6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 |
17 | 18 | ## Description 19 | 20 | **Tabby** is the ultimate tab bar, a full substitution for those UITabBarControllers, UITabBars and UITabBarItems that are not customizable at all. **Tabby** has animations, behaviors and it has the easiness you would expect from any of our libraries. 21 | 22 | ![sample](/Web/sample.gif) 23 | 24 | ## Usage 25 | 26 | **Tabby** begins with a controller, the called `TabbyController`. That one has an initializer taking `TabbyBarItems`. Each item has a `controller`, an `image` and an `animation` that defaults to a constant. 27 | 28 | Once you have created the array of items, you can initialize the `TabbyController` like so: 29 | 30 | ```swift 31 | let items = [ 32 | TabbyBarItem(controller: firstController, image: UIImage(named: "first")), 33 | TabbyBarItem(controller: secondController, image: UIImage(named: "second")) 34 | ] 35 | ``` 36 | 37 | ```swift 38 | let controller = TabbyController(items: items) 39 | ``` 40 | 41 | #### Customization 42 | 43 | As stated before, there are lots of customization points in **Tabby**, you can find the [constants](https://github.com/hyperoslo/Tabby/blob/master/Sources/Library/Constant.swift#L3) file with fonts, colors and animations. 44 | 45 | A part from the typical constants, you'll be able to change the translucency, the indicator or the separator between the tab and the controller, with the possibility to add a shadow if you want. 46 | 47 | ```swift 48 | controller.translucent = true 49 | controller.showSeparator = false 50 | controller.showIndicator = false 51 | controller.barVisible = false 52 | ``` 53 | 54 | ##### Behaviors 55 | 56 | **Tabby** is built upon behaviors. As soon as we add more customization points within the source code, constants will emerge that will let you control more parts of the insights of **Tabby**. As for now, the first behavior dictates weather the title should be displayed, displayed only in the selected one, or not displayed at all. 57 | 58 | To change that, you just set: 59 | 60 | `Tabby.Constant.Behavior.labelVisibility = .ActiveVisible` 61 | 62 | #### Animations 63 | 64 | There are lots of default [animations](https://github.com/hyperoslo/Tabby/blob/master/Sources/Animations/TabbyAnimation.swift#L5) that you can use. We'll be adding more and more of those. 65 | 66 | The default animations are: 67 | 68 | ```swift 69 | Pop, Flip, Morph, Shake, Swing, PushUp, PushDown, None 70 | ``` 71 | 72 | #### Delegates 73 | 74 | As for now, there is one delegate method that informs you which button was just pressed. This will let you rebuild the tab bar, reload it, add different items, etc. 75 | 76 | ```swift 77 | func tabbyButtonDidPress(index: Int) 78 | ``` 79 | 80 | Be sure to check our [demo](https://github.com/hyperoslo/Tabby/tree/master/Demo/TabbyDemo) if you have any further questions! :) 81 | 82 | ## Installation 83 | 84 | **Tabby** is available through [CocoaPods](http://cocoapods.org). To install 85 | it, simply add the following line to your Podfile: 86 | 87 | ```ruby 88 | pod 'Tabby' 89 | ``` 90 | 91 | **Tabby** is also available through [Carthage](https://github.com/Carthage/Carthage). 92 | To install just write into your Cartfile: 93 | 94 | ```ruby 95 | github "hyperoslo/Tabby" 96 | ``` 97 | 98 | ## Author 99 | 100 | Made by Hyper Oslo. Contact us at ios@hyper.no. 101 | 102 | ## Contributing 103 | 104 | We would love you to contribute to **Tabby**, check the [CONTRIBUTING](https://github.com/hyperoslo/Tabby/blob/master/CONTRIBUTING.md) file for more info. 105 | 106 | ## License 107 | 108 | **Tabby** is available under the MIT license. See the [LICENSE](https://github.com/hyperoslo/Tabby/blob/master/LICENSE.md) file for more info. 109 | -------------------------------------------------------------------------------- /Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Cells/TabbyCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol TabbyCell: class { 4 | func configureCell(_ item: TabbyBarItem, at index: Int, selected: Bool, count: Int?) 5 | } 6 | 7 | public extension TabbyCell { 8 | static var reusableIdentifier: String { 9 | return "TabbyCellReusableIdentifier" 10 | } 11 | 12 | var collectionViewCell: UICollectionViewCell { 13 | return self as? UICollectionViewCell ?? UICollectionViewCell() 14 | } 15 | } 16 | 17 | open class TabbyDefaultCell: UICollectionViewCell, TabbyCell { 18 | 19 | public lazy var imageView: UIImageView = { 20 | let imageView = UIImageView() 21 | imageView.contentMode = .scaleAspectFit 22 | 23 | return imageView 24 | }() 25 | 26 | public lazy var label: UILabel = { 27 | let label = UILabel() 28 | label.font = Constant.Font.title 29 | label.textColor = Constant.Color.disabled 30 | 31 | return label 32 | }() 33 | 34 | public lazy var badge: TabbyBadge = TabbyBadge() 35 | 36 | // MARK: - Configuration 37 | 38 | open func configureCell(_ item: TabbyBarItem, at index: Int, selected: Bool = false, count: Int?) { 39 | let color = selected ? Constant.Color.selected : Constant.Color.disabled 40 | 41 | imageView.image = UIImage(named: item.image)?.withRenderingMode(.alwaysTemplate) 42 | imageView.tintColor = color 43 | 44 | label.text = item.controller.title 45 | label.textColor = color 46 | 47 | handleBadge(count) 48 | handleBehaviors(selected) 49 | setupConstraints() 50 | 51 | label.font = Constant.Font.title 52 | } 53 | 54 | // MARK: - Helper methods 55 | 56 | open func handleBadge(_ count: Int?) { 57 | guard badge.number != count else { badge.number = count ?? 0; return } 58 | 59 | badge.number = count ?? 0 60 | badge.transform = count == 0 ? .identity : .init(scaleX: 0, y: 0) 61 | 62 | UIView.animate( 63 | withDuration: 0.5, delay: 0, 64 | usingSpringWithDamping: 0.6, 65 | initialSpringVelocity: 0.6, 66 | options: [], animations: { 67 | self.badge.transform = count == 0 ? .init(scaleX: 0, y: 0) : .identity 68 | }, completion: nil) 69 | } 70 | 71 | open func handleBehaviors(_ selected: Bool) { 72 | switch Constant.Behavior.labelVisibility { 73 | case .invisible: 74 | label.alpha = 0 75 | case .activeVisible: 76 | label.alpha = selected ? 1 : 0 77 | default: 78 | label.alpha = 1 79 | } 80 | } 81 | 82 | // MARK: - Constraints 83 | 84 | open func setupConstraints() { 85 | let offset: CGFloat = label.alpha == 1 ? 8 : 0 86 | 87 | imageView.translatesAutoresizingMaskIntoConstraints = false 88 | imageView.removeFromSuperview() 89 | 90 | addSubview(imageView) 91 | 92 | constraint(imageView, attributes: .centerX) 93 | addConstraints([ 94 | NSLayoutConstraint(item: imageView, 95 | attribute: .width, relatedBy: .equal, 96 | toItem: nil, attribute: .notAnAttribute, 97 | multiplier: 1, constant: Constant.Dimension.Icon.width), 98 | 99 | NSLayoutConstraint(item: imageView, 100 | attribute: .height, relatedBy: .equal, 101 | toItem: nil, attribute: .notAnAttribute, 102 | multiplier: 1, constant: Constant.Dimension.Icon.height), 103 | 104 | NSLayoutConstraint(item: imageView, 105 | attribute: .centerY, relatedBy: .equal, 106 | toItem: self, attribute: .centerY, 107 | multiplier: 1, constant: -offset) 108 | ]) 109 | 110 | label.translatesAutoresizingMaskIntoConstraints = false 111 | label.removeFromSuperview() 112 | 113 | addSubview(label) 114 | 115 | constraint(label, attributes: .centerX) 116 | addConstraint(NSLayoutConstraint(item: label, 117 | attribute: .centerY, relatedBy: .equal, 118 | toItem: self, attribute: .centerY, 119 | multiplier: 1, constant: offset + 5) 120 | ) 121 | 122 | badge.translatesAutoresizingMaskIntoConstraints = false 123 | badge.removeFromSuperview() 124 | 125 | addSubview(badge) 126 | addConstraints([ 127 | NSLayoutConstraint(item: badge, 128 | attribute: .centerY, relatedBy: .equal, 129 | toItem: imageView, attribute: .top, 130 | multiplier: 1, constant: 1), 131 | 132 | NSLayoutConstraint(item: badge, 133 | attribute: .centerX, relatedBy: .equal, 134 | toItem: imageView, attribute: .right, 135 | multiplier: 1, constant: 0) 136 | ]) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Sources/Extensions/UIView+Shadow.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIView { 4 | 5 | func prepareShadow(_ color: UIColor = Constant.Color.shadow, height: CGFloat = -5) { 6 | layer.shadowColor = color.cgColor 7 | layer.shadowOffset = CGSize(width: 0, height: height) 8 | layer.shadowRadius = abs(height) 9 | layer.shadowOpacity = 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Extensions/UIViewController+Constraint.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIView { 4 | 5 | func constraint(_ subview: UIView, attributes: NSLayoutAttribute...) { 6 | for attribute in attributes { 7 | addConstraint(NSLayoutConstraint( 8 | item: subview, attribute: attribute, 9 | relatedBy: .equal, toItem: self, 10 | attribute: attribute, multiplier: 1, constant: 0) 11 | ) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Items/TabbyBarItem.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public struct TabbyBarItem: Equatable { 4 | 5 | public var controller: UIViewController 6 | public var image: String 7 | public var selection: Behavior.Selection 8 | 9 | public init(controller: UIViewController, image: String, 10 | selection: Behavior.Selection = .systematic) { 11 | 12 | self.controller = controller 13 | self.image = image 14 | self.selection = selection 15 | } 16 | } 17 | 18 | public func ==(lhs: TabbyBarItem, rhs: TabbyBarItem) -> Bool { 19 | return lhs.controller == rhs.controller 20 | && lhs.image == rhs.image 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Layout/TabbyLayout.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class TabbyLayout: UICollectionViewFlowLayout { 4 | 5 | override var collectionViewContentSize : CGSize { 6 | return CGSize(width: UIScreen.main.bounds.width, 7 | height: Constant.Dimension.height) 8 | } 9 | 10 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 11 | var attributes: [UICollectionViewLayoutAttributes] = [] 12 | var offset: CGFloat = sectionInset.left 13 | 14 | guard let 15 | defaults = super.layoutAttributesForElements(in: rect), 16 | let collectionView = collectionView, 17 | let items = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: 0) 18 | else { return attributes } 19 | 20 | for attribute in defaults { 21 | guard let attribute = attribute.copy() as? UICollectionViewLayoutAttributes else { continue } 22 | 23 | attribute.frame.size.width = UIScreen.main.bounds.width / CGFloat(items) 24 | attribute.frame.size.height = Constant.Dimension.height 25 | attribute.frame.origin.x = offset 26 | offset += attribute.size.width + minimumInteritemSpacing 27 | 28 | attributes.append(attribute) 29 | } 30 | 31 | return attributes 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Library/Behavior.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// Collection of behaviors within the app. 4 | public struct Behavior { 5 | 6 | /// Label visibility behavior. 7 | public enum Label { 8 | case visible, invisible, activeVisible 9 | } 10 | 11 | /// Selected behavior. 12 | public enum Selection { 13 | case systematic, custom 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Library/Constant.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public struct Constant { 4 | 5 | public static var cellType: TabbyCell.Type = TabbyDefaultCell.self 6 | 7 | /// This is the font that will be displayed in the tab bar. For your controller's title. 8 | public struct Font { 9 | /// The font of the title. 10 | public static var title = UIFont.systemFont(ofSize: 9) 11 | /// The font of the badge. 12 | public static var badge = UIFont.boldSystemFont(ofSize: 9) 13 | } 14 | 15 | /// This represents multiple colors of the tab bar. 16 | public struct Color { 17 | /// The background of the tab bar. 18 | public static var background = UIColor.black 19 | 20 | /// The color of the disabled icon, the icon that is not tapped. 21 | public static var disabled = UIColor.lightGray 22 | 23 | /// The color of the enabled state. 24 | public static var enabled = UIColor.white 25 | 26 | /// The color of the selected state. 27 | public static var selected = UIColor.white 28 | 29 | /// The color of the separator between the tab bar and the controller. 30 | public static var separator = UIColor.lightGray 31 | 32 | /// The color of the collection view. 33 | public static var collection = UIColor.clear 34 | 35 | /// The color of the indicator in the tab bar. 36 | public static var indicator = Color.selected 37 | 38 | /// The color of the shadow that will substitute the indicator. 39 | public static var shadow = UIColor.black.withAlphaComponent(0.1) 40 | 41 | public struct Badge { 42 | /// The color of the badge. 43 | public static var background = UIColor.red 44 | 45 | /// The color of the border of the badge. 46 | public static var border = UIColor.white 47 | 48 | /// The color of the text of the badge. 49 | public static var text = UIColor.white 50 | } 51 | } 52 | 53 | /// The dimensions of the tab bar. 54 | public struct Dimension { 55 | /// The height of the tab bar. 56 | public static var height: CGFloat = 50 57 | 58 | /// The dimensions of the icons inside the tab bar. 59 | public struct Icon { 60 | 61 | /// The width of the icon of the tab bar. 62 | public static var width: CGFloat = 30 63 | 64 | /// The height of the icon of the tab bar. 65 | public static var height: CGFloat = 22.5 66 | } 67 | 68 | /// This represents the dimensions of the indicator. 69 | public struct Indicator { 70 | /// The position of the indicator, either `.top` or `.bottom`. 71 | public static var position: NSLayoutAttribute = .bottom 72 | 73 | /// The width of the indicator. 74 | public static var width: CGFloat = 25 75 | 76 | /// The height of the indicator. 77 | public static var height: CGFloat = 3 78 | } 79 | 80 | /// This represents the dimensions of the separator. 81 | public struct Separator { 82 | /// The height of the separator. 83 | public static var height: CGFloat = 0.25 84 | } 85 | 86 | /// This represents the badge size constants. 87 | public struct Badge { 88 | /// The size of the badge. 89 | public static var size: CGFloat = 18 90 | 91 | /// The size of the border radius. 92 | public static var border: CGFloat = 1.5 93 | } 94 | } 95 | 96 | /// Different kinds of behaviors for the tab bar. 97 | public struct Behavior { 98 | /// Label visibility behavior. 99 | public static var labelVisibility: Tabby.Behavior.Label = .invisible 100 | } 101 | 102 | /// Animation when tapping on the tabbar item 103 | public static var tapItemAnimation: (UIView) -> Void = { view in 104 | UIView.animate(withDuration: 0.1, animations: { 105 | view.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) 106 | }, completion: { _ in 107 | UIView.animate( 108 | withDuration: 0.4, 109 | delay: 0, 110 | usingSpringWithDamping: 0.4, 111 | initialSpringVelocity: 0, 112 | options: UIViewAnimationOptions(), 113 | animations: { 114 | view.transform = CGAffineTransform.identity 115 | }, completion: nil) 116 | }) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/Library/Constraints.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | // https://github.com/hyperoslo/Sugar/blob/master/Sources/iOS/Constraint.swift 4 | public struct Constraint { 5 | static func on(constraints: [NSLayoutConstraint]) { 6 | constraints.forEach { 7 | ($0.firstItem as? UIView)?.translatesAutoresizingMaskIntoConstraints = false 8 | $0.isActive = true 9 | } 10 | } 11 | 12 | static func on(_ constraints: NSLayoutConstraint ...) { 13 | on(constraints: constraints) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/TabbyController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /** 4 | The protocol that will inform you when an item of the tab bar is tapped. 5 | */ 6 | public protocol TabbyDelegate: class { 7 | 8 | func tabbyDidPress(_ item: TabbyBarItem) 9 | } 10 | 11 | /** 12 | TabbyController is the controller that will contain all the other controllers. 13 | */ 14 | open class TabbyController: UIViewController, UINavigationControllerDelegate { 15 | 16 | public var heightConstraint: NSLayoutConstraint? 17 | 18 | /// Used when toggling tabbyBar visibility 19 | private var tabbyBarBottomConstraint: NSLayoutConstraint? 20 | 21 | /** 22 | The actual tab bar that will contain the buttons, indicator, separator, etc. 23 | */ 24 | open lazy var tabbyBar: TabbyBar = { [unowned self] in 25 | let tabby = TabbyBar(items: self.items) 26 | tabby.translatesAutoresizingMaskIntoConstraints = false 27 | tabby.delegate = self 28 | 29 | return tabby 30 | }() 31 | 32 | /// A view behind TabbyBar to patch in the bottom in case of safeArea 33 | fileprivate lazy var patchyView: UIView = { 34 | let view = UIView() 35 | view.backgroundColor = Constant.Color.background 36 | return view 37 | }() 38 | 39 | /** 40 | The current selected controller in the tab bar. 41 | */ 42 | open var selectedController: UIViewController { 43 | return items[index].controller 44 | } 45 | 46 | /** 47 | Represents if the bar is hidden or not. 48 | */ 49 | open var barHidden: Bool = false { 50 | didSet { 51 | // Delay necessary when changing the whole controller -> UIViewController 52 | // to UINavigationController for instance. The inner constraints change (and break). 53 | let when = DispatchTime.now() + 0.1 54 | DispatchQueue.main.asyncAfter(deadline: when) { 55 | self.toggleBar() 56 | } 57 | } 58 | } 59 | 60 | /** 61 | An array of TabbyBarItems. The initializer contains the following parameters: 62 | 63 | - Parameter controller: The controller that you set as the one that will appear when tapping the view. 64 | - Parameter image: The image that will appear in the TabbyBarItem. 65 | */ 66 | open var items: [TabbyBarItem] { 67 | didSet { 68 | let currentItem = index < tabbyBar.items.count 69 | ? tabbyBar.items[index] 70 | : items[index] 71 | 72 | if let index = items.index(of: currentItem) { 73 | self.index = index 74 | } 75 | 76 | tabbyBar.items = items 77 | setupDelegate() 78 | } 79 | } 80 | 81 | /** 82 | The property to set the current tab bar index. 83 | */ 84 | open var setIndex = 0 { 85 | didSet { 86 | guard setIndex < items.count else { return } 87 | 88 | index = setIndex 89 | tabbyBar.selectedItem = index 90 | } 91 | } 92 | 93 | /** 94 | Weather the tab bar is translucent or not, this will make you to have to care about the offsets in your controller. 95 | */ 96 | open var translucent: Bool = false { 97 | didSet { 98 | guard index < items.count else { return } 99 | 100 | prepareCurrentController() 101 | 102 | if !showSeparator { 103 | tabbyBar.layer.shadowOpacity = translucent ? 0 : 1 104 | tabbyBar.translucentView.layer.shadowOpacity = translucent ? 1 : 0 105 | } 106 | } 107 | } 108 | 109 | /** 110 | A property indicating weather the tab bar should be visible or not. True by default. 111 | */ 112 | open var barVisible: Bool = true { 113 | didSet { 114 | tabbyBar.alpha = barVisible ? 1 : 0 115 | prepareCurrentController() 116 | } 117 | } 118 | 119 | /** 120 | Weather or not it should show the indicator or not to show in which tab the user is in. 121 | */ 122 | open var showIndicator: Bool = true { 123 | didSet { 124 | tabbyBar.indicator.alpha = showIndicator ? 1 : 0 125 | } 126 | } 127 | 128 | /** 129 | Weather or not it should display a separator or a shadow. 130 | */ 131 | open var showSeparator: Bool = true { 132 | didSet { 133 | tabbyBar.separator.alpha = showSeparator ? 1 : 0 134 | 135 | if showSeparator { 136 | tabbyBar.layer.shadowOpacity = 0 137 | tabbyBar.translucentView.layer.shadowOpacity = 0 138 | } else { 139 | if translucent { 140 | tabbyBar.translucentView.layer.shadowOpacity = 1 141 | } else { 142 | tabbyBar.layer.shadowOpacity = 1 143 | } 144 | } 145 | } 146 | } 147 | 148 | var heightConstant: CGFloat = 0 149 | 150 | /** 151 | The delegate that will tell you when a tab bar is tapped. 152 | */ 153 | open weak var delegate: TabbyDelegate? 154 | 155 | // MARK: - Private variables 156 | 157 | fileprivate var index = 0 158 | 159 | // MARK: - Initializers 160 | 161 | /** 162 | Initializer with a touple of controllers and images for it. 163 | */ 164 | public init(items: [TabbyBarItem]) { 165 | self.items = items 166 | 167 | super.init(nibName: nil, bundle: nil) 168 | 169 | view.addSubview(patchyView) 170 | view.addSubview(tabbyBar) 171 | 172 | setupConstraints() 173 | } 174 | 175 | /** 176 | Initializer. 177 | */ 178 | public required init?(coder aDecoder: NSCoder) { 179 | fatalError("init(coder:) has not been implemented") 180 | } 181 | 182 | // MARK: - View lifecycle 183 | 184 | /** 185 | Did appear. 186 | */ 187 | open override func viewDidAppear(_ animated: Bool) { 188 | super.viewDidAppear(animated) 189 | 190 | guard childViewControllers.isEmpty else { 191 | return 192 | } 193 | 194 | tabbyButtonDidPress(index) 195 | } 196 | 197 | open override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { 198 | tabbyBar.collectionView.reloadData() 199 | } 200 | 201 | // MARK: - Configurations 202 | 203 | open func setBadge(_ value: Int, _ itemImage: String) { 204 | guard !items.filter({ $0.image == itemImage }).isEmpty else { return } 205 | 206 | tabbyBar.badges[itemImage] = value 207 | } 208 | 209 | // MARK: - Helper methods 210 | 211 | func setupDelegate() { 212 | for case let controller as UINavigationController in items.map({ $0.controller }) { 213 | controller.delegate = self 214 | } 215 | } 216 | 217 | func prepareCurrentController() { 218 | let controller = items[index].controller 219 | controller.removeFromParentViewController() 220 | controller.view.removeFromSuperview() 221 | controller.view.translatesAutoresizingMaskIntoConstraints = false 222 | 223 | addChildViewController(controller) 224 | view.insertSubview(controller.view, belowSubview: patchyView) 225 | tabbyBar.prepareTranslucency(translucent) 226 | applyNewConstraints(controller) 227 | } 228 | 229 | func toggleBar() { 230 | animateNewConstraints() 231 | 232 | UIView.animate(withDuration: 0.3, animations: { 233 | if self.barHidden { 234 | self.hideTabbar() 235 | } else { 236 | self.showTabbar() 237 | } 238 | }, completion: { _ in 239 | self.tabbyBar.positionIndicator(self.index) 240 | }) 241 | } 242 | 243 | func animateNewConstraints() { 244 | UIView.animate(withDuration: 0.3, animations: { 245 | self.prepareCurrentController() 246 | self.view.layoutIfNeeded() 247 | }) 248 | } 249 | 250 | open func applyNewConstraints(_ controller: UIViewController) { 251 | var constant: CGFloat = 0 252 | 253 | if barVisible { 254 | constant = -Constant.Dimension.height 255 | } 256 | 257 | if translucent { 258 | constant = 0 259 | } 260 | 261 | if barHidden { 262 | constant = 0 263 | } 264 | 265 | heightConstant = constant 266 | 267 | view.constraint(controller.view, attributes: .leading, .trailing, .top) 268 | 269 | let constraint = NSLayoutConstraint( 270 | item: controller.view, attribute: .height, 271 | relatedBy: .equal, toItem: view, 272 | attribute: .height, multiplier: 1, 273 | constant: constant) 274 | 275 | view.addConstraints([constraint]) 276 | heightConstraint = constraint 277 | } 278 | 279 | // MARK: - Constraints 280 | 281 | func setupConstraints() { 282 | if #available(iOS 9.0, *) { 283 | Constraint.on( 284 | tabbyBar.rightAnchor.constraint(equalTo: view.rightAnchor), 285 | tabbyBar.leftAnchor.constraint(equalTo: view.leftAnchor), 286 | tabbyBar.heightAnchor.constraint(equalToConstant: Constant.Dimension.height), 287 | 288 | patchyView.topAnchor.constraint(equalTo: tabbyBar.bottomAnchor), 289 | patchyView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 290 | patchyView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 291 | patchyView.bottomAnchor.constraint(equalTo: view.bottomAnchor) 292 | ) 293 | } else { 294 | // Fallback on earlier versions 295 | } 296 | 297 | if #available(iOS 11, *) { 298 | tabbyBarBottomConstraint = tabbyBar.bottomAnchor.constraint( 299 | equalTo: view.safeAreaLayoutGuide.bottomAnchor 300 | ) 301 | 302 | Constraint.on( 303 | tabbyBarBottomConstraint! 304 | ) 305 | } else { 306 | if #available(iOS 9.0, *) { 307 | tabbyBarBottomConstraint = tabbyBar.bottomAnchor.constraint( 308 | equalTo: view.bottomAnchor 309 | ) 310 | } else { 311 | // Fallback on earlier versions 312 | } 313 | 314 | Constraint.on( 315 | tabbyBarBottomConstraint! 316 | ) 317 | } 318 | } 319 | 320 | // MARK: - Tabbar 321 | 322 | public func showTabbar() { 323 | tabbyBarBottomConstraint?.constant = 0 324 | tabbyBar.indicator.alpha = showIndicator ? 1 : 0 325 | 326 | UIView.animate(withDuration: 0.25) { 327 | self.view.layoutIfNeeded() 328 | } 329 | } 330 | 331 | public func hideTabbar() { 332 | tabbyBarBottomConstraint?.constant = 200 333 | tabbyBar.indicator.alpha = 0 334 | 335 | UIView.animate(withDuration: 0.25) { 336 | self.view.layoutIfNeeded() 337 | } 338 | } 339 | } 340 | 341 | extension TabbyController { 342 | 343 | public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { 344 | 345 | if viewController.hidesBottomBarWhenPushed { 346 | hideTabbar() 347 | } else { 348 | showTabbar() 349 | } 350 | } 351 | } 352 | 353 | extension TabbyController: TabbyBarDelegate { 354 | 355 | /** 356 | The delegate method comming from the tab bar. 357 | 358 | - Parameter index: The index that was just tapped. 359 | */ 360 | public func tabbyButtonDidPress(_ index: Int) { 361 | self.index = index 362 | 363 | guard index < items.count else { return } 364 | 365 | let item = items[index] 366 | let controller = item.controller 367 | 368 | delegate?.tabbyDidPress(item) 369 | 370 | guard item.selection == .systematic else { return } 371 | /// Check if it should do another action rather than removing the view. 372 | guard !view.subviews.contains(controller.view) else { 373 | if let navigationController = controller as? UINavigationController { 374 | navigationController.popViewController(animated: true) 375 | } else { 376 | for case let subview as UIScrollView in controller.view.subviews { 377 | subview.setContentOffset(CGPoint.zero, animated: true) 378 | } 379 | } 380 | 381 | return 382 | } 383 | 384 | items.forEach { 385 | $0.controller.removeFromParentViewController() 386 | $0.controller.view.removeFromSuperview() 387 | } 388 | 389 | controller.view.translatesAutoresizingMaskIntoConstraints = false 390 | 391 | addChildViewController(controller) 392 | view.insertSubview(controller.view, belowSubview: patchyView) 393 | 394 | applyNewConstraints(controller) 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /Sources/Views/TabbyBadge.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | open class TabbyBadge: UIView { 4 | 5 | public lazy var containerView: UIView = UIView() 6 | 7 | public lazy var numberLabel: UILabel = { 8 | let label = UILabel() 9 | label.textAlignment = .center 10 | label.font = Constant.Font.badge 11 | label.textColor = Constant.Color.Badge.text 12 | 13 | return label 14 | }() 15 | 16 | public var number: Int = 0 { 17 | didSet { 18 | var text = "\(number)" 19 | if number >= 99 { 20 | text = "+99" 21 | } 22 | 23 | visible = number > 0 24 | numberLabel.text = text 25 | 26 | setupConstraints() 27 | } 28 | } 29 | 30 | public var visible: Bool = false { 31 | didSet { 32 | alpha = visible ? 1 : 0 33 | } 34 | } 35 | 36 | public override init(frame: CGRect) { 37 | super.init(frame: frame) 38 | 39 | defer { 40 | number = 0 41 | } 42 | 43 | backgroundColor = Constant.Color.Badge.border 44 | containerView.backgroundColor = Constant.Color.Badge.background 45 | 46 | setupConstraints() 47 | } 48 | 49 | public required init?(coder aDecoder: NSCoder) { 50 | fatalError("init(coder:) has not been implemented") 51 | } 52 | 53 | // MARK: - Constraints 54 | 55 | open func setupConstraints() { 56 | containerView.translatesAutoresizingMaskIntoConstraints = false 57 | containerView.removeFromSuperview() 58 | 59 | addSubview(containerView) 60 | constraint(containerView, attributes: .centerX, .centerY) 61 | addConstraints([ 62 | NSLayoutConstraint(item: self, 63 | attribute: .width, relatedBy: .equal, 64 | toItem: containerView, attribute: .width, 65 | multiplier: 1, constant: Constant.Dimension.Badge.border * 2), 66 | 67 | NSLayoutConstraint(item: self, 68 | attribute: .height, relatedBy: .equal, 69 | toItem: containerView, attribute: .height, 70 | multiplier: 1, constant: Constant.Dimension.Badge.border * 2) 71 | ]) 72 | 73 | numberLabel.translatesAutoresizingMaskIntoConstraints = false 74 | numberLabel.removeFromSuperview() 75 | 76 | containerView.addSubview(numberLabel) 77 | containerView.constraint(numberLabel, attributes: .centerX, .centerY) 78 | 79 | numberLabel.sizeToFit() 80 | 81 | addConstraints([ 82 | NSLayoutConstraint(item: containerView, 83 | attribute: .width, relatedBy: .equal, 84 | toItem: numberLabel, attribute: .width, 85 | multiplier: 1, constant: 6), 86 | 87 | NSLayoutConstraint(item: containerView, 88 | attribute: .height, relatedBy: .equal, 89 | toItem: numberLabel, attribute: .height, 90 | multiplier: 1, constant: 1) 91 | ]) 92 | 93 | containerView.layer.cornerRadius = (numberLabel.frame.height + 1) / 2 94 | layer.cornerRadius = containerView.layer.cornerRadius + Constant.Dimension.Badge.border 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/Views/TabbyBar.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /** 4 | The tab bar delegate that will tell you when a button in the tab bar was pressed. 5 | */ 6 | public protocol TabbyBarDelegate: class { 7 | 8 | func tabbyButtonDidPress(_ index: Int) 9 | } 10 | 11 | /** 12 | The actual tab bar. 13 | */ 14 | open class TabbyBar: UIView { 15 | 16 | lazy var translucentView: UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) 17 | 18 | lazy var layout: TabbyLayout = { 19 | let layout = TabbyLayout() 20 | layout.minimumInteritemSpacing = 0 21 | 22 | return layout 23 | }() 24 | 25 | lazy var collectionView: UICollectionView = 26 | UICollectionView(frame: .zero, collectionViewLayout: self.layout) 27 | 28 | lazy var indicator: UIView = { 29 | let view = UIView() 30 | view.layer.zPosition = 2 31 | view.backgroundColor = Constant.Color.indicator 32 | 33 | return view 34 | }() 35 | 36 | lazy var separator: UIView = { 37 | let view = UIView() 38 | view.backgroundColor = Constant.Color.separator 39 | view.layer.zPosition = 1 40 | 41 | return view 42 | }() 43 | 44 | var items: [TabbyBarItem] { 45 | didSet { 46 | // TODO: Configure the did set and delete the reload. 47 | collectionView.reloadData() 48 | } 49 | } 50 | 51 | var selectedItem: Int = 0 { 52 | didSet { 53 | positionIndicator(selectedItem) 54 | } 55 | } 56 | 57 | var badges: [String : Int] = [:] { 58 | didSet { 59 | collectionView.reloadData() 60 | } 61 | } 62 | 63 | weak var delegate: TabbyBarDelegate? 64 | 65 | // MARK: - Initializers 66 | 67 | /** 68 | Initializer 69 | */ 70 | public init(items: [TabbyBarItem]) { 71 | self.items = items 72 | super.init(frame: .zero) 73 | 74 | backgroundColor = .clear 75 | 76 | setupCollectionView() 77 | setupConstraints() 78 | } 79 | 80 | /** 81 | Initializer. 82 | */ 83 | public required init?(coder aDecoder: NSCoder) { 84 | fatalError("init(coder:) has not been implemented") 85 | } 86 | 87 | // MARK: - Collection View setup 88 | 89 | func setupCollectionView() { 90 | collectionView.delegate = self 91 | collectionView.dataSource = self 92 | collectionView.clipsToBounds = false 93 | collectionView.backgroundColor = Constant.Color.collection 94 | 95 | collectionView.register( 96 | Constant.cellType.self, forCellWithReuseIdentifier: Constant.cellType.reusableIdentifier) 97 | } 98 | 99 | // Animations 100 | 101 | func positionIndicator(_ index: Int, animate: Bool = true) { 102 | guard collectionView.dataSource != nil, 103 | index < items.count else { 104 | return 105 | } 106 | 107 | let indexPath = IndexPath(row: index, section: 0) 108 | guard let cell = collectionView.cellForItem(at: indexPath) else { 109 | return 110 | } 111 | 112 | UIView.animate( 113 | withDuration: animate ? 0.7 : 0, delay: 0, usingSpringWithDamping: 0.6, 114 | initialSpringVelocity: 0, options: [.curveEaseIn], animations: { 115 | self.indicator.center.x = cell.center.x 116 | }, completion: nil) 117 | } 118 | 119 | // MARK: - Translucency 120 | 121 | func prepareTranslucency(_ translucent: Bool) { 122 | translucentView.removeFromSuperview() 123 | 124 | if translucent { 125 | translucentView.translatesAutoresizingMaskIntoConstraints = false 126 | insertSubview(translucentView, at: 0) 127 | constraint(translucentView, attributes: .width, .height, .top, .left) 128 | backgroundColor = Constant.Color.background.withAlphaComponent(0.85) 129 | } else { 130 | backgroundColor = Constant.Color.background 131 | } 132 | } 133 | 134 | // MARK: - Constraints 135 | 136 | func setupConstraints() { 137 | indicator.translatesAutoresizingMaskIntoConstraints = false 138 | addSubview(indicator) 139 | 140 | let indicatorLayoutAttribute: NSLayoutAttribute = Constant.Dimension.Indicator.position == .top 141 | ? .top 142 | : .bottom 143 | constraint(indicator, attributes: .left, indicatorLayoutAttribute) 144 | addConstraints([ 145 | NSLayoutConstraint( 146 | item: indicator, 147 | attribute: .width, relatedBy: .equal, 148 | toItem: nil, attribute: .notAnAttribute, 149 | multiplier: 1, constant: Constant.Dimension.Indicator.width), 150 | 151 | NSLayoutConstraint( 152 | item: indicator, 153 | attribute: .height, relatedBy: .equal, 154 | toItem: nil, attribute: .notAnAttribute, 155 | multiplier: 1, constant: Constant.Dimension.Indicator.height) 156 | ]) 157 | 158 | separator.translatesAutoresizingMaskIntoConstraints = false 159 | addSubview(separator) 160 | constraint(separator, attributes: .width, .top, .right) 161 | addConstraint( 162 | NSLayoutConstraint(item: separator, 163 | attribute: .height, relatedBy: .equal, 164 | toItem: nil, attribute: .notAnAttribute, 165 | multiplier: 1, constant: Constant.Dimension.Separator.height) 166 | ) 167 | 168 | collectionView.translatesAutoresizingMaskIntoConstraints = false 169 | addSubview(collectionView) 170 | constraint(collectionView, attributes: .top, .bottom, .leading, .trailing) 171 | } 172 | 173 | // MAKR: - Selection 174 | 175 | /// Handle selection on a cell 176 | /// This call Constant.tapItemAnimation on the found UIImageView by default. 177 | /// 178 | /// - Parameter indexPath: The indexPath of the selected cell 179 | open func handleSelection(indexPath: IndexPath) { 180 | guard let cell = collectionView.cellForItem(at: indexPath) else { 181 | return 182 | } 183 | 184 | guard let imageView = cell.contentView.subviews 185 | .flatMap({ $0 as? UIImageView }).first else { 186 | return 187 | } 188 | 189 | Constant.tapItemAnimation(imageView) 190 | } 191 | } 192 | 193 | extension TabbyBar: UICollectionViewDelegate { 194 | 195 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 196 | let position = indexPath.row 197 | let item = items[position] 198 | 199 | if item.selection == .systematic { 200 | UIView.animate(withDuration: 0.3, animations: { 201 | self.indicator.backgroundColor = Constant.Color.selected 202 | }) 203 | 204 | selectedItem = position 205 | } 206 | 207 | handleSelection(indexPath: indexPath) 208 | delegate?.tabbyButtonDidPress(position) 209 | } 210 | } 211 | 212 | extension TabbyBar: UICollectionViewDataSource { 213 | 214 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 215 | return items.count 216 | } 217 | 218 | public func collectionView(_ collectionView: UICollectionView, 219 | cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 220 | 221 | guard let cell = collectionView.dequeueReusableCell( 222 | withReuseIdentifier: Constant.cellType.reusableIdentifier, for: indexPath) 223 | as? TabbyCell else { return UICollectionViewCell() } 224 | 225 | let item = items[indexPath.row] 226 | 227 | cell.configureCell(item, at: indexPath.row, 228 | selected: selectedItem == indexPath.row, 229 | count: badges[item.image]) 230 | 231 | return cell.collectionViewCell 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Tabby.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Tabby" 3 | s.summary = "A fancy tabbar" 4 | s.version = "0.2.0" 5 | s.homepage = "https://github.com/hyperoslo/Tabby" 6 | s.license = 'MIT' 7 | s.author = { "Hyper Interaktiv AS" => "ios@hyper.no" } 8 | s.source = { 9 | :git => "https://github.com/hyperoslo/Tabby.git", 10 | :tag => s.version.to_s 11 | } 12 | s.social_media_url = 'https://twitter.com/hyperoslo' 13 | 14 | s.ios.deployment_target = '9.0' 15 | 16 | s.requires_arc = true 17 | s.ios.source_files = 'Sources/**/*' 18 | end 19 | -------------------------------------------------------------------------------- /Tabby.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 292B02731D8189F700DCA1C1 /* TabbyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B02641D8189F700DCA1C1 /* TabbyCell.swift */; }; 11 | 292B02741D8189F700DCA1C1 /* UIView+Shadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B02661D8189F700DCA1C1 /* UIView+Shadow.swift */; }; 12 | 292B02751D8189F700DCA1C1 /* UIViewController+Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B02671D8189F700DCA1C1 /* UIViewController+Constraint.swift */; }; 13 | 292B02761D8189F700DCA1C1 /* TabbyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B02691D8189F700DCA1C1 /* TabbyBarItem.swift */; }; 14 | 292B02771D8189F700DCA1C1 /* TabbyLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B026B1D8189F700DCA1C1 /* TabbyLayout.swift */; }; 15 | 292B02781D8189F700DCA1C1 /* Behavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B026D1D8189F700DCA1C1 /* Behavior.swift */; }; 16 | 292B02791D8189F700DCA1C1 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B026E1D8189F700DCA1C1 /* Constant.swift */; }; 17 | 292B027A1D8189F700DCA1C1 /* TabbyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B026F1D8189F700DCA1C1 /* TabbyController.swift */; }; 18 | 292B027B1D8189F700DCA1C1 /* TabbyBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292B02711D8189F700DCA1C1 /* TabbyBar.swift */; }; 19 | D2EB46A91FCC635100BA6307 /* Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EB46A81FCC635100BA6307 /* Constraints.swift */; }; 20 | F631D20B1DF1A223002D0B8F /* TabbyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F631D20A1DF1A223002D0B8F /* TabbyTests.swift */; }; 21 | F631D20D1DF1A223002D0B8F /* Tabby.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5B2E89F1C3A780C00C0327D /* Tabby.framework */; }; 22 | F631D2141DF1A26D002D0B8F /* TabbyBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = F631D2131DF1A26D002D0B8F /* TabbyBadge.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | F631D20E1DF1A223002D0B8F /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = D5B2E8961C3A780C00C0327D /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = D5B2E89E1C3A780C00C0327D; 31 | remoteInfo = Tabby; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 292B02641D8189F700DCA1C1 /* TabbyCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbyCell.swift; sourceTree = ""; }; 37 | 292B02661D8189F700DCA1C1 /* UIView+Shadow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Shadow.swift"; sourceTree = ""; }; 38 | 292B02671D8189F700DCA1C1 /* UIViewController+Constraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Constraint.swift"; sourceTree = ""; }; 39 | 292B02691D8189F700DCA1C1 /* TabbyBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbyBarItem.swift; sourceTree = ""; }; 40 | 292B026B1D8189F700DCA1C1 /* TabbyLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbyLayout.swift; sourceTree = ""; }; 41 | 292B026D1D8189F700DCA1C1 /* Behavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Behavior.swift; sourceTree = ""; }; 42 | 292B026E1D8189F700DCA1C1 /* Constant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; 43 | 292B026F1D8189F700DCA1C1 /* TabbyController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbyController.swift; sourceTree = ""; }; 44 | 292B02711D8189F700DCA1C1 /* TabbyBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbyBar.swift; sourceTree = ""; }; 45 | 29E9FD211CE318F1003F9384 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | D2EB46A81FCC635100BA6307 /* Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constraints.swift; sourceTree = ""; }; 47 | D5B2E89F1C3A780C00C0327D /* Tabby.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Tabby.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | F631D2081DF1A223002D0B8F /* TabbyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TabbyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | F631D20A1DF1A223002D0B8F /* TabbyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbyTests.swift; sourceTree = ""; }; 50 | F631D20C1DF1A223002D0B8F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | F631D2131DF1A26D002D0B8F /* TabbyBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabbyBadge.swift; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | D5B2E89B1C3A780C00C0327D /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | F631D2051DF1A223002D0B8F /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | F631D20D1DF1A223002D0B8F /* Tabby.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 292B02601D8189F700DCA1C1 /* Sources */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 292B02631D8189F700DCA1C1 /* Cells */, 77 | 292B02651D8189F700DCA1C1 /* Extensions */, 78 | 292B02681D8189F700DCA1C1 /* Items */, 79 | 292B026A1D8189F700DCA1C1 /* Layout */, 80 | 292B026C1D8189F700DCA1C1 /* Library */, 81 | 292B026F1D8189F700DCA1C1 /* TabbyController.swift */, 82 | 292B02701D8189F700DCA1C1 /* Views */, 83 | ); 84 | path = Sources; 85 | sourceTree = ""; 86 | }; 87 | 292B02631D8189F700DCA1C1 /* Cells */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 292B02641D8189F700DCA1C1 /* TabbyCell.swift */, 91 | ); 92 | path = Cells; 93 | sourceTree = ""; 94 | }; 95 | 292B02651D8189F700DCA1C1 /* Extensions */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 292B02661D8189F700DCA1C1 /* UIView+Shadow.swift */, 99 | 292B02671D8189F700DCA1C1 /* UIViewController+Constraint.swift */, 100 | ); 101 | path = Extensions; 102 | sourceTree = ""; 103 | }; 104 | 292B02681D8189F700DCA1C1 /* Items */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 292B02691D8189F700DCA1C1 /* TabbyBarItem.swift */, 108 | ); 109 | path = Items; 110 | sourceTree = ""; 111 | }; 112 | 292B026A1D8189F700DCA1C1 /* Layout */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 292B026B1D8189F700DCA1C1 /* TabbyLayout.swift */, 116 | ); 117 | path = Layout; 118 | sourceTree = ""; 119 | }; 120 | 292B026C1D8189F700DCA1C1 /* Library */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 292B026D1D8189F700DCA1C1 /* Behavior.swift */, 124 | 292B026E1D8189F700DCA1C1 /* Constant.swift */, 125 | D2EB46A81FCC635100BA6307 /* Constraints.swift */, 126 | ); 127 | path = Library; 128 | sourceTree = ""; 129 | }; 130 | 292B02701D8189F700DCA1C1 /* Views */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | F631D2131DF1A26D002D0B8F /* TabbyBadge.swift */, 134 | 292B02711D8189F700DCA1C1 /* TabbyBar.swift */, 135 | ); 136 | path = Views; 137 | sourceTree = ""; 138 | }; 139 | 29E9FD201CE318F1003F9384 /* Resources */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 29E9FD211CE318F1003F9384 /* Info.plist */, 143 | ); 144 | path = Resources; 145 | sourceTree = ""; 146 | }; 147 | D5B2E8951C3A780C00C0327D = { 148 | isa = PBXGroup; 149 | children = ( 150 | 292B02601D8189F700DCA1C1 /* Sources */, 151 | 29E9FD201CE318F1003F9384 /* Resources */, 152 | F631D2091DF1A223002D0B8F /* TabbyTests */, 153 | D5B2E8A01C3A780C00C0327D /* Products */, 154 | ); 155 | sourceTree = ""; 156 | }; 157 | D5B2E8A01C3A780C00C0327D /* Products */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | D5B2E89F1C3A780C00C0327D /* Tabby.framework */, 161 | F631D2081DF1A223002D0B8F /* TabbyTests.xctest */, 162 | ); 163 | name = Products; 164 | sourceTree = ""; 165 | }; 166 | F631D2091DF1A223002D0B8F /* TabbyTests */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | F631D20A1DF1A223002D0B8F /* TabbyTests.swift */, 170 | F631D20C1DF1A223002D0B8F /* Info.plist */, 171 | ); 172 | path = TabbyTests; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXGroup section */ 176 | 177 | /* Begin PBXHeadersBuildPhase section */ 178 | D5B2E89C1C3A780C00C0327D /* Headers */ = { 179 | isa = PBXHeadersBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXHeadersBuildPhase section */ 186 | 187 | /* Begin PBXNativeTarget section */ 188 | D5B2E89E1C3A780C00C0327D /* Tabby */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = D5B2E8B31C3A780C00C0327D /* Build configuration list for PBXNativeTarget "Tabby" */; 191 | buildPhases = ( 192 | D5B2E89A1C3A780C00C0327D /* Sources */, 193 | D5B2E89B1C3A780C00C0327D /* Frameworks */, 194 | D5B2E89C1C3A780C00C0327D /* Headers */, 195 | D5B2E89D1C3A780C00C0327D /* Resources */, 196 | ); 197 | buildRules = ( 198 | ); 199 | dependencies = ( 200 | ); 201 | name = Tabby; 202 | productName = Tabby; 203 | productReference = D5B2E89F1C3A780C00C0327D /* Tabby.framework */; 204 | productType = "com.apple.product-type.framework"; 205 | }; 206 | F631D2071DF1A223002D0B8F /* TabbyTests */ = { 207 | isa = PBXNativeTarget; 208 | buildConfigurationList = F631D2101DF1A223002D0B8F /* Build configuration list for PBXNativeTarget "TabbyTests" */; 209 | buildPhases = ( 210 | F631D2041DF1A223002D0B8F /* Sources */, 211 | F631D2051DF1A223002D0B8F /* Frameworks */, 212 | F631D2061DF1A223002D0B8F /* Resources */, 213 | ); 214 | buildRules = ( 215 | ); 216 | dependencies = ( 217 | F631D20F1DF1A223002D0B8F /* PBXTargetDependency */, 218 | ); 219 | name = TabbyTests; 220 | productName = TabbyTests; 221 | productReference = F631D2081DF1A223002D0B8F /* TabbyTests.xctest */; 222 | productType = "com.apple.product-type.bundle.unit-test"; 223 | }; 224 | /* End PBXNativeTarget section */ 225 | 226 | /* Begin PBXProject section */ 227 | D5B2E8961C3A780C00C0327D /* Project object */ = { 228 | isa = PBXProject; 229 | attributes = { 230 | LastSwiftUpdateCheck = 0810; 231 | LastUpgradeCheck = 0910; 232 | ORGANIZATIONNAME = "Hyper Interaktiv AS"; 233 | TargetAttributes = { 234 | D5B2E89E1C3A780C00C0327D = { 235 | CreatedOnToolsVersion = 7.2; 236 | LastSwiftMigration = 0800; 237 | }; 238 | F631D2071DF1A223002D0B8F = { 239 | CreatedOnToolsVersion = 8.1; 240 | ProvisioningStyle = Automatic; 241 | }; 242 | }; 243 | }; 244 | buildConfigurationList = D5B2E8991C3A780C00C0327D /* Build configuration list for PBXProject "Tabby" */; 245 | compatibilityVersion = "Xcode 3.2"; 246 | developmentRegion = English; 247 | hasScannedForEncodings = 0; 248 | knownRegions = ( 249 | en, 250 | ); 251 | mainGroup = D5B2E8951C3A780C00C0327D; 252 | productRefGroup = D5B2E8A01C3A780C00C0327D /* Products */; 253 | projectDirPath = ""; 254 | projectRoot = ""; 255 | targets = ( 256 | D5B2E89E1C3A780C00C0327D /* Tabby */, 257 | F631D2071DF1A223002D0B8F /* TabbyTests */, 258 | ); 259 | }; 260 | /* End PBXProject section */ 261 | 262 | /* Begin PBXResourcesBuildPhase section */ 263 | D5B2E89D1C3A780C00C0327D /* Resources */ = { 264 | isa = PBXResourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | F631D2061DF1A223002D0B8F /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXResourcesBuildPhase section */ 278 | 279 | /* Begin PBXSourcesBuildPhase section */ 280 | D5B2E89A1C3A780C00C0327D /* Sources */ = { 281 | isa = PBXSourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | 292B02731D8189F700DCA1C1 /* TabbyCell.swift in Sources */, 285 | 292B02741D8189F700DCA1C1 /* UIView+Shadow.swift in Sources */, 286 | 292B02771D8189F700DCA1C1 /* TabbyLayout.swift in Sources */, 287 | 292B02791D8189F700DCA1C1 /* Constant.swift in Sources */, 288 | D2EB46A91FCC635100BA6307 /* Constraints.swift in Sources */, 289 | 292B027A1D8189F700DCA1C1 /* TabbyController.swift in Sources */, 290 | 292B02751D8189F700DCA1C1 /* UIViewController+Constraint.swift in Sources */, 291 | 292B02761D8189F700DCA1C1 /* TabbyBarItem.swift in Sources */, 292 | 292B027B1D8189F700DCA1C1 /* TabbyBar.swift in Sources */, 293 | F631D2141DF1A26D002D0B8F /* TabbyBadge.swift in Sources */, 294 | 292B02781D8189F700DCA1C1 /* Behavior.swift in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | F631D2041DF1A223002D0B8F /* Sources */ = { 299 | isa = PBXSourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | F631D20B1DF1A223002D0B8F /* TabbyTests.swift in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | /* End PBXSourcesBuildPhase section */ 307 | 308 | /* Begin PBXTargetDependency section */ 309 | F631D20F1DF1A223002D0B8F /* PBXTargetDependency */ = { 310 | isa = PBXTargetDependency; 311 | target = D5B2E89E1C3A780C00C0327D /* Tabby */; 312 | targetProxy = F631D20E1DF1A223002D0B8F /* PBXContainerItemProxy */; 313 | }; 314 | /* End PBXTargetDependency section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | D5B2E8B11C3A780C00C0327D /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 322 | CLANG_CXX_LIBRARY = "libc++"; 323 | CLANG_ENABLE_MODULES = YES; 324 | CLANG_ENABLE_OBJC_ARC = YES; 325 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_COMMA = YES; 328 | CLANG_WARN_CONSTANT_CONVERSION = YES; 329 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 330 | CLANG_WARN_EMPTY_BODY = YES; 331 | CLANG_WARN_ENUM_CONVERSION = YES; 332 | CLANG_WARN_INFINITE_RECURSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 335 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 338 | CLANG_WARN_STRICT_PROTOTYPES = YES; 339 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 340 | CLANG_WARN_UNREACHABLE_CODE = YES; 341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 342 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 343 | COPY_PHASE_STRIP = NO; 344 | CURRENT_PROJECT_VERSION = 1; 345 | DEBUG_INFORMATION_FORMAT = dwarf; 346 | ENABLE_STRICT_OBJC_MSGSEND = YES; 347 | ENABLE_TESTABILITY = YES; 348 | GCC_C_LANGUAGE_STANDARD = gnu99; 349 | GCC_DYNAMIC_NO_PIC = NO; 350 | GCC_NO_COMMON_BLOCKS = YES; 351 | GCC_OPTIMIZATION_LEVEL = 0; 352 | GCC_PREPROCESSOR_DEFINITIONS = ( 353 | "DEBUG=1", 354 | "$(inherited)", 355 | ); 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 363 | MTL_ENABLE_DEBUG_INFO = YES; 364 | ONLY_ACTIVE_ARCH = YES; 365 | SDKROOT = iphoneos; 366 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 367 | SWIFT_VERSION = 4.0; 368 | TARGETED_DEVICE_FAMILY = "1,2"; 369 | VERSIONING_SYSTEM = "apple-generic"; 370 | VERSION_INFO_PREFIX = ""; 371 | }; 372 | name = Debug; 373 | }; 374 | D5B2E8B21C3A780C00C0327D /* Release */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ALWAYS_SEARCH_USER_PATHS = NO; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 379 | CLANG_CXX_LIBRARY = "libc++"; 380 | CLANG_ENABLE_MODULES = YES; 381 | CLANG_ENABLE_OBJC_ARC = YES; 382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 383 | CLANG_WARN_BOOL_CONVERSION = YES; 384 | CLANG_WARN_COMMA = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 387 | CLANG_WARN_EMPTY_BODY = YES; 388 | CLANG_WARN_ENUM_CONVERSION = YES; 389 | CLANG_WARN_INFINITE_RECURSION = YES; 390 | CLANG_WARN_INT_CONVERSION = YES; 391 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 392 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 393 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 394 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 395 | CLANG_WARN_STRICT_PROTOTYPES = YES; 396 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 397 | CLANG_WARN_UNREACHABLE_CODE = YES; 398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 399 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 400 | COPY_PHASE_STRIP = NO; 401 | CURRENT_PROJECT_VERSION = 1; 402 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 403 | ENABLE_NS_ASSERTIONS = NO; 404 | ENABLE_STRICT_OBJC_MSGSEND = YES; 405 | GCC_C_LANGUAGE_STANDARD = gnu99; 406 | GCC_NO_COMMON_BLOCKS = YES; 407 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 408 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 409 | GCC_WARN_UNDECLARED_SELECTOR = YES; 410 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 411 | GCC_WARN_UNUSED_FUNCTION = YES; 412 | GCC_WARN_UNUSED_VARIABLE = YES; 413 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 414 | MTL_ENABLE_DEBUG_INFO = NO; 415 | SDKROOT = iphoneos; 416 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 417 | SWIFT_VERSION = 4.0; 418 | TARGETED_DEVICE_FAMILY = "1,2"; 419 | VALIDATE_PRODUCT = YES; 420 | VERSIONING_SYSTEM = "apple-generic"; 421 | VERSION_INFO_PREFIX = ""; 422 | }; 423 | name = Release; 424 | }; 425 | D5B2E8B41C3A780C00C0327D /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | CLANG_ENABLE_MODULES = YES; 429 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 430 | DEFINES_MODULE = YES; 431 | DYLIB_COMPATIBILITY_VERSION = 1; 432 | DYLIB_CURRENT_VERSION = 1; 433 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 434 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 435 | INFOPLIST_FILE = "$(SRCROOT)/Resources/Info.plist"; 436 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 437 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 439 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.Tabby; 440 | PRODUCT_NAME = Tabby; 441 | SKIP_INSTALL = YES; 442 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 443 | }; 444 | name = Debug; 445 | }; 446 | D5B2E8B51C3A780C00C0327D /* Release */ = { 447 | isa = XCBuildConfiguration; 448 | buildSettings = { 449 | CLANG_ENABLE_MODULES = YES; 450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 451 | DEFINES_MODULE = YES; 452 | DYLIB_COMPATIBILITY_VERSION = 1; 453 | DYLIB_CURRENT_VERSION = 1; 454 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 455 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 456 | INFOPLIST_FILE = "$(SRCROOT)/Resources/Info.plist"; 457 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 458 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 460 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.Tabby; 461 | PRODUCT_NAME = Tabby; 462 | SKIP_INSTALL = YES; 463 | }; 464 | name = Release; 465 | }; 466 | F631D2111DF1A223002D0B8F /* Debug */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | CLANG_ANALYZER_NONNULL = YES; 470 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 471 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 472 | INFOPLIST_FILE = TabbyTests/Info.plist; 473 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 474 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 475 | PRODUCT_BUNDLE_IDENTIFIER = com.ramongilabert.TabbyTests; 476 | PRODUCT_NAME = "$(TARGET_NAME)"; 477 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 478 | }; 479 | name = Debug; 480 | }; 481 | F631D2121DF1A223002D0B8F /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | CLANG_ANALYZER_NONNULL = YES; 485 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 486 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 487 | INFOPLIST_FILE = TabbyTests/Info.plist; 488 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 489 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 490 | PRODUCT_BUNDLE_IDENTIFIER = com.ramongilabert.TabbyTests; 491 | PRODUCT_NAME = "$(TARGET_NAME)"; 492 | }; 493 | name = Release; 494 | }; 495 | /* End XCBuildConfiguration section */ 496 | 497 | /* Begin XCConfigurationList section */ 498 | D5B2E8991C3A780C00C0327D /* Build configuration list for PBXProject "Tabby" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | D5B2E8B11C3A780C00C0327D /* Debug */, 502 | D5B2E8B21C3A780C00C0327D /* Release */, 503 | ); 504 | defaultConfigurationIsVisible = 0; 505 | defaultConfigurationName = Release; 506 | }; 507 | D5B2E8B31C3A780C00C0327D /* Build configuration list for PBXNativeTarget "Tabby" */ = { 508 | isa = XCConfigurationList; 509 | buildConfigurations = ( 510 | D5B2E8B41C3A780C00C0327D /* Debug */, 511 | D5B2E8B51C3A780C00C0327D /* Release */, 512 | ); 513 | defaultConfigurationIsVisible = 0; 514 | defaultConfigurationName = Release; 515 | }; 516 | F631D2101DF1A223002D0B8F /* Build configuration list for PBXNativeTarget "TabbyTests" */ = { 517 | isa = XCConfigurationList; 518 | buildConfigurations = ( 519 | F631D2111DF1A223002D0B8F /* Debug */, 520 | F631D2121DF1A223002D0B8F /* Release */, 521 | ); 522 | defaultConfigurationIsVisible = 0; 523 | defaultConfigurationName = Release; 524 | }; 525 | /* End XCConfigurationList section */ 526 | }; 527 | rootObject = D5B2E8961C3A780C00C0327D /* Project object */; 528 | } 529 | -------------------------------------------------------------------------------- /Tabby.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tabby.xcodeproj/xcshareddata/xcschemes/Tabby.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /TabbyTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TabbyTests/TabbyTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Tabby 2 | import XCTest 3 | import UIKit 4 | 5 | class TabbyTests: XCTestCase { 6 | 7 | /** 8 | Test to know in which position the controller 9 | should be at every time in relation with the bar. 10 | */ 11 | func testControllerPosition() { 12 | let constant = -Tabby.Constant.Dimension.height 13 | let firstController = UIViewController() 14 | let secondController = UIViewController() 15 | let thirdController = UIViewController() 16 | 17 | let items: [TabbyBarItem] = [ 18 | TabbyBarItem(controller: firstController, image: "cow"), 19 | TabbyBarItem(controller: secondController, image: "donut"), 20 | TabbyBarItem(controller: thirdController, image: "fish") 21 | ] 22 | 23 | let tabbyController = TabbyController(items: items) 24 | 25 | tabbyController.translucent = true 26 | XCTAssertTrue(tabbyController.heightConstant == 0) 27 | 28 | tabbyController.translucent = false 29 | XCTAssertTrue(tabbyController.heightConstant == constant) 30 | 31 | tabbyController.barHidden = false 32 | XCTAssertTrue(tabbyController.heightConstant == constant) 33 | 34 | tabbyController.barVisible = true 35 | XCTAssertTrue(tabbyController.heightConstant == constant) 36 | 37 | tabbyController.barVisible = false 38 | XCTAssertTrue(tabbyController.heightConstant == 0) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Web/sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/Tabby/d30fbffb490c01925c44460c6f1508ce13ce1373/Web/sample.gif --------------------------------------------------------------------------------