Skip to content

Instantly share code, notes, and snippets.

@Coder-ACJHP
Last active July 13, 2020 14:04
Show Gist options
  • Save Coder-ACJHP/1400449977ee70fa46bb42e4565f9c87 to your computer and use it in GitHub Desktop.
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
//
// 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
}
}
@Coder-ACJHP
Copy link
Author

Coder-ACJHP commented Apr 2, 2019

It's look like this:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment