123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- //
- // ESTabBar.swift
- //
- // Created by Vincent Li on 2017/2/8.
- // Copyright (c) 2013-2018 ESTabBarController (https://github.com/eggswift/ESTabBarController)
- //
- // 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
- /// 对原生的UITabBarItemPositioning进行扩展,通过UITabBarItemPositioning设置时,系统会自动添加insets,这使得添加背景样式的需求变得不可能实现。ESTabBarItemPositioning完全支持原有的item Position 类型,除此之外还支持完全fill模式。
- ///
- /// - automatic: UITabBarItemPositioning.automatic
- /// - fill: UITabBarItemPositioning.fill
- /// - centered: UITabBarItemPositioning.centered
- /// - fillExcludeSeparator: 完全fill模式,布局不覆盖tabBar顶部分割线
- /// - fillIncludeSeparator: 完全fill模式,布局覆盖tabBar顶部分割线
- public enum ESTabBarItemPositioning : Int {
-
- case automatic
-
- case fill
-
- case centered
-
- case fillExcludeSeparator
-
- case fillIncludeSeparator
- }
- /// 对UITabBarDelegate进行扩展,以支持UITabBarControllerDelegate的相关方法桥接
- internal protocol ESTabBarDelegate: NSObjectProtocol {
- /// 当前item是否支持选中
- ///
- /// - Parameters:
- /// - tabBar: tabBar
- /// - item: 当前item
- /// - Returns: Bool
- func tabBar(_ tabBar: UITabBar, shouldSelect item: UITabBarItem) -> Bool
-
- /// 当前item是否需要被劫持
- ///
- /// - Parameters:
- /// - tabBar: tabBar
- /// - item: 当前item
- /// - Returns: Bool
- func tabBar(_ tabBar: UITabBar, shouldHijack item: UITabBarItem) -> Bool
-
- /// 当前item的点击被劫持
- ///
- /// - Parameters:
- /// - tabBar: tabBar
- /// - item: 当前item
- /// - Returns: Void
- func tabBar(_ tabBar: UITabBar, didHijack item: UITabBarItem)
- }
- /// ESTabBar是高度自定义的UITabBar子类,通过添加UIControl的方式实现自定义tabBarItem的效果。目前支持tabBar的大部分属性的设置,例如delegate,items,selectedImge,itemPositioning,itemWidth,itemSpacing等,以后会更加细致的优化tabBar原有属性的设置效果。
- open class ESTabBar: UITabBar {
- internal weak var customDelegate: ESTabBarDelegate?
-
- /// tabBar中items布局偏移量
- public var itemEdgeInsets = UIEdgeInsets.zero
- /// 是否设置为自定义布局方式,默认为空。如果为空,则通过itemPositioning属性来设置。如果不为空则忽略itemPositioning,所以当tabBar的itemCustomPositioning属性不为空时,如果想改变布局规则,请设置此属性而非itemPositioning。
- public var itemCustomPositioning: ESTabBarItemPositioning? {
- didSet {
- if let itemCustomPositioning = itemCustomPositioning {
- switch itemCustomPositioning {
- case .fill:
- itemPositioning = .fill
- case .automatic:
- itemPositioning = .automatic
- case .centered:
- itemPositioning = .centered
- default:
- break
- }
- }
- self.reload()
- }
- }
- /// tabBar自定义item的容器view
- internal var containers = [ESTabBarItemContainer]()
- /// 缓存当前tabBarController用来判断是否存在"More"Tab
- internal weak var tabBarController: UITabBarController?
- /// 自定义'More'按钮样式,继承自ESTabBarItemContentView
- open var moreContentView: ESTabBarItemContentView? = ESTabBarItemMoreContentView.init() {
- didSet { self.reload() }
- }
-
- open override var items: [UITabBarItem]? {
- didSet {
- self.reload()
- }
- }
-
- open var isEditing: Bool = false {
- didSet {
- if oldValue != isEditing {
- self.updateLayout()
- }
- }
- }
-
- open override func setItems(_ items: [UITabBarItem]?, animated: Bool) {
- super.setItems(items, animated: animated)
- self.reload()
- }
-
- open override func beginCustomizingItems(_ items: [UITabBarItem]) {
- ESTabBarController.printError("beginCustomizingItems(_:) is unsupported in ESTabBar.")
- super.beginCustomizingItems(items)
- }
-
- open override func endCustomizing(animated: Bool) -> Bool {
- ESTabBarController.printError("endCustomizing(_:) is unsupported in ESTabBar.")
- return super.endCustomizing(animated: animated)
- }
-
- open override func layoutSubviews() {
- super.layoutSubviews()
- self.updateLayout()
- }
-
- open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
- var b = super.point(inside: point, with: event)
- if !b {
- for container in containers {
- if container.point(inside: CGPoint.init(x: point.x - container.frame.origin.x, y: point.y - container.frame.origin.y), with: event) {
- b = true
- }
- }
- }
- return b
- }
-
- }
- internal extension ESTabBar /* Layout */ {
-
- func updateLayout() {
- guard let tabBarItems = self.items else {
- ESTabBarController.printError("empty items")
- return
- }
-
- let tabBarButtons = subviews.filter { subview -> Bool in
- if let cls = NSClassFromString("UITabBarButton") {
- return subview.isKind(of: cls)
- }
- return false
- } .sorted { (subview1, subview2) -> Bool in
- return subview1.frame.origin.x < subview2.frame.origin.x
- }
-
- if isCustomizing {
- for (idx, _) in tabBarItems.enumerated() {
- tabBarButtons[idx].isHidden = false
- moreContentView?.isHidden = true
- }
- for (_, container) in containers.enumerated(){
- container.isHidden = true
- }
- } else {
- for (idx, item) in tabBarItems.enumerated() {
- if let _ = item as? ESTabBarItem {
- tabBarButtons[idx].isHidden = true
- } else {
- tabBarButtons[idx].isHidden = false
- }
- if isMoreItem(idx), let _ = moreContentView {
- tabBarButtons[idx].isHidden = true
- }
- }
- for (_, container) in containers.enumerated(){
- container.isHidden = false
- }
- }
-
- var layoutBaseSystem = true
- if let itemCustomPositioning = itemCustomPositioning {
- switch itemCustomPositioning {
- case .fill, .automatic, .centered:
- break
- case .fillIncludeSeparator, .fillExcludeSeparator:
- layoutBaseSystem = false
- }
- }
-
- if layoutBaseSystem {
- // System itemPositioning
- for (idx, container) in containers.enumerated(){
- if !tabBarButtons[idx].frame.isEmpty {
- container.frame = tabBarButtons[idx].frame
- }
- }
- } else {
- // Custom itemPositioning
- var x: CGFloat = itemEdgeInsets.left
- var y: CGFloat = itemEdgeInsets.top
- switch itemCustomPositioning! {
- case .fillExcludeSeparator:
- if y <= 0.0 {
- y += 1.0
- }
- default:
- break
- }
- let width = bounds.size.width - itemEdgeInsets.left - itemEdgeInsets.right
- let height = bounds.size.height - y - itemEdgeInsets.bottom
- let eachWidth = itemWidth == 0.0 ? width / CGFloat(containers.count) : itemWidth
- let eachSpacing = itemSpacing == 0.0 ? 0.0 : itemSpacing
-
- for container in containers {
- container.frame = CGRect.init(x: x, y: y, width: eachWidth, height: height)
- x += eachWidth
- x += eachSpacing
- }
- }
- }
- }
- internal extension ESTabBar /* Actions */ {
-
- func isMoreItem(_ index: Int) -> Bool {
- return ESTabBarController.isShowingMore(tabBarController) && (index == (items?.count ?? 0) - 1)
- }
-
- func removeAll() {
- for container in containers {
- container.removeFromSuperview()
- }
- containers.removeAll()
- }
-
- func reload() {
- removeAll()
- guard let tabBarItems = self.items else {
- ESTabBarController.printError("empty items")
- return
- }
- for (idx, item) in tabBarItems.enumerated() {
- let container = ESTabBarItemContainer.init(self, tag: 1000 + idx)
- self.addSubview(container)
- self.containers.append(container)
-
- if let item = item as? ESTabBarItem, let contentView = item.contentView {
- container.addSubview(contentView)
- }
- if self.isMoreItem(idx), let moreContentView = moreContentView {
- container.addSubview(moreContentView)
- }
- }
-
- self.updateAccessibilityLabels()
- self.setNeedsLayout()
- }
-
- @objc func highlightAction(_ sender: AnyObject?) {
- guard let container = sender as? ESTabBarItemContainer else {
- return
- }
- let newIndex = max(0, container.tag - 1000)
- guard newIndex < items?.count ?? 0, let item = self.items?[newIndex], item.isEnabled == true else {
- return
- }
-
- if (customDelegate?.tabBar(self, shouldSelect: item) ?? true) == false {
- return
- }
-
- if let item = item as? ESTabBarItem {
- item.contentView?.highlight(animated: true, completion: nil)
- } else if self.isMoreItem(newIndex) {
- moreContentView?.highlight(animated: true, completion: nil)
- }
- }
-
- @objc func dehighlightAction(_ sender: AnyObject?) {
- guard let container = sender as? ESTabBarItemContainer else {
- return
- }
- let newIndex = max(0, container.tag - 1000)
- guard newIndex < items?.count ?? 0, let item = self.items?[newIndex], item.isEnabled == true else {
- return
- }
-
- if (customDelegate?.tabBar(self, shouldSelect: item) ?? true) == false {
- return
- }
-
- if let item = item as? ESTabBarItem {
- item.contentView?.dehighlight(animated: true, completion: nil)
- } else if self.isMoreItem(newIndex) {
- moreContentView?.dehighlight(animated: true, completion: nil)
- }
- }
-
- @objc func selectAction(_ sender: AnyObject?) {
- guard let container = sender as? ESTabBarItemContainer else {
- return
- }
- select(itemAtIndex: container.tag - 1000, animated: true)
- }
-
- @objc func select(itemAtIndex idx: Int, animated: Bool) {
- let newIndex = max(0, idx)
- let currentIndex = (selectedItem != nil) ? (items?.firstIndex(of: selectedItem!) ?? -1) : -1
- guard newIndex < items?.count ?? 0, let item = self.items?[newIndex], item.isEnabled == true else {
- return
- }
-
- if (customDelegate?.tabBar(self, shouldSelect: item) ?? true) == false {
- return
- }
-
- if (customDelegate?.tabBar(self, shouldHijack: item) ?? false) == true {
- customDelegate?.tabBar(self, didHijack: item)
- if animated {
- if let item = item as? ESTabBarItem {
- item.contentView?.select(animated: animated, completion: {
- item.contentView?.deselect(animated: false, completion: nil)
- })
- } else if self.isMoreItem(newIndex) {
- moreContentView?.select(animated: animated, completion: {
- self.moreContentView?.deselect(animated: animated, completion: nil)
- })
- }
- }
- return
- }
-
- if currentIndex != newIndex {
- if currentIndex != -1 && currentIndex < items?.count ?? 0{
- if let currentItem = items?[currentIndex] as? ESTabBarItem {
- currentItem.contentView?.deselect(animated: animated, completion: nil)
- } else if self.isMoreItem(currentIndex) {
- moreContentView?.deselect(animated: animated, completion: nil)
- }
- }
- if let item = item as? ESTabBarItem {
- item.contentView?.select(animated: animated, completion: nil)
- } else if self.isMoreItem(newIndex) {
- moreContentView?.select(animated: animated, completion: nil)
- }
- } else if currentIndex == newIndex {
- if let item = item as? ESTabBarItem {
- item.contentView?.reselect(animated: animated, completion: nil)
- } else if self.isMoreItem(newIndex) {
- moreContentView?.reselect(animated: animated, completion: nil)
- }
-
- if let tabBarController = tabBarController {
- var navVC: UINavigationController?
- if let n = tabBarController.selectedViewController as? UINavigationController {
- navVC = n
- } else if let n = tabBarController.selectedViewController?.navigationController {
- navVC = n
- }
-
- if let navVC = navVC {
- if navVC.viewControllers.contains(tabBarController) {
- if navVC.viewControllers.count > 1 && navVC.viewControllers.last != tabBarController {
- navVC.popToViewController(tabBarController, animated: true);
- }
- } else {
- if navVC.viewControllers.count > 1 {
- navVC.popToRootViewController(animated: animated)
- }
- }
- }
-
- }
- }
-
- delegate?.tabBar?(self, didSelect: item)
- self.updateAccessibilityLabels()
- }
-
- func updateAccessibilityLabels() {
- guard let tabBarItems = self.items, tabBarItems.count == self.containers.count else {
- return
- }
-
- for (idx, item) in tabBarItems.enumerated() {
- let container = self.containers[idx]
- container.accessibilityIdentifier = item.accessibilityIdentifier
- container.accessibilityTraits = item.accessibilityTraits
-
- if item == selectedItem {
- container.accessibilityTraits = container.accessibilityTraits.union(.selected)
- }
-
- if let explicitLabel = item.accessibilityLabel {
- container.accessibilityLabel = explicitLabel
- container.accessibilityHint = item.accessibilityHint ?? container.accessibilityHint
- } else {
- var accessibilityTitle = ""
- if let item = item as? ESTabBarItem {
- accessibilityTitle = item.accessibilityLabel ?? item.title ?? ""
- }
- if self.isMoreItem(idx) {
- accessibilityTitle = NSLocalizedString("More_TabBarItem", bundle: Bundle(for:ESTabBarController.self), comment: "")
- }
-
- let formatString = NSLocalizedString(item == selectedItem ? "TabBarItem_Selected_AccessibilityLabel" : "TabBarItem_AccessibilityLabel",
- bundle: Bundle(for: ESTabBarController.self),
- comment: "")
- container.accessibilityLabel = String(format: formatString, accessibilityTitle, idx + 1, tabBarItems.count)
- }
-
- }
- }
- }
|