Last active
July 13, 2020 14:04
-
-
Save Coder-ACJHP/1400449977ee70fa46bb42e4565f9c87 to your computer and use it in GitHub Desktop.
Fully customizable π¨ tab bar without using storyboard or .xib files for IOS written by Swift 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.swift | |
// UICTabbar | |
// | |
// Created by Coder ACJHP on 2.04.2019. | |
// Copyright Β© 2019 Onur IΕΔ±k. All rights reserved. | |
// | |
import UIKit | |
let showBadge = Notification.Name.init("showBadge") | |
class UICTabbarViewController: UIViewController { | |
private var buttonsStackView: UIStackView? | |
private var tabButtonLabelsList = [UILabel]() | |
private var tabButtonBadgeLabelsList = [UILabel]() | |
private var tabButtonContainerViewList = [UIView]() | |
private var tabButtonImageViewList = [UIImageView]() | |
private var previousContentViewController: UIViewController? | |
public var tabbarView: UIView! | |
public var tabbarHeight: CGFloat = 60 | |
public var tabNameSet = [String]() | |
public var tabIconSet = [UIImage]() | |
public var tabSelectedIconSet = [UIImage]() | |
public var tabViewControllerSet = [UIViewController]() | |
public var isGlowing: Bool = false { | |
didSet { | |
if isGlowing { | |
selectedButtonBackgroundColor = .clear | |
} | |
} | |
} | |
public var glowColor: UIColor = .red | |
public var selectedButtonTitleColor: UIColor = .darkGray | |
public var unSelectedButtonTitleColor: UIColor = .white | |
public var selectedButtonBackgroundColor: UIColor = .clear | |
public var buttonTitleFont: UIFont = UIFont.boldSystemFont(ofSize: 12) | |
public var backgroundImage: UIImage? | |
private var tabbarBGImageView: UIImageView = { | |
let screenDimension = UIScreen.main.bounds | |
let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: screenDimension.width, height: 60)) | |
imgView.contentMode = .scaleAspectFill | |
imgView.clipsToBounds = true | |
return imgView | |
}() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(_:)), name: showBadge, object: nil) | |
} | |
@objc fileprivate func handleNotification(_ notification: Notification) { | |
if let userInfo = notification.userInfo as? Dictionary<String, AnyObject> { | |
guard let badgeIndex = userInfo["index"] as? Int else { return } | |
let currentBadge = tabButtonBadgeLabelsList[badgeIndex] | |
currentBadge.isHidden = false | |
currentBadge.text = "1" | |
} | |
} | |
// hide home indicator on iPhoneX group | |
override var prefersHomeIndicatorAutoHidden: Bool { return true } | |
override func viewWillAppear(_ animated: Bool) { | |
super.viewWillAppear(animated) | |
// Create and initialize tabbar view | |
createTabbar() | |
// Set first button as selected | |
animatePressedButton(UIButton()) | |
// Initialize content view controller | |
let firstViewController = tabViewControllerSet.first! | |
previousContentViewController = firstViewController | |
displayContentController(content: firstViewController) | |
} | |
fileprivate func createTabbar() { | |
// Create simple view than add stack view on it to be container | |
// Stack view it's container for tab buttons | |
// Tab buttons UIView -> UIImageView (for icon) + UILabel (for title) + UILabel (for badge) + UIButton (for press handling) | |
let screenDimension = UIScreen.main.bounds | |
tabbarView = UIView(frame: CGRect(x: 0, y: 0, width: screenDimension.width, height: tabbarHeight)) | |
tabbarView.backgroundColor = .purple | |
tabbarView.translatesAutoresizingMaskIntoConstraints = false | |
view.addSubview(tabbarView) | |
NSLayoutConstraint.activate([ | |
tabbarView.widthAnchor.constraint(equalTo: view.widthAnchor), | |
tabbarView.heightAnchor.constraint(equalToConstant: tabbarHeight), | |
tabbarView.bottomAnchor.constraint(equalTo: view.bottomAnchor), | |
tabbarView.centerXAnchor.constraint(equalTo: view.centerXAnchor) | |
]) | |
// Check if backgorund image is defined setup background image container | |
if backgroundImage != nil { | |
tabbarBGImageView.image = backgroundImage | |
tabbarView.backgroundColor = .clear | |
tabbarView.insertSubview(tabbarBGImageView, at: 0) | |
tabbarBGImageView.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate([ | |
tabbarBGImageView.widthAnchor.constraint(equalTo: tabbarView.widthAnchor), | |
tabbarBGImageView.heightAnchor.constraint(equalToConstant: tabbarHeight), | |
tabbarBGImageView.bottomAnchor.constraint(equalTo: tabbarView.bottomAnchor), | |
tabbarBGImageView.centerXAnchor.constraint(equalTo: tabbarView.centerXAnchor) | |
]) | |
} | |
// Create stackView + tab bar buttons | |
createTabbarButtons() | |
} | |
fileprivate func createTabbarButtons() { | |
if buttonsStackView == nil { | |
buttonsStackView = UIStackView(frame: tabbarView.frame) | |
buttonsStackView?.alignment = .center | |
buttonsStackView?.distribution = .equalSpacing | |
buttonsStackView?.axis = .horizontal | |
let estimatedLeftRightInset = tabbarView.frame.width * 0.04 | |
buttonsStackView?.layoutMargins = UIEdgeInsets(top: 0, left: estimatedLeftRightInset, bottom: 0, right: estimatedLeftRightInset) | |
buttonsStackView?.isLayoutMarginsRelativeArrangement = true | |
buttonsStackView?.translatesAutoresizingMaskIntoConstraints = false | |
tabbarView.addSubview(buttonsStackView!) | |
NSLayoutConstraint.activate([ | |
buttonsStackView!.widthAnchor.constraint(equalTo: tabbarView.widthAnchor), | |
buttonsStackView!.heightAnchor.constraint(equalTo: tabbarView.heightAnchor), | |
buttonsStackView!.centerXAnchor.constraint(equalTo: tabbarView.centerXAnchor), | |
buttonsStackView!.centerYAnchor.constraint(equalTo: tabbarView.centerYAnchor) | |
]) | |
// Create buttons depended on icon set count | |
for index in 0 ..< tabIconSet.count { | |
// Button container view | |
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: tabbarHeight, height: tabbarHeight)) | |
containerView.backgroundColor = .clear | |
tabButtonContainerViewList.append(containerView) | |
containerView.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate([ | |
containerView.widthAnchor.constraint(equalToConstant: tabbarHeight), | |
containerView.heightAnchor.constraint(equalToConstant: tabbarHeight) | |
]) | |
// Icon image view | |
let iconView = UIImageView(image: tabIconSet[index]) | |
iconView.frame = CGRect(x: 0, y: 0, width: 30, height: 30) | |
iconView.contentMode = .scaleAspectFit | |
containerView.addSubview(iconView) | |
iconView.center = CGPoint(x: containerView.center.x, y: containerView.center.y - 7) | |
tabButtonImageViewList.append(iconView) | |
// Badge label view | |
let badgelabel = UILabel(frame: CGRect(x: tabbarHeight - 20, y: 5, width: 15, height: 15)) | |
badgelabel.backgroundColor = .red | |
badgelabel.layer.cornerRadius = badgelabel.frame.width / 2 | |
badgelabel.layer.masksToBounds = true | |
badgelabel.textColor = .white | |
badgelabel.textAlignment = .center | |
badgelabel.font = UIFont.boldSystemFont(ofSize: 11) | |
badgelabel.isHidden = true | |
containerView.addSubview(badgelabel) | |
tabButtonBadgeLabelsList.append(badgelabel) | |
// Title label view | |
let nameLabel = UILabel(frame: CGRect(x: 0, y: containerView.frame.height - 22, width: containerView.frame.width, height: 22)) | |
nameLabel.font = buttonTitleFont | |
nameLabel.textColor = unSelectedButtonTitleColor | |
nameLabel.textAlignment = .center | |
nameLabel.text = tabNameSet[index] | |
containerView.addSubview(nameLabel) | |
tabButtonLabelsList.append(nameLabel) | |
// Hidden (transparent) button | |
let button = UIButton(type: .system) | |
button.frame = containerView.frame | |
button.setTitle("", for: .normal) | |
button.tag = index | |
button.addTarget(self, action: #selector(handleButtonPress(_:)), for: .touchUpInside) | |
containerView.addSubview(button) | |
// Add button container to stack view | |
buttonsStackView?.addArrangedSubview(containerView) | |
buttonsStackView?.layoutIfNeeded() | |
} | |
buttonsStackView?.layoutSubviews() | |
} | |
} | |
@objc fileprivate func handleButtonPress(_ sender: UIButton) { | |
animatePressedButton(sender) | |
showViewController(forIndex: sender.tag) | |
} | |
fileprivate func animatePressedButton(_ sender: UIButton) { | |
tabButtonContainerViewList.forEach { | |
$0.backgroundColor = .clear | |
// Remove glow layer from the view | |
if isGlowing { removeGlowAnimation(fromView: $0) } | |
} | |
// Pick selected button container view | |
let view = tabButtonContainerViewList[sender.tag] | |
view.alpha = 0 | |
UIView.transition(with: view, duration: 0.25, options: [.transitionCrossDissolve], animations: { | |
view.alpha = 1.0 | |
view.backgroundColor = self.selectedButtonBackgroundColor | |
// Revert all unselected icons to buttons | |
for (index, item) in self.tabButtonImageViewList.enumerated() { | |
item.transform = .identity | |
item.image = self.tabIconSet[index] | |
} | |
// Hide badge if it's appear | |
self.tabButtonBadgeLabelsList[sender.tag].isHidden = true | |
// Make all button label text color to default | |
self.tabButtonLabelsList.forEach { | |
$0.textColor = self.unSelectedButtonTitleColor | |
$0.font = self.buttonTitleFont | |
} | |
let label = self.tabButtonLabelsList[sender.tag] | |
label.textColor = self.selectedButtonTitleColor | |
label.font = self.buttonTitleFont.withSize(14) | |
// Be sure to selected button icons not nil | |
if self.tabSelectedIconSet.count > 0 { | |
let imageView = self.tabButtonImageViewList[sender.tag] | |
imageView.image = self.tabSelectedIconSet[sender.tag] | |
imageView.transform = CGAffineTransform.init(scaleX: 1.2, y: 1.2) | |
} | |
// Apply glow animation if enabled | |
if self.isGlowing { self.applyGlow(toView: view, withColor: self.glowColor) } | |
}, completion: nil ) | |
} | |
private func showViewController(forIndex index: Int) { | |
if index < tabViewControllerSet.count && index > -1 { | |
// remove old content view controller if exist | |
hideContentController(content: previousContentViewController!) | |
// add new content view controller | |
let currentViewController = tabViewControllerSet[index] | |
previousContentViewController = currentViewController | |
displayContentController(content: currentViewController) | |
} else { | |
debugPrint("Given index out of bounds!") | |
} | |
} | |
private func displayContentController(content: UIViewController) { | |
self.addChild(content) | |
// Change size content view controller to fit the container view controller size | |
content.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height - tabbarHeight) | |
self.view.addSubview(content.view) | |
content.didMove(toParent: self) | |
} | |
private func hideContentController(content: UIViewController) { | |
content.willMove(toParent: nil) | |
content.view.removeFromSuperview() | |
content.removeFromParent() | |
} | |
private func applyGlow(toView: UIView, withColor: UIColor) { | |
toView.layer.shadowPath = CGPath(roundedRect: toView.bounds, cornerWidth: 5, cornerHeight: 5, transform: nil) | |
toView.layer.shadowColor = withColor.cgColor | |
toView.layer.shadowOffset = CGSize.zero | |
toView.layer.shadowRadius = 10 | |
toView.layer.shadowOpacity = 1 | |
} | |
private func removeGlowAnimation(fromView: UIView) { | |
fromView.layer.shadowPath = nil | |
fromView.layer.shadowColor = UIColor.clear.cgColor | |
fromView.layer.shadowOffset = CGSize.zero | |
fromView.layer.shadowRadius = 0 | |
fromView.layer.shadowOpacity = 0 | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to implement it?
From View controller
Or you can call it directly from appDelegate class as rootViewController
Properties that you can customize it as you like:
1- Tabbar background color (default value = purple)
2- Tabbar background image (default value = nil)
3- Tabbar selected button background color (default value = clear)
4- Tabbar selected button glow effect (default value = false)
5- Tabbar titles font size and colors (on selected & unselected) (default value = white & 12 bold)
6- Tabbar selected icons (change icon when tab button is selected) (default value = nil)
7- Add badge to tabbar button {
(You can show badge by sending notification with name "showBadge" and userInfo["index": IndexNumberOfTab])
For example:
}