├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── KRTabBar │ ├── KRTabBarController.swift │ └── KRTabBarItem.swift ├── Tests ├── KRTabBarTests │ ├── KRTabBarTests.swift │ └── XCTestManifests.swift └── LinuxMain.swift └── images ├── KRTabBar-01.png ├── KRTabBar-02.png ├── KRTabBar-03.png ├── KRTabBar-04.png ├── KRTabBarGIF.gif ├── KRTabBarGIF2.gif ├── baritem1.png ├── baritem2.png └── controller.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kerolles Roshdi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "KRTabBar", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "KRTabBar", 12 | targets: ["KRTabBar"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "KRTabBar", 23 | dependencies: []), 24 | .testTarget( 25 | name: "KRTabBarTests", 26 | dependencies: ["KRTabBar"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KRTabBar 2 | 3 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/kerollesroshdi/KRTabBar) ![GitHub last commit](https://img.shields.io/github/last-commit/kerollesroshdi/KRTabBar) ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/kerollesroshdi/KRTabBar?sort=semver) ![](https://img.shields.io/badge/Platform-iOS-orange) 4 | ![GitHub forks](https://img.shields.io/github/forks/kerollesroshdi/KRTabBar?style=social) ![GitHub stars](https://img.shields.io/github/stars/kerollesroshdi/KRTabBar?style=social) [![Twitter Follow](https://img.shields.io/twitter/follow/kerollesroshdi?style=social)](https://twitter.com/intent/follow?screen_name=kerollesroshdi) [![Open Source Helpers](https://www.codetriage.com/kerollesroshdi/krtabbar/badges/users.svg)](https://www.codetriage.com/kerollesroshdi/krtabbar) 5 | ![GitHub contributors](https://img.shields.io/github/contributors/kerollesroshdi/krtabbar) 6 | 7 | This package provides a beautiful easy to use UITabBarController 8 | 9 | 10 | 11 | 12 | 13 | ## Installation 14 | ### Swift Package Manager 15 | The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. 16 | To integrate using Apple's Swift package manager from xcode : 17 | 18 | File -> Swift Packages -> Add Package Dependency... 19 | 20 | enter package URL : https://github.com/kerollesroshdi/KRTabBar.git , choose the latest release 21 | 22 | ## Usage 23 | #### set UITabBarController class to KRTabBarController 24 | 25 | 26 | #### set UITabBarItem class to KRTabBarItem 27 | 28 | 29 | #### set UITabBarItem Color and image 30 | 31 | 32 | ## Licence 33 | KRTabBar is released under an MIT license. See [License](https://github.com/kerollesroshdi/KRTabBar/blob/master/LICENSE) for more information. 34 | -------------------------------------------------------------------------------- /Sources/KRTabBar/KRTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KRTabBar.swift 3 | // KRTabBarController 4 | // 5 | // Created by Kerolles Roshdi on 2/14/20. 6 | // Copyright © 2020 Kerolles Roshdi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @available(iOS 9.0, *) 12 | class KRTabBarController: UITabBarController, UITabBarControllerDelegate { 13 | 14 | private var selectedTab: Int = 0 15 | 16 | private var buttons = [UIButton]() 17 | private var buttonsColors = [UIColor]() 18 | 19 | private let customTabBarView: UIView = { 20 | let view = UIView(frame: .zero) 21 | view.backgroundColor = .systemBackground 22 | view.clipsToBounds = true 23 | view.translatesAutoresizingMaskIntoConstraints = false 24 | return view 25 | }() 26 | 27 | private let indexView: UIView = { 28 | let view = UIView(frame: .zero) 29 | view.layer.shadowOpacity = 1 30 | view.layer.shadowOffset = CGSize(width: 2, height: 2) 31 | view.layer.shadowRadius = 10 32 | view.translatesAutoresizingMaskIntoConstraints = false 33 | return view 34 | }() 35 | 36 | private let stackView: UIStackView = { 37 | let stackView = UIStackView() 38 | stackView.alignment = .center 39 | stackView.distribution = .fillProportionally 40 | stackView.axis = .horizontal 41 | stackView.translatesAutoresizingMaskIntoConstraints = false 42 | return stackView 43 | }() 44 | 45 | override var viewControllers: [UIViewController]? { 46 | didSet { 47 | createButtonsStack(viewControllers!) 48 | } 49 | } 50 | 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | tabBar.isHidden = true 54 | addCustomTabBarView() 55 | createButtonsStack(viewControllers!) 56 | autolayout() 57 | } 58 | 59 | override func viewDidLayoutSubviews() { 60 | super.viewDidLayoutSubviews() 61 | customTabBarView.layer.cornerRadius = customTabBarView.frame.size.height / 2 62 | indexView.layer.cornerRadius = indexView.frame.size.height / 2.5 63 | indexView.backgroundColor = buttonsColors[selectedTab] 64 | indexView.layer.shadowColor = buttonsColors[selectedTab].cgColor 65 | } 66 | 67 | private func createButtonsStack(_ viewControllers: [UIViewController]) { 68 | 69 | // clean : 70 | buttons.removeAll() 71 | buttonsColors.removeAll() 72 | 73 | stackView.arrangedSubviews.forEach { 74 | stackView.removeArrangedSubview($0) 75 | $0.removeFromSuperview() 76 | } 77 | 78 | for (index, viewController) in viewControllers.enumerated() { 79 | guard let tabBarItem = viewController.tabBarItem as? KRTabBarItem else { 80 | assertionFailure("TabBarItems class must be KRTabBarItem") 81 | return 82 | } 83 | buttonsColors.append(tabBarItem.backgroundColor) 84 | 85 | let button = UIButton() 86 | button.tag = index 87 | button.addTarget(self, action: #selector(didSelectIndex(sender:)), for: .touchUpInside) 88 | let image = viewController.tabBarItem.image?.withRenderingMode(.alwaysTemplate) 89 | button.imageView?.tintColor = tabBarItem.iconColor 90 | button.setImage(image, for: .normal) 91 | button.translatesAutoresizingMaskIntoConstraints = false 92 | stackView.addArrangedSubview(button) 93 | buttons.append(button) 94 | } 95 | 96 | view.setNeedsLayout() 97 | } 98 | 99 | private func autolayout() { 100 | let margins = view.layoutMarginsGuide 101 | NSLayoutConstraint.activate([ 102 | customTabBarView.widthAnchor.constraint(equalTo: tabBar.widthAnchor, constant: -20), 103 | customTabBarView.heightAnchor.constraint(equalToConstant: 70), 104 | customTabBarView.bottomAnchor.constraint(equalTo: margins.bottomAnchor, constant: -10), 105 | customTabBarView.centerXAnchor.constraint(equalTo: tabBar.centerXAnchor), 106 | stackView.leadingAnchor.constraint(equalTo: customTabBarView.leadingAnchor), 107 | stackView.trailingAnchor.constraint(equalTo: customTabBarView.trailingAnchor), 108 | stackView.heightAnchor.constraint(equalTo: customTabBarView.heightAnchor), 109 | indexView.widthAnchor.constraint(equalToConstant: customTabBarView.bounds.height), 110 | indexView.heightAnchor.constraint(equalToConstant: customTabBarView.bounds.height), 111 | indexView.centerYAnchor.constraint(equalTo: customTabBarView.centerYAnchor), 112 | indexView.centerXAnchor.constraint(equalTo: buttons.first?.centerXAnchor ?? customTabBarView.centerXAnchor) 113 | ]) 114 | } 115 | 116 | private func addCustomTabBarView() { 117 | customTabBarView.frame = tabBar.frame 118 | indexView.frame = tabBar.frame 119 | view.addSubview(customTabBarView) 120 | 121 | view.bringSubviewToFront(self.tabBar) 122 | 123 | customTabBarView.addSubview(indexView) 124 | customTabBarView.addSubview(stackView) 125 | } 126 | 127 | @objc private func didSelectIndex(sender: UIButton) { 128 | let index = sender.tag 129 | self.selectedIndex = index 130 | self.selectedTab = index 131 | UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 1, options: .curveEaseInOut, animations: { 132 | self.indexView.center.x = self.buttons[index].center.x 133 | self.indexView.backgroundColor = self.buttonsColors[index] 134 | self.indexView.layer.shadowColor = self.buttonsColors[index].cgColor 135 | }, completion: nil) 136 | } 137 | 138 | 139 | // Delegate: 140 | override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { 141 | guard 142 | let items = tabBar.items, 143 | let index = items.firstIndex(of: item) 144 | else { 145 | print("not found") 146 | return 147 | } 148 | didSelectIndex(sender: self.buttons[index]) 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /Sources/KRTabBar/KRTabBarItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KRTabBarItem.swift 3 | // KRTabBarController 4 | // 5 | // Created by Kerolles Roshdi on 2/15/20. 6 | // Copyright © 2020 Kerolles Roshdi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class KRTabBarItem: UITabBarItem { 12 | @IBInspectable 13 | var backgroundColor: UIColor = UIColor.systemBlue 14 | 15 | @IBInspectable 16 | var iconColor: UIColor = .darkText 17 | } 18 | -------------------------------------------------------------------------------- /Tests/KRTabBarTests/KRTabBarTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import KRTabBar 3 | 4 | final class KRTabBarTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(KRTabBar().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/KRTabBarTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(KRTabBarTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import KRTabBarTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += KRTabBarTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /images/KRTabBar-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/KRTabBar-01.png -------------------------------------------------------------------------------- /images/KRTabBar-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/KRTabBar-02.png -------------------------------------------------------------------------------- /images/KRTabBar-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/KRTabBar-03.png -------------------------------------------------------------------------------- /images/KRTabBar-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/KRTabBar-04.png -------------------------------------------------------------------------------- /images/KRTabBarGIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/KRTabBarGIF.gif -------------------------------------------------------------------------------- /images/KRTabBarGIF2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/KRTabBarGIF2.gif -------------------------------------------------------------------------------- /images/baritem1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/baritem1.png -------------------------------------------------------------------------------- /images/baritem2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/baritem2.png -------------------------------------------------------------------------------- /images/controller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kerollesroshdi/KRTabBar/63f58eda95ec52bd78c6315bc3f0d27b0952771a/images/controller.png --------------------------------------------------------------------------------