├── .gitignore ├── Printerest.png ├── PrinterestTabBar.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── PrinterestTabBar ├── AppDelegate.swift ├── Controller │ ├── Autolayout.swift │ ├── BaseViewController.swift │ ├── FloatingBarView.swift │ ├── PushViewController.swift │ ├── TabBarController.swift │ └── ViewController.swift ├── Info.plist ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ └── Base.lproj │ │ └── LaunchScreen.storyboard └── SceneDelegate.swift ├── README.md └── img ├── capture.gif └── capture2.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Printerest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishoste/printerest-tabbar/888cc5941ba6d4bf4de2bb101950db66ef347763/Printerest.png -------------------------------------------------------------------------------- /PrinterestTabBar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C42098D7245C213400D66F93 /* PushViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42098D6245C213400D66F93 /* PushViewController.swift */; }; 11 | C42098D9245C24D000D66F93 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42098D8245C24D000D66F93 /* BaseViewController.swift */; }; 12 | C4BCEA862430B7180062A872 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BCEA852430B7180062A872 /* AppDelegate.swift */; }; 13 | C4BCEA882430B7180062A872 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BCEA872430B7180062A872 /* SceneDelegate.swift */; }; 14 | C4BCEA8A2430B7180062A872 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BCEA892430B7180062A872 /* ViewController.swift */; }; 15 | C4BCEA8F2430B7190062A872 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C4BCEA8E2430B7190062A872 /* Assets.xcassets */; }; 16 | C4BCEA922430B7190062A872 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C4BCEA902430B7190062A872 /* LaunchScreen.storyboard */; }; 17 | C4BCEA9A2430B7330062A872 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BCEA992430B7330062A872 /* TabBarController.swift */; }; 18 | C4BCEA9C2430B8C90062A872 /* Autolayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BCEA9B2430B8C90062A872 /* Autolayout.swift */; }; 19 | C4BCEA9E2430BC770062A872 /* FloatingBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BCEA9D2430BC770062A872 /* FloatingBarView.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | C42098D6245C213400D66F93 /* PushViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushViewController.swift; sourceTree = ""; }; 24 | C42098D8245C24D000D66F93 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 25 | C4BCEA822430B7180062A872 /* PrinterestTabBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PrinterestTabBar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | C4BCEA852430B7180062A872 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | C4BCEA872430B7180062A872 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 28 | C4BCEA892430B7180062A872 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 29 | C4BCEA8E2430B7190062A872 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | C4BCEA912430B7190062A872 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | C4BCEA932430B7190062A872 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | C4BCEA992430B7330062A872 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; 33 | C4BCEA9B2430B8C90062A872 /* Autolayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Autolayout.swift; sourceTree = ""; }; 34 | C4BCEA9D2430BC770062A872 /* FloatingBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingBarView.swift; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | C4BCEA7F2430B7180062A872 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | C452833F243B36F000753D3C /* Controller */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | C4BCEA892430B7180062A872 /* ViewController.swift */, 52 | C4BCEA992430B7330062A872 /* TabBarController.swift */, 53 | C4BCEA9B2430B8C90062A872 /* Autolayout.swift */, 54 | C4BCEA9D2430BC770062A872 /* FloatingBarView.swift */, 55 | C42098D6245C213400D66F93 /* PushViewController.swift */, 56 | C42098D8245C24D000D66F93 /* BaseViewController.swift */, 57 | ); 58 | path = Controller; 59 | sourceTree = ""; 60 | }; 61 | C4528340243B36F600753D3C /* Resources */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | C4BCEA8E2430B7190062A872 /* Assets.xcassets */, 65 | C4BCEA902430B7190062A872 /* LaunchScreen.storyboard */, 66 | ); 67 | path = Resources; 68 | sourceTree = ""; 69 | }; 70 | C4BCEA792430B7180062A872 = { 71 | isa = PBXGroup; 72 | children = ( 73 | C4BCEA842430B7180062A872 /* PrinterestTabBar */, 74 | C4BCEA832430B7180062A872 /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | C4BCEA832430B7180062A872 /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | C4BCEA822430B7180062A872 /* PrinterestTabBar.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | C4BCEA842430B7180062A872 /* PrinterestTabBar */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | C4BCEA932430B7190062A872 /* Info.plist */, 90 | C4BCEA852430B7180062A872 /* AppDelegate.swift */, 91 | C4BCEA872430B7180062A872 /* SceneDelegate.swift */, 92 | C4528340243B36F600753D3C /* Resources */, 93 | C452833F243B36F000753D3C /* Controller */, 94 | ); 95 | path = PrinterestTabBar; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | C4BCEA812430B7180062A872 /* PrinterestTabBar */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = C4BCEA962430B7190062A872 /* Build configuration list for PBXNativeTarget "PrinterestTabBar" */; 104 | buildPhases = ( 105 | C4BCEA7E2430B7180062A872 /* Sources */, 106 | C4BCEA7F2430B7180062A872 /* Frameworks */, 107 | C4BCEA802430B7180062A872 /* Resources */, 108 | C4528341243B372E00753D3C /* SwiftLint */, 109 | ); 110 | buildRules = ( 111 | ); 112 | dependencies = ( 113 | ); 114 | name = PrinterestTabBar; 115 | productName = PrinterestTabBar; 116 | productReference = C4BCEA822430B7180062A872 /* PrinterestTabBar.app */; 117 | productType = "com.apple.product-type.application"; 118 | }; 119 | /* End PBXNativeTarget section */ 120 | 121 | /* Begin PBXProject section */ 122 | C4BCEA7A2430B7180062A872 /* Project object */ = { 123 | isa = PBXProject; 124 | attributes = { 125 | LastSwiftUpdateCheck = 1140; 126 | LastUpgradeCheck = 1140; 127 | ORGANIZATIONNAME = "Christophe Hoste"; 128 | TargetAttributes = { 129 | C4BCEA812430B7180062A872 = { 130 | CreatedOnToolsVersion = 11.4; 131 | }; 132 | }; 133 | }; 134 | buildConfigurationList = C4BCEA7D2430B7180062A872 /* Build configuration list for PBXProject "PrinterestTabBar" */; 135 | compatibilityVersion = "Xcode 9.3"; 136 | developmentRegion = en; 137 | hasScannedForEncodings = 0; 138 | knownRegions = ( 139 | en, 140 | Base, 141 | ); 142 | mainGroup = C4BCEA792430B7180062A872; 143 | productRefGroup = C4BCEA832430B7180062A872 /* Products */; 144 | projectDirPath = ""; 145 | projectRoot = ""; 146 | targets = ( 147 | C4BCEA812430B7180062A872 /* PrinterestTabBar */, 148 | ); 149 | }; 150 | /* End PBXProject section */ 151 | 152 | /* Begin PBXResourcesBuildPhase section */ 153 | C4BCEA802430B7180062A872 /* Resources */ = { 154 | isa = PBXResourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | C4BCEA922430B7190062A872 /* LaunchScreen.storyboard in Resources */, 158 | C4BCEA8F2430B7190062A872 /* Assets.xcassets in Resources */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXResourcesBuildPhase section */ 163 | 164 | /* Begin PBXShellScriptBuildPhase section */ 165 | C4528341243B372E00753D3C /* SwiftLint */ = { 166 | isa = PBXShellScriptBuildPhase; 167 | buildActionMask = 2147483647; 168 | files = ( 169 | ); 170 | inputFileListPaths = ( 171 | ); 172 | inputPaths = ( 173 | ); 174 | name = SwiftLint; 175 | outputFileListPaths = ( 176 | ); 177 | outputPaths = ( 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | shellPath = /bin/sh; 181 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint autocorrect\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 182 | }; 183 | /* End PBXShellScriptBuildPhase section */ 184 | 185 | /* Begin PBXSourcesBuildPhase section */ 186 | C4BCEA7E2430B7180062A872 /* Sources */ = { 187 | isa = PBXSourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | C4BCEA8A2430B7180062A872 /* ViewController.swift in Sources */, 191 | C4BCEA9C2430B8C90062A872 /* Autolayout.swift in Sources */, 192 | C4BCEA9A2430B7330062A872 /* TabBarController.swift in Sources */, 193 | C4BCEA862430B7180062A872 /* AppDelegate.swift in Sources */, 194 | C4BCEA9E2430BC770062A872 /* FloatingBarView.swift in Sources */, 195 | C42098D9245C24D000D66F93 /* BaseViewController.swift in Sources */, 196 | C4BCEA882430B7180062A872 /* SceneDelegate.swift in Sources */, 197 | C42098D7245C213400D66F93 /* PushViewController.swift in Sources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXSourcesBuildPhase section */ 202 | 203 | /* Begin PBXVariantGroup section */ 204 | C4BCEA902430B7190062A872 /* LaunchScreen.storyboard */ = { 205 | isa = PBXVariantGroup; 206 | children = ( 207 | C4BCEA912430B7190062A872 /* Base */, 208 | ); 209 | name = LaunchScreen.storyboard; 210 | sourceTree = ""; 211 | }; 212 | /* End PBXVariantGroup section */ 213 | 214 | /* Begin XCBuildConfiguration section */ 215 | C4BCEA942430B7190062A872 /* Debug */ = { 216 | isa = XCBuildConfiguration; 217 | buildSettings = { 218 | ALWAYS_SEARCH_USER_PATHS = NO; 219 | CLANG_ANALYZER_NONNULL = YES; 220 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 221 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 222 | CLANG_CXX_LIBRARY = "libc++"; 223 | CLANG_ENABLE_MODULES = YES; 224 | CLANG_ENABLE_OBJC_ARC = YES; 225 | CLANG_ENABLE_OBJC_WEAK = YES; 226 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 227 | CLANG_WARN_BOOL_CONVERSION = YES; 228 | CLANG_WARN_COMMA = YES; 229 | CLANG_WARN_CONSTANT_CONVERSION = YES; 230 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 231 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 232 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INFINITE_RECURSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 238 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 239 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 241 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 242 | CLANG_WARN_STRICT_PROTOTYPES = YES; 243 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 244 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 245 | CLANG_WARN_UNREACHABLE_CODE = YES; 246 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 247 | COPY_PHASE_STRIP = NO; 248 | DEBUG_INFORMATION_FORMAT = dwarf; 249 | ENABLE_STRICT_OBJC_MSGSEND = YES; 250 | ENABLE_TESTABILITY = YES; 251 | GCC_C_LANGUAGE_STANDARD = gnu11; 252 | GCC_DYNAMIC_NO_PIC = NO; 253 | GCC_NO_COMMON_BLOCKS = YES; 254 | GCC_OPTIMIZATION_LEVEL = 0; 255 | GCC_PREPROCESSOR_DEFINITIONS = ( 256 | "DEBUG=1", 257 | "$(inherited)", 258 | ); 259 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 261 | GCC_WARN_UNDECLARED_SELECTOR = YES; 262 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 266 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 267 | MTL_FAST_MATH = YES; 268 | ONLY_ACTIVE_ARCH = YES; 269 | SDKROOT = iphoneos; 270 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 271 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 272 | }; 273 | name = Debug; 274 | }; 275 | C4BCEA952430B7190062A872 /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ALWAYS_SEARCH_USER_PATHS = NO; 279 | CLANG_ANALYZER_NONNULL = YES; 280 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_ENABLE_OBJC_WEAK = YES; 286 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 287 | CLANG_WARN_BOOL_CONVERSION = YES; 288 | CLANG_WARN_COMMA = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 291 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 292 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INFINITE_RECURSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 301 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 302 | CLANG_WARN_STRICT_PROTOTYPES = YES; 303 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 304 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 305 | CLANG_WARN_UNREACHABLE_CODE = YES; 306 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 307 | COPY_PHASE_STRIP = NO; 308 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 309 | ENABLE_NS_ASSERTIONS = NO; 310 | ENABLE_STRICT_OBJC_MSGSEND = YES; 311 | GCC_C_LANGUAGE_STANDARD = gnu11; 312 | GCC_NO_COMMON_BLOCKS = YES; 313 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 314 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 315 | GCC_WARN_UNDECLARED_SELECTOR = YES; 316 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 317 | GCC_WARN_UNUSED_FUNCTION = YES; 318 | GCC_WARN_UNUSED_VARIABLE = YES; 319 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 320 | MTL_ENABLE_DEBUG_INFO = NO; 321 | MTL_FAST_MATH = YES; 322 | SDKROOT = iphoneos; 323 | SWIFT_COMPILATION_MODE = wholemodule; 324 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 325 | VALIDATE_PRODUCT = YES; 326 | }; 327 | name = Release; 328 | }; 329 | C4BCEA972430B7190062A872 /* Debug */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 333 | CODE_SIGN_STYLE = Automatic; 334 | DEVELOPMENT_TEAM = CCCCMKZ642; 335 | INFOPLIST_FILE = PrinterestTabBar/Info.plist; 336 | LD_RUNPATH_SEARCH_PATHS = ( 337 | "$(inherited)", 338 | "@executable_path/Frameworks", 339 | ); 340 | PRODUCT_BUNDLE_IDENTIFIER = hoste.PrinterestTabBar; 341 | PRODUCT_NAME = "$(TARGET_NAME)"; 342 | SWIFT_VERSION = 5.0; 343 | TARGETED_DEVICE_FAMILY = "1,2"; 344 | }; 345 | name = Debug; 346 | }; 347 | C4BCEA982430B7190062A872 /* Release */ = { 348 | isa = XCBuildConfiguration; 349 | buildSettings = { 350 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 351 | CODE_SIGN_STYLE = Automatic; 352 | DEVELOPMENT_TEAM = CCCCMKZ642; 353 | INFOPLIST_FILE = PrinterestTabBar/Info.plist; 354 | LD_RUNPATH_SEARCH_PATHS = ( 355 | "$(inherited)", 356 | "@executable_path/Frameworks", 357 | ); 358 | PRODUCT_BUNDLE_IDENTIFIER = hoste.PrinterestTabBar; 359 | PRODUCT_NAME = "$(TARGET_NAME)"; 360 | SWIFT_VERSION = 5.0; 361 | TARGETED_DEVICE_FAMILY = "1,2"; 362 | }; 363 | name = Release; 364 | }; 365 | /* End XCBuildConfiguration section */ 366 | 367 | /* Begin XCConfigurationList section */ 368 | C4BCEA7D2430B7180062A872 /* Build configuration list for PBXProject "PrinterestTabBar" */ = { 369 | isa = XCConfigurationList; 370 | buildConfigurations = ( 371 | C4BCEA942430B7190062A872 /* Debug */, 372 | C4BCEA952430B7190062A872 /* Release */, 373 | ); 374 | defaultConfigurationIsVisible = 0; 375 | defaultConfigurationName = Release; 376 | }; 377 | C4BCEA962430B7190062A872 /* Build configuration list for PBXNativeTarget "PrinterestTabBar" */ = { 378 | isa = XCConfigurationList; 379 | buildConfigurations = ( 380 | C4BCEA972430B7190062A872 /* Debug */, 381 | C4BCEA982430B7190062A872 /* Release */, 382 | ); 383 | defaultConfigurationIsVisible = 0; 384 | defaultConfigurationName = Release; 385 | }; 386 | /* End XCConfigurationList section */ 387 | }; 388 | rootObject = C4BCEA7A2430B7180062A872 /* Project object */; 389 | } 390 | -------------------------------------------------------------------------------- /PrinterestTabBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PrinterestTabBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PrinterestTabBar/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PrinterestTabBar 4 | // 5 | // Created by Christophe Hoste on 29.03.20. 6 | // Copyright © 2020 Christophe Hoste. All rights reserved. 7 | // 8 | // swiftlint:disable all 9 | 10 | import UIKit 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /PrinterestTabBar/Controller/Autolayout.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | // Reference Video: https://youtu.be/iqpAP7s3b-8 5 | // swiftlint:disable all 6 | extension UIView { 7 | 8 | @discardableResult 9 | func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints { 10 | 11 | translatesAutoresizingMaskIntoConstraints = false 12 | var anchoredConstraints = AnchoredConstraints() 13 | 14 | if let top = top { 15 | anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top) 16 | } 17 | 18 | if let leading = leading { 19 | anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left) 20 | } 21 | 22 | if let bottom = bottom { 23 | anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom) 24 | } 25 | 26 | if let trailing = trailing { 27 | anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right) 28 | } 29 | 30 | if size.width != 0 { 31 | anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width) 32 | } 33 | 34 | if size.height != 0 { 35 | anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height) 36 | } 37 | 38 | [anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach {$0?.isActive = true} 39 | 40 | return anchoredConstraints 41 | } 42 | 43 | func fillSuperview(padding: UIEdgeInsets = .zero) { 44 | translatesAutoresizingMaskIntoConstraints = false 45 | if let superviewTopAnchor = superview?.topAnchor { 46 | topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true 47 | } 48 | 49 | if let superviewBottomAnchor = superview?.bottomAnchor { 50 | bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: -padding.bottom).isActive = true 51 | } 52 | 53 | if let superviewLeadingAnchor = superview?.leadingAnchor { 54 | leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true 55 | } 56 | 57 | if let superviewTrailingAnchor = superview?.trailingAnchor { 58 | trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: -padding.right).isActive = true 59 | } 60 | } 61 | 62 | func centerInSuperview(size: CGSize = .zero) { 63 | translatesAutoresizingMaskIntoConstraints = false 64 | if let superviewCenterXAnchor = superview?.centerXAnchor { 65 | centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true 66 | } 67 | 68 | if let superviewCenterYAnchor = superview?.centerYAnchor { 69 | centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true 70 | } 71 | 72 | if size.width != 0 { 73 | widthAnchor.constraint(equalToConstant: size.width).isActive = true 74 | } 75 | 76 | if size.height != 0 { 77 | heightAnchor.constraint(equalToConstant: size.height).isActive = true 78 | } 79 | } 80 | 81 | func centerXInSuperview() { 82 | translatesAutoresizingMaskIntoConstraints = false 83 | if let superViewCenterXAnchor = superview?.centerXAnchor { 84 | centerXAnchor.constraint(equalTo: superViewCenterXAnchor).isActive = true 85 | } 86 | } 87 | 88 | func centerYInSuperview() { 89 | translatesAutoresizingMaskIntoConstraints = false 90 | if let centerY = superview?.centerYAnchor { 91 | centerYAnchor.constraint(equalTo: centerY).isActive = true 92 | } 93 | } 94 | 95 | func constrainWidth(constant: CGFloat) { 96 | translatesAutoresizingMaskIntoConstraints = false 97 | widthAnchor.constraint(equalToConstant: constant).isActive = true 98 | } 99 | 100 | func constrainHeight(constant: CGFloat) { 101 | translatesAutoresizingMaskIntoConstraints = false 102 | heightAnchor.constraint(equalToConstant: constant).isActive = true 103 | } 104 | } 105 | 106 | struct AnchoredConstraints { 107 | var top, leading, bottom, trailing, width, height: NSLayoutConstraint? 108 | } 109 | -------------------------------------------------------------------------------- /PrinterestTabBar/Controller/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // PrinterestTabBar 4 | // 5 | // Created by Christophe Hoste on 01.05.20. 6 | // Copyright © 2020 Christophe Hoste. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseViewController: UIViewController { 12 | 13 | func toogleTabbar(hide: Bool) { 14 | guard let tabBar = tabBarController as? Tabbarcontoller else { return } 15 | tabBar.toggle(hide: hide) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PrinterestTabBar/Controller/FloatingBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FloatingBarView.swift 3 | // PrinterestTabBar 4 | // 5 | // Created by Christophe Hoste on 29.03.20. 6 | // Copyright © 2020 Christophe Hoste. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol FloatingBarViewDelegate: AnyObject { 12 | func did(selectindex: Int) 13 | } 14 | 15 | class FloatingBarView: UIView { 16 | 17 | weak var delegate: FloatingBarViewDelegate? 18 | 19 | var buttons: [UIButton] = [] 20 | 21 | init(_ items: [String]) { 22 | super.init(frame: .zero) 23 | backgroundColor = .white 24 | 25 | setupStackView(items) 26 | updateUI(selectedIndex: 0) 27 | } 28 | 29 | override func layoutSubviews() { 30 | super.layoutSubviews() 31 | 32 | layer.cornerRadius = bounds.height / 2 33 | 34 | layer.shadowPath = UIBezierPath(rect: bounds).cgPath 35 | layer.shadowColor = UIColor.black.cgColor 36 | layer.shadowOpacity = 0.1 37 | layer.shadowOffset = .zero 38 | layer.shadowRadius = bounds.height / 2 39 | } 40 | 41 | func setupStackView(_ items: [String]) { 42 | for (index, item) in items.enumerated() { 43 | let symbolConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .bold, scale: .medium) 44 | let normalImage = UIImage(systemName: item, withConfiguration: symbolConfig) 45 | let selectedImage = UIImage(systemName: "\(item).fill", withConfiguration: symbolConfig) 46 | let button = createButton(normalImage: normalImage!, selectedImage: selectedImage!, index: index) 47 | buttons.append(button) 48 | } 49 | 50 | let stackView = UIStackView(arrangedSubviews: buttons) 51 | 52 | addSubview(stackView) 53 | stackView.fillSuperview(padding: .init(top: 0, left: 16, bottom: 0, right: 16)) 54 | } 55 | 56 | func createButton(normalImage: UIImage, selectedImage: UIImage, index: Int) -> UIButton { 57 | let button = UIButton() 58 | button.constrainWidth(constant: 60) 59 | button.constrainHeight(constant: 60) 60 | button.setImage(normalImage, for: .normal) 61 | button.setImage(selectedImage, for: .selected) 62 | button.tag = index 63 | button.adjustsImageWhenHighlighted = false 64 | button.addTarget(self, action: #selector(changeTab(_:)), for: .touchUpInside) 65 | return button 66 | } 67 | 68 | @objc 69 | func changeTab(_ sender: UIButton) { 70 | sender.pulse() 71 | delegate?.did(selectindex: sender.tag) 72 | updateUI(selectedIndex: sender.tag) 73 | } 74 | 75 | func updateUI(selectedIndex: Int) { 76 | for (index, button) in buttons.enumerated() { 77 | if index == selectedIndex { 78 | button.isSelected = true 79 | if index == 0 { 80 | button.tintColor = .red 81 | } else { 82 | button.tintColor = .black 83 | } 84 | } else { 85 | button.isSelected = false 86 | button.tintColor = .gray 87 | } 88 | } 89 | } 90 | 91 | required init?(coder: NSCoder) { 92 | fatalError("init(coder:) has not been implemented") 93 | } 94 | 95 | func toggle(hide: Bool) { 96 | if !hide { 97 | isHidden = hide 98 | } 99 | 100 | UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 1, 101 | initialSpringVelocity: 0.5, options: .curveEaseOut, animations: { 102 | self.alpha = hide ? 0 : 1 103 | self.transform = hide ? CGAffineTransform(translationX: 0, y: 10) : .identity 104 | }) { (_) in 105 | if hide { 106 | self.isHidden = hide 107 | } 108 | } 109 | } 110 | } 111 | 112 | extension UIButton { 113 | 114 | func pulse() { 115 | let pulse = CASpringAnimation(keyPath: "transform.scale") 116 | pulse.duration = 0.15 117 | pulse.fromValue = 0.95 118 | pulse.toValue = 1.0 119 | layer.add(pulse, forKey: "pulse") 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /PrinterestTabBar/Controller/PushViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushViewController.swift 3 | // PrinterestTabBar 4 | // 5 | // Created by Christophe Hoste on 01.05.20. 6 | // Copyright © 2020 Christophe Hoste. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PushViewController: BaseViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | view.backgroundColor = .white 16 | title = "Pushed VC" 17 | } 18 | 19 | override func viewWillAppear(_ animated: Bool) { 20 | super.viewWillAppear(animated) 21 | 22 | toogleTabbar(hide: true) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PrinterestTabBar/Controller/TabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarController.swift 3 | // PrinterestTabBar 4 | // 5 | // Created by Christophe Hoste on 29.03.20. 6 | // Copyright © 2020 Christophe Hoste. All rights reserved. 7 | // 8 | // swiftlint:disable all 9 | import UIKit 10 | 11 | class Tabbarcontoller: UITabBarController { 12 | 13 | let floatingTabbarView = FloatingBarView(["house", "bubble.middle.bottom", "bolt.circle", "rectangle.3.offgrid"]) 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | viewControllers = [ 19 | createNavViewController(viewController: ViewController(showPushButton: true), title: "Home", imageName: "house.fill"), 20 | createNavViewController(viewController: ViewController(), title: "About", imageName: "bubble.middle.bottom.fill"), 21 | createNavViewController(viewController: ViewController(), title: "Trending", imageName: "flame.fill"), 22 | createNavViewController(viewController: ViewController(), title: "Settings", imageName: "rectangle.3.offgrid.fill") 23 | ] 24 | tabBar.isHidden = true 25 | 26 | setupFloatingTabBar() 27 | } 28 | 29 | private func createNavViewController(viewController: UIViewController, title: String, imageName: String) -> UIViewController { 30 | 31 | viewController.navigationItem.title = title 32 | 33 | let navController = UINavigationController(rootViewController: viewController) 34 | navController.navigationBar.prefersLargeTitles = true 35 | navController.tabBarItem.title = title 36 | navController.tabBarItem.image = UIImage(systemName: imageName) 37 | 38 | return navController 39 | } 40 | 41 | func setupFloatingTabBar() { 42 | floatingTabbarView.delegate = self 43 | view.addSubview(floatingTabbarView) 44 | floatingTabbarView.centerXInSuperview() 45 | floatingTabbarView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true 46 | } 47 | 48 | func toggle(hide: Bool) { 49 | floatingTabbarView.toggle(hide: hide) 50 | } 51 | } 52 | 53 | extension Tabbarcontoller: FloatingBarViewDelegate { 54 | func did(selectindex: Int) { 55 | selectedIndex = selectindex 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /PrinterestTabBar/Controller/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PrinterestTabBar 4 | // 5 | // Created by Christophe Hoste on 29.03.20. 6 | // Copyright © 2020 Christophe Hoste. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: BaseViewController { 12 | 13 | lazy var button: UIButton = { 14 | let button = UIButton() 15 | button.setTitle("Push new VC", for: .normal) 16 | button.addTarget(self, action: #selector(handeAction(_:)), for: .touchUpInside) 17 | button.backgroundColor = .systemBlue 18 | button.layer.cornerRadius = 5 19 | button.titleEdgeInsets = .init(top: 8, left: 8, bottom: 8, right: 8) 20 | button.constrainWidth(constant: 150) 21 | return button 22 | }() 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | view.backgroundColor = .white 27 | } 28 | 29 | init(showPushButton: Bool = false) { 30 | super.init(nibName: nil, bundle: nil) 31 | 32 | if showPushButton { 33 | setupButton() 34 | } 35 | } 36 | 37 | override func viewWillAppear(_ animated: Bool) { 38 | super.viewWillAppear(animated) 39 | 40 | toogleTabbar(hide: false) 41 | } 42 | 43 | func setupButton() { 44 | 45 | view.addSubview(button) 46 | button.centerInSuperview() 47 | } 48 | 49 | @objc 50 | func handeAction(_ sender: UIButton) { 51 | let newVC = PushViewController() 52 | navigationController?.navigationBar.tintColor = .black 53 | navigationController?.pushViewController(newVC, animated: true) 54 | } 55 | 56 | required init?(coder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PrinterestTabBar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /PrinterestTabBar/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /PrinterestTabBar/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PrinterestTabBar/Resources/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 | -------------------------------------------------------------------------------- /PrinterestTabBar/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // PrinterestTabBar 4 | // 5 | // Created by Christophe Hoste on 29.03.20. 6 | // Copyright © 2020 Christophe Hoste. All rights reserved. 7 | // 8 | // swiftlint:disable all 9 | 10 | import UIKit 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let windowScene = (scene as? UIWindowScene) else { return } 21 | 22 | window = UIWindow(windowScene: windowScene) 23 | window?.windowScene = windowScene 24 | window?.rootViewController = Tabbarcontoller() 25 | window?.makeKeyAndVisible() 26 | } 27 | 28 | func sceneDidDisconnect(_ scene: UIScene) { 29 | // Called as the scene is being released by the system. 30 | // This occurs shortly after the scene enters the background, or when its session is discarded. 31 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 32 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 33 | } 34 | 35 | func sceneDidBecomeActive(_ scene: UIScene) { 36 | // Called when the scene has moved from an inactive state to an active state. 37 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 38 | } 39 | 40 | func sceneWillResignActive(_ scene: UIScene) { 41 | // Called when the scene will move from an active state to an inactive state. 42 | // This may occur due to temporary interruptions (ex. an incoming phone call). 43 | } 44 | 45 | func sceneWillEnterForeground(_ scene: UIScene) { 46 | // Called as the scene transitions from the background to the foreground. 47 | // Use this method to undo the changes made on entering the background. 48 | } 49 | 50 | func sceneDidEnterBackground(_ scene: UIScene) { 51 | // Called as the scene transitions from the foreground to the background. 52 | // Use this method to save data, release shared resources, and store enough scene-specific state information 53 | // to restore the scene back to its current state. 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Printerest `like` floating Tab Bar 2 | 3 | > This was created during my [quarantine challenges](https://github.com/chrishoste/quarantine-challenge). Check out the more stuff I did during this time. 4 | 5 | ## Design 6 | After some exploring this time on [Pinterest](https://www.pinterest.de/) I decided to go for their own `Tab-Bar`-Implementation. 7 | 8 | 9 | 10 | ## My final implementation 11 | 12 | As in my [first challenge](https://github.com/chrishoste/simpleTabBarOnboarding), I have tried to approach the original implementation as closely as possible. 13 | 14 | ### Youtube ([Link](https://youtu.be/xXa8CetKSSM)) 15 |

