iOS 小組件 - 自定義導(dǎo)航欄 + 原有業(yè)務(wù)自定義導(dǎo)航欄替換

2025.01.31 工作變動(dòng)原因,故將一些工作期間Tapd內(nèi)部寫的Wiki文檔轉(zhuǎn)移到個(gè)人博客。

借著建立新項(xiàng)目(成長體系)的時(shí)候,寫了一個(gè)通用導(dǎo)航欄供業(yè)務(wù)使用居扒,并對(duì)舊業(yè)務(wù)(上進(jìn)青年)中的自定義導(dǎo)航欄進(jìn)行重構(gòu)替換。

一丑慎、設(shè)計(jì)思路

從兼容 上進(jìn)青年APP 所有現(xiàn)有的業(yè)務(wù)考慮出發(fā)喜喂,需要考慮快捷兼容3種常用的樣式:

1.不需要額外設(shè)置瓤摧,默認(rèn)為系統(tǒng)樣式(固定顯示)


tapd_44062861_1716891573_115.jpg

2.從0到1滑動(dòng)顯示的樣式(跟隨滾動(dòng)透明度變化)


tapd_44062861_1716891583_635.jpg

3.自定義樣式(右側(cè)item \ 返回按鈕 \ 白色和彩色底色 \ 分割線)


tapd_44062861_1716892527_658.jpg

二、基礎(chǔ)類具體實(shí)現(xiàn)

好的封裝是可以一個(gè)簡單的方法就完成調(diào)用的玉吁,常用的樣式配置照弥,只需要一個(gè)自定義的初始化方法就可以完成。

初始化:

    /**
         導(dǎo)航欄初始化
         參數(shù):
         style: UIUserInterfaceStyle 普通模式 / 暗黑模式(已禁用进副,無效)
         alwaysDisplay: 固定顯示(默認(rèn)不固定)
         navTitle: 標(biāo)題
         navBGColor: 背景顏色(默認(rèn)純白)
         needBackButton: 需要左側(cè)返回按鈕
         needDivider: 需要分割線(默認(rèn)不需要)
         */
        init(style: YGUIUserInterfaceStyle = .light, alwaysDisplay: Bool = false, navTitle: String = "", navBGColor: UIColor = .white, needBackButton: Bool = true, needDivider: Bool = false)

如果所有參數(shù)不傳这揣,只傳標(biāo)題文本,就會(huì)得到一個(gè)簡單的默認(rèn)導(dǎo)航欄影斑,開箱即用给赞。

三、更多的自定義實(shí)現(xiàn)

1. 跟隨頁面滑動(dòng)鸥昏,透明度變化的導(dǎo)航欄

日常的導(dǎo)航欄樣式中塞俱,還需要兼容一個(gè) 根據(jù)滑動(dòng)姐帚,0-1透明度變化的樣式吏垮,只需要監(jiān)聽滑動(dòng)的時(shí)候調(diào)用一下 scrollToChangeStatus(_ viewController: UIViewController, currentContentOffsetY: CGFloat, maxOffsetY: CGFloat) 方法即可。

/**
 滾動(dòng)頁面罐旗,改變導(dǎo)航欄狀態(tài)
 
 viewController: 持有YGNavView的控制器
 currentContentOffsetY: 當(dāng)前y軸滑動(dòng)距離
 maxOffsetY: 直到完全顯示的最大滾動(dòng)距離
 */
func scrollToChangeStatus(_ viewController: UIViewController, currentContentOffsetY: CGFloat, maxOffsetY: CGFloat) {
    // 導(dǎo)航欄漸變透明度
    var alpha: CGFloat = 0
    // 超出最大距離膳汪,完全顯示導(dǎo)航欄
    if currentContentOffsetY >= (maxOffsetY - navBar_Height) {
        alpha = 1
        self.navTitleLabel.isHidden = false
    } else {
        alpha = currentContentOffsetY / (maxOffsetY - navBar_Height)
        self.navTitleLabel.isHidden = true
    }
    self.backgroundColor = self.navBGColor.withAlphaComponent(alpha)
}

2. 自定義右側(cè)item

tapd_44062861_1716892527_658.jpg

如圖,有一些特殊的常見頁面也需要右側(cè)的item(比如分享按鈕)九秀。

