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