├── CustomTabBar.gif ├── CustomTabBarExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcuserdata │ └── jedrzejcholuj.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── CustomTabBarExample ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── CustomItemView.swift ├── CustomTabBar.swift ├── CustomTabBarController.swift ├── CustomTabItem.swift ├── Extensions.swift ├── Info.plist ├── SceneDelegate.swift └── ViewController.swift ├── CustomTabBarExampleTests ├── CustomTabBarExampleTests.swift └── Info.plist ├── CustomTabBarExampleUITests ├── CustomTabBarExampleUITests.swift └── Info.plist └── README.md /CustomTabBar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcholuj/CustomTabBarExample/95f9c40ad51c7c10f84e73d9ef6fe2b90ad23353/CustomTabBar.gif -------------------------------------------------------------------------------- /CustomTabBarExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B6976842276E360300AA8776 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976841276E360300AA8776 /* AppDelegate.swift */; }; 11 | B6976844276E360300AA8776 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976843276E360300AA8776 /* SceneDelegate.swift */; }; 12 | B697684B276E361B00AA8776 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B697684A276E361B00AA8776 /* Assets.xcassets */; }; 13 | B697684E276E361B00AA8776 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B697684C276E361B00AA8776 /* LaunchScreen.storyboard */; }; 14 | B6976859276E361B00AA8776 /* CustomTabBarExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976858276E361B00AA8776 /* CustomTabBarExampleTests.swift */; }; 15 | B6976864276E361B00AA8776 /* CustomTabBarExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976863276E361B00AA8776 /* CustomTabBarExampleUITests.swift */; }; 16 | B6976872276E37F100AA8776 /* CustomItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976871276E37F100AA8776 /* CustomItemView.swift */; }; 17 | B6976875276E384B00AA8776 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = B6976874276E384B00AA8776 /* SnapKit */; }; 18 | B6976877276E389900AA8776 /* CustomTabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976876276E389900AA8776 /* CustomTabItem.swift */; }; 19 | B6976879276E392100AA8776 /* CustomTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976878276E392100AA8776 /* CustomTabBarController.swift */; }; 20 | B697687C276E395000AA8776 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = B697687B276E395000AA8776 /* RxCocoa */; }; 21 | B697687E276E395000AA8776 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B697687D276E395000AA8776 /* RxSwift */; }; 22 | B6976880276E396200AA8776 /* CustomTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697687F276E396200AA8776 /* CustomTabBar.swift */; }; 23 | B6976883276E399800AA8776 /* RxGesture in Frameworks */ = {isa = PBXBuildFile; productRef = B6976882276E399800AA8776 /* RxGesture */; }; 24 | B6976885276E39D100AA8776 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6976884276E39D100AA8776 /* Extensions.swift */; }; 25 | B697688D276E498300AA8776 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697688C276E498300AA8776 /* ViewController.swift */; }; 26 | B697688F276E526000AA8776 /* CustomTabBar.gif in Resources */ = {isa = PBXBuildFile; fileRef = B697688E276E526000AA8776 /* CustomTabBar.gif */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | B6976855276E361B00AA8776 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = B6976836276E360300AA8776 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = B697683D276E360300AA8776; 35 | remoteInfo = CustomTabBarExample; 36 | }; 37 | B6976860276E361B00AA8776 /* PBXContainerItemProxy */ = { 38 | isa = PBXContainerItemProxy; 39 | containerPortal = B6976836276E360300AA8776 /* Project object */; 40 | proxyType = 1; 41 | remoteGlobalIDString = B697683D276E360300AA8776; 42 | remoteInfo = CustomTabBarExample; 43 | }; 44 | /* End PBXContainerItemProxy section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | B697683E276E360300AA8776 /* CustomTabBarExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CustomTabBarExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | B6976841276E360300AA8776 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | B6976843276E360300AA8776 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 50 | B697684A276E361B00AA8776 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | B697684D276E361B00AA8776 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | B697684F276E361B00AA8776 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | B6976854276E361B00AA8776 /* CustomTabBarExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CustomTabBarExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | B6976858276E361B00AA8776 /* CustomTabBarExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabBarExampleTests.swift; sourceTree = ""; }; 55 | B697685A276E361B00AA8776 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | B697685F276E361B00AA8776 /* CustomTabBarExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CustomTabBarExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | B6976863276E361B00AA8776 /* CustomTabBarExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabBarExampleUITests.swift; sourceTree = ""; }; 58 | B6976865276E361B00AA8776 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | B6976871276E37F100AA8776 /* CustomItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomItemView.swift; sourceTree = ""; }; 60 | B6976876276E389900AA8776 /* CustomTabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabItem.swift; sourceTree = ""; }; 61 | B6976878276E392100AA8776 /* CustomTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabBarController.swift; sourceTree = ""; }; 62 | B697687F276E396200AA8776 /* CustomTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabBar.swift; sourceTree = ""; }; 63 | B6976884276E39D100AA8776 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 64 | B697688C276E498300AA8776 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 65 | B697688E276E526000AA8776 /* CustomTabBar.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = CustomTabBar.gif; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | B697683B276E360300AA8776 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | B697687E276E395000AA8776 /* RxSwift in Frameworks */, 74 | B697687C276E395000AA8776 /* RxCocoa in Frameworks */, 75 | B6976883276E399800AA8776 /* RxGesture in Frameworks */, 76 | B6976875276E384B00AA8776 /* SnapKit in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | B6976851276E361B00AA8776 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | B697685C276E361B00AA8776 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | B6976835276E360300AA8776 = { 98 | isa = PBXGroup; 99 | children = ( 100 | B697688E276E526000AA8776 /* CustomTabBar.gif */, 101 | B6976840276E360300AA8776 /* CustomTabBarExample */, 102 | B6976857276E361B00AA8776 /* CustomTabBarExampleTests */, 103 | B6976862276E361B00AA8776 /* CustomTabBarExampleUITests */, 104 | B697683F276E360300AA8776 /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | B697683F276E360300AA8776 /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | B697683E276E360300AA8776 /* CustomTabBarExample.app */, 112 | B6976854276E361B00AA8776 /* CustomTabBarExampleTests.xctest */, 113 | B697685F276E361B00AA8776 /* CustomTabBarExampleUITests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | B6976840276E360300AA8776 /* CustomTabBarExample */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | B6976841276E360300AA8776 /* AppDelegate.swift */, 122 | B6976843276E360300AA8776 /* SceneDelegate.swift */, 123 | B697684A276E361B00AA8776 /* Assets.xcassets */, 124 | B697684C276E361B00AA8776 /* LaunchScreen.storyboard */, 125 | B697684F276E361B00AA8776 /* Info.plist */, 126 | B6976871276E37F100AA8776 /* CustomItemView.swift */, 127 | B6976876276E389900AA8776 /* CustomTabItem.swift */, 128 | B6976878276E392100AA8776 /* CustomTabBarController.swift */, 129 | B697687F276E396200AA8776 /* CustomTabBar.swift */, 130 | B6976884276E39D100AA8776 /* Extensions.swift */, 131 | B697688C276E498300AA8776 /* ViewController.swift */, 132 | ); 133 | path = CustomTabBarExample; 134 | sourceTree = ""; 135 | }; 136 | B6976857276E361B00AA8776 /* CustomTabBarExampleTests */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | B6976858276E361B00AA8776 /* CustomTabBarExampleTests.swift */, 140 | B697685A276E361B00AA8776 /* Info.plist */, 141 | ); 142 | path = CustomTabBarExampleTests; 143 | sourceTree = ""; 144 | }; 145 | B6976862276E361B00AA8776 /* CustomTabBarExampleUITests */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | B6976863276E361B00AA8776 /* CustomTabBarExampleUITests.swift */, 149 | B6976865276E361B00AA8776 /* Info.plist */, 150 | ); 151 | path = CustomTabBarExampleUITests; 152 | sourceTree = ""; 153 | }; 154 | /* End PBXGroup section */ 155 | 156 | /* Begin PBXNativeTarget section */ 157 | B697683D276E360300AA8776 /* CustomTabBarExample */ = { 158 | isa = PBXNativeTarget; 159 | buildConfigurationList = B6976868276E361B00AA8776 /* Build configuration list for PBXNativeTarget "CustomTabBarExample" */; 160 | buildPhases = ( 161 | B697683A276E360300AA8776 /* Sources */, 162 | B697683B276E360300AA8776 /* Frameworks */, 163 | B697683C276E360300AA8776 /* Resources */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = CustomTabBarExample; 170 | packageProductDependencies = ( 171 | B6976874276E384B00AA8776 /* SnapKit */, 172 | B697687B276E395000AA8776 /* RxCocoa */, 173 | B697687D276E395000AA8776 /* RxSwift */, 174 | B6976882276E399800AA8776 /* RxGesture */, 175 | ); 176 | productName = CustomTabBarExample; 177 | productReference = B697683E276E360300AA8776 /* CustomTabBarExample.app */; 178 | productType = "com.apple.product-type.application"; 179 | }; 180 | B6976853276E361B00AA8776 /* CustomTabBarExampleTests */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = B697686B276E361B00AA8776 /* Build configuration list for PBXNativeTarget "CustomTabBarExampleTests" */; 183 | buildPhases = ( 184 | B6976850276E361B00AA8776 /* Sources */, 185 | B6976851276E361B00AA8776 /* Frameworks */, 186 | B6976852276E361B00AA8776 /* Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | B6976856276E361B00AA8776 /* PBXTargetDependency */, 192 | ); 193 | name = CustomTabBarExampleTests; 194 | productName = CustomTabBarExampleTests; 195 | productReference = B6976854276E361B00AA8776 /* CustomTabBarExampleTests.xctest */; 196 | productType = "com.apple.product-type.bundle.unit-test"; 197 | }; 198 | B697685E276E361B00AA8776 /* CustomTabBarExampleUITests */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = B697686E276E361B00AA8776 /* Build configuration list for PBXNativeTarget "CustomTabBarExampleUITests" */; 201 | buildPhases = ( 202 | B697685B276E361B00AA8776 /* Sources */, 203 | B697685C276E361B00AA8776 /* Frameworks */, 204 | B697685D276E361B00AA8776 /* Resources */, 205 | ); 206 | buildRules = ( 207 | ); 208 | dependencies = ( 209 | B6976861276E361B00AA8776 /* PBXTargetDependency */, 210 | ); 211 | name = CustomTabBarExampleUITests; 212 | productName = CustomTabBarExampleUITests; 213 | productReference = B697685F276E361B00AA8776 /* CustomTabBarExampleUITests.xctest */; 214 | productType = "com.apple.product-type.bundle.ui-testing"; 215 | }; 216 | /* End PBXNativeTarget section */ 217 | 218 | /* Begin PBXProject section */ 219 | B6976836276E360300AA8776 /* Project object */ = { 220 | isa = PBXProject; 221 | attributes = { 222 | LastSwiftUpdateCheck = 1250; 223 | LastUpgradeCheck = 1250; 224 | TargetAttributes = { 225 | B697683D276E360300AA8776 = { 226 | CreatedOnToolsVersion = 12.5.1; 227 | }; 228 | B6976853276E361B00AA8776 = { 229 | CreatedOnToolsVersion = 12.5.1; 230 | TestTargetID = B697683D276E360300AA8776; 231 | }; 232 | B697685E276E361B00AA8776 = { 233 | CreatedOnToolsVersion = 12.5.1; 234 | TestTargetID = B697683D276E360300AA8776; 235 | }; 236 | }; 237 | }; 238 | buildConfigurationList = B6976839276E360300AA8776 /* Build configuration list for PBXProject "CustomTabBarExample" */; 239 | compatibilityVersion = "Xcode 9.3"; 240 | developmentRegion = en; 241 | hasScannedForEncodings = 0; 242 | knownRegions = ( 243 | en, 244 | Base, 245 | ); 246 | mainGroup = B6976835276E360300AA8776; 247 | packageReferences = ( 248 | B6976873276E384B00AA8776 /* XCRemoteSwiftPackageReference "SnapKit" */, 249 | B697687A276E395000AA8776 /* XCRemoteSwiftPackageReference "RxSwift" */, 250 | B6976881276E399800AA8776 /* XCRemoteSwiftPackageReference "RxGesture" */, 251 | ); 252 | productRefGroup = B697683F276E360300AA8776 /* Products */; 253 | projectDirPath = ""; 254 | projectRoot = ""; 255 | targets = ( 256 | B697683D276E360300AA8776 /* CustomTabBarExample */, 257 | B6976853276E361B00AA8776 /* CustomTabBarExampleTests */, 258 | B697685E276E361B00AA8776 /* CustomTabBarExampleUITests */, 259 | ); 260 | }; 261 | /* End PBXProject section */ 262 | 263 | /* Begin PBXResourcesBuildPhase section */ 264 | B697683C276E360300AA8776 /* Resources */ = { 265 | isa = PBXResourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | B697684E276E361B00AA8776 /* LaunchScreen.storyboard in Resources */, 269 | B697688F276E526000AA8776 /* CustomTabBar.gif in Resources */, 270 | B697684B276E361B00AA8776 /* Assets.xcassets in Resources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | B6976852276E361B00AA8776 /* Resources */ = { 275 | isa = PBXResourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | B697685D276E361B00AA8776 /* Resources */ = { 282 | isa = PBXResourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | /* End PBXResourcesBuildPhase section */ 289 | 290 | /* Begin PBXSourcesBuildPhase section */ 291 | B697683A276E360300AA8776 /* Sources */ = { 292 | isa = PBXSourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | B6976872276E37F100AA8776 /* CustomItemView.swift in Sources */, 296 | B6976880276E396200AA8776 /* CustomTabBar.swift in Sources */, 297 | B697688D276E498300AA8776 /* ViewController.swift in Sources */, 298 | B6976885276E39D100AA8776 /* Extensions.swift in Sources */, 299 | B6976879276E392100AA8776 /* CustomTabBarController.swift in Sources */, 300 | B6976877276E389900AA8776 /* CustomTabItem.swift in Sources */, 301 | B6976842276E360300AA8776 /* AppDelegate.swift in Sources */, 302 | B6976844276E360300AA8776 /* SceneDelegate.swift in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | B6976850276E361B00AA8776 /* Sources */ = { 307 | isa = PBXSourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | B6976859276E361B00AA8776 /* CustomTabBarExampleTests.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | B697685B276E361B00AA8776 /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | B6976864276E361B00AA8776 /* CustomTabBarExampleUITests.swift in Sources */, 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | }; 322 | /* End PBXSourcesBuildPhase section */ 323 | 324 | /* Begin PBXTargetDependency section */ 325 | B6976856276E361B00AA8776 /* PBXTargetDependency */ = { 326 | isa = PBXTargetDependency; 327 | target = B697683D276E360300AA8776 /* CustomTabBarExample */; 328 | targetProxy = B6976855276E361B00AA8776 /* PBXContainerItemProxy */; 329 | }; 330 | B6976861276E361B00AA8776 /* PBXTargetDependency */ = { 331 | isa = PBXTargetDependency; 332 | target = B697683D276E360300AA8776 /* CustomTabBarExample */; 333 | targetProxy = B6976860276E361B00AA8776 /* PBXContainerItemProxy */; 334 | }; 335 | /* End PBXTargetDependency section */ 336 | 337 | /* Begin PBXVariantGroup section */ 338 | B697684C276E361B00AA8776 /* LaunchScreen.storyboard */ = { 339 | isa = PBXVariantGroup; 340 | children = ( 341 | B697684D276E361B00AA8776 /* Base */, 342 | ); 343 | name = LaunchScreen.storyboard; 344 | sourceTree = ""; 345 | }; 346 | /* End PBXVariantGroup section */ 347 | 348 | /* Begin XCBuildConfiguration section */ 349 | B6976866276E361B00AA8776 /* Debug */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | CLANG_ANALYZER_NONNULL = YES; 354 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 356 | CLANG_CXX_LIBRARY = "libc++"; 357 | CLANG_ENABLE_MODULES = YES; 358 | CLANG_ENABLE_OBJC_ARC = YES; 359 | CLANG_ENABLE_OBJC_WEAK = YES; 360 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_COMMA = YES; 363 | CLANG_WARN_CONSTANT_CONVERSION = YES; 364 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 366 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INFINITE_RECURSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 372 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 373 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 375 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 376 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 377 | CLANG_WARN_STRICT_PROTOTYPES = YES; 378 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 379 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 380 | CLANG_WARN_UNREACHABLE_CODE = YES; 381 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 382 | COPY_PHASE_STRIP = NO; 383 | DEBUG_INFORMATION_FORMAT = dwarf; 384 | ENABLE_STRICT_OBJC_MSGSEND = YES; 385 | ENABLE_TESTABILITY = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu11; 387 | GCC_DYNAMIC_NO_PIC = NO; 388 | GCC_NO_COMMON_BLOCKS = YES; 389 | GCC_OPTIMIZATION_LEVEL = 0; 390 | GCC_PREPROCESSOR_DEFINITIONS = ( 391 | "DEBUG=1", 392 | "$(inherited)", 393 | ); 394 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 395 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 396 | GCC_WARN_UNDECLARED_SELECTOR = YES; 397 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 398 | GCC_WARN_UNUSED_FUNCTION = YES; 399 | GCC_WARN_UNUSED_VARIABLE = YES; 400 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 401 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 402 | MTL_FAST_MATH = YES; 403 | ONLY_ACTIVE_ARCH = YES; 404 | SDKROOT = iphoneos; 405 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 406 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 407 | }; 408 | name = Debug; 409 | }; 410 | B6976867276E361B00AA8776 /* Release */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ALWAYS_SEARCH_USER_PATHS = NO; 414 | CLANG_ANALYZER_NONNULL = YES; 415 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 417 | CLANG_CXX_LIBRARY = "libc++"; 418 | CLANG_ENABLE_MODULES = YES; 419 | CLANG_ENABLE_OBJC_ARC = YES; 420 | CLANG_ENABLE_OBJC_WEAK = YES; 421 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 422 | CLANG_WARN_BOOL_CONVERSION = YES; 423 | CLANG_WARN_COMMA = YES; 424 | CLANG_WARN_CONSTANT_CONVERSION = YES; 425 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 426 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 427 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 428 | CLANG_WARN_EMPTY_BODY = YES; 429 | CLANG_WARN_ENUM_CONVERSION = YES; 430 | CLANG_WARN_INFINITE_RECURSION = YES; 431 | CLANG_WARN_INT_CONVERSION = YES; 432 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 434 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 436 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 437 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 438 | CLANG_WARN_STRICT_PROTOTYPES = YES; 439 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 440 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 441 | CLANG_WARN_UNREACHABLE_CODE = YES; 442 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 443 | COPY_PHASE_STRIP = NO; 444 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 445 | ENABLE_NS_ASSERTIONS = NO; 446 | ENABLE_STRICT_OBJC_MSGSEND = YES; 447 | GCC_C_LANGUAGE_STANDARD = gnu11; 448 | GCC_NO_COMMON_BLOCKS = YES; 449 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 450 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 451 | GCC_WARN_UNDECLARED_SELECTOR = YES; 452 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 453 | GCC_WARN_UNUSED_FUNCTION = YES; 454 | GCC_WARN_UNUSED_VARIABLE = YES; 455 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 456 | MTL_ENABLE_DEBUG_INFO = NO; 457 | MTL_FAST_MATH = YES; 458 | SDKROOT = iphoneos; 459 | SWIFT_COMPILATION_MODE = wholemodule; 460 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 461 | VALIDATE_PRODUCT = YES; 462 | }; 463 | name = Release; 464 | }; 465 | B6976869276E361B00AA8776 /* Debug */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 469 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 470 | CODE_SIGN_STYLE = Automatic; 471 | DEVELOPMENT_TEAM = TYN595GFH6; 472 | INFOPLIST_FILE = CustomTabBarExample/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = ( 474 | "$(inherited)", 475 | "@executable_path/Frameworks", 476 | ); 477 | PRODUCT_BUNDLE_IDENTIFIER = com.jedrzejcholuj.CustomTabBarExample; 478 | PRODUCT_NAME = "$(TARGET_NAME)"; 479 | SWIFT_VERSION = 5.0; 480 | TARGETED_DEVICE_FAMILY = 1; 481 | }; 482 | name = Debug; 483 | }; 484 | B697686A276E361B00AA8776 /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 488 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 489 | CODE_SIGN_STYLE = Automatic; 490 | DEVELOPMENT_TEAM = TYN595GFH6; 491 | INFOPLIST_FILE = CustomTabBarExample/Info.plist; 492 | LD_RUNPATH_SEARCH_PATHS = ( 493 | "$(inherited)", 494 | "@executable_path/Frameworks", 495 | ); 496 | PRODUCT_BUNDLE_IDENTIFIER = com.jedrzejcholuj.CustomTabBarExample; 497 | PRODUCT_NAME = "$(TARGET_NAME)"; 498 | SWIFT_VERSION = 5.0; 499 | TARGETED_DEVICE_FAMILY = 1; 500 | }; 501 | name = Release; 502 | }; 503 | B697686C276E361B00AA8776 /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 507 | BUNDLE_LOADER = "$(TEST_HOST)"; 508 | CODE_SIGN_STYLE = Automatic; 509 | DEVELOPMENT_TEAM = TYN595GFH6; 510 | INFOPLIST_FILE = CustomTabBarExampleTests/Info.plist; 511 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 512 | LD_RUNPATH_SEARCH_PATHS = ( 513 | "$(inherited)", 514 | "@executable_path/Frameworks", 515 | "@loader_path/Frameworks", 516 | ); 517 | PRODUCT_BUNDLE_IDENTIFIER = com.jedrzejcholuj.CustomTabBarExampleTests; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_VERSION = 5.0; 520 | TARGETED_DEVICE_FAMILY = "1,2"; 521 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CustomTabBarExample.app/CustomTabBarExample"; 522 | }; 523 | name = Debug; 524 | }; 525 | B697686D276E361B00AA8776 /* Release */ = { 526 | isa = XCBuildConfiguration; 527 | buildSettings = { 528 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 529 | BUNDLE_LOADER = "$(TEST_HOST)"; 530 | CODE_SIGN_STYLE = Automatic; 531 | DEVELOPMENT_TEAM = TYN595GFH6; 532 | INFOPLIST_FILE = CustomTabBarExampleTests/Info.plist; 533 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 534 | LD_RUNPATH_SEARCH_PATHS = ( 535 | "$(inherited)", 536 | "@executable_path/Frameworks", 537 | "@loader_path/Frameworks", 538 | ); 539 | PRODUCT_BUNDLE_IDENTIFIER = com.jedrzejcholuj.CustomTabBarExampleTests; 540 | PRODUCT_NAME = "$(TARGET_NAME)"; 541 | SWIFT_VERSION = 5.0; 542 | TARGETED_DEVICE_FAMILY = "1,2"; 543 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CustomTabBarExample.app/CustomTabBarExample"; 544 | }; 545 | name = Release; 546 | }; 547 | B697686F276E361B00AA8776 /* Debug */ = { 548 | isa = XCBuildConfiguration; 549 | buildSettings = { 550 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 551 | CODE_SIGN_STYLE = Automatic; 552 | DEVELOPMENT_TEAM = TYN595GFH6; 553 | INFOPLIST_FILE = CustomTabBarExampleUITests/Info.plist; 554 | LD_RUNPATH_SEARCH_PATHS = ( 555 | "$(inherited)", 556 | "@executable_path/Frameworks", 557 | "@loader_path/Frameworks", 558 | ); 559 | PRODUCT_BUNDLE_IDENTIFIER = com.jedrzejcholuj.CustomTabBarExampleUITests; 560 | PRODUCT_NAME = "$(TARGET_NAME)"; 561 | SWIFT_VERSION = 5.0; 562 | TARGETED_DEVICE_FAMILY = "1,2"; 563 | TEST_TARGET_NAME = CustomTabBarExample; 564 | }; 565 | name = Debug; 566 | }; 567 | B6976870276E361B00AA8776 /* Release */ = { 568 | isa = XCBuildConfiguration; 569 | buildSettings = { 570 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 571 | CODE_SIGN_STYLE = Automatic; 572 | DEVELOPMENT_TEAM = TYN595GFH6; 573 | INFOPLIST_FILE = CustomTabBarExampleUITests/Info.plist; 574 | LD_RUNPATH_SEARCH_PATHS = ( 575 | "$(inherited)", 576 | "@executable_path/Frameworks", 577 | "@loader_path/Frameworks", 578 | ); 579 | PRODUCT_BUNDLE_IDENTIFIER = com.jedrzejcholuj.CustomTabBarExampleUITests; 580 | PRODUCT_NAME = "$(TARGET_NAME)"; 581 | SWIFT_VERSION = 5.0; 582 | TARGETED_DEVICE_FAMILY = "1,2"; 583 | TEST_TARGET_NAME = CustomTabBarExample; 584 | }; 585 | name = Release; 586 | }; 587 | /* End XCBuildConfiguration section */ 588 | 589 | /* Begin XCConfigurationList section */ 590 | B6976839276E360300AA8776 /* Build configuration list for PBXProject "CustomTabBarExample" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | B6976866276E361B00AA8776 /* Debug */, 594 | B6976867276E361B00AA8776 /* Release */, 595 | ); 596 | defaultConfigurationIsVisible = 0; 597 | defaultConfigurationName = Release; 598 | }; 599 | B6976868276E361B00AA8776 /* Build configuration list for PBXNativeTarget "CustomTabBarExample" */ = { 600 | isa = XCConfigurationList; 601 | buildConfigurations = ( 602 | B6976869276E361B00AA8776 /* Debug */, 603 | B697686A276E361B00AA8776 /* Release */, 604 | ); 605 | defaultConfigurationIsVisible = 0; 606 | defaultConfigurationName = Release; 607 | }; 608 | B697686B276E361B00AA8776 /* Build configuration list for PBXNativeTarget "CustomTabBarExampleTests" */ = { 609 | isa = XCConfigurationList; 610 | buildConfigurations = ( 611 | B697686C276E361B00AA8776 /* Debug */, 612 | B697686D276E361B00AA8776 /* Release */, 613 | ); 614 | defaultConfigurationIsVisible = 0; 615 | defaultConfigurationName = Release; 616 | }; 617 | B697686E276E361B00AA8776 /* Build configuration list for PBXNativeTarget "CustomTabBarExampleUITests" */ = { 618 | isa = XCConfigurationList; 619 | buildConfigurations = ( 620 | B697686F276E361B00AA8776 /* Debug */, 621 | B6976870276E361B00AA8776 /* Release */, 622 | ); 623 | defaultConfigurationIsVisible = 0; 624 | defaultConfigurationName = Release; 625 | }; 626 | /* End XCConfigurationList section */ 627 | 628 | /* Begin XCRemoteSwiftPackageReference section */ 629 | B6976873276E384B00AA8776 /* XCRemoteSwiftPackageReference "SnapKit" */ = { 630 | isa = XCRemoteSwiftPackageReference; 631 | repositoryURL = "https://github.com/SnapKit/SnapKit.git"; 632 | requirement = { 633 | kind = upToNextMajorVersion; 634 | minimumVersion = 5.0.1; 635 | }; 636 | }; 637 | B697687A276E395000AA8776 /* XCRemoteSwiftPackageReference "RxSwift" */ = { 638 | isa = XCRemoteSwiftPackageReference; 639 | repositoryURL = "https://github.com/ReactiveX/RxSwift.git"; 640 | requirement = { 641 | kind = upToNextMajorVersion; 642 | minimumVersion = 6.2.0; 643 | }; 644 | }; 645 | B6976881276E399800AA8776 /* XCRemoteSwiftPackageReference "RxGesture" */ = { 646 | isa = XCRemoteSwiftPackageReference; 647 | repositoryURL = "https://github.com/RxSwiftCommunity/RxGesture.git"; 648 | requirement = { 649 | kind = upToNextMajorVersion; 650 | minimumVersion = 4.0.2; 651 | }; 652 | }; 653 | /* End XCRemoteSwiftPackageReference section */ 654 | 655 | /* Begin XCSwiftPackageProductDependency section */ 656 | B6976874276E384B00AA8776 /* SnapKit */ = { 657 | isa = XCSwiftPackageProductDependency; 658 | package = B6976873276E384B00AA8776 /* XCRemoteSwiftPackageReference "SnapKit" */; 659 | productName = SnapKit; 660 | }; 661 | B697687B276E395000AA8776 /* RxCocoa */ = { 662 | isa = XCSwiftPackageProductDependency; 663 | package = B697687A276E395000AA8776 /* XCRemoteSwiftPackageReference "RxSwift" */; 664 | productName = RxCocoa; 665 | }; 666 | B697687D276E395000AA8776 /* RxSwift */ = { 667 | isa = XCSwiftPackageProductDependency; 668 | package = B697687A276E395000AA8776 /* XCRemoteSwiftPackageReference "RxSwift" */; 669 | productName = RxSwift; 670 | }; 671 | B6976882276E399800AA8776 /* RxGesture */ = { 672 | isa = XCSwiftPackageProductDependency; 673 | package = B6976881276E399800AA8776 /* XCRemoteSwiftPackageReference "RxGesture" */; 674 | productName = RxGesture; 675 | }; 676 | /* End XCSwiftPackageProductDependency section */ 677 | }; 678 | rootObject = B6976836276E360300AA8776 /* Project object */; 679 | } 680 | -------------------------------------------------------------------------------- /CustomTabBarExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CustomTabBarExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CustomTabBarExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "RxGesture", 6 | "repositoryURL": "https://github.com/RxSwiftCommunity/RxGesture.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "e29e65a58c2f670677d17df9a18e846b87e78592", 10 | "version": "4.0.2" 11 | } 12 | }, 13 | { 14 | "package": "RxSwift", 15 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "7c17a6ccca06b5c107cfa4284e634562ddaf5951", 19 | "version": "6.2.0" 20 | } 21 | }, 22 | { 23 | "package": "SnapKit", 24 | "repositoryURL": "https://github.com/SnapKit/SnapKit.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "d458564516e5676af9c70b4f4b2a9178294f1bc6", 28 | "version": "5.0.1" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /CustomTabBarExample.xcodeproj/xcuserdata/jedrzejcholuj.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CustomTabBarExample.xcodeproj/xcuserdata/jedrzejcholuj.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CustomTabBarExample.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | Rx (Playground) 1.xcscheme 13 | 14 | isShown 15 | 16 | orderHint 17 | 5 18 | 19 | Rx (Playground) 2.xcscheme 20 | 21 | isShown 22 | 23 | orderHint 24 | 6 25 | 26 | Rx (Playground).xcscheme 27 | 28 | isShown 29 | 30 | orderHint 31 | 4 32 | 33 | SnapKitPlayground (Playground) 1.xcscheme 34 | 35 | isShown 36 | 37 | orderHint 38 | 2 39 | 40 | SnapKitPlayground (Playground) 2.xcscheme 41 | 42 | isShown 43 | 44 | orderHint 45 | 3 46 | 47 | SnapKitPlayground (Playground).xcscheme 48 | 49 | isShown 50 | 51 | orderHint 52 | 1 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CustomTabBarExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 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 | } 36 | 37 | -------------------------------------------------------------------------------- /CustomTabBarExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CustomTabBarExample/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 | -------------------------------------------------------------------------------- /CustomTabBarExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CustomTabBarExample/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 | -------------------------------------------------------------------------------- /CustomTabBarExample/CustomItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomItemView.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class CustomItemView: UIView { 12 | 13 | private let nameLabel = UILabel() 14 | private let iconImageView = UIImageView() 15 | private let underlineView = UIView() 16 | private let containerView = UIView() 17 | let index: Int 18 | 19 | var isSelected = false { 20 | didSet { 21 | animateItems() 22 | } 23 | } 24 | 25 | private let item: CustomTabItem 26 | 27 | init(with item: CustomTabItem, index: Int) { 28 | self.item = item 29 | self.index = index 30 | 31 | super.init(frame: .zero) 32 | 33 | setupHierarchy() 34 | setupLayout() 35 | setupProperties() 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | private func setupHierarchy() { 43 | addSubview(containerView) 44 | containerView.addSubviews(nameLabel, iconImageView, underlineView) 45 | } 46 | 47 | private func setupLayout() { 48 | containerView.snp.makeConstraints { 49 | $0.edges.equalToSuperview() 50 | $0.center.equalToSuperview() 51 | } 52 | 53 | iconImageView.snp.makeConstraints { 54 | $0.height.width.equalTo(40) 55 | $0.top.equalToSuperview() 56 | $0.bottom.equalTo(nameLabel.snp.top) 57 | $0.centerX.equalToSuperview() 58 | } 59 | 60 | nameLabel.snp.makeConstraints { 61 | $0.bottom.leading.trailing.equalToSuperview() 62 | $0.height.equalTo(16) 63 | } 64 | 65 | underlineView.snp.makeConstraints { 66 | $0.width.equalTo(40) 67 | $0.height.equalTo(4) 68 | $0.centerX.equalToSuperview() 69 | $0.centerY.equalTo(nameLabel.snp.centerY) 70 | } 71 | } 72 | 73 | private func setupProperties() { 74 | nameLabel.configureWith(item.name, 75 | color: .white.withAlphaComponent(0.4), 76 | alignment: .center, 77 | size: 11, 78 | weight: .semibold) 79 | underlineView.backgroundColor = .white 80 | underlineView.setupCornerRadius(2) 81 | 82 | iconImageView.image = isSelected ? item.selectedIcon : item.icon 83 | } 84 | 85 | private func animateItems() { 86 | UIView.animate(withDuration: 0.4) { [unowned self] in 87 | self.nameLabel.alpha = self.isSelected ? 0.0 : 1.0 88 | self.underlineView.alpha = self.isSelected ? 1.0 : 0.0 89 | } 90 | UIView.transition(with: iconImageView, 91 | duration: 0.4, 92 | options: .transitionCrossDissolve) { [unowned self] in 93 | self.iconImageView.image = self.isSelected ? self.item.selectedIcon : self.item.icon 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /CustomTabBarExample/CustomTabBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTabBar.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | import RxCocoa 11 | import RxGesture 12 | 13 | final class CustomTabBar: UIStackView { 14 | 15 | var itemTapped: Observable { itemTappedSubject.asObservable() } 16 | 17 | private lazy var customItemViews: [CustomItemView] = [profileItem, searchItem, favoriteItem] 18 | 19 | private let profileItem = CustomItemView(with: .profile, index: 0) 20 | private let searchItem = CustomItemView(with: .search, index: 1) 21 | private let favoriteItem = CustomItemView(with: .favorite, index: 2) 22 | 23 | private let itemTappedSubject = PublishSubject() 24 | private let disposeBag = DisposeBag() 25 | 26 | init() { 27 | super.init(frame: .zero) 28 | 29 | setupHierarchy() 30 | setupProperties() 31 | bind() 32 | 33 | setNeedsLayout() 34 | layoutIfNeeded() 35 | selectItem(index: 0) 36 | } 37 | 38 | required init(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | private func setupHierarchy() { 43 | addArrangedSubviews([profileItem, searchItem, favoriteItem]) 44 | } 45 | 46 | private func setupProperties() { 47 | distribution = .fillEqually 48 | alignment = .center 49 | 50 | backgroundColor = .systemIndigo 51 | setupCornerRadius(30) 52 | 53 | customItemViews.forEach { 54 | $0.translatesAutoresizingMaskIntoConstraints = false 55 | $0.clipsToBounds = true 56 | } 57 | } 58 | 59 | private func selectItem(index: Int) { 60 | customItemViews.forEach { $0.isSelected = $0.index == index } 61 | itemTappedSubject.onNext(index) 62 | } 63 | 64 | //MARK: - Bindings 65 | 66 | private func bind() { 67 | profileItem.rx.tapGesture() 68 | .when(.recognized) 69 | .bind { [weak self] _ in 70 | guard let self = self else { return } 71 | self.profileItem.animateClick { 72 | self.selectItem(index: self.profileItem.index) 73 | } 74 | } 75 | .disposed(by: disposeBag) 76 | 77 | searchItem.rx.tapGesture() 78 | .when(.recognized) 79 | .bind { [weak self] _ in 80 | guard let self = self else { return } 81 | self.searchItem.animateClick { 82 | self.selectItem(index: self.searchItem.index) 83 | } 84 | } 85 | .disposed(by: disposeBag) 86 | 87 | favoriteItem.rx.tapGesture() 88 | .when(.recognized) 89 | .bind { [weak self] _ in 90 | guard let self = self else { return } 91 | self.favoriteItem.animateClick { 92 | self.selectItem(index: self.favoriteItem.index) 93 | } 94 | } 95 | .disposed(by: disposeBag) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /CustomTabBarExample/CustomTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTabBarViewController.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | import RxSwift 10 | import SnapKit 11 | 12 | class CustomTabBarController: UITabBarController { 13 | 14 | private let customTabBar = CustomTabBar() 15 | 16 | private let disposeBag = DisposeBag() 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | setupHierarchy() 21 | setupLayout() 22 | setupProperties() 23 | bind() 24 | view.layoutIfNeeded() 25 | } 26 | 27 | override func viewWillAppear(_ animated: Bool) { 28 | super.viewWillAppear(animated) 29 | navigationController?.isNavigationBarHidden = true 30 | } 31 | 32 | private func setupHierarchy() { 33 | view.addSubview(customTabBar) 34 | } 35 | 36 | private func setupLayout() { 37 | customTabBar.snp.makeConstraints { 38 | $0.leading.trailing.bottom.equalToSuperview().inset(24) 39 | $0.height.equalTo(90) 40 | } 41 | } 42 | 43 | private func setupProperties() { 44 | tabBar.isHidden = true 45 | 46 | customTabBar.translatesAutoresizingMaskIntoConstraints = false 47 | customTabBar.addShadow() 48 | 49 | selectedIndex = 0 50 | let controllers = CustomTabItem.allCases.map { $0.viewController } 51 | setViewControllers(controllers, animated: true) 52 | } 53 | 54 | private func selectTabWith(index: Int) { 55 | self.selectedIndex = index 56 | } 57 | 58 | //MARK: - Bindings 59 | 60 | private func bind() { 61 | customTabBar.itemTapped 62 | .bind { [weak self] in self?.selectTabWith(index: $0) } 63 | .disposed(by: disposeBag) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CustomTabBarExample/CustomTabItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTabItem.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | enum CustomTabItem: String, CaseIterable { 11 | case profile 12 | case search 13 | case favorite 14 | } 15 | 16 | extension CustomTabItem { 17 | var viewController: UIViewController { 18 | switch self { 19 | case .profile: 20 | return ViewController(item: .profile) 21 | case .search: 22 | return ViewController(item: .search) 23 | case .favorite: 24 | return ViewController(item: .favorite) 25 | } 26 | } 27 | 28 | var icon: UIImage? { 29 | switch self { 30 | case .search: 31 | return UIImage(systemName: "magnifyingglass.circle")?.withTintColor(.white.withAlphaComponent(0.4), renderingMode: .alwaysOriginal) 32 | case .favorite: 33 | return UIImage(systemName: "heart.circle")?.withTintColor(.white.withAlphaComponent(0.4), renderingMode: .alwaysOriginal) 34 | case .profile: 35 | return UIImage(systemName: "person.crop.circle")?.withTintColor(.white.withAlphaComponent(0.4), renderingMode: .alwaysOriginal) 36 | } 37 | } 38 | 39 | var selectedIcon: UIImage? { 40 | switch self { 41 | case .search: 42 | return UIImage(systemName: "magnifyingglass.circle.fill")?.withTintColor(.white, renderingMode: .alwaysOriginal) 43 | case .favorite: 44 | return UIImage(systemName: "heart.circle.fill")?.withTintColor(.white, renderingMode: .alwaysOriginal) 45 | case .profile: 46 | return UIImage(systemName: "person.crop.circle.fill")?.withTintColor(.white, renderingMode: .alwaysOriginal) 47 | } 48 | } 49 | 50 | var name: String { 51 | return self.rawValue.capitalized 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /CustomTabBarExample/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UILabel { 11 | func configureWith(_ text: String, 12 | color: UIColor, 13 | alignment: NSTextAlignment, 14 | size: CGFloat, 15 | weight: UIFont.Weight = .regular) { 16 | self.font = .systemFont(ofSize: size, weight: weight) 17 | self.text = text 18 | self.textColor = color 19 | self.textAlignment = alignment 20 | } 21 | } 22 | 23 | extension UIView { 24 | func setupCornerRadius(_ cornerRadius: CGFloat = 0, maskedCorners: CACornerMask? = nil) { 25 | layer.cornerRadius = cornerRadius 26 | if let corners = maskedCorners { 27 | layer.maskedCorners = corners 28 | } 29 | } 30 | 31 | func animateClick(completion: @escaping () -> Void) { 32 | UIView.animate(withDuration: 0.15) { 33 | self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) 34 | } completion: { _ in 35 | UIView.animate(withDuration: 0.15) { 36 | self.transform = CGAffineTransform.identity 37 | } completion: { _ in completion() } 38 | } 39 | } 40 | 41 | func addSubviews(_ views: UIView...) { 42 | views.forEach { addSubview($0) } 43 | } 44 | 45 | func addShadow() { 46 | layer.shadowColor = UIColor.black.cgColor 47 | layer.shadowOffset = .zero 48 | layer.shadowOpacity = 0.4 49 | layer.shadowRadius = 7 50 | } 51 | } 52 | 53 | extension UIStackView { 54 | func addArrangedSubviews(_ subviews: [UIView]) { 55 | subviews.forEach { addArrangedSubview($0) } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CustomTabBarExample/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 | UIApplicationSupportsIndirectInputEvents 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /CustomTabBarExample/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | private let navigationController = UINavigationController() 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | window = UIWindow(windowScene: windowScene) 18 | navigationController.setViewControllers([CustomTabBarController()], animated: true) 19 | window?.rootViewController = navigationController 20 | window?.makeKeyAndVisible() 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /CustomTabBarExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CustomTabBarExample 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | private let titleLabel = UILabel() 13 | private let item: CustomTabItem 14 | 15 | init(item: CustomTabItem) { 16 | self.item = item 17 | super.init(nibName: nil, bundle: nil) 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | setupHierarchy() 27 | setupLayout() 28 | setupProperties() 29 | } 30 | 31 | private func setupHierarchy() { 32 | view.addSubview(titleLabel) 33 | } 34 | 35 | private func setupLayout() { 36 | titleLabel.snp.makeConstraints { 37 | $0.center.equalToSuperview() 38 | } 39 | } 40 | 41 | private func setupProperties() { 42 | titleLabel.configureWith(item.name, color: .black, alignment: .center, size: 28, weight: .bold) 43 | view.backgroundColor = .white 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CustomTabBarExampleTests/CustomTabBarExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTabBarExampleTests.swift 3 | // CustomTabBarExampleTests 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import XCTest 9 | @testable import CustomTabBarExample 10 | 11 | class CustomTabBarExampleTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /CustomTabBarExampleTests/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 | 22 | 23 | -------------------------------------------------------------------------------- /CustomTabBarExampleUITests/CustomTabBarExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTabBarExampleUITests.swift 3 | // CustomTabBarExampleUITests 4 | // 5 | // Created by Jędrzej Chołuj on 18/12/2021. 6 | // 7 | 8 | import XCTest 9 | 10 | class CustomTabBarExampleUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CustomTabBarExampleUITests/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 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Tab Bar 2 | 3 | 4 | The implementation of custom TabBarController. Moving between tabs is fully animated, and items are easy to configure or add a new one. 5 |

6 | animated 7 |


8 | Custom Tab Bar's items changes are handled using RxSwift. For faster implementation purposes SnapKit has been used for the autolayout part, and RxGesture for handling touch gestures on Tab Bar's items. 9 |

10 | The implementation has been described in the Medium's article:
11 | https://medium.com/@jdrzejchouj/how-to-build-an-animated-custom-tab-bar-for-ios-application-5eb3a72e07a8 12 | 13 | ## Libraries 14 | 15 | - RxSwift 16 | - RxGesture 17 | - SnapKit 18 | 19 | All libraries are installed using Swift Package Manager. 20 | 21 | ## License 22 | 23 | MIT 24 | --------------------------------------------------------------------------------