這時(shí)候只需要調(diào)用一下 reloadRightItem(items: [YGNavItem], itemSpacing: CGFloat = 8.0) 即可遗嗽。

    /**
     加載右側(cè)自定義item
     
     items: 從右到左排列的自定義item
     itemSpacing: item間距
     */
    func reloadRightItem(items: [YGNavItem], itemSpacing: CGFloat = 8.0, itemsAction: @escaping ((Int, YGNavItem) -> ())) {
        self.navItemModelArray = items
        // 移除原有的items
        for (_, item) in rightItems.enumerated() {
            item.removeFromSuperview()
        }
        rightItems.removeAll()
        
        // 添加items
        for (index, item) in items.enumerated() {
            // 容錯(cuò)處理
            if item.localImage == nil && item.imageUrl == nil && item.itemTitle == nil { return }
            // 根據(jù)item屬性生成button
            let button = createUIButtonWith(item, itemSpacing: itemSpacing, index: index)
            rightItems.append(button)
        }
        /// 賦值點(diǎn)擊事件
        self.itemsAction = itemsAction
    }

四、實(shí)際應(yīng)用

項(xiàng)目里重構(gòu)替換鼓蜒,比如設(shè)計(jì)思路中痹换,圖1默認(rèn)樣式的調(diào)用:

    /// 導(dǎo)航欄
    lazy var navView: YGNavView = {
        let navView = YGNavView(alwaysDisplay: true, navTitle: "上進(jìn)分明細(xì)", needDivider: true)
        return navView
    }()

viewDidLoad() 中,調(diào)用 view.addSubview(navView) 后都弹,具體效果如下:

tapd_44062861_1716891573_115.jpg

五娇豫、具體源碼

在 iOS 17.0 系統(tǒng)版本之后的蘋果手機(jī)里,iPhone狀態(tài)欄已經(jīng)可以歸系統(tǒng)管理畅厢,能夠自動(dòng)適應(yīng)APP背景來對(duì)狀態(tài)欄進(jìn)行管理冯痢,故在源碼中去掉了手動(dòng)管理狀態(tài)欄的代碼

因此在源碼中的 style: YGUIUserInterfaceStyle 已經(jīng)沒有管理狀態(tài)欄框杜,只對(duì)標(biāo)題和返回按鈕進(jìn)行管理浦楣,如果有需要的話請(qǐng)自己適配。

//  Created by Yim on 2024/3/7.
//  通用導(dǎo)航欄

import UIKit

public enum YGUIUserInterfaceStyle: Int {
    
//    case unspecified = 0

    case light = 1

    case dark = 2
}


/// 通用導(dǎo)航欄
class YGNavView: UIView {
    
    /// 導(dǎo)航欄樣式
    var navBarStyle: YGUIUserInterfaceStyle = .light
    /// 導(dǎo)航欄背景顏色
    var navBGColor: UIColor = .white
    /// 右側(cè)item回調(diào) ( 從右到左的index, 當(dāng)前model )
    var itemsAction: ((Int, YGNavItem) -> ())?
    /// item模型
    var navItemModelArray: [YGNavItem]?
    
    /**
     導(dǎo)航欄初始化
     參數(shù):
     style: UIUserInterfaceStyle 普通模式 / 暗黑模式(已禁用咪辱,無效)
     alwaysDisplay: 固定顯示(默認(rèn)不固定)
     navTitle: 標(biāo)題
     navBGColor: 背景顏色(默認(rèn)純白)
     needBackButton: 需要左側(cè)返回按鈕
     needDivider: 需要分割線(默認(rèn)不需要)
     */
    init(style: YGUIUserInterfaceStyle = .light, alwaysDisplay: Bool = false, navTitle: String = "", navBGColor: UIColor = .white, needBackButton: Bool = true, needDivider: Bool = false) {
        super.init(frame: CGRect.init(x: 0, y: 0, width: screenWidth, height: navBar_Height))
        self.navBarStyle = style
        self.navTitleLabel.text = navTitle
        self.navTitleLabel.isHidden = !alwaysDisplay
        self.navBGColor = navBGColor
        self.backgroundColor = alwaysDisplay ? navBGColor : navBGColor.withAlphaComponent(0.0)
        self.grayLine.isHidden = !needDivider
        
        // 普通模式振劳,白底黑字
        if style == .light {
            backBtn.setImage(UIImage.yg_image("navi_leftBack_black"), for: .normal)
            navTitleLabel.textColor = contentColor
        }
        // 暗黑模式,彩底白字
        else if style == .dark {
            backBtn.setImage(UIImage.yg_image("navi_leftBack_white"), for: .normal)
            navTitleLabel.textColor = .white
        }
        
        // ———————— setupUI ————————

        addSubview(navTitleLabel)
        navTitleLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.bottom.equalToSuperview().offset(-10.5)
            make.height.equalTo(21)
        }
        
