溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Tab Bar圖標(biāo)怎么用

發(fā)布時間:2021-09-13 14:30:43 來源:億速云 閱讀:122 作者:小新 欄目:移動開發(fā)

這篇文章主要介紹了Tab Bar圖標(biāo)怎么用,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

背景

框架自帶的 Tab Bar 相信大家已經(jīng)熟悉得不能再熟悉了,一般使用的時候不過是設(shè)置兩個圖標(biāo)代表選中和未選中兩種狀態(tài),難免有一些平淡。后來很多控件就在標(biāo)簽選中時進(jìn)行一些比較抓眼球的動畫,不過我覺得大部分都是為了動畫而動畫。直到后來我看到Outlook客戶端的動畫時,我才意識到原來還可以跟用戶的交互結(jié)合在一起。

Tab Bar圖標(biāo)怎么用

圖1 標(biāo)簽圖標(biāo)跟隨手勢進(jìn)行不同的動畫

有意思吧,不過本文并不是要仿制個一模一樣的出來,會有稍微變化:

Tab Bar圖標(biāo)怎么用
圖2 本文完成的最終效果

實(shí)現(xiàn)分析

寫代碼之前,咱先討論下實(shí)現(xiàn)的方法,相信你已經(jīng)猜到標(biāo)簽頁的圖標(biāo)顯然已經(jīng)不是圖片,而是一個自定義的UIView。將一個視圖掛載到原本圖標(biāo)的位置并不是一件難事,稍微有些復(fù)雜的是數(shù)字滾輪效果的實(shí)現(xiàn),別看它數(shù)字不停地在滾動,仔細(xì)看其實(shí)最多顯示2種數(shù)字,也就說只要2個Label就夠了。

基于篇幅,文章不會涉及右側(cè)的時鐘效果,感興趣請直接參考源碼。

數(shù)字滾輪

打開項(xiàng)目TabBarInteraction,新建文件WheelView.swift,它是UIView的子類。首先設(shè)置好初始化函數(shù):

class WheelView: UIView {
 required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  setupView()
 }
 override init(frame: CGRect) {
  super.init(frame: frame)
  setupView()
 }
}

接著創(chuàng)建兩個Label實(shí)例,代表滾輪中的上下兩個Label:

private lazy var toplabel: UILabel = {
 return createDefaultLabel()
}()

private lazy var bottomLabel: UILabel = {
 return createDefaultLabel()
}()

private func createDefaultLabel() -> UILabel {
 let label = UILabel() 
 label.textAlignment = NSTextAlignment.center
 label.adjustsFontSizeToFitWidth = true
 label.translatesAutoresizingMaskIntoConstraints = false
 return label
}

現(xiàn)在來完成setupView()方法,在這方法中將上述兩個Label添加到視圖中,然后設(shè)置約束將它們的四邊都與layoutMarginsGuide對齊。

private func setupView() {
 translatesAutoresizingMaskIntoConstraints = false
 for label in [toplabel, bottomLabel] {
  addSubview(label)
  NSLayoutConstraint.activate([
   label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
   label.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
   label.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor),
   label.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor)
  ])
 }
}

有人可能會問現(xiàn)在這樣兩個Label不是重疊的狀態(tài)嗎?不著急,接下來我們會根據(jù)參數(shù)動態(tài)地調(diào)整它們的大小和位置。

添加兩個實(shí)例變量progress和contents,分別表示滾動的總體進(jìn)度和顯示的全部內(nèi)容。

var progress: Float = 0.0
var contents = [String]()

我們接下來要根據(jù)這兩個變量計(jì)算出當(dāng)前兩個Label顯示的內(nèi)容以及它們的縮放位置。這些計(jì)算都在progress的didSet里完成:

