iOS讓UIButton自適應(yīng)圖片和文案位置,絕對(duì)萬(wàn)能

按鈕需要同時(shí)設(shè)置圖片和文案且兩者的相對(duì)位置不一定,這個(gè)需求十分常見,稍有經(jīng)驗(yàn)的開發(fā)者一定會(huì)想到自己寫個(gè)可以自適應(yīng)各種情況的擴(kuò)展方法,以前需要用的時(shí)候找過一些別人寫的,拷貝過來發(fā)現(xiàn)并不能適應(yīng)所有情況,后來自己研究下寫了一個(gè),可適配項(xiàng)目中各種使用場(chǎng)景。最近工作比較空一些想把以前積累的一些東西重新回顧總結(jié)一下,就先把這個(gè)分享出來。

首先要了解UIButton內(nèi)部是怎么布局的
  1. UIButton同時(shí)設(shè)置了image和title的情況下默認(rèn)布局是圖片在左、文字在右,圖片和文字之間間距為0,圖片和文字整體居中顯示
  2. UIButton提供了imageEdgeInsets、titleEdgeInsets這兩個(gè)屬性,便是用于調(diào)整兩個(gè)控件的相對(duì)位置,imageEdgeInsets中的top、left、bottom相對(duì)于UIButton,right相對(duì)于title的left,同理,titleEdgeInsets中的top、bottom、right相對(duì)于UIButton,left相對(duì)于image的right,每個(gè)方向的賦值:正值代表這個(gè)方向的內(nèi)邊距增大,負(fù)值代表減小。
  • 如果要圖片相比原始位置向左偏移5,那么可以設(shè)置
self.imageEdgeInsets = UIEdgeInsets.init(top: 0, left: -5, bottom: 0, right: 5)

左右兩邊都要設(shè)置,如果只設(shè)置left:-5或者只設(shè)置right:5的話,偏移量只有2.5

  • 如果想要圖片相比原始位置往左偏移5,往下偏移10,則設(shè)置
self.imageEdgeInsets = UIEdgeInsets.init(top: 10, left: -5, bottom: -10, right: 5)
根據(jù)以上的了解我們可以開始處理以下4種情況:
原始位置,灰色區(qū)域是按鈕,按鈕size=(150,150),圖片size=(50,50)

1. 圖片在左,文案在右,間距20

默認(rèn)即是圖片在左,所以只考慮間距就可以了
let spacing = 20
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing/2, bottom: 0, right: spacing/2)
self..titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing/2, bottom: 0, right: -spacing/2)
圖片在左,文案在右,間距20

2. 圖片在右,文案在左,間距20

這里要考慮到圖片和文案本身的寬高
let imageSize = self.imageView?.image?.size ?? .zero
let titleSize = self.titleRect(forContentRect: self.frame).size//系統(tǒng)為titleLabel分配的size
*這里注意下,獲取系統(tǒng)為文案提供的size,不一定是文案完全展示所需的size,原因后面會(huì)解釋;圖片和文案水平布局的情況下不需要考慮這個(gè)問題*
let spacing = 20
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: titleSize.width + spacing/2, bottom: 0,
                                       right: -(titleSize.width + spacing/2))
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(imageSize.width + spacing/2), bottom: 0, right: imageSize.width + spacing/2)

3. 圖片在上,文案在下,間距spacing

let imageTop = -(titleSize.height/2 + spacing/2)
let titleTop = imageSize.height/2 + spacing/2
self.imageEdgeInsets = UIEdgeInsets(top: imageTop, left: titleSize.width/2, bottom: -imageTop, right: -titleSize.width/2)
self.titleEdgeInsets = UIEdgeInsets(top: titleTop, left: -imageSize.width/2, bottom: -titleTop, right: imageSize.width/2)

4. 圖片在下,文案在上,間距spacing

let imageTop = titleSize.height/2 + spacing/2
let titleTop = -(imageSize.height/2 + spacing/2)
self.imageEdgeInsets = UIEdgeInsets(top: imageTop, left: titleSize.width/2, bottom: -imageTop, right: -titleSize.width/2)
self.titleEdgeInsets = UIEdgeInsets(top: titleTop, left: -imageSize.width/2, bottom: -titleTop, right: imageSize.width/2)
特殊情況

