├── .gitignore ├── MaterialTabBar ├── MaterialTabBar.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── christian.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── christian.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── MaterialTabBar │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Model │ └── TabBarItem.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── SampleApp.swift │ └── Views │ ├── ContentView.swift │ ├── MaterialTabBar.swift │ ├── SampleView.swift │ ├── TabBarContainer.swift │ └── TabBarItemPreferenceKey.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | .DS_Store 6 | 7 | ## User settings 8 | xcuserdata/ 9 | 10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 11 | *.xcscmblueprint 12 | *.xccheckout 13 | 14 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 15 | build/ 16 | DerivedData/ 17 | *.moved-aside 18 | *.pbxuser 19 | !default.pbxuser 20 | *.mode1v3 21 | !default.mode1v3 22 | *.mode2v3 23 | !default.mode2v3 24 | *.perspectivev3 25 | !default.perspectivev3 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | 30 | ## App packaging 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | # Swift Package Manager 40 | # 41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 42 | # Packages/ 43 | # Package.pins 44 | # Package.resolved 45 | # *.xcodeproj 46 | # 47 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 48 | # hence it is not needed unless you have added a package configuration file to your project 49 | # .swiftpm 50 | 51 | .build/ 52 | 53 | # CocoaPods 54 | # 55 | # We recommend against adding the Pods directory to your .gitignore. However 56 | # you should judge for yourself, the pros and cons are mentioned at: 57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 58 | # 59 | # Pods/ 60 | # 61 | # Add this line if you want to avoid checking in source code from the Xcode workspace 62 | # *.xcworkspace 63 | 64 | # Carthage 65 | # 66 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 67 | # Carthage/Checkouts 68 | 69 | Carthage/Build/ 70 | 71 | # Accio dependency management 72 | Dependencies/ 73 | .accio/ 74 | 75 | # fastlane 76 | # 77 | # It is recommended to not store the screenshots in the git repo. 78 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 79 | # For more information about the recommended setup visit: 80 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 81 | 82 | fastlane/report.xml 83 | fastlane/Preview.html 84 | fastlane/screenshots/**/*.png 85 | fastlane/test_output 86 | 87 | # Code Injection 88 | # 89 | # After new code Injection tools there's a generated folder /iOSInjectionProject 90 | # https://github.com/johnno1962/injectionforxcode 91 | 92 | iOSInjectionProject/ -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AA9686F52AFD3EE500A68924 /* SampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9686F42AFD3EE500A68924 /* SampleApp.swift */; }; 11 | AA9686F72AFD3EE500A68924 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9686F62AFD3EE500A68924 /* ContentView.swift */; }; 12 | AA9686F92AFD3EE600A68924 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA9686F82AFD3EE600A68924 /* Assets.xcassets */; }; 13 | AA9686FC2AFD3EE600A68924 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA9686FB2AFD3EE600A68924 /* Preview Assets.xcassets */; }; 14 | AA9687032AFD3F0E00A68924 /* TabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9687022AFD3F0E00A68924 /* TabBarItem.swift */; }; 15 | AA9687052AFD3F2600A68924 /* MaterialTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9687042AFD3F2600A68924 /* MaterialTabBar.swift */; }; 16 | AA9687072AFD3F5D00A68924 /* TabBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9687062AFD3F5D00A68924 /* TabBarContainer.swift */; }; 17 | AA9687092AFD3F8100A68924 /* TabBarItemPreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9687082AFD3F8100A68924 /* TabBarItemPreferenceKey.swift */; }; 18 | AA96870B2AFD410B00A68924 /* SampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA96870A2AFD410B00A68924 /* SampleView.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | AA9686F12AFD3EE500A68924 /* MaterialTabBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MaterialTabBar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | AA9686F42AFD3EE500A68924 /* SampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleApp.swift; sourceTree = ""; }; 24 | AA9686F62AFD3EE500A68924 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 25 | AA9686F82AFD3EE600A68924 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | AA9686FB2AFD3EE600A68924 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 27 | AA9687022AFD3F0E00A68924 /* TabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItem.swift; sourceTree = ""; }; 28 | AA9687042AFD3F2600A68924 /* MaterialTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaterialTabBar.swift; sourceTree = ""; }; 29 | AA9687062AFD3F5D00A68924 /* TabBarContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarContainer.swift; sourceTree = ""; }; 30 | AA9687082AFD3F8100A68924 /* TabBarItemPreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItemPreferenceKey.swift; sourceTree = ""; }; 31 | AA96870A2AFD410B00A68924 /* SampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleView.swift; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | AA9686EE2AFD3EE500A68924 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | AA9686E82AFD3EE500A68924 = { 46 | isa = PBXGroup; 47 | children = ( 48 | AA9686F32AFD3EE500A68924 /* MaterialTabBar */, 49 | AA9686F22AFD3EE500A68924 /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | AA9686F22AFD3EE500A68924 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | AA9686F12AFD3EE500A68924 /* MaterialTabBar.app */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | AA9686F32AFD3EE500A68924 /* MaterialTabBar */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | AA9686F42AFD3EE500A68924 /* SampleApp.swift */, 65 | AAD03C942AFDF1BA00CA77C4 /* Model */, 66 | AAD03C952AFDF1C600CA77C4 /* Views */, 67 | AA9686F82AFD3EE600A68924 /* Assets.xcassets */, 68 | AA9686FA2AFD3EE600A68924 /* Preview Content */, 69 | ); 70 | path = MaterialTabBar; 71 | sourceTree = ""; 72 | }; 73 | AA9686FA2AFD3EE600A68924 /* Preview Content */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | AA9686FB2AFD3EE600A68924 /* Preview Assets.xcassets */, 77 | ); 78 | path = "Preview Content"; 79 | sourceTree = ""; 80 | }; 81 | AAD03C942AFDF1BA00CA77C4 /* Model */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | AA9687022AFD3F0E00A68924 /* TabBarItem.swift */, 85 | ); 86 | path = Model; 87 | sourceTree = ""; 88 | }; 89 | AAD03C952AFDF1C600CA77C4 /* Views */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | AA9686F62AFD3EE500A68924 /* ContentView.swift */, 93 | AA9687062AFD3F5D00A68924 /* TabBarContainer.swift */, 94 | AA9687042AFD3F2600A68924 /* MaterialTabBar.swift */, 95 | AA9687082AFD3F8100A68924 /* TabBarItemPreferenceKey.swift */, 96 | AA96870A2AFD410B00A68924 /* SampleView.swift */, 97 | ); 98 | path = Views; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | AA9686F02AFD3EE500A68924 /* MaterialTabBar */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = AA9686FF2AFD3EE600A68924 /* Build configuration list for PBXNativeTarget "MaterialTabBar" */; 107 | buildPhases = ( 108 | AA9686ED2AFD3EE500A68924 /* Sources */, 109 | AA9686EE2AFD3EE500A68924 /* Frameworks */, 110 | AA9686EF2AFD3EE500A68924 /* Resources */, 111 | ); 112 | buildRules = ( 113 | ); 114 | dependencies = ( 115 | ); 116 | name = MaterialTabBar; 117 | productName = MaterialTabBar; 118 | productReference = AA9686F12AFD3EE500A68924 /* MaterialTabBar.app */; 119 | productType = "com.apple.product-type.application"; 120 | }; 121 | /* End PBXNativeTarget section */ 122 | 123 | /* Begin PBXProject section */ 124 | AA9686E92AFD3EE500A68924 /* Project object */ = { 125 | isa = PBXProject; 126 | attributes = { 127 | BuildIndependentTargetsInParallel = 1; 128 | LastSwiftUpdateCheck = 1500; 129 | LastUpgradeCheck = 1500; 130 | TargetAttributes = { 131 | AA9686F02AFD3EE500A68924 = { 132 | CreatedOnToolsVersion = 15.0.1; 133 | }; 134 | }; 135 | }; 136 | buildConfigurationList = AA9686EC2AFD3EE500A68924 /* Build configuration list for PBXProject "MaterialTabBar" */; 137 | compatibilityVersion = "Xcode 14.0"; 138 | developmentRegion = en; 139 | hasScannedForEncodings = 0; 140 | knownRegions = ( 141 | en, 142 | Base, 143 | ); 144 | mainGroup = AA9686E82AFD3EE500A68924; 145 | productRefGroup = AA9686F22AFD3EE500A68924 /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | AA9686F02AFD3EE500A68924 /* MaterialTabBar */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXResourcesBuildPhase section */ 155 | AA9686EF2AFD3EE500A68924 /* Resources */ = { 156 | isa = PBXResourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | AA9686FC2AFD3EE600A68924 /* Preview Assets.xcassets in Resources */, 160 | AA9686F92AFD3EE600A68924 /* Assets.xcassets in Resources */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | /* End PBXResourcesBuildPhase section */ 165 | 166 | /* Begin PBXSourcesBuildPhase section */ 167 | AA9686ED2AFD3EE500A68924 /* Sources */ = { 168 | isa = PBXSourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | AA9687032AFD3F0E00A68924 /* TabBarItem.swift in Sources */, 172 | AA9687072AFD3F5D00A68924 /* TabBarContainer.swift in Sources */, 173 | AA96870B2AFD410B00A68924 /* SampleView.swift in Sources */, 174 | AA9686F72AFD3EE500A68924 /* ContentView.swift in Sources */, 175 | AA9687092AFD3F8100A68924 /* TabBarItemPreferenceKey.swift in Sources */, 176 | AA9687052AFD3F2600A68924 /* MaterialTabBar.swift in Sources */, 177 | AA9686F52AFD3EE500A68924 /* SampleApp.swift in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin XCBuildConfiguration section */ 184 | AA9686FD2AFD3EE600A68924 /* Debug */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | ALWAYS_SEARCH_USER_PATHS = NO; 188 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 189 | CLANG_ANALYZER_NONNULL = YES; 190 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 191 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 192 | CLANG_ENABLE_MODULES = YES; 193 | CLANG_ENABLE_OBJC_ARC = YES; 194 | CLANG_ENABLE_OBJC_WEAK = YES; 195 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 196 | CLANG_WARN_BOOL_CONVERSION = YES; 197 | CLANG_WARN_COMMA = YES; 198 | CLANG_WARN_CONSTANT_CONVERSION = YES; 199 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 200 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 201 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 202 | CLANG_WARN_EMPTY_BODY = YES; 203 | CLANG_WARN_ENUM_CONVERSION = YES; 204 | CLANG_WARN_INFINITE_RECURSION = YES; 205 | CLANG_WARN_INT_CONVERSION = YES; 206 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 207 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 208 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 209 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 210 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 211 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 212 | CLANG_WARN_STRICT_PROTOTYPES = YES; 213 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 214 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 215 | CLANG_WARN_UNREACHABLE_CODE = YES; 216 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 217 | COPY_PHASE_STRIP = NO; 218 | DEBUG_INFORMATION_FORMAT = dwarf; 219 | ENABLE_STRICT_OBJC_MSGSEND = YES; 220 | ENABLE_TESTABILITY = YES; 221 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 222 | GCC_C_LANGUAGE_STANDARD = gnu17; 223 | GCC_DYNAMIC_NO_PIC = NO; 224 | GCC_NO_COMMON_BLOCKS = YES; 225 | GCC_OPTIMIZATION_LEVEL = 0; 226 | GCC_PREPROCESSOR_DEFINITIONS = ( 227 | "DEBUG=1", 228 | "$(inherited)", 229 | ); 230 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 231 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 232 | GCC_WARN_UNDECLARED_SELECTOR = YES; 233 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 234 | GCC_WARN_UNUSED_FUNCTION = YES; 235 | GCC_WARN_UNUSED_VARIABLE = YES; 236 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 237 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 238 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 239 | MTL_FAST_MATH = YES; 240 | ONLY_ACTIVE_ARCH = YES; 241 | SDKROOT = iphoneos; 242 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 243 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 244 | }; 245 | name = Debug; 246 | }; 247 | AA9686FE2AFD3EE600A68924 /* Release */ = { 248 | isa = XCBuildConfiguration; 249 | buildSettings = { 250 | ALWAYS_SEARCH_USER_PATHS = NO; 251 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 252 | CLANG_ANALYZER_NONNULL = YES; 253 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 254 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 255 | CLANG_ENABLE_MODULES = YES; 256 | CLANG_ENABLE_OBJC_ARC = YES; 257 | CLANG_ENABLE_OBJC_WEAK = YES; 258 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 259 | CLANG_WARN_BOOL_CONVERSION = YES; 260 | CLANG_WARN_COMMA = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 263 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 264 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INFINITE_RECURSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 275 | CLANG_WARN_STRICT_PROTOTYPES = YES; 276 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 277 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | COPY_PHASE_STRIP = NO; 281 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 282 | ENABLE_NS_ASSERTIONS = NO; 283 | ENABLE_STRICT_OBJC_MSGSEND = YES; 284 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 285 | GCC_C_LANGUAGE_STANDARD = gnu17; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 288 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 289 | GCC_WARN_UNDECLARED_SELECTOR = YES; 290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 291 | GCC_WARN_UNUSED_FUNCTION = YES; 292 | GCC_WARN_UNUSED_VARIABLE = YES; 293 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 294 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 295 | MTL_ENABLE_DEBUG_INFO = NO; 296 | MTL_FAST_MATH = YES; 297 | SDKROOT = iphoneos; 298 | SWIFT_COMPILATION_MODE = wholemodule; 299 | VALIDATE_PRODUCT = YES; 300 | }; 301 | name = Release; 302 | }; 303 | AA9687002AFD3EE600A68924 /* Debug */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 308 | CODE_SIGN_STYLE = Automatic; 309 | CURRENT_PROJECT_VERSION = 1; 310 | DEVELOPMENT_ASSET_PATHS = "\"MaterialTabBar/Preview Content\""; 311 | DEVELOPMENT_TEAM = N6RF4M6NH8; 312 | ENABLE_PREVIEWS = YES; 313 | GENERATE_INFOPLIST_FILE = YES; 314 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 315 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 316 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 317 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 318 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 319 | LD_RUNPATH_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "@executable_path/Frameworks", 322 | ); 323 | MARKETING_VERSION = 1.0; 324 | PRODUCT_BUNDLE_IDENTIFIER = com.christianlavelle.MaterialTabBar; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SWIFT_EMIT_LOC_STRINGS = YES; 327 | SWIFT_VERSION = 5.0; 328 | TARGETED_DEVICE_FAMILY = "1,2"; 329 | }; 330 | name = Debug; 331 | }; 332 | AA9687012AFD3EE600A68924 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 336 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 337 | CODE_SIGN_STYLE = Automatic; 338 | CURRENT_PROJECT_VERSION = 1; 339 | DEVELOPMENT_ASSET_PATHS = "\"MaterialTabBar/Preview Content\""; 340 | DEVELOPMENT_TEAM = N6RF4M6NH8; 341 | ENABLE_PREVIEWS = YES; 342 | GENERATE_INFOPLIST_FILE = YES; 343 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 344 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 345 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 346 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 347 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 348 | LD_RUNPATH_SEARCH_PATHS = ( 349 | "$(inherited)", 350 | "@executable_path/Frameworks", 351 | ); 352 | MARKETING_VERSION = 1.0; 353 | PRODUCT_BUNDLE_IDENTIFIER = com.christianlavelle.MaterialTabBar; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | SWIFT_EMIT_LOC_STRINGS = YES; 356 | SWIFT_VERSION = 5.0; 357 | TARGETED_DEVICE_FAMILY = "1,2"; 358 | }; 359 | name = Release; 360 | }; 361 | /* End XCBuildConfiguration section */ 362 | 363 | /* Begin XCConfigurationList section */ 364 | AA9686EC2AFD3EE500A68924 /* Build configuration list for PBXProject "MaterialTabBar" */ = { 365 | isa = XCConfigurationList; 366 | buildConfigurations = ( 367 | AA9686FD2AFD3EE600A68924 /* Debug */, 368 | AA9686FE2AFD3EE600A68924 /* Release */, 369 | ); 370 | defaultConfigurationIsVisible = 0; 371 | defaultConfigurationName = Release; 372 | }; 373 | AA9686FF2AFD3EE600A68924 /* Build configuration list for PBXNativeTarget "MaterialTabBar" */ = { 374 | isa = XCConfigurationList; 375 | buildConfigurations = ( 376 | AA9687002AFD3EE600A68924 /* Debug */, 377 | AA9687012AFD3EE600A68924 /* Release */, 378 | ); 379 | defaultConfigurationIsVisible = 0; 380 | defaultConfigurationName = Release; 381 | }; 382 | /* End XCConfigurationList section */ 383 | }; 384 | rootObject = AA9686E92AFD3EE500A68924 /* Project object */; 385 | } 386 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar.xcodeproj/project.xcworkspace/xcuserdata/christian.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bodhichristian/MaterialTabBar/bb0cfbe9be30c3aefa5f9bdbc92a0ba690b65d4a/MaterialTabBar/MaterialTabBar.xcodeproj/project.xcworkspace/xcuserdata/christian.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar.xcodeproj/xcuserdata/christian.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MaterialTabBar.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/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 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Model/TabBarItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarItem.swift 3 | // MaterialTabBar 4 | // 5 | // Created by christian on 11/9/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | enum TabBarItem: Hashable { 12 | case home, favorites, profile, messages 13 | 14 | var iconName: String { 15 | switch self { 16 | case .home: return "house" 17 | case .favorites: return "heart" 18 | case .profile: return "person" 19 | case .messages: return "message" 20 | } 21 | } 22 | 23 | var title: String { 24 | switch self { 25 | case .home: return "Home" 26 | case .favorites: return "Favorites" 27 | case .profile: return "Profile" 28 | case .messages: return "Messages" 29 | } 30 | } 31 | 32 | var color: Color { 33 | switch self { 34 | case .home: return .blue 35 | case .favorites: return .red 36 | case .profile: return .orange 37 | case .messages: return .green 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/SampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MaterialTabBarApp.swift 3 | // MaterialTabBar 4 | // 5 | // Created by christian on 11/9/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct SampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // MaterialTabBar 4 | // 5 | // Created by christian on 11/9/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @State private var tabSelection: TabBarItem = .home 12 | 13 | var body: some View { 14 | TabBarContainer(selection: $tabSelection) { 15 | SampleView(item: .home) 16 | .tabBarItem(tab: .home, 17 | selection: $tabSelection) 18 | 19 | SampleView(item: .favorites) 20 | .tabBarItem(tab: .favorites, 21 | selection: $tabSelection) 22 | 23 | SampleView(item: .messages) 24 | .tabBarItem(tab: .messages, 25 | selection: $tabSelection) 26 | 27 | SampleView(item: .profile) 28 | .tabBarItem(tab: .profile, 29 | selection: $tabSelection) 30 | 31 | } 32 | } 33 | } 34 | 35 | #Preview { 36 | ContentView() 37 | } 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Views/MaterialTabBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MaterialTabBar.swift 3 | // MaterialTabBar 4 | // 5 | // Created by christian on 11/9/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MaterialTabBar: View { 11 | let tabs: [TabBarItem] 12 | let contentShape = RoundedRectangle(cornerRadius: 5) 13 | @Binding var selection: TabBarItem 14 | 15 | // For matchedGeometryEffect 16 | @State var localSelection: TabBarItem 17 | @Namespace private var namespace 18 | 19 | var body: some View { 20 | VStack { 21 | Spacer() 22 | 23 | HStack { 24 | ForEach(tabs, id: \.self) { tab in 25 | tabView(tab: tab) 26 | 27 | } 28 | } 29 | 30 | .background { 31 | contentShape 32 | .foregroundStyle(.ultraThinMaterial) 33 | } 34 | .ignoresSafeArea(edges: .bottom) 35 | .clipShape(Capsule()) 36 | .shadow(color: .secondary.opacity(0.3), radius: 10, y: 5) 37 | .padding(.horizontal) 38 | .onChange(of: selection) { _, newValue in 39 | withAnimation(.bouncy(duration: 0.3)) { 40 | localSelection = newValue 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | #Preview { 48 | MaterialTabBar(tabs: [.home, .favorites, .messages, .profile], selection: .constant(.home), localSelection: .home) 49 | } 50 | 51 | extension MaterialTabBar { 52 | private func tabView(tab: TabBarItem) -> some View { 53 | VStack { 54 | 55 | if tab == selection { 56 | Image(systemName: "\(tab.iconName).fill") 57 | .font(.subheadline).bold() 58 | .symbolEffect(.bounce, value: localSelection) 59 | .foregroundStyle(localSelection == tab ? tab.color : .secondary) 60 | .frame(height: 15) 61 | .shadow(radius: 0.5, y: 1) 62 | } else { 63 | Image(systemName: tab.iconName) 64 | .font(.subheadline) 65 | .foregroundStyle(localSelection == tab ? tab.color : .secondary) 66 | .frame(height: 15) 67 | } 68 | 69 | Text(tab.title) 70 | .font(.system(size: 10, weight: tab == selection ? .bold : .semibold, design: .rounded)) 71 | .foregroundStyle(localSelection == tab ? .white : .secondary) 72 | } 73 | 74 | .padding(.vertical, 8) 75 | .frame(maxWidth: .infinity) 76 | .frame(height: 50) 77 | .background( 78 | ZStack { 79 | if localSelection == tab { 80 | contentShape 81 | .fill(tab.color.opacity(0.2)) 82 | .matchedGeometryEffect(id: "tabHighlighting", in: namespace) 83 | } 84 | } 85 | ) 86 | .accessibilityElement(children: .combine) 87 | .accessibilityLabel("\(tab.title), tab") 88 | .accessibilityHint("Double tap to open the \(tab.title) tab") 89 | .contentShape(contentShape) 90 | .onTapGesture { 91 | switchToTab(tab) 92 | } 93 | } 94 | 95 | private func switchToTab(_ newTab: TabBarItem) { 96 | selection = newTab 97 | } 98 | } 99 | 100 | 101 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Views/SampleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleView.swift 3 | // MaterialTabBar 4 | // 5 | // Created by christian on 11/9/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SampleView: View { 11 | let item: TabBarItem 12 | let sectionTitles = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", 13 | "Zeta", "Eta", "Theta", "Iota", "Kappa"] 14 | 15 | var body: some View { 16 | NavigationView { 17 | ScrollView { 18 | VStack(alignment: .leading) { 19 | ForEach(sectionTitles, id: \.self) { title in 20 | Text(title) 21 | .font(.title) 22 | .fontWeight(.semibold) 23 | .padding(.leading) 24 | 25 | ScrollView(.horizontal) { 26 | HStack { 27 | ForEach(0..<5) { index in 28 | RoundedRectangle(cornerRadius: 25) 29 | .foregroundStyle( 30 | LinearGradient( 31 | colors: [.white, item.color.opacity(0.5), .black], 32 | startPoint: .topLeading, 33 | endPoint: .bottomTrailing) 34 | ) 35 | .frame(width: 300, height: 200) 36 | .padding(.horizontal, 10) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | .navigationTitle(item.title) 44 | .toolbar(content: { 45 | Image(systemName: "questionmark.circle") 46 | }) 47 | } 48 | } 49 | } 50 | 51 | 52 | #Preview { 53 | SampleView(item: TabBarItem.home) 54 | } 55 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Views/TabBarContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarContainer.swift 3 | // MaterialTabBar 4 | // 5 | // Created by christian on 11/9/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TabBarContainer: View { 11 | let content: Content 12 | @Binding var selection: TabBarItem 13 | @State private var tabs: [TabBarItem] = [] 14 | 15 | init(selection: Binding, 16 | @ViewBuilder content: () -> Content) { 17 | self._selection = selection 18 | self.content = content() 19 | } 20 | 21 | var body: some View { 22 | ZStack { 23 | content 24 | .ignoresSafeArea() 25 | MaterialTabBar(tabs: tabs, 26 | selection: $selection, 27 | localSelection: selection) 28 | } 29 | .onPreferenceChange(TabBarItemPreferenceKey.self, perform: { value in 30 | self.tabs = value 31 | }) 32 | } 33 | } 34 | 35 | #Preview { 36 | TabBarContainer(selection: .constant(.home)) { 37 | Color.blue 38 | } 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /MaterialTabBar/MaterialTabBar/Views/TabBarItemPreferenceKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarItemPreferenceKey.swift 3 | // MaterialTabBar 4 | // 5 | // Created by christian on 11/9/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct TabBarItemPreferenceKey: PreferenceKey { 12 | static var defaultValue: [TabBarItem] = [] 13 | static func reduce(value: inout [TabBarItem], nextValue: () -> [TabBarItem]) { 14 | value += nextValue() 15 | } 16 | } 17 | 18 | struct TabBarItemViewModifier: ViewModifier { 19 | let tab: TabBarItem 20 | @Binding var selection: TabBarItem 21 | 22 | func body(content: Content) -> some View { 23 | content 24 | .opacity(selection == tab ? 1.0 : 0.0) 25 | .preference(key: TabBarItemPreferenceKey.self, value: [tab]) 26 | } 27 | } 28 | 29 | extension View { 30 | func tabBarItem(tab: TabBarItem, selection: Binding) -> some View { 31 | self 32 | .modifier(TabBarItemViewModifier(tab: tab, selection: selection)) 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaterialTabBar 2 | A customizable, floating tab bar, with a material background, written entirely with SwiftUI. 3 | 4 | - TabBarContainer binds your apps views to tabs, using @ViewBuilder. 5 | - TabBarItemPreferenceKey is used to pass data upwards from child views to parent. 6 | - matchedGeometryEffect adds selective highlighting to a floating capsule, ultraThinMaterial tab bar. 7 | - symbolEffect adds animations to icons when a user switches tabs. 8 | 9 | ![MaterialTabBar 001](https://github.com/bodhichristian/MaterialTabBar/assets/110639779/d2f4c927-0697-49fb-a3f7-7bfd805f21d0) 10 | 11 | --------------------------------------------------------------------------------