var progress: Float = 0.0 {
 didSet {
  progress = min(max(progress, 0.0), 1.0) 
  guard contents.count > 0 else { return }
  
  /** 根據(jù) progress 和 contents 計(jì)算出上下兩個 label 顯示的內(nèi)容以及 label 的壓縮程度和位置
   *
   * Example: 
   * progress = 0.4, contents = ["A","B","C","D"]
   *
   * 1)計(jì)算兩個label顯示的內(nèi)容
   * topIndex = 4 * 0.4 = 1.6, topLabel.text = contents[1] = "B"
   * bottomIndex = 1.6 + 1 = 2.6, bottomLabel.text = contents[2] = "C" 
   * 
   * 2) 計(jì)算兩個label如何壓縮和位置調(diào)整,這是實(shí)現(xiàn)滾輪效果的原理
   * indexOffset = 1.6 % 1 = 0.6
   * halfHeight = bounds.height / 2
   * ┌─────────────┐    ┌─────────────┐         
   * |┌───────────┐| scaleY |    |       
   * ||   || 1-0.6=0.4 |    | translationY  
   * || topLabel || ----------> |┌─ topLabel─┐| ------------------ 
   * ||   ||    |└───────────┘| -halfHeight * 0.6 ? ┌─────────────┐
   * |└───────────┘|    |    |     ? |┌─ toplabel─┐|
   * └─────────────┘    └─────────────┘     ? |└───────────┘|
   *                 ? |┌───────────┐|
   * ┌─────────────┐    ┌─────────────┐     ? ||bottomLabel||
   * |┌───────────┐| scaleY |    |     ? |└───────────┘|
   * ||   || 0.6  |┌───────────┐| translationY  ? └─────────────┘
   * ||bottomLabel|| ----------> ||bottomLabel|| ----------------- 
   * ||   ||    |└───────────┘| halfHeight * 0.4  
   * |└───────────┘|    |    |      
   * └─────────────┘    └─────────────┘      
   *
   * 可以想象出,當(dāng) indexOffset 從 0.0 遞增到 0.999 過程中,
   * topLabel 從滿視圖越縮越小至0,而 bottomLabel剛好相反越變越大至滿視圖,即形成一次完整的滾動
   */
  let topIndex = min(max(0.0, Float(contents.count) * progress), Float(contents.count - 1))
  let bottomIndex = min(topIndex + 1, Float(contents.count - 1))
  let indexOffset = topIndex.truncatingRemainder(dividingBy: 1)
  
  toplabel.text = contents[Int(topIndex)]
  toplabel.transform = CGAffineTransform(scaleX: 1.0, y: CGFloat(1 - indexOffset))
   .concatenating(CGAffineTransform(translationX: 0, y: -(toplabel.bounds.height / 2) * CGFloat(indexOffset)))
   
  bottomLabel.text = contents[Int(bottomIndex)]
  bottomLabel.transform = CGAffineTransform(scaleX: 1.0, y: CGFloat(indexOffset))
   .concatenating(CGAffineTransform(translationX: 0, y: (bottomLabel.bounds.height / 2) * (1 - CGFloat(indexOffset))))
 }
}

最后我們還要向外公開一些樣式進(jìn)行自定義:

extension WheelView {
 /// 前景色變化事件
 override func tintColorDidChange() {
  [toplabel, bottomLabel].forEach { $0.textColor = tintColor }
  layer.borderColor = tintColor.cgColor
 }
 /// 背景色
 override var backgroundColor: UIColor? {
  get { return toplabel.backgroundColor }
  set { [toplabel, bottomLabel].forEach { $0.backgroundColor = newValue } }
 }
 /// 邊框?qū)挾?
 var borderWidth: CGFloat {
  get { return layer.borderWidth }
  set {
   layoutMargins = UIEdgeInsets(top: newValue, left: newValue, bottom: newValue, right: newValue)
   layer.borderWidth = newValue
  }
 }
 /// 字體
 var font: UIFont {
  get { return toplabel.font }
  set { [toplabel, bottomLabel].forEach { $0.font = newValue } }
 }
}

至此,整個滾輪效果已經(jīng)完成。

掛載視圖