還記得我開頭說的之前拷貝過別人寫的不能很好地適配各種情況嗎?
當(dāng)圖標(biāo)和文字上下布局但圖片和文案所需寬度之和大于button的實(shí)際寬度時(shí),默認(rèn)先保證圖片的完整顯示同時(shí)將文字部分縮小,導(dǎo)致系統(tǒng)給分配的寬度不足以完整顯示文字,這個(gè)時(shí)候上面的布局方法就不能完美自適應(yīng)了可能會(huì)出現(xiàn)水平方向上位置異常的現(xiàn)象,這種情況需要特殊處理一下,還記得前面說的注意點(diǎn)嗎,為什么獲取系統(tǒng)分配的size,主要是用在這里判斷文案size是否被系統(tǒng)擠壓了

var titleNeedSize: CGSize = .zero//展示文字實(shí)際所需的size
if let font = self.titleLabel?.font {
    titleNeedSize = title.size(withAttributes: [NSAttributedString.Key.font: font])
}
var isTitleCompress = false//文字是否被系統(tǒng)壓縮
if isSureTitleCompress {
    isTitleCompress = true
} else if titleNeedSize.width > titleSize.width {
    isTitleCompress = true
}

isSureTitleCompress這個(gè)變量是該設(shè)置方法的一個(gè)參數(shù),setImage(image anImage: UIImage?, title: String, imagePosition: ImagePosition, additionalSpacing: CGFloat, isSureTitleCompress: Bool = false)因?yàn)檫壿嬛惺峭ㄟ^獲取系統(tǒng)分配文案size和實(shí)際所需size比較,決定走哪個(gè)分支邏輯的,如果明確知道button設(shè)置的尺寸會(huì)導(dǎo)致文案被擠壓且可能在button的生命周期多次調(diào)用這個(gè)方法的話需要把這個(gè)參數(shù)置為true,否則會(huì)有問題;試想一下,如果沒有設(shè)置為true,第一次調(diào)用這個(gè)方法時(shí)邏輯沒問題,第二次再調(diào)用時(shí)獲取的titleSize已經(jīng)滿足了實(shí)際展示的需求,就會(huì)走到文案沒被擠壓的邏輯分支中

圖片在上,文案在下的情況最終處理應(yīng)該是這樣

let imageTop = -(titleSize.height/2 + spacing/2)
let titleTop = imageSize.height/2 + spacing/2
if isTitleCompress {
    let imageLeft = (self.bounds.size.width - imageSize.width) / 2
    self.imageEdgeInsets = UIEdgeInsets.init(top: imageTop, left: imageLeft, bottom: -imageTop, right: 0)
    self.titleEdgeInsets = UIEdgeInsets(top: titleTop, left: -imageSize.width, bottom: -titleTop, right: 0)
} else {
    self.imageEdgeInsets = UIEdgeInsets(top: imageTop, left: titleSize.width/2, bottom: -imageTop, right: -titleSize.width/2)
    self.titleEdgeInsets = UIEdgeInsets(top: titleTop, left: -imageSize.width/2, bottom: -titleTop, right: imageSize.width/2)
}
完整代碼

最終設(shè)置方法的完整代碼如下,不用命名空間直接寫在擴(kuò)展中的朋友把代碼中的base換成self就可以了

extension YXKit where Base: UIButton {
    /*
     UIButton默認(rèn)布局是圖片在左、文字在右,圖片和文字之間邊距為0,圖片和文字整體居中顯示
     當(dāng)同時(shí)存在image和title時(shí),imageEdgeInsets中的top、left、bottom相對(duì)于UIButton,right相對(duì)于title,同理,titleEdgeInsets中的top、bottom、rright相對(duì)于UIButton,left相對(duì)于title
     imageEdgeInsets = UIEdgeInsets.init(top: 5, left: 0, bottom: -5, right: 0) 表示圖片整體向下移動(dòng)5
     imageEdgeInsets = UIEdgeInsets.init(top: -5, left: 4, bottom: 5, right: -4) 表示圖片整體向上移動(dòng)5,向右移動(dòng)4
     
     默認(rèn)的情況下當(dāng)按鈕比較小時(shí)會(huì)自動(dòng)保留圖片的尺寸和將文字部分縮小,一般出現(xiàn)在圖標(biāo)和文字上下布局,button的整體較小時(shí),因?yàn)榘粹o總體寬度比較小,導(dǎo)致系統(tǒng)給分配的寬度不足以完整顯示文字
     */
    enum ImagePosition: Int {
        case top    = 1
        case left   = 2
        case bottom = 3
        case right  = 4
    }
    