16 | 17 | youtube_thumbnail 18 | 19 |

20 | 21 | ### Screenshots/Gif's 22 | 23 |

24 | 25 | 26 |

27 | 28 | ## Features 29 | 30 | - [x] Uses `UITabBarController` (I'm only hiding the TabBar & triggering the items programmatically). 31 | - [x] Adapts dynamically to the number of items. 32 | - [x] `outline` & `filled`icons dependig on the state. 33 | - [x] First Item is `Red`in selected state, like the original implementation. 34 | - [x] Small pulse animation on tap. 35 | - [ ] When scrolling the TabBar disappears. 36 | 37 | ## Requirements 38 | 39 | - iOS 13.0+ 40 | - Xcode 11 41 | 42 | ## Usage 43 | 44 | Feel free to use anything you want or need. 45 | 46 | ## Meta 47 | 48 | Christophe Hoste 49 | 50 | [https://github.com/chrishoste/printeres-tabbar](https://github.com/chrishoste/printeres-tabbar) 51 | -------------------------------------------------------------------------------- /img/capture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishoste/printerest-tabbar/888cc5941ba6d4bf4de2bb101950db66ef347763/img/capture.gif -------------------------------------------------------------------------------- /img/capture2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishoste/printerest-tabbar/888cc5941ba6d4bf4de2bb101950db66ef347763/img/capture2.gif --------------------------------------------------------------------------------