You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

500 lines
18 KiB

// AnimationTabBarController.swift
//
// Copyright (c) 11/10/14 Ramotion Inc. (http://ramotion.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
// MARK: Custom Badge
extension RAMAnimatedTabBarItem {
/// The current badge value
open override var badgeValue: String? {
get {
return badge?.text
}
set(newValue) {
if newValue == nil {
badge?.removeFromSuperview()
badge = nil
return
}
if let iconView = iconView, let contanerView = iconView.icon.superview, badge == nil {
badge = RAMBadge.badge()
badge!.addBadgeOnView(contanerView)
}
badge?.text = newValue
}
}
}
/// UITabBarItem with animation
open class RAMAnimatedTabBarItem: UITabBarItem {
@IBInspectable open var yOffSet: CGFloat = 0
open override var isEnabled: Bool {
didSet {
iconView?.icon.alpha = isEnabled == true ? 1 : 0.5
iconView?.textLabel.alpha = isEnabled == true ? 1 : 0.5
}
}
/// animation for UITabBarItem. use RAMFumeAnimation, RAMBounceAnimation, RAMRotationAnimation, RAMFrameItemAnimation, RAMTransitionAnimation
/// or create custom anmation inherit RAMItemAnimation
@IBOutlet open var animation: RAMItemAnimation!
/// The font used to render the UITabBarItem text.
@IBInspectable open var textFontSize: CGFloat = 10
/// The color of the UITabBarItem text.
@IBInspectable open var textColor: UIColor = UIColor.black
/// The tint color of the UITabBarItem icon.
@IBInspectable open var iconColor: UIColor = UIColor.clear // if alpha color is 0 color ignoring
open var bgDefaultColor: UIColor = UIColor.clear // background color
open var bgSelectedColor: UIColor = UIColor.clear
// The current badge value
open var badge: RAMBadge? // use badgeValue to show badge
// Container for icon and text in UITableItem.
open var iconView: (icon: UIImageView, textLabel: UILabel)?
/**
Start selected animation
*/
open func playAnimation() {
assert(animation != nil, "add animation in UITabBarItem")
guard animation != nil && iconView != nil else {
return
}
animation.playAnimation(iconView!.icon, textLabel: iconView!.textLabel)
}
/**
Start unselected animation
*/
open func deselectAnimation() {
guard animation != nil && iconView != nil else {
return
}
animation.deselectAnimation(
iconView!.icon,
textLabel: iconView!.textLabel,
defaultTextColor: textColor,
defaultIconColor: iconColor)
}
/**
Set selected state without animation
*/
open func selectedState() {
guard animation != nil && iconView != nil else {
return
}
animation.selectedState(iconView!.icon, textLabel: iconView!.textLabel)
}
/**
Set deselected state without animation
*/
open func deselectedState() {
guard animation != nil && iconView != nil else {
return
}
animation.deselectedState(iconView!.icon, textLabel: iconView!.textLabel)
}
}
extension RAMAnimatedTabBarController {
/**
Change selected color for each UITabBarItem
- parameter textSelectedColor: set new color for text
- parameter iconSelectedColor: set new color for icon
*/
open func changeSelectedColor(_ textSelectedColor: UIColor, iconSelectedColor: UIColor) {
let items = tabBar.items as! [RAMAnimatedTabBarItem]
for index in 0 ..< items.count {
let item = items[index]
item.animation.textSelectedColor = textSelectedColor
item.animation.iconSelectedColor = iconSelectedColor
if item == tabBar.selectedItem {
item.selectedState()
}
}
}
/**
Hide UITabBarController
- parameter isHidden: A Boolean indicating whether the UITabBarController is displayed
*/
open func animationTabBarHidden(_ isHidden: Bool) {
guard let items = tabBar.items as? [RAMAnimatedTabBarItem] else {
fatalError("items must inherit RAMAnimatedTabBarItem")
}
for item in items {
if let iconView = item.iconView {
iconView.icon.superview?.isHidden = isHidden
}
}
tabBar.isHidden = isHidden
}
/**
Selected UITabBarItem with animaton
- parameter from: Index for unselected animation
- parameter to: Index for selected animation
*/
open func setSelectIndex(from: Int, to: Int) {
selectedIndex = to
guard let items = tabBar.items as? [RAMAnimatedTabBarItem] else {
fatalError("items must inherit RAMAnimatedTabBarItem")
}
let containerFrom = items[from].iconView?.icon.superview
containerFrom?.backgroundColor = items[from].bgDefaultColor
items[from].deselectAnimation()
let containerTo = items[to].iconView?.icon.superview
containerTo?.backgroundColor = items[to].bgSelectedColor
items[to].playAnimation()
}
}
/// UITabBarController with item animations
open class RAMAnimatedTabBarController: UITabBarController {
/**
The animated items displayed by the tab bar.
**/
open var animatedItems: [RAMAnimatedTabBarItem] {
return tabBar.items as? [RAMAnimatedTabBarItem] ?? []
}
/**
Show bottom line for indicating selected item, default value is false
**/
open var isBottomLineShow: Bool = false {
didSet {
if isBottomLineShow {
if bottomLine == nil { createBottomLine() }
} else {
if bottomLine != nil { removeBottomLine() }
}
}
}
/**
Bottom line color
**/
open var bottomLineColor: UIColor = .black {
didSet {
bottomLine?.backgroundColor = bottomLineColor
}
}
/**
Bottom line time of animations duration
**/
open var bottomLineMoveDuration: TimeInterval = 0.3
var containers: [String: UIView] = [:]
open override var viewControllers: [UIViewController]? {
didSet {
initializeContainers()
}
}
open override func setViewControllers(_ viewControllers: [UIViewController]?, animated: Bool) {
super.setViewControllers(viewControllers, animated: animated)
initializeContainers()
}
open override var selectedIndex: Int {
didSet {
self.setBottomLinePosition(index: selectedIndex)
}
}
var lineLeadingConstraint: NSLayoutConstraint?
var bottomLine: UIView?
// MARK: life circle
open override func viewDidLoad() {
super.viewDidLoad()
initializeContainers()
}
fileprivate func initializeContainers() {
containers.values.forEach { $0.removeFromSuperview() }
containers = createViewContainers()
if !containers.isEmpty {
createCustomIcons(containers)
}
}
// MARK: create methods
fileprivate func createCustomIcons(_ containers: [String: UIView]) {
if let items = tabBar.items, items.count > 5 { fatalError("More button not supported") }
guard let items = tabBar.items as? [RAMAnimatedTabBarItem] else {
fatalError("items must inherit RAMAnimatedTabBarItem")
}
var index = 0
for item in items {
guard let container = containers["container\(items.count - 1 - index)"] else {
fatalError()
}
container.tag = index
let renderMode = item.iconColor.cgColor.alpha == 0 ? UIImageRenderingMode.alwaysOriginal :
UIImageRenderingMode.alwaysTemplate
let iconImage = item.image ?? item.iconView?.icon.image
let icon = UIImageView(image: iconImage?.withRenderingMode(renderMode))
icon.translatesAutoresizingMaskIntoConstraints = false
icon.tintColor = item.iconColor
icon.highlightedImage = item.selectedImage?.withRenderingMode(renderMode)
// text
let textLabel = UILabel()
if let title = item.title, !title.isEmpty {
textLabel.text = title
} else {
textLabel.text = item.iconView?.textLabel.text
}
textLabel.backgroundColor = UIColor.clear
textLabel.textColor = item.textColor
textLabel.font = UIFont.systemFont(ofSize: item.textFontSize)
textLabel.textAlignment = NSTextAlignment.center
textLabel.translatesAutoresizingMaskIntoConstraints = false
container.backgroundColor = (items as [RAMAnimatedTabBarItem])[index].bgDefaultColor
container.addSubview(icon)
let itemSize = item.image?.size ?? CGSize(width: 25, height: 25)
createConstraints(icon, container: container, size: itemSize, yOffset: -5 - 2)
container.addSubview(textLabel)
let textLabelWidth = tabBar.frame.size.width / CGFloat(items.count) - 5.0
createConstraints(textLabel, container: container, width: textLabelWidth, yOffset: 16 - 5, heightRelation: .greaterThanOrEqual)
if item.isEnabled == false {
icon.alpha = 0.5
textLabel.alpha = 0.5
}
item.iconView = (icon: icon, textLabel: textLabel)
if 0 == index { // selected first elemet
item.selectedState()
container.backgroundColor = (items as [RAMAnimatedTabBarItem])[index].bgSelectedColor
} else {
item.deselectedState()
container.backgroundColor = (items as [RAMAnimatedTabBarItem])[index].bgDefaultColor
}
item.image = nil
item.title = ""
index += 1
}
}
fileprivate func createConstraints(_ view: UIView, container: UIView, size: CGSize, yOffset: CGFloat) {
createConstraints(view, container: container, width: size.width, height: size.height, yOffset: yOffset)
}
fileprivate func createConstraints(_ view: UIView, container: UIView, width: CGFloat? = nil, height: CGFloat? = nil, yOffset: CGFloat, heightRelation: NSLayoutRelation = .equal) {
let constX = NSLayoutConstraint(item: view,
attribute: NSLayoutAttribute.centerX,
relatedBy: NSLayoutRelation.equal,
toItem: container,
attribute: NSLayoutAttribute.centerX,
multiplier: 1,
constant: 0)
container.addConstraint(constX)
let constY = NSLayoutConstraint(item: view,
attribute: NSLayoutAttribute.centerY,
relatedBy: NSLayoutRelation.equal,
toItem: container,
attribute: NSLayoutAttribute.centerY,
multiplier: 1,
constant: yOffset)
container.addConstraint(constY)
if let width = width {
let constW = NSLayoutConstraint(item: view,
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: nil,
attribute: NSLayoutAttribute.notAnAttribute,
multiplier: 1,
constant: width)
view.addConstraint(constW)
}
if let height = height {
let constH = NSLayoutConstraint(item: view,
attribute: NSLayoutAttribute.height,
relatedBy: heightRelation,
toItem: nil,
attribute: NSLayoutAttribute.notAnAttribute,
multiplier: 1,
constant: height)
view.addConstraint(constH)
}
}
fileprivate func createViewContainers() -> [String: UIView] {
guard let items = tabBar.items, items.count > 0 else {
return [:]
}
var containersDict: [String: UIView] = [:]
for index in 0 ..< items.count {
let viewContainer = createViewContainer()
containersDict["container\(index)"] = viewContainer
}
var formatString = "H:|-(0)-[container0]"
for index in 1 ..< items.count {
formatString += "-(0)-[container\(index)(==container0)]"
}
formatString += "-(0)-|"
var constranints:[NSLayoutConstraint]!
if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft{
constranints = NSLayoutConstraint.constraints(withVisualFormat: formatString,
options: NSLayoutFormatOptions.directionLeftToRight,
metrics: nil,
views: (containersDict as [String: AnyObject]))
}else{
constranints = NSLayoutConstraint.constraints(withVisualFormat: formatString,
options: NSLayoutFormatOptions.directionRightToLeft,
metrics: nil,
views: (containersDict as [String: AnyObject]))
}
view.addConstraints(constranints)
return containersDict
}
fileprivate func createViewContainer() -> UIView {
let viewContainer = UIView()
viewContainer.translatesAutoresizingMaskIntoConstraints = false
viewContainer.isExclusiveTouch = true
view.addSubview(viewContainer)
// add gesture
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(RAMAnimatedTabBarController.tapHandler(_:)))
tapGesture.numberOfTouchesRequired = 1
viewContainer.addGestureRecognizer(tapGesture)
// add constrains
viewContainer.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
let constH = NSLayoutConstraint(item: viewContainer,
attribute: NSLayoutAttribute.height,
relatedBy: NSLayoutRelation.equal,
toItem: nil,
attribute: NSLayoutAttribute.notAnAttribute,
multiplier: 1,
constant: 49)
viewContainer.addConstraint(constH)
return viewContainer
}
// MARK: actions
@objc open func tapHandler(_ gesture: UIGestureRecognizer) {
guard let items = tabBar.items as? [RAMAnimatedTabBarItem],
let gestureView = gesture.view else {
fatalError("items must inherit RAMAnimatedTabBarItem")
}
let currentIndex = gestureView.tag
if items[currentIndex].isEnabled == false { return }
let controller = childViewControllers[currentIndex]
if let shouldSelect = delegate?.tabBarController?(self, shouldSelect: controller)
, !shouldSelect {
return
}
if selectedIndex != currentIndex {
let animationItem: RAMAnimatedTabBarItem = items[currentIndex]
animationItem.playAnimation()
let deselectItem = items[selectedIndex]
let containerPrevious: UIView = deselectItem.iconView!.icon.superview!
containerPrevious.backgroundColor = items[currentIndex].bgDefaultColor
deselectItem.deselectAnimation()
let container: UIView = animationItem.iconView!.icon.superview!
container.backgroundColor = items[currentIndex].bgSelectedColor
selectedIndex = gestureView.tag
} else if selectedIndex == currentIndex {
if let navVC = self.viewControllers![selectedIndex] as? UINavigationController {
navVC.popToRootViewController(animated: true)
}
}
delegate?.tabBarController?(self, didSelect: controller)
}
}