    /// 設(shè)置label相對(duì)于圖片的位置
    /// - Parameters:
    ///   - anImage: 按鈕圖片
    ///   - title: 標(biāo)題
    ///   - imagePosition: label相對(duì)于圖片的位置(上下左右)
    ///   - additionalSpacing: 文字和圖片的間隔
    ///   - state: UIControl.State
    ///   - isSureTitleCompress: 是否明確文字被系統(tǒng)擠壓,true:使用文字被壓縮的調(diào)整模式,false:根據(jù)系統(tǒng)為文字分配的size自動(dòng)適配(主要是了為了應(yīng)對(duì)有些文字?jǐn)D壓的按鈕被重復(fù)設(shè)置的情況)
    func setImage(image anImage: UIImage?, title: String, imagePosition: ImagePosition, additionalSpacing: CGFloat, state: UIControl.State = .normal, isSureTitleCompress: Bool = false){
        base.setImage(anImage, for: state)
        base.setTitle(title, for: state)
        positionLabelRespectToImage(title: title, position: imagePosition, spacing: additionalSpacing, isSureTitleCompress: isSureTitleCompress)
    }
    
    private func positionLabelRespectToImage(title: String, position: ImagePosition,spacing: CGFloat, isSureTitleCompress: Bool = false) {
        base.layoutIfNeeded()//這一步很重要,否則如果UIButton通過約束布局會(huì)導(dǎo)致titleRect獲取的rect不準(zhǔn)
        let imageSize = base.imageView?.image?.size ?? .zero
        let titleSize = base.titleRect(forContentRect: base.frame).size//系統(tǒng)為titleLabel分配的size
        
        var titleNeedSize: CGSize = .zero//展示文字實(shí)際所需的size
        if let font = base.titleLabel?.font {
            titleNeedSize = title.size(withAttributes: [NSAttributedString.Key.font: font])
        }
        var isTitleCompress = false//文字是否被系統(tǒng)壓縮
        if isSureTitleCompress {
            isTitleCompress = true
        } else if titleNeedSize.width > titleSize.width {
            isTitleCompress = true
        }
        
        switch (position){
        case .top:
            let imageTop = -(titleSize.height/2 + spacing/2)
            let titleTop = imageSize.height/2 + spacing/2
            if isTitleCompress {
                let imageLeft = (base.bounds.size.width - imageSize.width) / 2
                base.imageEdgeInsets = UIEdgeInsets.init(top: imageTop, left: imageLeft, bottom: -imageTop, right: 0)
                base.titleEdgeInsets = UIEdgeInsets(top: titleTop, left: -imageSize.width, bottom: -titleTop, right: 0)
            } else {
                base.imageEdgeInsets = UIEdgeInsets(top: imageTop, left: titleSize.width/2, bottom: -imageTop, right: -titleSize.width/2)
                base.titleEdgeInsets = UIEdgeInsets(top: titleTop, left: -imageSize.width/2, bottom: -titleTop, right: imageSize.width/2)
            }
            
        case .left:
            base.imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing/2, bottom: 0, right: spacing/2)
            base.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing/2, bottom: 0, right: -spacing/2)
            
        case .bottom:
            let imageTop = titleSize.height/2 + spacing/2
            let titleTop = -(imageSize.height/2 + spacing/2)
            if isTitleCompress {
                let imageLeft = (base.bounds.size.width - imageSize.width) / 2
                base.imageEdgeInsets = UIEdgeInsets(top: imageTop, left: imageLeft, bottom: -imageTop, right: 0)
                base.titleEdgeInsets = UIEdgeInsets(top: titleTop,
                                           left: -imageSize.width, bottom: -titleTop, right: 0)
            } else {
                base.imageEdgeInsets = UIEdgeInsets(top: imageTop, left: titleSize.width/2, bottom: -imageTop, right: -titleSize.width/2)
                base.titleEdgeInsets = UIEdgeInsets(top: titleTop,
                                           left: -imageSize.width/2, bottom: -titleTop, right: imageSize.width/2)
            }
            
        case .right:
            base.imageEdgeInsets = UIEdgeInsets(top: 0, left: titleSize.width + spacing/2, bottom: 0,
                                       right: -(titleSize.width + spacing/2))
            base.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(imageSize.width + spacing/2), bottom: 0, right: imageSize.width + spacing/2)
        }
    }
    
}

為什么不考慮高度不夠的情況?因?yàn)閷?shí)際需求中不存在

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容