        addSubview(grayLine)
        grayLine.snp.makeConstraints { make in
            make.left.right.equalToSuperview()
            make.bottom.equalToSuperview()
            make.height.equalTo(0.5)
        }
        
        // 是否需要返回按鈕
        if needBackButton {
            addSubview(backBtn)
            backBtn.snp.makeConstraints { make in
                make.left.equalToSuperview().offset(14)
                make.size.equalTo(CGSize(width: 32, height: 32))
                make.centerY.equalTo(navTitleLabel)
            }
            // rxSwift的按鈕點(diǎn)擊事件綁定油狂,請(qǐng)自己更換按鈕事件方式
            backBtn.rx.tap.subscribe(onNext: { _ in
                // 退出頁面
                YAYNavigator.shared.pop()
            })
            .disposed(by: rx.disposeBag)
        }
        
        // 記錄進(jìn)來頁面的狀態(tài)欄(已棄用历恐,iOS 17.0之后的系統(tǒng)會(huì)自動(dòng)管理狀態(tài)欄)
        if originalStyle == nil {
            if #available(iOS 13.0, *) {
                switch traitCollection.userInterfaceStyle {
                case .light:
                    self.originalStyle = .light
                    
                case .dark:
                    self.originalStyle = .dark
                    
                default:
                    break
                }
            } else {
                // iOS 13.0以下不做適配
            }
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    /**
     加載右側(cè)自定義item
     
     items: 從右到左排列的自定義item
     itemSpacing: item間距
     */
    func reloadRightItem(items: [YGNavItem], itemSpacing: CGFloat = 8.0, itemsAction: @escaping ((Int, YGNavItem) -> ())) {
        self.navItemModelArray = items
        // 移除原有的items
        for (_, item) in rightItems.enumerated() {
            item.removeFromSuperview()
        }
        rightItems.removeAll()
        
        // 添加items
        for (index, item) in items.enumerated() {
            // 容錯(cuò)處理
            if item.localImage == nil && item.imageUrl == nil && item.itemTitle == nil { return }
            // 根據(jù)item屬性生成button
            let button = createUIButtonWith(item, itemSpacing: itemSpacing, index: index)
            rightItems.append(button)
        }
        /// 賦值點(diǎn)擊事件
        self.itemsAction = itemsAction
    }
    
    /**
     滾動(dòng)頁面庐杨,改變導(dǎo)航欄狀態(tài)
     
     viewController: 持有YGNavView的控制器
     currentContentOffsetY: 當(dāng)前y軸滑動(dòng)距離
     maxOffsetY: 直到完全顯示的最大滾動(dòng)距離
     */
    func scrollToChangeStatus(_ viewController: UIViewController, currentContentOffsetY: CGFloat, maxOffsetY: CGFloat) {
        // 導(dǎo)航欄漸變透明度
        var alpha: CGFloat = 0
        // 超出最大距離,完全顯示導(dǎo)航欄
        if currentContentOffsetY >= (maxOffsetY - navBar_Height) {
            alpha = 1
            self.navTitleLabel.isHidden = false
        } else {
            alpha = currentContentOffsetY / (maxOffsetY - navBar_Height)
            self.navTitleLabel.isHidden = true
        }
        self.backgroundColor = self.navBGColor.withAlphaComponent(alpha)
    }
    
    
    // MARK: - Private
    
    /// 之前導(dǎo)航欄樣式(用來恢復(fù))(已棄用夹供,iOS 17.0之后的系統(tǒng)會(huì)自動(dòng)管理狀態(tài)欄)
    private var originalStyle: YGUIUserInterfaceStyle?
    
    /// 右側(cè)自定義item集合
    private var rightItems: [UIButton] = []
    
    /// 點(diǎn)擊右側(cè)items
    @objc private func clickItem(sender: UIButton) {
        itemsAction?(sender.tag, navItemModelArray?[sender.tag] ?? YGNavItem())
    }
    