在FirstViewController中實(shí)例化剛才自定義的視圖,設(shè)置好字體、邊框、背景色、Contents等內(nèi)容,別忘了isUserInteractionEnabled設(shè)置為false,這樣就不會影響原先的事件響應(yīng)。

 override func viewDidLoad() {
  super.viewDidLoad()
  // Do any additional setup after loading the view.
  
  tableView.delegate = self
  tableView.dataSource = self
  tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")
  tableView.rowHeight = 44

  wheelView = WheelView(frame: CGRect.zero)
  wheelView.font = UIFont.systemFont(ofSize: 15, weight: .bold)
  wheelView.borderWidth = 1
  wheelView.backgroundColor = UIColor.white
  wheelView.contents = data
  wheelView.isUserInteractionEnabled = false
}

然后要把視圖掛載到原先的圖標(biāo)上,viewDidLoad()方法底部新增代碼:

 override func viewDidLoad() {
 ...
 guard let parentController = self.parent as? UITabBarController else { return }
 let controllerIndex = parentController.children.firstIndex(of: self)!
 var tabBarButtons = parentController.tabBar.subviews.filter({
  type(of: $0).description().isEqual("UITabBarButton")
 })
 guard !tabBarButtons.isEmpty else { return }
 let tabBarButton = tabBarButtons[controllerIndex]
 let swappableImageViews = tabBarButton.subviews.filter({
  type(of: $0).description().isEqual("UITabBarSwappableImageView")
 })
 guard !swappableImageViews.isEmpty else { return }
 let swappableImageView = swappableImageViews.first!
 tabBarButton.addSubview(wheelView)
 swappableImageView.isHidden = true
 NSLayoutConstraint.activate([
  wheelView.widthAnchor.constraint(equalToConstant: 25),
  wheelView.heightAnchor.constraint(equalToConstant: 25),
  wheelView.centerXAnchor.constraint(equalTo: swappableImageView.centerXAnchor),
  wheelView.centerYAnchor.constraint(equalTo: swappableImageView.centerYAnchor)
 ])
 }

上述代碼的目的是最終找到對應(yīng)標(biāo)簽UITabBarButton內(nèi)類型為UITabBarSwappableImageView的視圖并替換它??瓷先ハ喈?dāng)復(fù)雜,但是它盡可能地避免出現(xiàn)意外情況導(dǎo)致程序異常。只要以后UIkit不更改類型UITabBarButton和UITabBarSwappableImageView,以及他們的包含關(guān)系,程序基本不會出現(xiàn)意外,最多導(dǎo)致自定義的視圖掛載不上去而已。另外一個好處是FirstViewController不用去擔(dān)心它被添加到TabBarController中的第幾個標(biāo)簽上??傮w來說這個方法并不完美,但目前似乎也沒有更好的方法?

實(shí)際上還可以將上面的代碼剝離出來,放到名為TabbarInteractable的protocol的默認(rèn)實(shí)現(xiàn)上。有需要的ViewController只要宣布遵守該協(xié)議,然后在viewDidLoad方法中調(diào)用一個方法即可實(shí)現(xiàn)整個替換過程。

只剩下最后一步了,我們知道UITableView是UIScrollView的子類。在它滾動的時候,F(xiàn)irsViewController作為UITableView的delegate,同樣會收到scrollViewDidScroll方法的調(diào)用,所以在這個方法里更新滾動的進(jìn)度再合適不過了:

// MARK: UITableViewDelegate
extension FirstViewController: UITableViewDelegate {
 func scrollViewDidScroll(_ scrollView: UIScrollView) {
  //`progress`怎么計(jì)算取決于你需求,這里的是為了把`tableview`當(dāng)前可見區(qū)域最底部的2個數(shù)字給顯示出來。
  let progress = Float((scrollView.contentOffset.y + tableView.bounds.height - tableView.rowHeight) / scrollView.contentSize.height)
  wheelView.progress = progress
 }
}

把項(xiàng)目跑起來看看吧,你會得到文章開頭的效果。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Tab Bar圖標(biāo)怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI