├── CBTabBarController ├── .gitkeep ├── Classes │ ├── .gitkeep │ ├── .DS_Store │ ├── Helpers │ │ ├── Bundle.swift │ │ └── CGPoint.swift │ ├── Menu │ │ ├── CBMenuTabItem.swift │ │ ├── CBMenuTabButtonFactory.swift │ │ ├── CBMenuButton.swift │ │ ├── CBMenuController.swift │ │ ├── CBMenuDismissAnimationController.swift │ │ ├── CBMenuPresenter.swift │ │ └── CBMenuPresentAnimationController.swift │ ├── Fade │ │ ├── CBFadeTabButtonFactory.swift │ │ └── CBFadeTabBarButton.swift │ ├── Animation │ │ └── CBTabItemAnimation.swift │ ├── CBTabButtonFactory.swift │ ├── Flashy │ │ ├── CBFlashyTabButtonFactory.swift │ │ ├── Animations │ │ │ ├── CBFlashyTabItemBasicAnimation.swift │ │ │ ├── CBTabItemSelectAnimation.swift │ │ │ └── CBTabItemDeselectAnimation.swift │ │ └── CBFlashyTabBarButton.swift │ ├── CBTabBarStyle.swift │ ├── CBTabBarController.swift │ ├── CBBaseTabButton.swift │ └── CBTabBar.swift ├── .DS_Store ├── Assets │ ├── .DS_Store │ ├── Images │ │ ├── btnClose.png │ │ ├── btnMenu.png │ │ ├── btnMenu@2x.png │ │ └── btnClose@2x.png │ └── Storyboard │ │ └── CBMenuController.storyboard ├── CBFlashyTabBarController.h └── Info.plist ├── Screenshots ├── flashy.gif └── gooey.gif ├── Example ├── Images.xcassets │ ├── Contents.json │ ├── Events.imageset │ │ ├── Events.png │ │ ├── Events@2x.png │ │ ├── Events@3x.png │ │ └── Contents.json │ ├── Search.imageset │ │ ├── Search.png │ │ ├── Search@2x.png │ │ ├── Search@3x.png │ │ └── Contents.json │ ├── Settings.imageset │ │ ├── Settings.png │ │ ├── Settings@2x.png │ │ ├── Settings@3x.png │ │ └── Contents.json │ ├── btnClose.imageset │ │ ├── btnClose.png │ │ ├── btnClose@2x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Icon_60@2x.png │ │ ├── Icon_60@3x.png │ │ └── Contents.json │ ├── Highlights.imageset │ │ ├── Highlights.png │ │ ├── Highlights@2x.png │ │ ├── Highlights@3x.png │ │ └── Contents.json │ └── cuberto-logo.imageset │ │ ├── cuberto-logo.png │ │ ├── cuberto-logo@2x.png │ │ ├── cuberto-logo@3x.png │ │ └── Contents.json ├── AppDelegate.swift ├── Info.plist ├── CBSampleViewController.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard └── ViewController.swift ├── CBTabBar.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── CBFlashyTabBarController.xcscheme └── project.pbxproj ├── CBTabBarController.podspec ├── LICENSE ├── .gitignore └── README.md /CBTabBarController/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Screenshots/flashy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Screenshots/flashy.gif -------------------------------------------------------------------------------- /Screenshots/gooey.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Screenshots/gooey.gif -------------------------------------------------------------------------------- /CBTabBarController/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/CBTabBarController/.DS_Store -------------------------------------------------------------------------------- /CBTabBarController/Assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/CBTabBarController/Assets/.DS_Store -------------------------------------------------------------------------------- /Example/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CBTabBarController/Classes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/CBTabBarController/Classes/.DS_Store -------------------------------------------------------------------------------- /CBTabBarController/Assets/Images/btnClose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/CBTabBarController/Assets/Images/btnClose.png -------------------------------------------------------------------------------- /CBTabBarController/Assets/Images/btnMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/CBTabBarController/Assets/Images/btnMenu.png -------------------------------------------------------------------------------- /CBTabBarController/Assets/Images/btnMenu@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/CBTabBarController/Assets/Images/btnMenu@2x.png -------------------------------------------------------------------------------- /CBTabBarController/Assets/Images/btnClose@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/CBTabBarController/Assets/Images/btnClose@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Events.imageset/Events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Events.imageset/Events.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Search.imageset/Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Search.imageset/Search.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Events.imageset/Events@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Events.imageset/Events@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Events.imageset/Events@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Events.imageset/Events@3x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Search.imageset/Search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Search.imageset/Search@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Search.imageset/Search@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Search.imageset/Search@3x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Settings.imageset/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Settings.imageset/Settings.png -------------------------------------------------------------------------------- /Example/Images.xcassets/btnClose.imageset/btnClose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/btnClose.imageset/btnClose.png -------------------------------------------------------------------------------- /Example/Images.xcassets/AppIcon.appiconset/Icon_60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/AppIcon.appiconset/Icon_60@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/AppIcon.appiconset/Icon_60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/AppIcon.appiconset/Icon_60@3x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Settings.imageset/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Settings.imageset/Settings@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Settings.imageset/Settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Settings.imageset/Settings@3x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/btnClose.imageset/btnClose@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/btnClose.imageset/btnClose@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Highlights.imageset/Highlights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Highlights.imageset/Highlights.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Highlights.imageset/Highlights@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Highlights.imageset/Highlights@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/Highlights.imageset/Highlights@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/Highlights.imageset/Highlights@3x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/cuberto-logo.imageset/cuberto-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/cuberto-logo.imageset/cuberto-logo.png -------------------------------------------------------------------------------- /Example/Images.xcassets/cuberto-logo.imageset/cuberto-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/cuberto-logo.imageset/cuberto-logo@2x.png -------------------------------------------------------------------------------- /Example/Images.xcassets/cuberto-logo.imageset/cuberto-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cuberto/cb-tabbar/HEAD/Example/Images.xcassets/cuberto-logo.imageset/cuberto-logo@3x.png -------------------------------------------------------------------------------- /CBTabBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CBTabBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Helpers/Bundle.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // Bundle.swift 4 | // liquid-swipe 5 | // 6 | // Created by Anton Skopin on 06/01/2019. 7 | // 8 | import Foundation 9 | 10 | extension Bundle { 11 | static var resourseBundle: Bundle { 12 | let frameworkBundle = Bundle(for: CBTabBar.self) 13 | return frameworkBundle 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Helpers/CGPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 22/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGPoint { 12 | func offsetBy(dx: CGFloat = 0, dy: CGFloat = 0) -> CGPoint { 13 | return CGPoint(x: x + dx, y: y + dy) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Menu/CBMenuTabItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBMenuTabItem.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 20/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBMenuTabItem: UITabBarItem, CBExtendedTabItem { 12 | 13 | var attributedTitle: NSAttributedString? { 14 | return nil 15 | } 16 | 17 | var tintColor: UIColor? 18 | } 19 | -------------------------------------------------------------------------------- /Example/Images.xcassets/btnClose.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btnClose.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "btnClose@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CBTabBarController/Classes/Fade/CBFadeTabButtonFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBFadeTabButtonFactory.swift 3 | // CBFlashyTabBarController 4 | // 5 | // Created by Anton Skopin on 22/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBFadeTabButtonFactory: CBTabButtonFactory { 12 | 13 | func buttons(forItems items: [UITabBarItem]) -> [CBTabBarButton] { 14 | return items.map { CBFadeTabBarButton(item: $0) } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Example/Images.xcassets/Events.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Events.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Events@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Events@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Images.xcassets/Search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Search.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Search@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Search@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Images.xcassets/Settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Settings.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Settings@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Settings@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Images.xcassets/Highlights.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Highlights.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Highlights@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Highlights@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Images.xcassets/cuberto-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cuberto-logo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cuberto-logo@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "cuberto-logo@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CBTabBarController/CBFlashyTabBarController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CBFlashyTabBarController.h 3 | // CBFlashyTabBarController 4 | // 5 | // Created by Anton Skopin on 04/12/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for test. 12 | FOUNDATION_EXPORT double testVersionNumber; 13 | 14 | //! Project version string for test. 15 | FOUNDATION_EXPORT const unsigned char testVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Animation/CBTabItemAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBTabItemAnimation.swift 3 | // CBFlashyTabBarController 4 | // 5 | // Created by Anton Skopin on 28/11/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol CBTabItemAnimation { 12 | 13 | func playAnimation(forTabBarItem item: CBTabBarButton) 14 | func playAnimation(forTabBarItem item: CBTabBarButton, completion: (() -> Void)?) 15 | 16 | } 17 | 18 | extension CBTabItemAnimation { 19 | 20 | func playAnimation(forTabBarItem item: CBTabBarButton) { 21 | playAnimation(forTabBarItem: item, completion: nil) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /CBTabBarController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CBTabBarController' 3 | s.version = '0.9.5' 4 | s.summary = 'One another nice animated tabbar' 5 | s.homepage = 'https://github.com/Cuberto/cb-tabbar' 6 | s.license = 'MIT' 7 | s.author = { 'askopin@gmail.com' => 'askopin@gmail.com' } 8 | s.source = { :git => 'https://github.com/Cuberto/cb-tabbar.git', :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/cuberto' 10 | s.ios.deployment_target = '10.0' 11 | s.swift_version = '4.2' 12 | s.source_files = 'CBTabBarController/Classes/**/*' 13 | s.resources = 'CBTabBarController/Assets/**/*.{png,storyboard}' 14 | end 15 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/CBTabButtonFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBTabBarStyle.swift 3 | // CBFlashyTabBarController 4 | // 5 | // Created by Anton Skopin on 19/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | typealias CBTabBarButton = UIControl & CBTabBarButtonProtocol 12 | 13 | protocol CBTabBarButtonProtocol { 14 | init(item: UITabBarItem) 15 | var item: UITabBarItem? { get set } 16 | func setSelected(_ selected: Bool, animated: Bool) 17 | var requiredSize: CGSize? { get } 18 | } 19 | 20 | 21 | extension CBTabBarButtonProtocol { 22 | var requiredSize: CGSize? { 23 | return nil 24 | } 25 | } 26 | 27 | protocol CBTabButtonFactory { 28 | func buttons(forItems item: [UITabBarItem]) -> [CBTabBarButton] 29 | } 30 | -------------------------------------------------------------------------------- /CBTabBarController/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 | FMWK 17 | CFBundleShortVersionString 18 | 0.9.5 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Cuberto Design 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon_60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon_60@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "idiom" : "ios-marketing", 47 | "size" : "1024x1024", 48 | "scale" : "1x" 49 | } 50 | ], 51 | "info" : { 52 | "version" : 1, 53 | "author" : "xcode" 54 | } 55 | } -------------------------------------------------------------------------------- /Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CBFlashyTabBarController 4 | // 5 | // Created by askopin@gmail.com on 11/29/2018. 6 | // Copyright (c) 2018 askopin@gmail.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CBTabBarController 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, 18 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | CBTabBar.appearance().tintColor = #colorLiteral(red: 0.1176470588, green: 0.1176470588, blue: 0.431372549, alpha: 1) 20 | CBTabBar.appearance().barTintColor = .white 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(_ application: UIApplication) { 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Flashy/CBFlashyTabButtonFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBFlashyTabButtonFactory.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 20/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct CBFlashyTabBarConfig { 12 | let deselectedOpacity: CGFloat 13 | 14 | public init(deselectedOpacity: CGFloat) { 15 | self.deselectedOpacity = max(0.0, min(1.0, deselectedOpacity)) 16 | } 17 | } 18 | 19 | class CBFlashyTabButtonFactory: CBTabButtonFactory { 20 | 21 | let config: CBFlashyTabBarConfig? 22 | 23 | init(config: CBFlashyTabBarConfig? = nil) { 24 | self.config = config 25 | } 26 | 27 | private var animationSpeed: Double = 1.0 28 | 29 | func buttons(forItems items: [UITabBarItem]) -> [CBTabBarButton] { 30 | return items.map { item -> CBTabBarButton in 31 | let button = CBFlashyTabBarButton(item: item) 32 | button.config = config 33 | button.selectAnimation = CBFlashyTabItemSelectAnimation(duration: 0.3 / animationSpeed) 34 | button.deselectAnimation = CBFlashyTabItemDeselectAnimation(duration: 0.3 / animationSpeed) 35 | return button 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Menu/CBMenuTabButtonFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBSimpleTabButtonFactory.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 20/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBMenuTabButtonFactory: CBTabButtonFactory { 12 | private let menu: CBTabMenu 13 | private let menuItem = CBMenuTabItem() 14 | weak var presentationController: UIViewController? 15 | 16 | init(menu: CBTabMenu) { 17 | self.menu = menu 18 | } 19 | 20 | func buttons(forItems items: [UITabBarItem]) -> [CBTabBarButton] { 21 | var buttons: [CBTabBarButton] = items.map { CBFadeTabBarButton(item: $0) } 22 | if buttons.count >= menu.menuButtonIndex { 23 | let menuItem = CBMenuTabItem() 24 | menuItem.image = menu.icon ?? UIImage(named: "btnMenu", in: Bundle.resourseBundle, compatibleWith: nil) 25 | menuItem.tintColor = menu.menuColor 26 | let button = CBMenuButton(item: menuItem) 27 | let presenter = CBMenuPresenter(menu: menu) 28 | button.presenter = presenter 29 | presenter.menuButton = button 30 | presenter.presentationController = presentationController 31 | buttons.insert(button, at: menu.menuButtonIndex) 32 | } 33 | return buttons 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Animated Bar 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationPortraitUpsideDown 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Menu/CBMenuButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBMenuButton.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 20/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBMenuButton: CBBaseTabButton { 12 | 13 | private let btnSize: CGFloat = 60.0 14 | private let tabImage: UIImageView = { 15 | let imageView = UIImageView() 16 | imageView.contentMode = .center 17 | return imageView 18 | }() 19 | private let bgView = UIView() 20 | var presenter: CBMenuPresenter? 21 | 22 | override var requiredSize: CGSize? { 23 | return CGSize(width: btnSize, height: btnSize) 24 | } 25 | 26 | override var tintColor: UIColor! { 27 | didSet { 28 | bgView.backgroundColor = tintColor 29 | } 30 | } 31 | 32 | override func configure() { 33 | super.configure() 34 | bgView.frame = CGRect(origin: .zero, size: CGSize(width: btnSize, height: btnSize)) 35 | tabImage.frame = bgView.frame 36 | addSubview(bgView) 37 | addSubview(tabImage) 38 | bgView.layer.cornerRadius = btnSize / 2.0 39 | tabImage.isUserInteractionEnabled = false 40 | bgView.isUserInteractionEnabled = false 41 | } 42 | 43 | override func layoutSubviews() { 44 | super.layoutSubviews() 45 | bgView.center = CGPoint(x: bounds.midX, y: bounds.midY) 46 | tabImage.frame = bgView.frame 47 | } 48 | 49 | override func didUpdateItem() { 50 | tabImage.image = item?.image 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | *~ 5 | .DS_Store 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | # Pods/ 52 | 53 | # Carthage 54 | # 55 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 56 | # Carthage/Checkouts 57 | 58 | Carthage/Build 59 | 60 | # fastlane 61 | # 62 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 63 | # screenshots whenever they are needed. 64 | # For more information about the recommended setup visit: 65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 66 | 67 | fastlane/report.xml 68 | fastlane/Preview.html 69 | fastlane/screenshots/**/*.png 70 | fastlane/test_output 71 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/CBTabBarStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBTabBarStyle.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 22/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol CBExtendedTabItem { 12 | var attributedTitle: NSAttributedString? { get } 13 | var tintColor: UIColor? { get } 14 | } 15 | 16 | public extension CBExtendedTabItem { 17 | var attributedTitle: NSAttributedString? { 18 | return nil 19 | } 20 | var tintColor: UIColor? { 21 | return nil 22 | } 23 | } 24 | 25 | public protocol CBTabMenuItem { 26 | var title: String? { get } 27 | var attributedTitle: NSAttributedString? { get } 28 | } 29 | 30 | public struct CBTabMenu { 31 | let menuButtonIndex: Int 32 | let menuColor: UIColor 33 | let items: [CBTabMenuItem] 34 | let icon: UIImage? 35 | let callback: (UIViewController, CBTabMenuItem) -> Void 36 | 37 | public init(menuButtonIndex: Int, 38 | menuColor: UIColor, 39 | items: [CBTabMenuItem], 40 | icon: UIImage?, 41 | callback: @escaping (UIViewController, CBTabMenuItem) -> Void){ 42 | self.menuButtonIndex = menuButtonIndex 43 | self.menuColor = menuColor 44 | self.items = items 45 | self.icon = icon 46 | self.callback = callback 47 | } 48 | } 49 | 50 | public enum CBTabBarStyle { 51 | case flashy(config: CBFlashyTabBarConfig?) 52 | case fade 53 | case gooey(menu: CBTabMenu) 54 | } 55 | 56 | 57 | internal extension CBTabBarStyle { 58 | func configure(tabBar: CBTabBar, with controller: CBTabBarController) { 59 | switch self { 60 | case let .gooey(menu): 61 | let menuFactory = CBMenuTabButtonFactory(menu: menu) 62 | menuFactory.presentationController = controller 63 | tabBar.layer.borderWidth = 0.0 64 | tabBar.clipsToBounds = true 65 | tabBar.buttonFactory = menuFactory 66 | case .fade: 67 | tabBar.buttonFactory = CBFadeTabButtonFactory() 68 | case let .flashy(config): 69 | tabBar.buttonFactory = CBFlashyTabButtonFactory(config: config) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Fade/CBFadeTabBarButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBSimpleTabBarButton.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 20/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBFadeTabBarButton: CBBaseTabButton { 12 | 13 | private let deselectedAlpha: CGFloat = 0.5 14 | private let animationDuration: Double = 0.15 15 | 16 | override func configure() { 17 | super.configure() 18 | } 19 | 20 | private var _isSelected: Bool = false 21 | override var isSelected: Bool { 22 | get { 23 | return _isSelected 24 | } 25 | set { 26 | guard newValue != _isSelected else { 27 | return 28 | } 29 | if newValue { 30 | select(animated: false) 31 | } else { 32 | deselect(animated: false) 33 | } 34 | } 35 | } 36 | 37 | override func layoutSubviews() { 38 | super.layoutSubviews() 39 | imageView?.alpha = _isSelected ? 1 : deselectedAlpha 40 | titleLabel?.alpha = _isSelected ? 1 : deselectedAlpha 41 | } 42 | 43 | override func select(animated: Bool = true) { 44 | guard !_isSelected else { 45 | return 46 | } 47 | _isSelected = true 48 | let animation: () -> Void = {[weak self] in 49 | self?.imageView?.alpha = 1 50 | self?.titleLabel?.alpha = 1 51 | } 52 | guard animated else { 53 | animation() 54 | return 55 | } 56 | UIView.animate(withDuration: animationDuration, animations: animation) 57 | } 58 | 59 | override func deselect(animated: Bool = true) { 60 | guard _isSelected else { 61 | return 62 | } 63 | _isSelected = false 64 | let animation: () -> Void = {[weak self] in 65 | self?.imageView?.alpha = self?.deselectedAlpha ?? 0 66 | self?.titleLabel?.alpha = self?.deselectedAlpha ?? 0 67 | } 68 | guard animated else { 69 | animation() 70 | return 71 | } 72 | UIView.animate(withDuration: animationDuration, animations: animation) 73 | } 74 | 75 | override func didUpdateItem() { 76 | super.didUpdateItem() 77 | setImage(item?.image?.withRenderingMode(.alwaysTemplate), for: .normal) 78 | setNeedsLayout() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Example/CBSampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBSampleViewController.swift 3 | // CBFlashyTabBarController_Example 4 | // 5 | // Created by Anton Skopin on 03/12/2018. 6 | // Copyright © 2018 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBSampleViewController: UIViewController { 12 | 13 | var lblTitle: UILabel = { 14 | var label = UILabel() 15 | label.textColor = #colorLiteral(red: 0.1579992771, green: 0.1818160117, blue: 0.5072338581, alpha: 1) 16 | label.font = UIFont.systemFont(ofSize: 55.0, weight: .bold) 17 | label.translatesAutoresizingMaskIntoConstraints = false 18 | return label 19 | }() 20 | 21 | var btnClose: UIButton = { 22 | var btn = UIButton() 23 | btn.tintColor = #colorLiteral(red: 0.1579992771, green: 0.1818160117, blue: 0.5072338581, alpha: 1) 24 | btn.setImage(#imageLiteral(resourceName: "btnClose").withRenderingMode(.alwaysTemplate), for: .normal) 25 | btn.translatesAutoresizingMaskIntoConstraints = false 26 | return btn 27 | }() 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | view.backgroundColor = UIColor.white 32 | lblTitle.text = tabBarItem.title 33 | view.addSubview(lblTitle) 34 | 35 | view.addSubview(btnClose) 36 | btnClose.widthAnchor.constraint(equalToConstant: 45).isActive = true 37 | btnClose.heightAnchor.constraint(equalToConstant: 45).isActive = true 38 | let guide = view.layoutMarginsGuide 39 | btnClose.topAnchor.constraint(equalToSystemSpacingBelow: guide.topAnchor, multiplier: 1.0).isActive = true 40 | btnClose.trailingAnchor.constraint(equalToSystemSpacingAfter: guide.trailingAnchor, multiplier: 1.0).isActive = true 41 | btnClose.addTarget(self, action: #selector(btnClosePressed), for: .touchUpInside) 42 | 43 | lblTitle.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 44 | lblTitle.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 45 | view.setNeedsLayout() 46 | 47 | } 48 | 49 | func inverseColor() { 50 | view.backgroundColor = lblTitle.textColor 51 | lblTitle.textColor = UIColor.white 52 | btnClose.tintColor = .white 53 | } 54 | 55 | override var preferredStatusBarStyle: UIStatusBarStyle { 56 | return view.backgroundColor == UIColor.white ? .default : .lightContent 57 | } 58 | 59 | @objc func btnClosePressed() { 60 | navigationController?.popToRootViewController(animated: true) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Menu/CBMenuController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBMenuController.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 21/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private class CBMenuListButton: UIButton { 12 | var menuItem: CBTabMenuItem? { 13 | didSet { 14 | if let attributedTitle = menuItem?.attributedTitle { 15 | setAttributedTitle(attributedTitle, for: .normal) 16 | } else if let title = menuItem?.title { 17 | setAttributedTitle(nil, for: .normal) 18 | setTitle(title, for: .normal) 19 | } else { 20 | setAttributedTitle(nil, for: .normal) 21 | setTitle(nil, for: .normal) 22 | } 23 | } 24 | } 25 | } 26 | 27 | public class CBMenuController: UIViewController { 28 | 29 | @IBOutlet weak var dismissButton: UIButton! 30 | @IBOutlet weak var menuContainer: UIView! 31 | @IBOutlet private weak var menuStack: UIStackView! 32 | var menu: CBTabMenu? { 33 | didSet { 34 | loadMenu() 35 | } 36 | } 37 | 38 | @IBAction func btnDismissPressed(_ sender: AnyObject) { 39 | dismiss(animated: true, completion: nil) 40 | } 41 | 42 | override public func viewDidLoad() { 43 | super.viewDidLoad() 44 | loadMenu() 45 | } 46 | 47 | override public func viewDidLayoutSubviews() { 48 | super.viewDidLayoutSubviews() 49 | dismissButton.layer.cornerRadius = min(dismissButton.frame.height/2.0, dismissButton.frame.width/2.0) 50 | } 51 | 52 | private func loadMenu() { 53 | guard let menu = menu, isViewLoaded else { return } 54 | view.backgroundColor = menu.menuColor 55 | let dismissImage = UIImage(named: "btnClose", 56 | in: Bundle.resourseBundle, 57 | compatibleWith: nil) 58 | dismissButton.backgroundColor = .white 59 | dismissButton.tintColor = menu.menuColor 60 | dismissButton.setImage(dismissImage?.withRenderingMode(.alwaysTemplate), for: .normal) 61 | for menuItem in menu.items { 62 | let button = CBMenuListButton(type: .custom) 63 | button.titleLabel?.font = UIFont.systemFont(ofSize: 17) 64 | button.setTitleColor(.white, for: .normal) 65 | button.menuItem = menuItem 66 | menuStack.addArrangedSubview(button) 67 | button.addTarget(self, action: #selector(btnMenuPressed), for: .touchUpInside) 68 | button.heightAnchor.constraint(greaterThanOrEqualToConstant: 50.0).isActive = true 69 | } 70 | } 71 | 72 | @objc private func btnMenuPressed(sender: CBMenuListButton) { 73 | if let item = sender.menuItem { 74 | menu?.callback(self, item) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Menu/CBMenuDismissAnimationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBMenuDismissAnimationController.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 22/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | 11 | class CBMenuDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning { 12 | 13 | private let duration: Double = 0.25 14 | weak var menuButton: UIView? 15 | 16 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 17 | return duration 18 | } 19 | 20 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 21 | guard let fromView = transitionContext.view(forKey: .from), 22 | let toView = transitionContext.view(forKey: .to) else { return } 23 | 24 | let fromViewController = transitionContext.viewController(forKey: .from) as? CBMenuController 25 | let container = transitionContext.containerView 26 | let animationDismissButton = fromViewController?.dismissButton?.snapshotView(afterScreenUpdates: false) 27 | let animationPresentButton = menuButton?.snapshotView(afterScreenUpdates: true) 28 | var targetFrame: CGRect? 29 | 30 | container.insertSubview(toView, at: 0) 31 | if let menuButton = menuButton, 32 | let presentButton = animationPresentButton, 33 | let dismissButton = animationDismissButton, 34 | let sourceFrame = fromViewController?.dismissButton?.frame { 35 | 36 | targetFrame = fromView.convert(menuButton.frame, from: menuButton.superview) 37 | container.addSubview(presentButton) 38 | container.addSubview(dismissButton) 39 | fromViewController?.dismissButton?.isHidden = true 40 | presentButton.frame = sourceFrame 41 | dismissButton.frame = sourceFrame 42 | } 43 | menuButton?.isHidden = true 44 | UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: { 45 | fromView.alpha = 0 46 | if let targetFrame = targetFrame { 47 | animationPresentButton?.frame = targetFrame 48 | animationDismissButton?.frame = targetFrame 49 | animationDismissButton?.alpha = 0 50 | } 51 | }) { (_) in 52 | self.menuButton?.isHidden = false 53 | animationPresentButton?.removeFromSuperview() 54 | animationDismissButton?.removeFromSuperview() 55 | transitionContext.completeTransition(true) 56 | } 57 | } 58 | 59 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 60 | if let completion: () -> Void = anim.value(forKey: "completion") as? (() -> Void) { 61 | completion() 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CBTabBarController 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/Cuberto/cb-tabbar/master/LICENSE) 4 | [![CocoaPods](https://img.shields.io/cocoapods/v/CBTabBarController.svg)](http://cocoapods.org/pods/CBTabBarController) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Cuberto/cb-tabbar) 6 | [![Swift 4.0](https://img.shields.io/badge/Swift-4.2-green.svg?style=flat)](https://developer.apple.com/swift/) 7 | 8 | ![Animation](https://raw.githubusercontent.com/Cuberto/cb-tabbar/master/Screenshots/gooey.gif) 9 | 10 | ## Example 11 | 12 | To run the example project, clone the repo, and run `ExampleApp` scheme from CBTabBar.xcodeproj 13 | 14 | ## Requirements 15 | 16 | - iOS 10.0+ 17 | - Xcode 9 18 | 19 | ## Installation 20 | 21 | ### CocoaPods 22 | To install CBTabBarController add the following line to your Podfile: 23 | ```ruby 24 | pod 'CBTabBarController' 25 | ``` 26 | Then run `pod install`. 27 | 28 | ### Carthage 29 | 30 | Make the following entry in your Cartfile: 31 | 32 | ``` 33 | github "Cuberto/cb-tabbar" 34 | ``` 35 | 36 | Then run `carthage update`. 37 | 38 | If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained [over at Carthage](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application). 39 | 40 | ### Manual 41 | 42 | Add CBTabBarController folder to your project 43 | 44 | ## Usage 45 | 46 | ### With Storyboard 47 | 48 | 1. Create a new UITabBarController in your storyboard or nib. 49 | 50 | 2. Set the class of the UITabBarController to `CBTabBarController` in your Storyboard or nib. 51 | 52 | 3. Add a custom image icon and title for UITabBarItem of each child ViewContrroller 53 | 54 | 55 | ### Without Storyboard 56 | 57 | 1. Import CBTabBarController 58 | 2. Instantiate `CBTabBarController` 59 | 3. Add some child controllers and don't forget to set them tabBar items with title and image 60 | 61 | ## Styling 62 | 63 | Set appropriate style for tab bar using `style` property of `CBTabbarController` instance. 64 | You can subclass `UITabBarItem` and conform to `CBExtendedTabItem` protocol to define custom title style and tint for tab buttons. 65 | 66 | Styles supported at the moment: 67 | 68 | #### Gooey with menu: 69 | 70 | ![Animation](https://raw.githubusercontent.com/Cuberto/cb-tabbar/master/Screenshots/gooey.gif) 71 | 72 | Requies configured `CBTabMenu` instance as paramenter (see example). Any object conforming to `CBTabMenuItem` can be used as menu item 73 | 74 | #### Flashy: 75 | 76 | ![Animation](https://raw.githubusercontent.com/Cuberto/cb-tabbar/master/Screenshots/flashy.gif) 77 | 78 | #### Fade (default): 79 | 80 | Same as gooey but without menu button 81 | 82 | ## Author 83 | 84 | Cuberto Design, info@cuberto.com 85 | 86 | ## License 87 | 88 | CB 89 | TabBarController is available under the MIT license. See the LICENSE file for more info. 90 | -------------------------------------------------------------------------------- /CBTabBar.xcodeproj/xcshareddata/xcschemes/CBFlashyTabBarController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/CBTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBFlashyTabBarController.swift 3 | // CBFlashyTabBarController 4 | // 5 | // Created by Anton Skopin on 28/11/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class CBTabBarController: UITabBarController { 12 | 13 | fileprivate var shouldSelectOnTabBar = true 14 | 15 | public var style: CBTabBarStyle = .fade { 16 | didSet { 17 | updateTabBarStyle() 18 | } 19 | } 20 | 21 | open override var selectedViewController: UIViewController? { 22 | willSet { 23 | guard shouldSelectOnTabBar, 24 | let newValue = newValue else { 25 | shouldSelectOnTabBar = true 26 | return 27 | } 28 | guard let tabBar = tabBar as? CBTabBar, let index = viewControllers?.index(of: newValue) else { 29 | return 30 | } 31 | tabBar.select(itemAt: index, animated: false) 32 | } 33 | } 34 | 35 | open override var selectedIndex: Int { 36 | willSet { 37 | guard shouldSelectOnTabBar else { 38 | shouldSelectOnTabBar = true 39 | return 40 | } 41 | guard let tabBar = tabBar as? CBTabBar else { 42 | return 43 | } 44 | tabBar.select(itemAt: selectedIndex, animated: false) 45 | } 46 | } 47 | 48 | open override func viewDidLoad() { 49 | super.viewDidLoad() 50 | defer { 51 | updateTabBarStyle() 52 | } 53 | guard self.tabBar as? CBTabBar == nil else { 54 | return 55 | } 56 | let tabBar = CBTabBar() 57 | if let barTint = self.tabBar.barTintColor { 58 | tabBar.barTintColor = barTint 59 | } 60 | self.setValue(tabBar, forKey: "tabBar") 61 | } 62 | 63 | private func updateTabBarStyle() { 64 | guard let tabBar = tabBar as? CBTabBar else { 65 | return 66 | } 67 | style.configure(tabBar: tabBar, with: self) 68 | } 69 | 70 | open override func viewDidAppear(_ animated: Bool) { 71 | super.viewDidAppear(animated) 72 | } 73 | 74 | open var barHeight: CGFloat { 75 | get { 76 | return (tabBar as? CBTabBar)?.barHeight ?? tabBar.frame.height 77 | } 78 | set { 79 | (tabBar as? CBTabBar)?.barHeight = newValue 80 | self.setValue(tabBar, forKey: "tabBar") 81 | } 82 | } 83 | 84 | open override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { 85 | guard let idx = tabBar.items?.index(of: item) else { 86 | return 87 | } 88 | if let controller = viewControllers?[idx] { 89 | shouldSelectOnTabBar = false 90 | selectedIndex = idx 91 | delegate?.tabBarController?(self, didSelect: controller) 92 | } 93 | } 94 | 95 | private func updateTabBarFrame() { 96 | var tabFrame = tabBar.frame 97 | if #available(iOS 11.0, *) { 98 | tabFrame.size.height = barHeight + view.safeAreaInsets.bottom 99 | } else { 100 | tabFrame.size.height = barHeight 101 | } 102 | tabFrame.origin.y = self.view.frame.size.height - tabFrame.size.height 103 | tabBar.frame = tabFrame 104 | tabBar.setNeedsLayout() 105 | } 106 | 107 | @available(iOS 11.0, *) 108 | open override func viewSafeAreaInsetsDidChange() { 109 | super.viewSafeAreaInsetsDidChange() 110 | updateTabBarFrame() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Flashy/Animations/CBFlashyTabItemBasicAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBTabItemBasicAnimation.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 28/11/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol CBFlashyTabItemBasicAnimation: CBTabItemAnimation { 12 | 13 | func imageOffsetAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation 14 | func imageMaskAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation 15 | func labelOffsetAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation 16 | func labelMaskAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation 17 | func dotScaleAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation 18 | 19 | var duration: Double { get } 20 | 21 | } 22 | 23 | extension CBFlashyTabItemBasicAnimation { 24 | 25 | func playAnimation(forTabBarItem item: CBTabBarButton, completion: (() -> Void)? = nil) { 26 | guard let item = item as? CBFlashyTabBarButton else { 27 | fatalError("This animation can be used only with CBFlashyTabBarButton") 28 | } 29 | let animateImageOffset = imageOffsetAnimation(forTabBarItem: item) 30 | let animateImageMask = imageMaskAnimation(forTabBarItem: item) 31 | animateImageMask.isRemovedOnCompletion = false 32 | 33 | let animateLabelOffset = labelOffsetAnimation(forTabBarItem: item) 34 | let animateLabelMask = labelMaskAnimation(forTabBarItem: item) 35 | animateLabelMask.isRemovedOnCompletion = false 36 | 37 | let dotAnimation = dotScaleAnimation(forTabBarItem: item) 38 | 39 | let mainAnimations: [CAAnimation] = [animateImageOffset, 40 | animateImageMask, 41 | animateLabelOffset, 42 | animateLabelMask, 43 | dotAnimation] 44 | mainAnimations.forEach { (animation) in 45 | animation.duration = duration 46 | animation.isRemovedOnCompletion = false 47 | animation.fillMode = .forwards 48 | } 49 | if item.tabImage.layer.mask == nil { 50 | let imageMask = CAShapeLayer() 51 | imageMask.frame = item.tabImage.bounds 52 | item.tabImage.layer.mask = imageMask 53 | } 54 | 55 | if let animateImageMask = animateImageMask as? CAKeyframeAnimation, 56 | let imageMask = item.tabImage.layer.mask as? CAShapeLayer { 57 | imageMask.path = (animateImageMask.values as? [CGPath])?.last 58 | } 59 | 60 | if item.tabLabel.layer.mask == nil { 61 | let labelMask = CAShapeLayer() 62 | labelMask.frame = item.tabLabel.bounds 63 | item.tabLabel.layer.mask = labelMask 64 | } 65 | 66 | if let animateLabelMask = animateLabelMask as? CAKeyframeAnimation, 67 | let labelMask = item.tabLabel.layer.mask as? CAShapeLayer { 68 | labelMask.path = (animateLabelMask.values as? [CGPath])?.last 69 | } 70 | 71 | let timing = CAMediaTimingFunction(name: .easeOut) 72 | CATransaction.begin() 73 | CATransaction.setAnimationTimingFunction(timing) 74 | CATransaction.setCompletionBlock { completion?() } 75 | item.tabImage.layer.add(animateImageOffset, forKey: "offset") 76 | item.tabLabel.layer.add(animateLabelOffset, forKey: "offset") 77 | item.dotView.layer.add(dotAnimation, forKey: "scale") 78 | item.tabImage.layer.mask?.add(animateImageMask, forKey: "path") 79 | item.tabLabel.layer.mask?.add(animateLabelMask, forKey: "path") 80 | CATransaction.commit() 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CBFlashyTabBarController 4 | // 5 | // Created by askopin@gmail.com on 11/29/2018. 6 | // Copyright (c) 2018 askopin@gmail.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CBTabBarController 11 | 12 | extension String: CBTabMenuItem { 13 | public var title: String? { return self } 14 | public var attributedTitle: NSAttributedString? { return nil } 15 | } 16 | 17 | class SampleTabItem: UITabBarItem, CBExtendedTabItem { 18 | 19 | public var attributedTitle: NSAttributedString? { 20 | guard let title = title else { return nil } 21 | return NSAttributedString(string: title, attributes: [NSAttributedString.Key.foregroundColor: UIColor.red, 22 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15)]) 23 | } 24 | } 25 | 26 | class ViewController: UIViewController { 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | self.navigationController?.isNavigationBarHidden = true 31 | } 32 | 33 | func createSampleTabController() -> CBTabBarController { 34 | let eventsVC = CBSampleViewController() 35 | eventsVC.tabBarItem = UITabBarItem(title: "Events", image: #imageLiteral(resourceName: "Events"), tag: 0) 36 | let searchVC = CBSampleViewController() 37 | searchVC.tabBarItem = UITabBarItem(title: "Search", image: #imageLiteral(resourceName: "Search"), tag: 0) 38 | let activityVC = CBSampleViewController() 39 | activityVC.tabBarItem = UITabBarItem(title: "Activity", image: #imageLiteral(resourceName: "Highlights"), tag: 0) 40 | let settingsVC = CBSampleViewController() 41 | settingsVC.tabBarItem = SampleTabItem(title: "Settings", image: #imageLiteral(resourceName: "Settings"), tag: 0) 42 | settingsVC.tabBarItem?.badgeColor = .red 43 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 44 | settingsVC.tabBarItem?.badgeValue = "20" 45 | } 46 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 47 | settingsVC.tabBarItem?.badgeValue = "200" 48 | } 49 | settingsVC.tabBarItem?.badgeValue = "1" 50 | settingsVC.inverseColor() 51 | 52 | let tabBarController = CBTabBarController() 53 | tabBarController.viewControllers = [eventsVC, searchVC, activityVC, settingsVC] 54 | return tabBarController 55 | } 56 | 57 | @IBAction func btnFlashyPressed(_ sender: AnyObject) { 58 | let tabBarController = createSampleTabController() 59 | tabBarController.style = .flashy(config: nil) 60 | self.navigationController?.pushViewController(tabBarController, animated: true) 61 | } 62 | 63 | @IBAction func btnGooeyPressed(_ sender: AnyObject) { 64 | let tabBarController = createSampleTabController() 65 | let menuEntries = ["Reminder", "Camera", "Attachment", "Text Note"] 66 | let menu = CBTabMenu(menuButtonIndex: 2, 67 | menuColor: #colorLiteral(red: 0.368781209, green: 0.6813176274, blue: 1, alpha: 1), 68 | items: menuEntries, 69 | icon: nil, 70 | callback: { controller, item in 71 | controller.dismiss(animated: true, completion: { 72 | print("\(item) selected") 73 | }) 74 | }) 75 | tabBarController.style = .gooey(menu: menu) 76 | 77 | self.navigationController?.pushViewController(tabBarController, animated: true) 78 | } 79 | 80 | override var preferredStatusBarStyle: UIStatusBarStyle { 81 | return .lightContent 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Flashy/Animations/CBTabItemSelectAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBTabItemSelectAnimation.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 28/11/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CBFlashyTabItemSelectAnimation: CBFlashyTabItemBasicAnimation { 12 | 13 | let duration: Double 14 | 15 | func imageOffsetAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 16 | let imageOffsetAnimation = CABasicAnimation(keyPath: "transform.translation.y") 17 | imageOffsetAnimation.fromValue = 0.0 18 | imageOffsetAnimation.toValue = -((item.bounds.height - item.tabImage.frame.height) / 2.0 - 5.0) 19 | return imageOffsetAnimation 20 | } 21 | 22 | func imageMaskAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 23 | let maskBounds = item.tabImage.bounds 24 | 25 | let path1 = CGMutablePath() 26 | path1.move(to: .zero) 27 | path1.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 28 | path1.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height)) 29 | path1.addLine(to: CGPoint(x: 0, y: maskBounds.height)) 30 | path1.closeSubpath() 31 | 32 | let path2 = CGMutablePath() 33 | path2.move(to: .zero) 34 | path2.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 35 | path2.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.9)) 36 | path2.addLine(to: CGPoint(x: 0, y: maskBounds.height * 0.6)) 37 | path2.closeSubpath() 38 | 39 | let path3 = CGMutablePath() 40 | path3.move(to: .zero) 41 | path3.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 42 | path3.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.25)) 43 | path3.closeSubpath() 44 | 45 | let path4 = CGMutablePath() 46 | path4.move(to: CGPoint(x: maskBounds.width * 0.8, y: 0)) 47 | path4.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 48 | path4.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.15)) 49 | path4.closeSubpath() 50 | 51 | let path5 = CGMutablePath() 52 | 53 | let maskAnimation = CAKeyframeAnimation(keyPath: "path") 54 | maskAnimation.values = [path1, path2, path3, path4, path5] 55 | maskAnimation.keyTimes = [0, 0.1, 0.5, 0.9, 1.0] 56 | 57 | return maskAnimation 58 | } 59 | 60 | func labelOffsetAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 61 | let labelOffsetAnimation = CABasicAnimation(keyPath: "transform.translation.y") 62 | labelOffsetAnimation.fromValue = (item.bounds.height - item.tabLabel.frame.height)/2.0 - 5.0 63 | labelOffsetAnimation.toValue = 0.0 64 | return labelOffsetAnimation 65 | } 66 | 67 | func labelMaskAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 68 | let maskBounds = item.tabLabel.bounds 69 | 70 | let path1 = CGMutablePath() 71 | path1.move(to: .zero) 72 | 73 | let path2 = CGMutablePath() 74 | path2.move(to: .zero) 75 | path2.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 76 | path2.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.2)) 77 | path2.closeSubpath() 78 | 79 | let path3 = CGMutablePath() 80 | path3.move(to: .zero) 81 | path3.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 82 | path3.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.5)) 83 | path3.addLine(to: CGPoint(x: 0, y: maskBounds.height * 0.1)) 84 | path3.closeSubpath() 85 | 86 | let path4 = CGMutablePath() 87 | path4.move(to: .zero) 88 | path4.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 89 | path4.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height)) 90 | path4.addLine(to: CGPoint(x: 0, y: maskBounds.height)) 91 | path4.closeSubpath() 92 | 93 | let maskAnimation = CAKeyframeAnimation(keyPath: "path") 94 | maskAnimation.values = [path1, path2, path3, path4] 95 | maskAnimation.keyTimes = [0, 0.1, 0.3, 1.0] 96 | 97 | return maskAnimation 98 | } 99 | 100 | func dotScaleAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 101 | let dotAnimation = CAKeyframeAnimation(keyPath: "transform.scale") 102 | dotAnimation.values = [0.0, 0.0, 1.0, 1.0] 103 | dotAnimation.keyTimes = [0, 0.2, 0.7, 1.0] 104 | return dotAnimation 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Flashy/Animations/CBTabItemDeselectAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBTabItemDeselectAnimation.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 28/11/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct CBFlashyTabItemDeselectAnimation: CBFlashyTabItemBasicAnimation { 12 | 13 | let duration: Double 14 | 15 | func imageOffsetAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 16 | let imageOffsetAnimation = CABasicAnimation(keyPath: "transform.translation.y") 17 | imageOffsetAnimation.fromValue = -((item.bounds.height - item.tabImage.frame.height) / 2.0 - 5.0) 18 | imageOffsetAnimation.toValue = 0.0 19 | return imageOffsetAnimation 20 | } 21 | 22 | func imageMaskAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 23 | let maskBounds = item.tabImage.bounds 24 | 25 | let path1 = CGMutablePath() 26 | 27 | let path2 = CGMutablePath() 28 | path2.move(to: CGPoint(x: maskBounds.width * 0.8, y: 0)) 29 | path2.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 30 | path2.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.15)) 31 | path2.closeSubpath() 32 | 33 | let path3 = CGMutablePath() 34 | path3.move(to: .zero) 35 | path3.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 36 | path3.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.25)) 37 | path3.closeSubpath() 38 | 39 | let path4 = CGMutablePath() 40 | path4.move(to: .zero) 41 | path4.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 42 | path4.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.9)) 43 | path4.addLine(to: CGPoint(x: 0, y: maskBounds.height * 0.6)) 44 | path4.closeSubpath() 45 | 46 | let path5 = CGMutablePath() 47 | path5.move(to: .zero) 48 | path5.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 49 | path5.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height)) 50 | path5.addLine(to: CGPoint(x: 0, y: maskBounds.height)) 51 | path5.closeSubpath() 52 | 53 | let maskAnimation = CAKeyframeAnimation(keyPath: "path") 54 | maskAnimation.values = [path1, path2, path3, path4, path5] 55 | maskAnimation.keyTimes = [0.0, 0.1, 0.3, 0.7, 1.0] 56 | 57 | return maskAnimation 58 | } 59 | 60 | func labelOffsetAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 61 | let labelOffsetAnimation = CABasicAnimation(keyPath: "transform.translation.y") 62 | labelOffsetAnimation.fromValue = 0.0 63 | labelOffsetAnimation.toValue = (item.bounds.height - item.tabLabel.frame.height)/2.0 - 5.0 64 | return labelOffsetAnimation 65 | } 66 | 67 | func labelMaskAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 68 | let maskBounds = item.tabLabel.bounds 69 | 70 | let path1 = CGMutablePath() 71 | path1.move(to: .zero) 72 | 73 | let path2 = CGMutablePath() 74 | path2.move(to: .zero) 75 | path2.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 76 | path2.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.2)) 77 | path2.closeSubpath() 78 | 79 | let path3 = CGMutablePath() 80 | path3.move(to: .zero) 81 | path3.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 82 | path3.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height * 0.5)) 83 | path3.addLine(to: CGPoint(x: 0, y: maskBounds.height * 0.1)) 84 | path3.closeSubpath() 85 | 86 | let path4 = CGMutablePath() 87 | path4.move(to: .zero) 88 | path4.addLine(to: CGPoint(x: maskBounds.width, y: 0)) 89 | path4.addLine(to: CGPoint(x: maskBounds.width, y: maskBounds.height)) 90 | path4.addLine(to: CGPoint(x: 0, y: maskBounds.height)) 91 | path4.closeSubpath() 92 | 93 | let maskAnimation = CAKeyframeAnimation(keyPath: "path") 94 | maskAnimation.values = [path1, path2, path3, path4].reversed() 95 | maskAnimation.keyTimes = [0.0, 0.5, 0.7, 1.0] 96 | 97 | return maskAnimation 98 | } 99 | 100 | func dotScaleAnimation(forTabBarItem item: CBFlashyTabBarButton) -> CAAnimation { 101 | let dotAnimation = CAKeyframeAnimation(keyPath: "transform.scale") 102 | dotAnimation.values = [1.0, 1.0, 0.0, 0.0] 103 | dotAnimation.keyTimes = [0, 0.2, 0.7, 1.0] 104 | return dotAnimation 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/CBBaseTabButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBBaseTabButton.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 20/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBBaseTabButton: UIButton, CBTabBarButtonProtocol { 12 | 13 | var badgeContainer = UIView() 14 | var badgeLabel = UILabel() 15 | var item: UITabBarItem? { 16 | set { 17 | guard let item = newValue else { return } 18 | _item = item 19 | } 20 | get { 21 | return _item 22 | } 23 | } 24 | 25 | @objc dynamic var _item: UITabBarItem = UITabBarItem() { 26 | didSet { 27 | didUpdateItem() 28 | } 29 | } 30 | 31 | var requiredSize: CGSize? { 32 | return nil 33 | } 34 | 35 | override init(frame: CGRect) { 36 | super.init(frame: frame) 37 | configure() 38 | addObservers() 39 | } 40 | 41 | required init?(coder aDecoder: NSCoder) { 42 | super.init(coder: aDecoder) 43 | configure() 44 | addObservers() 45 | } 46 | 47 | required init(item: UITabBarItem) { 48 | super.init(frame: .zero) 49 | configure() 50 | addObservers() 51 | self.item = item 52 | } 53 | 54 | deinit { 55 | removeObserver(self, forKeyPath: #keyPath(_item.badgeValue)) 56 | removeObserver(self, forKeyPath: #keyPath(_item.badgeColor)) 57 | } 58 | 59 | func addObservers() { 60 | addObserver(self, forKeyPath: #keyPath(_item.badgeValue), options: [.initial, .new], context: nil) 61 | addObserver(self, forKeyPath: #keyPath(_item.badgeColor), options: [.initial, .new], context: nil) 62 | } 63 | 64 | func configure() { 65 | badgeContainer.isHidden = true 66 | badgeContainer.addSubview(badgeLabel) 67 | addSubview(badgeContainer) 68 | badgeLabel.font = UIFont.systemFont(ofSize: 12, weight: .semibold) 69 | badgeLabel.textColor = .white 70 | } 71 | 72 | override var tintColor: UIColor! { 73 | didSet { 74 | badgeContainer.backgroundColor = item?.badgeColor ?? tintColor 75 | } 76 | } 77 | 78 | override func layoutSubviews() { 79 | super.layoutSubviews() 80 | let badgeMargin: CGFloat = 3 81 | badgeLabel.sizeToFit() 82 | let badgeWidth = max(20, min(badgeLabel.frame.width + 2 * badgeMargin, bounds.width - 2 * badgeMargin)) 83 | badgeContainer.frame = CGRect(x: min(bounds.width - badgeWidth - badgeMargin, 84 | max(titleLabel?.frame.maxX ?? 0, imageView?.frame.maxX ?? 0)), 85 | y: badgeMargin, 86 | width: badgeWidth, 87 | height: 20) 88 | let lblWidth = min(badgeLabel.frame.width, badgeWidth - 2 * badgeMargin) 89 | badgeLabel.frame = CGRect(x: (badgeContainer.frame.width - lblWidth)/2.0, 90 | y: badgeMargin, 91 | width: lblWidth, 92 | height: badgeContainer.frame.height - 2 * badgeMargin) 93 | badgeContainer.layer.cornerRadius = badgeContainer.frame.height/2.0 94 | } 95 | 96 | private var _isSelected: Bool = false 97 | override var isSelected: Bool { 98 | get { 99 | return _isSelected 100 | } 101 | set { 102 | guard newValue != _isSelected else { 103 | return 104 | } 105 | if newValue { 106 | select(animated: false) 107 | } else { 108 | deselect(animated: false) 109 | } 110 | } 111 | } 112 | 113 | func setSelected(_ selected: Bool, animated: Bool) { 114 | if selected { 115 | select(animated: animated) 116 | } else { 117 | deselect(animated: animated) 118 | } 119 | } 120 | 121 | func select(animated: Bool = true) { 122 | } 123 | 124 | func deselect(animated: Bool = true) { 125 | } 126 | 127 | func didUpdateItem() { 128 | badgeContainer.backgroundColor = item?.badgeColor ?? tintColor 129 | badgeLabel.text = item?.badgeValue 130 | badgeContainer.isHidden = item?.badgeValue == nil 131 | setNeedsLayout() 132 | } 133 | 134 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 135 | switch keyPath { 136 | case #keyPath(_item.badgeValue), #keyPath(_item.badgeColor): 137 | didUpdateItem() 138 | default: 139 | break 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Menu/CBMenuPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBMenuPresenter.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 21/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBMenuPresenter: NSObject { 12 | 13 | private let menu: CBTabMenu 14 | 15 | init(menu: CBTabMenu) { 16 | self.menu = menu 17 | } 18 | 19 | weak var presentationController: UIViewController? 20 | weak var menuButton: CBMenuButton? { 21 | didSet { 22 | guard let button = menuButton else { return } 23 | let panGesture = UIPanGestureRecognizer(target: self, action: #selector(menuBtnPan)) 24 | button.addGestureRecognizer(panGesture) 25 | button.addTarget(self, action: #selector(menuBtnPressed), for: .touchUpInside) 26 | } 27 | } 28 | 29 | var presentAnimationController: CBMenuPresentAnimationController? 30 | var presentInteractionController: CBMenuPresentAnimationController? 31 | var dismissAnimationController: CBMenuDismissAnimationController? 32 | 33 | @objc func menuBtnPressed() { 34 | guard let menuController = createMenuController() else { 35 | return 36 | } 37 | configureAnimationControllers() 38 | presentInteractionController = nil 39 | menuController.loadViewIfNeeded() 40 | menuController.transitioningDelegate = self 41 | presentationController?.present(menuController, animated: true, completion: nil) 42 | } 43 | 44 | private func configureAnimationControllers() { 45 | presentAnimationController = CBMenuPresentAnimationController() 46 | presentAnimationController?.menuButton = menuButton 47 | presentAnimationController?.maxOffset = maxOffset 48 | dismissAnimationController = CBMenuDismissAnimationController() 49 | dismissAnimationController?.menuButton = menuButton 50 | } 51 | 52 | @objc func menuBtnPan(sender: UIPanGestureRecognizer) { 53 | switch sender.state { 54 | case .began: 55 | didBeganPan(withGesture: sender) 56 | case .changed: 57 | didChangePan(withGesture: sender) 58 | default: 59 | didEndPan(withGesture: sender) 60 | } 61 | } 62 | 63 | private func createMenuController() -> UIViewController? { 64 | let storyboard = UIStoryboard(name: "CBMenuController", bundle: Bundle.resourseBundle) 65 | let controller = storyboard.instantiateInitialViewController() as? CBMenuController 66 | controller?.menu = menu 67 | return controller 68 | } 69 | 70 | private var maxOffset: CGFloat = 180 71 | private var startY: CGFloat? 72 | private var lastProgress: CGFloat? 73 | private func didBeganPan(withGesture gesture: UIPanGestureRecognizer) { 74 | guard let container = gesture.view?.superview else { 75 | return 76 | } 77 | startY = gesture.location(in: container).y 78 | 79 | guard let menuController = createMenuController() else { 80 | return 81 | } 82 | configureAnimationControllers() 83 | presentInteractionController = presentAnimationController 84 | menuController.loadViewIfNeeded() 85 | menuController.transitioningDelegate = self 86 | presentationController?.present(menuController, animated: true, completion: nil) 87 | } 88 | 89 | private func didChangePan(withGesture gesture: UIPanGestureRecognizer) { 90 | guard let container = gesture.view?.superview else { 91 | return 92 | } 93 | let currentY = gesture.location(in: container).y 94 | let diff = max(0, (startY ?? 0) - currentY) 95 | let progress = min(1.0, diff / (maxOffset * 2)) 96 | lastProgress = progress 97 | if diff >= maxOffset { 98 | gesture.isEnabled = false 99 | presentAnimationController?.finishTransition() 100 | gesture.isEnabled = true 101 | return 102 | } 103 | presentAnimationController?.update(to: progress, offset: diff) 104 | } 105 | 106 | private func didEndPan(withGesture gesture: UIPanGestureRecognizer) { 107 | defer { 108 | startY = nil 109 | lastProgress = nil 110 | } 111 | guard let lastProgress = lastProgress else { 112 | return 113 | } 114 | if lastProgress > 0.4 { 115 | presentAnimationController?.finishTransition() 116 | } else { 117 | presentAnimationController?.cancelTransition() 118 | } 119 | } 120 | } 121 | 122 | extension CBMenuPresenter: UIViewControllerTransitioningDelegate { 123 | 124 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 125 | return presentAnimationController 126 | } 127 | 128 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 129 | return dismissAnimationController 130 | } 131 | 132 | 133 | func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 134 | return presentInteractionController 135 | } 136 | 137 | func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 138 | return nil 139 | } 140 | } 141 | 142 | 143 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/CBTabBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBFlashyTabBar.swift 3 | // CBFlashyTabBarController 4 | // 5 | // Created by Anton Skopin on 28/11/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class CBTabBar: UITabBar { 12 | 13 | private var buttons: [CBTabBarButton] = [] 14 | 15 | fileprivate var shouldSelectOnTabBar = true 16 | var buttonFactory: CBTabButtonFactory? { 17 | didSet { 18 | reloadViews() 19 | } 20 | } 21 | 22 | open override var selectedItem: UITabBarItem? { 23 | willSet { 24 | guard let newValue = newValue else { 25 | buttons.forEach { $0.setSelected(false, animated: false) } 26 | return 27 | } 28 | 29 | let btnItems: [UITabBarItem?] = buttons.map { $0.item } 30 | for (index, value) in btnItems.enumerated() { 31 | if value === newValue { 32 | select(itemAt: index, animated: false) 33 | } 34 | } 35 | } 36 | } 37 | 38 | open override var tintColor: UIColor! { 39 | didSet { 40 | buttons.forEach { button in 41 | if let item = button.item as? CBExtendedTabItem { 42 | button.tintColor = item.tintColor ?? tintColor 43 | } else { 44 | button.tintColor = tintColor 45 | } 46 | } 47 | } 48 | } 49 | 50 | var barHeight: CGFloat = 60 51 | 52 | override open func sizeThatFits(_ size: CGSize) -> CGSize { 53 | var sizeThatFits = super.sizeThatFits(size) 54 | sizeThatFits.height = barHeight 55 | if #available(iOS 11.0, *) { 56 | sizeThatFits.height = sizeThatFits.height + safeAreaInsets.bottom 57 | } 58 | return sizeThatFits 59 | } 60 | 61 | open override var items: [UITabBarItem]? { 62 | didSet { 63 | reloadViews() 64 | } 65 | } 66 | 67 | open override func setItems(_ items: [UITabBarItem]?, animated: Bool) { 68 | super.setItems(items, animated: animated) 69 | reloadViews() 70 | } 71 | 72 | open override func layoutSubviews() { 73 | super.layoutSubviews() 74 | 75 | let sizedButtons = buttons.filter { $0.requiredSize != nil } 76 | let minWidth = bounds.width / CGFloat(buttons.count) 77 | let predefinedWidth: CGFloat = sizedButtons.compactMap { $0.requiredSize?.width } 78 | .map { max(minWidth, $0) } 79 | .reduce(0, +) 80 | 81 | let btnWidth = max(0, (bounds.width - predefinedWidth) / CGFloat(buttons.count - sizedButtons.count)) 82 | let bottomOffset: CGFloat 83 | if #available(iOS 11.0, *) { 84 | bottomOffset = safeAreaInsets.bottom 85 | } else { 86 | bottomOffset = 0 87 | } 88 | let btnHeight = bounds.height - bottomOffset 89 | 90 | var lastX: CGFloat = 0 91 | for button in buttons { 92 | var padding: CGFloat = 0 93 | if let btnSize = button.requiredSize { 94 | let btnY = (btnHeight - btnSize.height)/2.0 95 | padding = max(0, (minWidth - btnSize.width)/2.0) 96 | button.frame = CGRect(origin: CGPoint(x: lastX + padding, y: btnY), 97 | size: btnSize) 98 | } else { 99 | button.frame = CGRect(x: lastX, y: 0, width: btnWidth, height: btnHeight) 100 | } 101 | lastX = button.frame.maxX + padding 102 | button.setNeedsLayout() 103 | } 104 | } 105 | 106 | private func reloadViews() { 107 | subviews.filter { String(describing: type(of: $0)) == "UITabBarButton" }.forEach { $0.removeFromSuperview() } 108 | buttons.forEach { $0.removeFromSuperview()} 109 | buttons = buttonFactory?.buttons(forItems: items ?? []) ?? [] 110 | buttons.forEach { button in 111 | if let item = button.item as? CBExtendedTabItem { 112 | button.tintColor = item.tintColor ?? tintColor 113 | } else { 114 | button.tintColor = tintColor 115 | } 116 | if selectedItem != nil && button.item === selectedItem { 117 | button.setSelected(true, animated: false) 118 | } 119 | button.addTarget(self, action: #selector(btnPressed), for: .touchUpInside) 120 | addSubview(button) 121 | } 122 | setNeedsLayout() 123 | } 124 | 125 | @objc private func btnPressed(sender: UIControl) { 126 | guard let sender = sender as? CBTabBarButton else { 127 | return 128 | } 129 | 130 | buttons.forEach { (button) in 131 | guard button !== sender else { 132 | return 133 | } 134 | button.setSelected(false, animated: true) 135 | } 136 | sender.setSelected(true, animated: true) 137 | if let item = sender.item, 138 | let items = items, 139 | items.contains(item) { 140 | delegate?.tabBar?(self, didSelect: item) 141 | } 142 | } 143 | 144 | func select(itemAt index: Int, animated: Bool = false) { 145 | guard index < buttons.count else { 146 | return 147 | } 148 | let selectedbutton = buttons[index] 149 | buttons.forEach { (button) in 150 | guard button !== selectedbutton else { 151 | return 152 | } 153 | button.setSelected(false, animated: false) 154 | } 155 | selectedbutton.setSelected(true, animated: false) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /CBTabBarController/Assets/Storyboard/CBMenuController.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 54 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Flashy/CBFlashyTabBarButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBTabBarButton.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 28/11/2018. 6 | // Copyright © 2018 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBFlashyTabBarButton: CBTabBarButton { 12 | 13 | var tabImage = UIImageView() 14 | var tabLabel = UILabel() 15 | var badgeContainer = UIView() 16 | var badgeLabel = UILabel() 17 | var dotView = UIView() 18 | 19 | var selectAnimation: CBTabItemAnimation? 20 | var deselectAnimation: CBTabItemAnimation? 21 | var config: CBFlashyTabBarConfig? 22 | 23 | private var _isSelected: Bool = false 24 | override var isSelected: Bool { 25 | get { 26 | return _isSelected 27 | } 28 | set { 29 | guard newValue != _isSelected else { 30 | return 31 | } 32 | if newValue { 33 | select(animated: false) 34 | } else { 35 | deselect(animated: false) 36 | } 37 | } 38 | } 39 | 40 | override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | configureSubviews() 43 | addObservers() 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | super.init(coder: aDecoder) 48 | configureSubviews() 49 | addObservers() 50 | } 51 | 52 | required init(item: UITabBarItem) { 53 | super.init(frame: .zero) 54 | addObservers() 55 | configureSubviews() 56 | self.item = item 57 | } 58 | 59 | deinit { 60 | removeObserver(self, forKeyPath: #keyPath(_item.badgeValue)) 61 | removeObserver(self, forKeyPath: #keyPath(_item.badgeColor)) 62 | } 63 | 64 | private func addObservers() { 65 | addObserver(self, forKeyPath: #keyPath(_item.badgeValue), options: [.initial, .new], context: nil) 66 | addObserver(self, forKeyPath: #keyPath(_item.badgeColor), options: [.initial, .new], context: nil) 67 | } 68 | 69 | var item: UITabBarItem? { 70 | set { 71 | guard let item = newValue else { return } 72 | _item = item 73 | } 74 | get { 75 | return _item 76 | } 77 | } 78 | 79 | @objc dynamic var _item: UITabBarItem = UITabBarItem(){ 80 | didSet { 81 | didUpdateItem() 82 | } 83 | } 84 | 85 | override var tintColor: UIColor! { 86 | didSet { 87 | tabImage.tintColor = tintColor.withAlphaComponent(config?.deselectedOpacity ?? 0.4) 88 | tabLabel.attributedText = (item as? CBExtendedTabItem)?.attributedTitle ?? attributedText(fortitle: item?.title) 89 | dotView.backgroundColor = tintColor 90 | badgeContainer.backgroundColor = item?.badgeColor ?? tintColor 91 | } 92 | } 93 | 94 | private func attributedText(fortitle title: String?) -> NSAttributedString { 95 | var attrs: [NSAttributedString.Key: Any] = [:] 96 | attrs[.kern] = -0.2 97 | attrs[.foregroundColor] = tintColor 98 | attrs[.font] = UIFont.systemFont(ofSize: 12, weight: .semibold) 99 | return NSAttributedString(string: title ?? "", attributes: attrs) 100 | } 101 | 102 | private func configureSubviews() { 103 | addSubview(tabLabel) 104 | addSubview(tabImage) 105 | addSubview(dotView) 106 | badgeContainer.addSubview(badgeLabel) 107 | addSubview(badgeContainer) 108 | tabLabel.numberOfLines = 2 109 | tabLabel.font = UIFont.systemFont(ofSize: 12, weight: .semibold) 110 | tabLabel.isHidden = true 111 | tabImage.contentMode = .center 112 | let dotSize: CGFloat = 5.0 113 | dotView.frame = CGRect(origin: .zero, size: CGSize(width: dotSize, height: dotSize)) 114 | dotView.layer.cornerRadius = dotSize / 2.0 115 | dotView.layer.shouldRasterize = true 116 | dotView.layer.rasterizationScale = UIScreen.main.scale 117 | dotView.isHidden = true 118 | badgeContainer.isHidden = true 119 | badgeLabel.font = UIFont.systemFont(ofSize: 12, weight: .semibold) 120 | badgeLabel.textColor = .white 121 | } 122 | 123 | override func layoutSubviews() { 124 | super.layoutSubviews() 125 | tabImage.sizeToFit() 126 | tabImage.center = CGPoint(x: bounds.width/2.0, y: bounds.height/2.0) 127 | tabLabel.frame = bounds 128 | tabLabel.sizeToFit() 129 | tabLabel.center = tabImage.center 130 | let dotX: CGFloat = tabImage.center.x - dotView.frame.width/2.0 131 | let dotY: CGFloat = tabLabel.frame.maxY + 13.0 132 | dotView.frame = CGRect(origin: CGPoint(x: dotX, y: dotY), size: dotView.frame.size) 133 | 134 | let badgeMargin: CGFloat = 3 135 | badgeLabel.sizeToFit() 136 | let badgeWidth = max(20, min(badgeLabel.frame.width + 2 * badgeMargin, bounds.width - 2 * badgeMargin)) 137 | badgeContainer.frame = CGRect(x: min(bounds.width - badgeWidth - badgeMargin, tabLabel.frame.maxX), 138 | y: badgeMargin, 139 | width: badgeWidth, 140 | height: 20) 141 | let lblWidth = min(badgeLabel.frame.width, badgeWidth - 2 * badgeMargin) 142 | badgeLabel.frame = CGRect(x: (badgeContainer.frame.width - lblWidth)/2.0, 143 | y: badgeMargin, 144 | width: lblWidth, 145 | height: badgeContainer.frame.height - 2 * badgeMargin) 146 | badgeContainer.layer.cornerRadius = badgeContainer.frame.height/2.0 147 | } 148 | 149 | func setSelected(_ selected: Bool, animated: Bool) { 150 | if selected { 151 | select(animated: animated) 152 | } else { 153 | deselect(animated: animated) 154 | } 155 | } 156 | 157 | func select(animated: Bool = true) { 158 | guard !_isSelected else { 159 | return 160 | } 161 | _isSelected = true 162 | guard animated, let selectAnimation = selectAnimation else { 163 | tabLabel.isHidden = false 164 | tabImage.isHidden = true 165 | dotView.isHidden = false 166 | return 167 | } 168 | tabLabel.isHidden = false 169 | dotView.isHidden = false 170 | selectAnimation.playAnimation(forTabBarItem: self) {[weak self] in 171 | if self?._isSelected ?? false { 172 | self?.tabImage.isHidden = true 173 | } 174 | } 175 | } 176 | 177 | func deselect(animated: Bool = true) { 178 | guard _isSelected else { 179 | return 180 | } 181 | _isSelected = false 182 | guard animated, let deselectAnimation = deselectAnimation else { 183 | tabLabel.isHidden = true 184 | tabImage.isHidden = false 185 | dotView.isHidden = true 186 | return 187 | } 188 | tabImage.isHidden = false 189 | deselectAnimation.playAnimation(forTabBarItem: self) {[weak self] in 190 | if !(self?._isSelected ?? true) { 191 | self?.tabLabel.isHidden = true 192 | self?.dotView.isHidden = true 193 | } 194 | } 195 | } 196 | 197 | private func didUpdateItem() { 198 | tabImage.image = item?.image?.withRenderingMode(.alwaysTemplate) 199 | tabLabel.attributedText = (item as? CBExtendedTabItem)?.attributedTitle ?? attributedText(fortitle: item?.title) 200 | badgeContainer.backgroundColor = item?.badgeColor ?? tintColor 201 | badgeLabel.text = item?.badgeValue 202 | badgeContainer.isHidden = item?.badgeValue == nil 203 | setNeedsLayout() 204 | } 205 | 206 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 207 | switch keyPath { 208 | case #keyPath(_item.badgeValue), #keyPath(_item.badgeColor): 209 | didUpdateItem() 210 | default: 211 | break 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /CBTabBarController/Classes/Menu/CBMenuPresentAnimationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CBMenuPresentAnimationController.swift 3 | // CBTabBarController 4 | // 5 | // Created by Anton Skopin on 21/03/2019. 6 | // Copyright © 2019 cuberto. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CBMenuPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning { 12 | 13 | var maxOffset: CGFloat = 0 14 | let offsetSpeed: CGFloat = 650 15 | private let duration: Double = 0.5 16 | private var interactiveContext: UIViewControllerContextTransitioning? 17 | private var transitionContext: UIViewControllerContextTransitioning? 18 | private var fromView: UIView? 19 | private var toView: UIView? 20 | private var toViewController: CBMenuController? 21 | private var displayLink: CADisplayLink? 22 | private var lastProgress: CGFloat? 23 | private var lastOffset: CGFloat = 0 24 | private var startTime: TimeInterval = CACurrentMediaTime() 25 | weak var menuButton: UIView? 26 | private var animationButton: UIView? 27 | private var animationButtonStart: CGRect = .zero 28 | deinit { 29 | clear() 30 | } 31 | 32 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 33 | return TimeInterval(maxOffset/offsetSpeed) 34 | } 35 | 36 | private func startTransition(using transitionContext: UIViewControllerContextTransitioning) { 37 | guard let fromView = transitionContext.view(forKey: .from) else { return } 38 | guard let toView = transitionContext.view(forKey: .to) else { return } 39 | toViewController = transitionContext.viewController(forKey: .to) as? CBMenuController 40 | self.fromView = fromView 41 | self.toView = toView 42 | self.transitionContext = transitionContext 43 | let mask = CAShapeLayer() 44 | 45 | let container = transitionContext.containerView 46 | container.addSubview(toView) 47 | if let menuButton = menuButton, 48 | let animationButton = menuButton.snapshotView(afterScreenUpdates: false) { 49 | container.addSubview(animationButton) 50 | animationButton.frame = fromView.convert(menuButton.frame, from: menuButton.superview) 51 | animationButtonStart = animationButton.frame 52 | self.animationButton = animationButton 53 | } 54 | toViewController?.menuContainer.alpha = 0 55 | toViewController?.dismissButton.alpha = 0 56 | menuButton?.isHidden = true 57 | toView.layer.mask = mask 58 | 59 | startTime = CACurrentMediaTime() 60 | lastOffset = 0 61 | let displayLink = CADisplayLink(target: self, selector: #selector(screenUpdate)) 62 | displayLink.add(to: .current, forMode: .common) 63 | self.displayLink = displayLink 64 | 65 | } 66 | 67 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 68 | startTransition(using: transitionContext) 69 | } 70 | 71 | func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { 72 | interactiveContext = transitionContext 73 | startTransition(using: transitionContext) 74 | } 75 | 76 | func update(to progress: CGFloat, offset: CGFloat) { 77 | lastProgress = progress 78 | lastOffset = offset 79 | } 80 | 81 | var finishing: Bool = false 82 | var cancelling: Bool = false 83 | var lastScreenUpdateTime: TimeInterval = CACurrentMediaTime() 84 | 85 | @objc func screenUpdate() { 86 | defer { 87 | lastScreenUpdateTime = CACurrentMediaTime() 88 | } 89 | 90 | let progress = lastProgress ?? CGFloat((CACurrentMediaTime() - startTime)/duration) 91 | let timeDiff = CGFloat(CACurrentMediaTime() - lastScreenUpdateTime) 92 | var alphaProgress = min(1.0, progress/0.3) 93 | 94 | if cancelling || finishing { 95 | lastOffset -= offsetSpeed * timeDiff 96 | lastOffset = max(minOffset, lastOffset) 97 | } else if lastProgress == nil { 98 | lastOffset += offsetSpeed * timeDiff 99 | finishing = lastOffset > maxOffset 100 | finishOffset = -lastOffset 101 | } 102 | 103 | var btnOffset = -lastOffset 104 | var buttonOnPlace: Bool = false 105 | var buttonAnimationFinished: Bool = false 106 | let maskOnPlace: Bool = (lastOffset <= minOffset) 107 | let menuTargetOffsetY = (toViewController?.dismissButton.frame.minY ?? 0) - animationButtonStart.minY 108 | 109 | if finishing { 110 | let btnOffsetSpeed = offsetSpeed * (abs(menuTargetOffsetY)/(maxOffset + 100)) 111 | finishOffset -= btnOffsetSpeed * timeDiff 112 | buttonOnPlace = finishOffset <= menuTargetOffsetY 113 | btnOffset = max(menuTargetOffsetY, finishOffset) 114 | let finishPercent = (maxOffset + finishOffset)/(menuTargetOffsetY + maxOffset) 115 | toViewController?.menuContainer.alpha = min(1.0, finishPercent) 116 | if buttonOnPlace && !buttonAnimationFinished { 117 | let btnAlphaSpeed: CGFloat = 4 118 | if let btnDismiss = toViewController?.dismissButton { 119 | btnDismiss.alpha = min(1.0, btnDismiss.alpha + btnAlphaSpeed * timeDiff) 120 | } 121 | if let btnPresent = animationButton { 122 | btnPresent.alpha = max(0.0, btnPresent.alpha - btnAlphaSpeed * timeDiff) 123 | buttonAnimationFinished = btnPresent.alpha <= 0 124 | } else { 125 | buttonAnimationFinished = true 126 | } 127 | } 128 | } 129 | 130 | if cancelling { 131 | alphaProgress = min(alphaProgress, (lastOffset - minOffset)/(cancelOffset - minOffset)) 132 | } 133 | 134 | if let toView = toView { 135 | toView.alpha = alphaProgress 136 | if finishing { 137 | (toView.layer.mask as? CAShapeLayer)?.path = finishPath(forView: toView, 138 | offset: lastOffset) 139 | } else { 140 | (toView.layer.mask as? CAShapeLayer)?.path = path(forView: toView, 141 | offset: lastOffset) 142 | } 143 | 144 | } 145 | if let animationButton = animationButton { 146 | animationButton.transform = CGAffineTransform(translationX: 0, y: min(0, btnOffset)) 147 | .rotated(by: btnOffset/menuTargetOffsetY * CGFloat.pi/4.0) 148 | } 149 | 150 | if finishing && buttonOnPlace && maskOnPlace && buttonAnimationFinished { 151 | transitionContext?.completeTransition(true) 152 | menuButton?.isHidden = false 153 | toViewController?.menuContainer.alpha = 1.0 154 | toViewController?.dismissButton.alpha = 1.0 155 | clear() 156 | return 157 | } 158 | if cancelling && maskOnPlace { 159 | toView?.removeFromSuperview() 160 | menuButton?.isHidden = false 161 | transitionContext?.completeTransition(false) 162 | clear() 163 | return 164 | } 165 | } 166 | 167 | var minOffset: CGFloat { 168 | return 0 169 | } 170 | 171 | func lineCoeff(pt1: CGPoint, pt2: CGPoint) -> (k: CGFloat, b: CGFloat) { 172 | if pt1.x == pt2.y { 173 | return (k: 0, b: 0) 174 | } 175 | let k = (pt1.y - pt2.y)/(pt1.x - pt2.x) 176 | let b = pt1.y - k * pt1.x 177 | return (k: k, b: b) 178 | } 179 | 180 | private let defaultCentralOffsetY: CGFloat = 60 181 | func path(forView view: UIView, offset: CGFloat) -> CGPath { 182 | let centerX = menuButton?.frame.midX ?? view.bounds.width/2 183 | let path = UIBezierPath() 184 | path.move(to: .zero) 185 | path.addLine(to: CGPoint(x: view.bounds.width, y: 0)) 186 | path.addLine(to: CGPoint(x: view.bounds.width, y: view.bounds.height)) 187 | let bottomOffset: CGFloat = 0 188 | let pt1 = CGPoint(x: centerX + view.bounds.width/2, y: view.bounds.height + bottomOffset) 189 | path.addLine(to: pt1) 190 | let centralOffsetX = max(28.0, 50 - abs(offset * 0.2)) 191 | let centralOffsetY = 60 / 375 * view.bounds.width + offset * (0.83 / 320 * view.bounds.width) 192 | let pt2 = CGPoint(x: centerX + centralOffsetX, y: view.bounds.height - centralOffsetY) 193 | let pt3 = CGPoint(x: centerX - centralOffsetX, y: view.bounds.height - centralOffsetY) 194 | let pt4 = CGPoint(x: centerX - view.bounds.width/2, y: view.bounds.height + bottomOffset) 195 | let dcp21x: CGFloat = 55 / 375 * view.bounds.width 196 | let cpt21 = pt2.offsetBy(dx: max(-(centralOffsetX + 10), dcp21x - offset * 0.6), 197 | dy: 50 + max(0, offset * 0.5)) 198 | 199 | let rightLineCoeff = lineCoeff(pt1: cpt21, pt2: pt2) 200 | let cpt22y: CGFloat = pt2.y - max(10.0, dcp21x - max(0, offset * 0.1)) 201 | let cpt22x: CGFloat = rightLineCoeff.k != 0 ? (cpt22y - rightLineCoeff.b)/rightLineCoeff.k : 0 202 | let cpt22 = CGPoint(x: cpt22x, y: cpt22y) 203 | let cpt31 = CGPoint(x: pt3.x + (pt2.x - cpt22.x), y: cpt22y) 204 | let cpt32 = CGPoint(x: pt3.x - (cpt21.x - pt2.x), y: cpt21.y) 205 | 206 | let dcpt12x = -44 / 375 * view.bounds.width - max(0, offset * 0.5) 207 | 208 | path.addCurve(to: pt2, 209 | controlPoint1: pt1.offsetBy(dx: dcpt12x, dy: 0), 210 | controlPoint2: cpt21) 211 | path.addCurve(to: pt3, 212 | controlPoint1: cpt22, 213 | controlPoint2: cpt31) 214 | path.addCurve(to: pt4, 215 | controlPoint1: cpt32, 216 | controlPoint2: pt4.offsetBy(dx: -dcpt12x, dy: 0)) 217 | path.addLine(to: CGPoint(x: 0, y: view.bounds.height)) 218 | path.close() 219 | return path.cgPath 220 | } 221 | 222 | func finishPath(forView view: UIView, offset: CGFloat) -> CGPath { 223 | let centerX = menuButton?.frame.midX ?? view.bounds.width/2 224 | let correctedOffset = offset - 50 225 | let path = UIBezierPath() 226 | path.move(to: .zero) 227 | path.addLine(to: CGPoint(x: view.bounds.width, y: 0)) 228 | path.addLine(to: CGPoint(x: view.bounds.width, y: view.bounds.height)) 229 | let bottomOffset: CGFloat = 0 230 | let pt1 = CGPoint(x: centerX + view.bounds.width/2, y: view.bounds.height + bottomOffset) 231 | path.addLine(to: pt1) 232 | let centralOffsetX: CGFloat = 10 233 | let centralOffsetY = correctedOffset * 0.9 234 | let pt2 = CGPoint(x: centerX + centralOffsetX, y: view.bounds.height - centralOffsetY) 235 | let pt3 = CGPoint(x: centerX - centralOffsetX, y: view.bounds.height - centralOffsetY) 236 | let pt4 = CGPoint(x: centerX - view.bounds.width/2, y: view.bounds.height + bottomOffset) 237 | let dcp21x: CGFloat = 10 / 375 * view.bounds.width 238 | let dcp21xCorrected: CGFloat = dcp21x 239 | let cpt21 = pt2.offsetBy(dx: dcp21xCorrected, 240 | dy: min(view.bounds.height - pt2.y, 50 + max(0, correctedOffset * 0.5))) 241 | 242 | let rightLineCoeff = lineCoeff(pt1: cpt21, pt2: pt2) 243 | let cpt22y: CGFloat = pt2.y - min(cpt21.y - pt2.y, 244 | max(10.0, dcp21x - max(0, correctedOffset * 0.1))) 245 | let cpt22x: CGFloat = rightLineCoeff.k != 0 ? (cpt22y - rightLineCoeff.b)/rightLineCoeff.k : 0 246 | let cpt22 = CGPoint(x: cpt22x, y: cpt22y) 247 | let cpt31 = CGPoint(x: pt3.x + (pt2.x - cpt22.x), y: cpt22y) 248 | let cpt32 = CGPoint(x: pt3.x - (cpt21.x - pt2.x), y: cpt21.y) 249 | 250 | let dcpt12x = -44 / 375 * view.bounds.width - max(0, correctedOffset * 0.5) 251 | 252 | path.addCurve(to: pt2, 253 | controlPoint1: pt1.offsetBy(dx: dcpt12x, dy: 0), 254 | controlPoint2: cpt21) 255 | path.addCurve(to: pt3, 256 | controlPoint1: cpt22, 257 | controlPoint2: cpt31) 258 | path.addCurve(to: pt4, 259 | controlPoint1: cpt32, 260 | controlPoint2: pt4.offsetBy(dx: -dcpt12x, dy: 0)) 261 | path.addLine(to: CGPoint(x: 0, y: view.bounds.height)) 262 | path.close() 263 | return path.cgPath 264 | } 265 | 266 | var cancelOffset: CGFloat = 0 267 | var finishOffset: CGFloat = 0 268 | 269 | func finishTransition() { 270 | finishOffset = -lastOffset 271 | interactiveContext?.finishInteractiveTransition() 272 | finishing = true 273 | } 274 | 275 | func cancelTransition() { 276 | cancelOffset = lastOffset 277 | interactiveContext?.cancelInteractiveTransition() 278 | cancelling = true 279 | } 280 | 281 | func clear() { 282 | toView?.layer.mask = nil 283 | fromView = nil 284 | toView = nil 285 | interactiveContext = nil 286 | displayLink?.remove(from: .current, forMode: .common) 287 | displayLink = nil 288 | cancelling = false 289 | cancelling = false 290 | menuButton = nil 291 | animationButton?.removeFromSuperview() 292 | animationButton = nil 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /CBTabBar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE53232E2241270C00D6C307 /* CBTabButtonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53232D2241270C00D6C307 /* CBTabButtonFactory.swift */; }; 11 | CE53233122424F2E00D6C307 /* CBFlashyTabButtonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53233022424F2E00D6C307 /* CBFlashyTabButtonFactory.swift */; }; 12 | CE532335224258CB00D6C307 /* CBFadeTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE532334224258CB00D6C307 /* CBFadeTabBarButton.swift */; }; 13 | CE53233722425A9F00D6C307 /* CBMenuTabButtonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53233622425A9F00D6C307 /* CBMenuTabButtonFactory.swift */; }; 14 | CE53233A2242B6D000D6C307 /* CBMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5323392242B6D000D6C307 /* CBMenuButton.swift */; }; 15 | CE53233C2242B71D00D6C307 /* CBBaseTabButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53233B2242B71D00D6C307 /* CBBaseTabButton.swift */; }; 16 | CE5323402242BD3B00D6C307 /* CBMenuTabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53233F2242BD3B00D6C307 /* CBMenuTabItem.swift */; }; 17 | CE5323432242F1FD00D6C307 /* btnMenu.png in Resources */ = {isa = PBXBuildFile; fileRef = CE5323412242F1FC00D6C307 /* btnMenu.png */; }; 18 | CE5323442242F1FD00D6C307 /* btnMenu@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CE5323422242F1FC00D6C307 /* btnMenu@2x.png */; }; 19 | CE53234622431B1800D6C307 /* CBMenuPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53234522431B1800D6C307 /* CBMenuPresenter.swift */; }; 20 | CE53234C2243332900D6C307 /* CBMenuController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE53234B2243332900D6C307 /* CBMenuController.storyboard */; }; 21 | CE53234E2243353B00D6C307 /* CBMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53234D2243353B00D6C307 /* CBMenuController.swift */; }; 22 | CE5323502243376200D6C307 /* CBMenuPresentAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53234F2243376200D6C307 /* CBMenuPresentAnimationController.swift */; }; 23 | CE5323532243BB8500D6C307 /* btnClose@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CE5323512243BB8500D6C307 /* btnClose@2x.png */; }; 24 | CE5323542243BB8500D6C307 /* btnClose.png in Resources */ = {isa = PBXBuildFile; fileRef = CE5323522243BB8500D6C307 /* btnClose.png */; }; 25 | CE5323572244AB5900D6C307 /* CGPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5323562244AB5900D6C307 /* CGPoint.swift */; }; 26 | CE5323592244ACA700D6C307 /* CBMenuDismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5323582244ACA700D6C307 /* CBMenuDismissAnimationController.swift */; }; 27 | CE53235B2244BF9E00D6C307 /* CBTabBarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53235A2244BF9D00D6C307 /* CBTabBarStyle.swift */; }; 28 | CE53235E2244C33A00D6C307 /* CBFadeTabButtonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53235D2244C33A00D6C307 /* CBFadeTabButtonFactory.swift */; }; 29 | CE53236022454B2400D6C307 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE53235F22454B2400D6C307 /* Bundle.swift */; }; 30 | CE5323832245583300D6C307 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE5323812245583300D6C307 /* LaunchScreen.storyboard */; }; 31 | CECEA1D821B6767100F15B7A /* CBTabBarController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CECEA1D121B6767100F15B7A /* CBTabBarController.framework */; }; 32 | CECEA1D921B6767100F15B7A /* CBTabBarController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CECEA1D121B6767100F15B7A /* CBTabBarController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 33 | CECEA1E821B676D000F15B7A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1DF21B676D000F15B7A /* ViewController.swift */; }; 34 | CECEA1EA21B676D000F15B7A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CECEA1E221B676D000F15B7A /* Main.storyboard */; }; 35 | CECEA1EB21B676D000F15B7A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CECEA1E421B676D000F15B7A /* Images.xcassets */; }; 36 | CECEA1EC21B676D000F15B7A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1E521B676D000F15B7A /* AppDelegate.swift */; }; 37 | CECEA1ED21B676D000F15B7A /* CBSampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1E621B676D000F15B7A /* CBSampleViewController.swift */; }; 38 | CECEA1FD21B676FF00F15B7A /* CBTabItemSelectAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1F421B676FF00F15B7A /* CBTabItemSelectAnimation.swift */; }; 39 | CECEA1FE21B676FF00F15B7A /* CBFlashyTabItemBasicAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1F521B676FF00F15B7A /* CBFlashyTabItemBasicAnimation.swift */; }; 40 | CECEA1FF21B676FF00F15B7A /* CBTabItemAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1F621B676FF00F15B7A /* CBTabItemAnimation.swift */; }; 41 | CECEA20021B676FF00F15B7A /* CBTabItemDeselectAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1F721B676FF00F15B7A /* CBTabItemDeselectAnimation.swift */; }; 42 | CECEA20121B676FF00F15B7A /* CBTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1F821B676FF00F15B7A /* CBTabBar.swift */; }; 43 | CECEA20221B676FF00F15B7A /* CBFlashyTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1F921B676FF00F15B7A /* CBFlashyTabBarButton.swift */; }; 44 | CECEA20321B676FF00F15B7A /* CBTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECEA1FA21B676FF00F15B7A /* CBTabBarController.swift */; }; 45 | CECEA21A21B679EE00F15B7A /* CBFlashyTabBarController.h in Headers */ = {isa = PBXBuildFile; fileRef = CECEA21921B679EE00F15B7A /* CBFlashyTabBarController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 46 | /* End PBXBuildFile section */ 47 | 48 | /* Begin PBXContainerItemProxy section */ 49 | CECEA1D621B6767100F15B7A /* PBXContainerItemProxy */ = { 50 | isa = PBXContainerItemProxy; 51 | containerPortal = CECEA19E21B6758200F15B7A /* Project object */; 52 | proxyType = 1; 53 | remoteGlobalIDString = CECEA1D021B6767100F15B7A; 54 | remoteInfo = CBFlashyTabBarController; 55 | }; 56 | /* End PBXContainerItemProxy section */ 57 | 58 | /* Begin PBXCopyFilesBuildPhase section */ 59 | CECEA1DD21B6767100F15B7A /* Embed Frameworks */ = { 60 | isa = PBXCopyFilesBuildPhase; 61 | buildActionMask = 2147483647; 62 | dstPath = ""; 63 | dstSubfolderSpec = 10; 64 | files = ( 65 | CECEA1D921B6767100F15B7A /* CBTabBarController.framework in Embed Frameworks */, 66 | ); 67 | name = "Embed Frameworks"; 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXCopyFilesBuildPhase section */ 71 | 72 | /* Begin PBXFileReference section */ 73 | CE53232D2241270C00D6C307 /* CBTabButtonFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBTabButtonFactory.swift; sourceTree = ""; }; 74 | CE53233022424F2E00D6C307 /* CBFlashyTabButtonFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBFlashyTabButtonFactory.swift; sourceTree = ""; }; 75 | CE532334224258CB00D6C307 /* CBFadeTabBarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBFadeTabBarButton.swift; sourceTree = ""; }; 76 | CE53233622425A9F00D6C307 /* CBMenuTabButtonFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBMenuTabButtonFactory.swift; sourceTree = ""; }; 77 | CE5323392242B6D000D6C307 /* CBMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBMenuButton.swift; sourceTree = ""; }; 78 | CE53233B2242B71D00D6C307 /* CBBaseTabButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBBaseTabButton.swift; sourceTree = ""; }; 79 | CE53233F2242BD3B00D6C307 /* CBMenuTabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBMenuTabItem.swift; sourceTree = ""; }; 80 | CE5323412242F1FC00D6C307 /* btnMenu.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = btnMenu.png; sourceTree = ""; }; 81 | CE5323422242F1FC00D6C307 /* btnMenu@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "btnMenu@2x.png"; sourceTree = ""; }; 82 | CE53234522431B1800D6C307 /* CBMenuPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBMenuPresenter.swift; sourceTree = ""; }; 83 | CE53234B2243332900D6C307 /* CBMenuController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = CBMenuController.storyboard; sourceTree = ""; }; 84 | CE53234D2243353B00D6C307 /* CBMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBMenuController.swift; sourceTree = ""; }; 85 | CE53234F2243376200D6C307 /* CBMenuPresentAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBMenuPresentAnimationController.swift; sourceTree = ""; }; 86 | CE5323512243BB8500D6C307 /* btnClose@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "btnClose@2x.png"; sourceTree = ""; }; 87 | CE5323522243BB8500D6C307 /* btnClose.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = btnClose.png; sourceTree = ""; }; 88 | CE5323562244AB5900D6C307 /* CGPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint.swift; sourceTree = ""; }; 89 | CE5323582244ACA700D6C307 /* CBMenuDismissAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBMenuDismissAnimationController.swift; sourceTree = ""; }; 90 | CE53235A2244BF9D00D6C307 /* CBTabBarStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBTabBarStyle.swift; sourceTree = ""; }; 91 | CE53235D2244C33A00D6C307 /* CBFadeTabButtonFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBFadeTabButtonFactory.swift; sourceTree = ""; }; 92 | CE53235F22454B2400D6C307 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; 93 | CE5323822245583300D6C307 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 94 | CECEA1A621B6758200F15B7A /* ExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 95 | CECEA1D121B6767100F15B7A /* CBTabBarController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CBTabBarController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 96 | CECEA1D421B6767100F15B7A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 97 | CECEA1DF21B676D000F15B7A /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 98 | CECEA1E321B676D000F15B7A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 99 | CECEA1E421B676D000F15B7A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 100 | CECEA1E521B676D000F15B7A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 101 | CECEA1E621B676D000F15B7A /* CBSampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBSampleViewController.swift; sourceTree = ""; }; 102 | CECEA1E721B676D000F15B7A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 103 | CECEA1F021B676FF00F15B7A /* .gitkeep */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = .gitkeep; path = ../.gitkeep; sourceTree = ""; }; 104 | CECEA1F221B676FF00F15B7A /* .gitkeep */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; 105 | CECEA1F421B676FF00F15B7A /* CBTabItemSelectAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBTabItemSelectAnimation.swift; sourceTree = ""; }; 106 | CECEA1F521B676FF00F15B7A /* CBFlashyTabItemBasicAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBFlashyTabItemBasicAnimation.swift; sourceTree = ""; }; 107 | CECEA1F621B676FF00F15B7A /* CBTabItemAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBTabItemAnimation.swift; sourceTree = ""; }; 108 | CECEA1F721B676FF00F15B7A /* CBTabItemDeselectAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBTabItemDeselectAnimation.swift; sourceTree = ""; }; 109 | CECEA1F821B676FF00F15B7A /* CBTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBTabBar.swift; sourceTree = ""; }; 110 | CECEA1F921B676FF00F15B7A /* CBFlashyTabBarButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBFlashyTabBarButton.swift; sourceTree = ""; }; 111 | CECEA1FA21B676FF00F15B7A /* CBTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBTabBarController.swift; sourceTree = ""; }; 112 | CECEA20521B6789C00F15B7A /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 113 | CECEA20621B6789C00F15B7A /* CBTabBarController.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBTabBarController.podspec; sourceTree = ""; }; 114 | CECEA20721B6789C00F15B7A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 115 | CECEA21921B679EE00F15B7A /* CBFlashyTabBarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBFlashyTabBarController.h; sourceTree = ""; }; 116 | /* End PBXFileReference section */ 117 | 118 | /* Begin PBXFrameworksBuildPhase section */ 119 | CECEA1A321B6758200F15B7A /* Frameworks */ = { 120 | isa = PBXFrameworksBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | CECEA1D821B6767100F15B7A /* CBTabBarController.framework in Frameworks */, 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | CECEA1CE21B6767100F15B7A /* Frameworks */ = { 128 | isa = PBXFrameworksBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXFrameworksBuildPhase section */ 135 | 136 | /* Begin PBXGroup section */ 137 | CE53232F22424F2000D6C307 /* Flashy */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | CE532332224254BF00D6C307 /* Animations */, 141 | CECEA1F921B676FF00F15B7A /* CBFlashyTabBarButton.swift */, 142 | CE53233022424F2E00D6C307 /* CBFlashyTabButtonFactory.swift */, 143 | ); 144 | path = Flashy; 145 | sourceTree = ""; 146 | }; 147 | CE532332224254BF00D6C307 /* Animations */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | CECEA1F521B676FF00F15B7A /* CBFlashyTabItemBasicAnimation.swift */, 151 | CECEA1F421B676FF00F15B7A /* CBTabItemSelectAnimation.swift */, 152 | CECEA1F721B676FF00F15B7A /* CBTabItemDeselectAnimation.swift */, 153 | ); 154 | path = Animations; 155 | sourceTree = ""; 156 | }; 157 | CE5323332242587E00D6C307 /* Menu */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | CE53233622425A9F00D6C307 /* CBMenuTabButtonFactory.swift */, 161 | CE5323392242B6D000D6C307 /* CBMenuButton.swift */, 162 | CE53233F2242BD3B00D6C307 /* CBMenuTabItem.swift */, 163 | CE53234522431B1800D6C307 /* CBMenuPresenter.swift */, 164 | CE53234D2243353B00D6C307 /* CBMenuController.swift */, 165 | CE53234F2243376200D6C307 /* CBMenuPresentAnimationController.swift */, 166 | CE5323582244ACA700D6C307 /* CBMenuDismissAnimationController.swift */, 167 | ); 168 | path = Menu; 169 | sourceTree = ""; 170 | }; 171 | CE532347224332E400D6C307 /* Images */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | CE5323522243BB8500D6C307 /* btnClose.png */, 175 | CE5323512243BB8500D6C307 /* btnClose@2x.png */, 176 | CE5323412242F1FC00D6C307 /* btnMenu.png */, 177 | CE5323422242F1FC00D6C307 /* btnMenu@2x.png */, 178 | ); 179 | path = Images; 180 | sourceTree = ""; 181 | }; 182 | CE532348224332ED00D6C307 /* Storyboard */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | CE53234B2243332900D6C307 /* CBMenuController.storyboard */, 186 | ); 187 | path = Storyboard; 188 | sourceTree = ""; 189 | }; 190 | CE5323552244AB4800D6C307 /* Helpers */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | CE5323562244AB5900D6C307 /* CGPoint.swift */, 194 | CE53235F22454B2400D6C307 /* Bundle.swift */, 195 | ); 196 | path = Helpers; 197 | sourceTree = ""; 198 | }; 199 | CE53235C2244C31500D6C307 /* Fade */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | CE532334224258CB00D6C307 /* CBFadeTabBarButton.swift */, 203 | CE53235D2244C33A00D6C307 /* CBFadeTabButtonFactory.swift */, 204 | ); 205 | path = Fade; 206 | sourceTree = ""; 207 | }; 208 | CECEA19D21B6758200F15B7A = { 209 | isa = PBXGroup; 210 | children = ( 211 | CECEA20421B6785D00F15B7A /* Supporting Files */, 212 | CECEA1DE21B676D000F15B7A /* Example */, 213 | CECEA1D221B6767100F15B7A /* CBTabBarController */, 214 | CECEA1A721B6758200F15B7A /* Products */, 215 | ); 216 | sourceTree = ""; 217 | }; 218 | CECEA1A721B6758200F15B7A /* Products */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | CECEA1A621B6758200F15B7A /* ExampleApp.app */, 222 | CECEA1D121B6767100F15B7A /* CBTabBarController.framework */, 223 | ); 224 | name = Products; 225 | sourceTree = ""; 226 | }; 227 | CECEA1D221B6767100F15B7A /* CBTabBarController */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | CECEA1EF21B676FF00F15B7A /* Assets */, 231 | CECEA1F121B676FF00F15B7A /* Classes */, 232 | CECEA1D421B6767100F15B7A /* Info.plist */, 233 | CECEA21921B679EE00F15B7A /* CBFlashyTabBarController.h */, 234 | ); 235 | path = CBTabBarController; 236 | sourceTree = ""; 237 | }; 238 | CECEA1DE21B676D000F15B7A /* Example */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | CECEA1DF21B676D000F15B7A /* ViewController.swift */, 242 | CE5323812245583300D6C307 /* LaunchScreen.storyboard */, 243 | CECEA1E221B676D000F15B7A /* Main.storyboard */, 244 | CECEA1E421B676D000F15B7A /* Images.xcassets */, 245 | CECEA1E521B676D000F15B7A /* AppDelegate.swift */, 246 | CECEA1E621B676D000F15B7A /* CBSampleViewController.swift */, 247 | CECEA1E721B676D000F15B7A /* Info.plist */, 248 | ); 249 | path = Example; 250 | sourceTree = ""; 251 | }; 252 | CECEA1EF21B676FF00F15B7A /* Assets */ = { 253 | isa = PBXGroup; 254 | children = ( 255 | CE532348224332ED00D6C307 /* Storyboard */, 256 | CE532347224332E400D6C307 /* Images */, 257 | ); 258 | path = Assets; 259 | sourceTree = ""; 260 | }; 261 | CECEA1F121B676FF00F15B7A /* Classes */ = { 262 | isa = PBXGroup; 263 | children = ( 264 | CE53235C2244C31500D6C307 /* Fade */, 265 | CE5323552244AB4800D6C307 /* Helpers */, 266 | CECEA1F021B676FF00F15B7A /* .gitkeep */, 267 | CE5323332242587E00D6C307 /* Menu */, 268 | CECEA1F221B676FF00F15B7A /* .gitkeep */, 269 | CE53232F22424F2000D6C307 /* Flashy */, 270 | CECEA1F321B676FF00F15B7A /* Animation */, 271 | CECEA1F821B676FF00F15B7A /* CBTabBar.swift */, 272 | CE53233B2242B71D00D6C307 /* CBBaseTabButton.swift */, 273 | CECEA1FA21B676FF00F15B7A /* CBTabBarController.swift */, 274 | CE53232D2241270C00D6C307 /* CBTabButtonFactory.swift */, 275 | CE53235A2244BF9D00D6C307 /* CBTabBarStyle.swift */, 276 | ); 277 | path = Classes; 278 | sourceTree = ""; 279 | }; 280 | CECEA1F321B676FF00F15B7A /* Animation */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | CECEA1F621B676FF00F15B7A /* CBTabItemAnimation.swift */, 284 | ); 285 | path = Animation; 286 | sourceTree = ""; 287 | }; 288 | CECEA20421B6785D00F15B7A /* Supporting Files */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | CECEA20621B6789C00F15B7A /* CBTabBarController.podspec */, 292 | CECEA20521B6789C00F15B7A /* LICENSE */, 293 | CECEA20721B6789C00F15B7A /* README.md */, 294 | ); 295 | name = "Supporting Files"; 296 | sourceTree = ""; 297 | }; 298 | /* End PBXGroup section */ 299 | 300 | /* Begin PBXHeadersBuildPhase section */ 301 | CECEA1CC21B6767100F15B7A /* Headers */ = { 302 | isa = PBXHeadersBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | CECEA21A21B679EE00F15B7A /* CBFlashyTabBarController.h in Headers */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXHeadersBuildPhase section */ 310 | 311 | /* Begin PBXNativeTarget section */ 312 | CECEA1A521B6758200F15B7A /* ExampleApp */ = { 313 | isa = PBXNativeTarget; 314 | buildConfigurationList = CECEA1B821B6758400F15B7A /* Build configuration list for PBXNativeTarget "ExampleApp" */; 315 | buildPhases = ( 316 | CECEA1A221B6758200F15B7A /* Sources */, 317 | CECEA1A321B6758200F15B7A /* Frameworks */, 318 | CECEA1A421B6758200F15B7A /* Resources */, 319 | CECEA1DD21B6767100F15B7A /* Embed Frameworks */, 320 | ); 321 | buildRules = ( 322 | ); 323 | dependencies = ( 324 | CECEA1D721B6767100F15B7A /* PBXTargetDependency */, 325 | ); 326 | name = ExampleApp; 327 | productName = FlashyTabBar; 328 | productReference = CECEA1A621B6758200F15B7A /* ExampleApp.app */; 329 | productType = "com.apple.product-type.application"; 330 | }; 331 | CECEA1D021B6767100F15B7A /* CBTabBarController */ = { 332 | isa = PBXNativeTarget; 333 | buildConfigurationList = CECEA1DA21B6767100F15B7A /* Build configuration list for PBXNativeTarget "CBTabBarController" */; 334 | buildPhases = ( 335 | CECEA1CC21B6767100F15B7A /* Headers */, 336 | CECEA1CD21B6767100F15B7A /* Sources */, 337 | CECEA1CE21B6767100F15B7A /* Frameworks */, 338 | CECEA1CF21B6767100F15B7A /* Resources */, 339 | ); 340 | buildRules = ( 341 | ); 342 | dependencies = ( 343 | ); 344 | name = CBTabBarController; 345 | productName = CBFlashyTabBarController; 346 | productReference = CECEA1D121B6767100F15B7A /* CBTabBarController.framework */; 347 | productType = "com.apple.product-type.framework"; 348 | }; 349 | /* End PBXNativeTarget section */ 350 | 351 | /* Begin PBXProject section */ 352 | CECEA19E21B6758200F15B7A /* Project object */ = { 353 | isa = PBXProject; 354 | attributes = { 355 | LastSwiftUpdateCheck = 1010; 356 | LastUpgradeCheck = 1010; 357 | ORGANIZATIONNAME = cuberto; 358 | TargetAttributes = { 359 | CECEA1A521B6758200F15B7A = { 360 | CreatedOnToolsVersion = 10.1; 361 | }; 362 | CECEA1D021B6767100F15B7A = { 363 | CreatedOnToolsVersion = 10.1; 364 | }; 365 | }; 366 | }; 367 | buildConfigurationList = CECEA1A121B6758200F15B7A /* Build configuration list for PBXProject "CBTabBar" */; 368 | compatibilityVersion = "Xcode 9.3"; 369 | developmentRegion = en; 370 | hasScannedForEncodings = 0; 371 | knownRegions = ( 372 | en, 373 | Base, 374 | ); 375 | mainGroup = CECEA19D21B6758200F15B7A; 376 | productRefGroup = CECEA1A721B6758200F15B7A /* Products */; 377 | projectDirPath = ""; 378 | projectRoot = ""; 379 | targets = ( 380 | CECEA1A521B6758200F15B7A /* ExampleApp */, 381 | CECEA1D021B6767100F15B7A /* CBTabBarController */, 382 | ); 383 | }; 384 | /* End PBXProject section */ 385 | 386 | /* Begin PBXResourcesBuildPhase section */ 387 | CECEA1A421B6758200F15B7A /* Resources */ = { 388 | isa = PBXResourcesBuildPhase; 389 | buildActionMask = 2147483647; 390 | files = ( 391 | CE5323832245583300D6C307 /* LaunchScreen.storyboard in Resources */, 392 | CECEA1EB21B676D000F15B7A /* Images.xcassets in Resources */, 393 | CECEA1EA21B676D000F15B7A /* Main.storyboard in Resources */, 394 | ); 395 | runOnlyForDeploymentPostprocessing = 0; 396 | }; 397 | CECEA1CF21B6767100F15B7A /* Resources */ = { 398 | isa = PBXResourcesBuildPhase; 399 | buildActionMask = 2147483647; 400 | files = ( 401 | CE5323542243BB8500D6C307 /* btnClose.png in Resources */, 402 | CE53234C2243332900D6C307 /* CBMenuController.storyboard in Resources */, 403 | CE5323442242F1FD00D6C307 /* btnMenu@2x.png in Resources */, 404 | CE5323532243BB8500D6C307 /* btnClose@2x.png in Resources */, 405 | CE5323432242F1FD00D6C307 /* btnMenu.png in Resources */, 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | /* End PBXResourcesBuildPhase section */ 410 | 411 | /* Begin PBXSourcesBuildPhase section */ 412 | CECEA1A221B6758200F15B7A /* Sources */ = { 413 | isa = PBXSourcesBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | CECEA1ED21B676D000F15B7A /* CBSampleViewController.swift in Sources */, 417 | CECEA1EC21B676D000F15B7A /* AppDelegate.swift in Sources */, 418 | CECEA1E821B676D000F15B7A /* ViewController.swift in Sources */, 419 | ); 420 | runOnlyForDeploymentPostprocessing = 0; 421 | }; 422 | CECEA1CD21B6767100F15B7A /* Sources */ = { 423 | isa = PBXSourcesBuildPhase; 424 | buildActionMask = 2147483647; 425 | files = ( 426 | CE53232E2241270C00D6C307 /* CBTabButtonFactory.swift in Sources */, 427 | CECEA1FD21B676FF00F15B7A /* CBTabItemSelectAnimation.swift in Sources */, 428 | CE53234622431B1800D6C307 /* CBMenuPresenter.swift in Sources */, 429 | CE5323402242BD3B00D6C307 /* CBMenuTabItem.swift in Sources */, 430 | CE5323502243376200D6C307 /* CBMenuPresentAnimationController.swift in Sources */, 431 | CECEA1FE21B676FF00F15B7A /* CBFlashyTabItemBasicAnimation.swift in Sources */, 432 | CE53233722425A9F00D6C307 /* CBMenuTabButtonFactory.swift in Sources */, 433 | CE5323572244AB5900D6C307 /* CGPoint.swift in Sources */, 434 | CECEA20321B676FF00F15B7A /* CBTabBarController.swift in Sources */, 435 | CE53235E2244C33A00D6C307 /* CBFadeTabButtonFactory.swift in Sources */, 436 | CECEA20221B676FF00F15B7A /* CBFlashyTabBarButton.swift in Sources */, 437 | CE53235B2244BF9E00D6C307 /* CBTabBarStyle.swift in Sources */, 438 | CE53234E2243353B00D6C307 /* CBMenuController.swift in Sources */, 439 | CECEA20021B676FF00F15B7A /* CBTabItemDeselectAnimation.swift in Sources */, 440 | CE53233A2242B6D000D6C307 /* CBMenuButton.swift in Sources */, 441 | CE532335224258CB00D6C307 /* CBFadeTabBarButton.swift in Sources */, 442 | CECEA1FF21B676FF00F15B7A /* CBTabItemAnimation.swift in Sources */, 443 | CE53233C2242B71D00D6C307 /* CBBaseTabButton.swift in Sources */, 444 | CECEA20121B676FF00F15B7A /* CBTabBar.swift in Sources */, 445 | CE53236022454B2400D6C307 /* Bundle.swift in Sources */, 446 | CE53233122424F2E00D6C307 /* CBFlashyTabButtonFactory.swift in Sources */, 447 | CE5323592244ACA700D6C307 /* CBMenuDismissAnimationController.swift in Sources */, 448 | ); 449 | runOnlyForDeploymentPostprocessing = 0; 450 | }; 451 | /* End PBXSourcesBuildPhase section */ 452 | 453 | /* Begin PBXTargetDependency section */ 454 | CECEA1D721B6767100F15B7A /* PBXTargetDependency */ = { 455 | isa = PBXTargetDependency; 456 | target = CECEA1D021B6767100F15B7A /* CBTabBarController */; 457 | targetProxy = CECEA1D621B6767100F15B7A /* PBXContainerItemProxy */; 458 | }; 459 | /* End PBXTargetDependency section */ 460 | 461 | /* Begin PBXVariantGroup section */ 462 | CE5323812245583300D6C307 /* LaunchScreen.storyboard */ = { 463 | isa = PBXVariantGroup; 464 | children = ( 465 | CE5323822245583300D6C307 /* Base */, 466 | ); 467 | name = LaunchScreen.storyboard; 468 | sourceTree = ""; 469 | }; 470 | CECEA1E221B676D000F15B7A /* Main.storyboard */ = { 471 | isa = PBXVariantGroup; 472 | children = ( 473 | CECEA1E321B676D000F15B7A /* Base */, 474 | ); 475 | name = Main.storyboard; 476 | sourceTree = ""; 477 | }; 478 | /* End PBXVariantGroup section */ 479 | 480 | /* Begin XCBuildConfiguration section */ 481 | CECEA1B621B6758400F15B7A /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | ALWAYS_SEARCH_USER_PATHS = NO; 485 | CLANG_ANALYZER_NONNULL = YES; 486 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 487 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 488 | CLANG_CXX_LIBRARY = "libc++"; 489 | CLANG_ENABLE_MODULES = YES; 490 | CLANG_ENABLE_OBJC_ARC = YES; 491 | CLANG_ENABLE_OBJC_WEAK = YES; 492 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 493 | CLANG_WARN_BOOL_CONVERSION = YES; 494 | CLANG_WARN_COMMA = YES; 495 | CLANG_WARN_CONSTANT_CONVERSION = YES; 496 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 497 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 498 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 499 | CLANG_WARN_EMPTY_BODY = YES; 500 | CLANG_WARN_ENUM_CONVERSION = YES; 501 | CLANG_WARN_INFINITE_RECURSION = YES; 502 | CLANG_WARN_INT_CONVERSION = YES; 503 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 504 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 505 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 506 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 507 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 508 | CLANG_WARN_STRICT_PROTOTYPES = YES; 509 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 510 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 511 | CLANG_WARN_UNREACHABLE_CODE = YES; 512 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 513 | CODE_SIGN_IDENTITY = "iPhone Developer"; 514 | COPY_PHASE_STRIP = NO; 515 | DEBUG_INFORMATION_FORMAT = dwarf; 516 | ENABLE_STRICT_OBJC_MSGSEND = YES; 517 | ENABLE_TESTABILITY = YES; 518 | GCC_C_LANGUAGE_STANDARD = gnu11; 519 | GCC_DYNAMIC_NO_PIC = NO; 520 | GCC_NO_COMMON_BLOCKS = YES; 521 | GCC_OPTIMIZATION_LEVEL = 0; 522 | GCC_PREPROCESSOR_DEFINITIONS = ( 523 | "DEBUG=1", 524 | "$(inherited)", 525 | ); 526 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 527 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 528 | GCC_WARN_UNDECLARED_SELECTOR = YES; 529 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 530 | GCC_WARN_UNUSED_FUNCTION = YES; 531 | GCC_WARN_UNUSED_VARIABLE = YES; 532 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 533 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 534 | MTL_FAST_MATH = YES; 535 | ONLY_ACTIVE_ARCH = YES; 536 | SDKROOT = iphoneos; 537 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 538 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 539 | }; 540 | name = Debug; 541 | }; 542 | CECEA1B721B6758400F15B7A /* Release */ = { 543 | isa = XCBuildConfiguration; 544 | buildSettings = { 545 | ALWAYS_SEARCH_USER_PATHS = NO; 546 | CLANG_ANALYZER_NONNULL = YES; 547 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 548 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 549 | CLANG_CXX_LIBRARY = "libc++"; 550 | CLANG_ENABLE_MODULES = YES; 551 | CLANG_ENABLE_OBJC_ARC = YES; 552 | CLANG_ENABLE_OBJC_WEAK = YES; 553 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 554 | CLANG_WARN_BOOL_CONVERSION = YES; 555 | CLANG_WARN_COMMA = YES; 556 | CLANG_WARN_CONSTANT_CONVERSION = YES; 557 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 558 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 559 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 560 | CLANG_WARN_EMPTY_BODY = YES; 561 | CLANG_WARN_ENUM_CONVERSION = YES; 562 | CLANG_WARN_INFINITE_RECURSION = YES; 563 | CLANG_WARN_INT_CONVERSION = YES; 564 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 565 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 566 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 567 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 568 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 569 | CLANG_WARN_STRICT_PROTOTYPES = YES; 570 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 571 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 572 | CLANG_WARN_UNREACHABLE_CODE = YES; 573 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 574 | CODE_SIGN_IDENTITY = "iPhone Developer"; 575 | COPY_PHASE_STRIP = NO; 576 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 577 | ENABLE_NS_ASSERTIONS = NO; 578 | ENABLE_STRICT_OBJC_MSGSEND = YES; 579 | GCC_C_LANGUAGE_STANDARD = gnu11; 580 | GCC_NO_COMMON_BLOCKS = YES; 581 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 582 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 583 | GCC_WARN_UNDECLARED_SELECTOR = YES; 584 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 585 | GCC_WARN_UNUSED_FUNCTION = YES; 586 | GCC_WARN_UNUSED_VARIABLE = YES; 587 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 588 | MTL_ENABLE_DEBUG_INFO = NO; 589 | MTL_FAST_MATH = YES; 590 | SDKROOT = iphoneos; 591 | SWIFT_COMPILATION_MODE = wholemodule; 592 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 593 | VALIDATE_PRODUCT = YES; 594 | }; 595 | name = Release; 596 | }; 597 | CECEA1B921B6758400F15B7A /* Debug */ = { 598 | isa = XCBuildConfiguration; 599 | buildSettings = { 600 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 601 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 602 | CODE_SIGN_STYLE = Automatic; 603 | DEVELOPMENT_TEAM = SK7A63GXF6; 604 | INFOPLIST_FILE = Example/Info.plist; 605 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 606 | LD_RUNPATH_SEARCH_PATHS = ( 607 | "$(inherited)", 608 | "@executable_path/Frameworks", 609 | ); 610 | PRODUCT_BUNDLE_IDENTIFIER = com.cuberto.components.FlashyTabBar; 611 | PRODUCT_NAME = "$(TARGET_NAME)"; 612 | SWIFT_VERSION = 4.2; 613 | TARGETED_DEVICE_FAMILY = "1,2"; 614 | }; 615 | name = Debug; 616 | }; 617 | CECEA1BA21B6758400F15B7A /* Release */ = { 618 | isa = XCBuildConfiguration; 619 | buildSettings = { 620 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 621 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 622 | CODE_SIGN_STYLE = Automatic; 623 | DEVELOPMENT_TEAM = SK7A63GXF6; 624 | INFOPLIST_FILE = Example/Info.plist; 625 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 626 | LD_RUNPATH_SEARCH_PATHS = ( 627 | "$(inherited)", 628 | "@executable_path/Frameworks", 629 | ); 630 | PRODUCT_BUNDLE_IDENTIFIER = com.cuberto.components.FlashyTabBar; 631 | PRODUCT_NAME = "$(TARGET_NAME)"; 632 | SWIFT_VERSION = 4.2; 633 | TARGETED_DEVICE_FAMILY = "1,2"; 634 | }; 635 | name = Release; 636 | }; 637 | CECEA1DB21B6767100F15B7A /* Debug */ = { 638 | isa = XCBuildConfiguration; 639 | buildSettings = { 640 | CODE_SIGN_IDENTITY = ""; 641 | CODE_SIGN_STYLE = Automatic; 642 | CURRENT_PROJECT_VERSION = 1; 643 | DEFINES_MODULE = YES; 644 | DEVELOPMENT_TEAM = SK7A63GXF6; 645 | DYLIB_COMPATIBILITY_VERSION = 1; 646 | DYLIB_CURRENT_VERSION = 1; 647 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 648 | INFOPLIST_FILE = CBTabBarController/Info.plist; 649 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 650 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 651 | LD_RUNPATH_SEARCH_PATHS = ( 652 | "$(inherited)", 653 | "@executable_path/Frameworks", 654 | "@loader_path/Frameworks", 655 | ); 656 | PRODUCT_BUNDLE_IDENTIFIER = com.cuberto.components.CBTabBarController; 657 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 658 | SKIP_INSTALL = YES; 659 | SWIFT_VERSION = 4.2; 660 | TARGETED_DEVICE_FAMILY = "1,2"; 661 | VERSIONING_SYSTEM = "apple-generic"; 662 | VERSION_INFO_PREFIX = ""; 663 | }; 664 | name = Debug; 665 | }; 666 | CECEA1DC21B6767100F15B7A /* Release */ = { 667 | isa = XCBuildConfiguration; 668 | buildSettings = { 669 | CODE_SIGN_IDENTITY = ""; 670 | CODE_SIGN_STYLE = Automatic; 671 | CURRENT_PROJECT_VERSION = 1; 672 | DEFINES_MODULE = YES; 673 | DEVELOPMENT_TEAM = SK7A63GXF6; 674 | DYLIB_COMPATIBILITY_VERSION = 1; 675 | DYLIB_CURRENT_VERSION = 1; 676 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 677 | INFOPLIST_FILE = CBTabBarController/Info.plist; 678 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 679 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 680 | LD_RUNPATH_SEARCH_PATHS = ( 681 | "$(inherited)", 682 | "@executable_path/Frameworks", 683 | "@loader_path/Frameworks", 684 | ); 685 | PRODUCT_BUNDLE_IDENTIFIER = com.cuberto.components.CBTabBarController; 686 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 687 | SKIP_INSTALL = YES; 688 | SWIFT_VERSION = 4.2; 689 | TARGETED_DEVICE_FAMILY = "1,2"; 690 | VERSIONING_SYSTEM = "apple-generic"; 691 | VERSION_INFO_PREFIX = ""; 692 | }; 693 | name = Release; 694 | }; 695 | /* End XCBuildConfiguration section */ 696 | 697 | /* Begin XCConfigurationList section */ 698 | CECEA1A121B6758200F15B7A /* Build configuration list for PBXProject "CBTabBar" */ = { 699 | isa = XCConfigurationList; 700 | buildConfigurations = ( 701 | CECEA1B621B6758400F15B7A /* Debug */, 702 | CECEA1B721B6758400F15B7A /* Release */, 703 | ); 704 | defaultConfigurationIsVisible = 0; 705 | defaultConfigurationName = Release; 706 | }; 707 | CECEA1B821B6758400F15B7A /* Build configuration list for PBXNativeTarget "ExampleApp" */ = { 708 | isa = XCConfigurationList; 709 | buildConfigurations = ( 710 | CECEA1B921B6758400F15B7A /* Debug */, 711 | CECEA1BA21B6758400F15B7A /* Release */, 712 | ); 713 | defaultConfigurationIsVisible = 0; 714 | defaultConfigurationName = Release; 715 | }; 716 | CECEA1DA21B6767100F15B7A /* Build configuration list for PBXNativeTarget "CBTabBarController" */ = { 717 | isa = XCConfigurationList; 718 | buildConfigurations = ( 719 | CECEA1DB21B6767100F15B7A /* Debug */, 720 | CECEA1DC21B6767100F15B7A /* Release */, 721 | ); 722 | defaultConfigurationIsVisible = 0; 723 | defaultConfigurationName = Release; 724 | }; 725 | /* End XCConfigurationList section */ 726 | }; 727 | rootObject = CECEA19E21B6758200F15B7A /* Project object */; 728 | } 729 | --------------------------------------------------------------------------------