    private func createUIButtonWith(_ item: YGNavItem, itemSpacing: CGFloat, index: Int) -> UIButton {
        let button = UIButton(type: .custom)
        addSubview(button)
        button.tag = index
        button.snp.makeConstraints { make in
            make.centerY.equalTo(navTitleLabel)
            make.height.equalTo(32)
            // 圖片類型灵份,固定寬度
            if item.localImage != nil || item.imageUrl != nil {
                make.width.equalTo(32)
            }
            // 從右往左排約束布局
            if index == 0 {
                make.right.equalToSuperview().offset(-14)
            }
            else {
                make.right.equalTo(rightItems[index - 1].snp.left).offset(-itemSpacing)
            }
        }

        // 本地圖片類型
        if item.localImage != nil {
            button.setImage(item.localImage, for: .normal)
        }
        // 網(wǎng)絡(luò)圖片類型
        else if item.imageUrl != nil {
            button.kf.setImage(with: URL(string: item.imageUrl ?? ""), for: .normal)
        }
        // 文本類型
        else if item.itemTitle != nil {
            button.setTitle(item.itemTitle, for: .normal)
            button.setTitleColor(item.itemTitleColor, for: .normal)
            button.titleLabel?.font = item.itemTitleFont
        }
        button.addTarget(self, action: #selector(clickItem), for: .touchUpInside)
        return button
    }
    
    
    // MARK: - Lazy
    
    lazy var backBtn: UIButton = {
        let button = UIButton(type: .custom)
        return button
    }()
    
    
    lazy var navTitleLabel = UILabel().then {
        $0.font = UIFont.getRegularFontSize(fontSize: 15, fontWeight: .medium)
        $0.textAlignment = .center
    }
    
    lazy var grayLine: UIView = {
        let view = UIView()
        view.backgroundColor = .lightGray.withAlphaComponent(0.3)
        return view
    }()
    
}


// MARK: —————— 導(dǎo)航欄自定義item ——————


/// 導(dǎo)航欄自定義item
class YGNavItem: NSObject {
    
    /// 本地圖片
    var localImage: UIImage?
    /// 網(wǎng)絡(luò)圖片
    var imageUrl: String?
    /// 標(biāo)題文本
    var itemTitle: String?
    /// 標(biāo)題字體
    var itemTitleFont: UIFont = .systemFont(ofSize: 12)
    /// 標(biāo)題顏色
    var itemTitleColor: UIColor = subContentColor

    /**
     navItem初始化,越靠前的參數(shù)哮洽,優(yōu)先級(jí)越高
     
     localImage: 本地圖片
     imageUrl: 網(wǎng)絡(luò)圖片
     itemTitle: 標(biāo)題文本
     itemTitleFont: 標(biāo)題字體
     itemTitleColor: 標(biāo)題顏色
     */
    init(localImage: UIImage? = nil, imageUrl: String? = nil, itemTitle: String? = nil, itemTitleFont: UIFont = .systemFont(ofSize: 12), itemTitleColor: UIColor = subContentColor) {
        self.localImage = localImage
        self.imageUrl = imageUrl
        self.itemTitle = itemTitle
        self.itemTitleFont = itemTitleFont
        self.itemTitleColor = itemTitleColor

    }
    
}

最最最后填渠,完結(jié)撒花

告辭.jpeg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸟辅,隨后出現(xiàn)的幾起案子氛什,更是在濱河造成了極大的恐慌,老刑警劉巖匪凉,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枪眉,死亡現(xiàn)場離奇詭異,居然都是意外死亡再层,警方通過查閱死者的電腦和手機(jī)贸铜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聂受,“玉大人蒿秦,你說我怎么就攤上這事〉凹茫” “怎么了棍鳖?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碗旅。 經(jīng)常有香客問我渡处,道長,這世上最難降的妖魔是什么祟辟? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任医瘫,我火速辦了婚禮,結(jié)果婚禮上川尖,老公的妹妹穿的比我還像新娘登下。我一直安慰自己,他們只是感情好叮喳,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布被芳。 她就那樣靜靜地躺著,像睡著了一般馍悟。 火紅的嫁衣襯著肌膚如雪畔濒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天锣咒,我揣著相機(jī)與錄音侵状,去河邊找鬼赞弥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛趣兄,可吹牛的內(nèi)容都是我干的绽左。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼艇潭,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼拼窥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蹋凝,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鲁纠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鳍寂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體改含,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年迄汛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捍壤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隔心,死狀恐怖白群,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硬霍,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布笼裳,位于F島的核電站唯卖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏躬柬。R本人自食惡果不足惜拜轨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望允青。 院中可真熱鬧橄碾,春花似錦、人聲如沸颠锉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琼掠。三九已至拒垃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓷蛙,已是汗流浹背悼瓮。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工戈毒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人横堡。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓埋市,卻偏偏與公主長得像,于是被迫代替她去往敵國和親命贴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恐疲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容