iOS 自動收起展開組件

自動收起展開組件

前情

當我們頁面中或者一個cell中有多個或一個View需要靈活展示灌侣,且我們用的是AutoLayout布局(包括Snapkit 和Masonry)唆香,我們隱藏或者展示一個View會很麻煩,如以下布局:

369746247-0de187f5-68b8-4ae2-b99d-b083d46b3545.png

我們有這樣一個cell布局嗤攻,其中tag行中的tag Label是靈活展示的判帮,也就是說根據(jù)服務(wù)返回各種標簽判斷標簽是否展示净宵,有可能只展示tag1 和 tag4, 也有可能全部展示拒炎,也有可能一個都不展示挪拟。

如果按照常規(guī)思路,比如tag2和tag3不展示击你,我們需要將tag2寬度設(shè)置為0玉组, 并且需要將左邊距重置為0, tag3亦是如此丁侄。

如果tag1 不展示惯雳,我們需要將其寬度設(shè)置為0, 且右邊距也設(shè)置為0鸿摇。多種情況都有可能存在石景,可想代碼多復(fù)雜且非常難維護。

有沒有一種方式能簡化代碼、且易懂鸵钝、代碼更好維護糙臼,成為開發(fā)此庫的目標。

思路

早之前還在使用XIB布局的時候恩商,曾經(jīng)使用過一個三方庫叫做FDCollapsibleConstraints, 它思路為 將 NSLayoutConstaints 加入到一個Array中变逃,記錄每個Constaints的原始值, 如果fd_collapsed設(shè)置為YES, 則就會將所有加入到Array中的約束設(shè)置為 0怠堪, 否則則還原至之前的關(guān)聯(lián)的原始值揽乱,并且作者將fd_collapsibleConstraints聲明為IBOutletCollection 也就是XIB中可直接進行連線約束的,對于XIB約束或者系統(tǒng)約束確實很方便粟矿。

但是由于XIB占用內(nèi)存大凰棉、沖突難解決的問題,一般項目中已經(jīng)禁止使用了陌粹。所以我們的項目亦是如此撒犀,那么我們通過Snapkit或者Masonry布局者因無法直接獲取到NSLayoutConstraints,也就無法直接加入到自動折疊約束Array中掏秩,如果變換思路取或舞,也會很麻煩。

所以我們也需要遺棄FDCollapsibleConstraints 以及它的思路蒙幻。

我們實現(xiàn)的思路為:

  1. 當給UIView調(diào)用一個實例方法時候映凳,傳入顯示且需要恢復(fù)的邊距 或者 隱藏且需要隱藏的邊距 或者 只需要隱藏

  2. 根據(jù)UIView的constraints 遍歷尋找到需要隱藏或者顯示的約束

  3. 重置約束

實現(xiàn)

import Foundation
extension NSLayoutConstraint {
 private struct AssociationKey {
 static var key = 0
 }
 func clear() {
 if constant != 0 {
 objc_setAssociatedObject(self, &AssociationKey.key, NSNumber(floatLiteral: self.constant), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
 self.constant = 0
 }
 }

 func restore() {
 guard let oldConstraint = objc_getAssociatedObject(self, &AssociationKey.key) as? NSNumber else {
 return
 }
 self.constant = oldConstraint.doubleValue
 objc_setAssociatedObject(self, &AssociationKey.key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
 }
}

首先給NSLayoutConstraints 增加兩個方法(不管是Snapkit還是Masonry 其實最后都是NSLayoutConstraints):

1、clear() 清除約束 && 保留原始約束值

2邮破、restore 恢復(fù)約束(恢復(fù)clear存儲下來的原始值)

public extension UIView {
 struct Direction: OptionSet {
 public var rawValue: Int

 public static let left = Direction(rawValue: 1 << 0)
 public static let right = Direction(rawValue: 1 << 1)
 public static let top = Direction(rawValue: 1 << 2)
 public static let bottom = Direction(rawValue: 1 << 3)
 public static let all: Direction = [.left, .right, .top, .bottom]

 public init(rawValue: Int) {
 self.rawValue = rawValue
 }
 }

 enum VisibleType {
 case visible(direction: Direction)
 case invisible
 case gone(direction: Direction)
 }

 func setVisible(_ visible: VisibleType) {
 switch visible {
 case .visible(let direction):
 guard let superview = self.superview else {
 return
 }
 if direction.contains(.left) {
 restoreConstraint(with: .left, on: superview)
 restoreConstraint(with: .leading, on: superview)
 }
 if direction.contains(.right) {
 restoreConstraint(with: .right, on: superview)
 restoreConstraint(with: .trailing, on: superview)
 }
 if direction.contains(.top) {
 restoreConstraint(with: .top, on: superview)
 }
 if direction.contains(.bottom) {
 restoreConstraint(with: .bottom, on: superview)
 }
 restoreConstraint(with: .width, on: self)
 restoreConstraint(with: .height, on: self)
 case .gone(let direction):
 guard let superview = self.superview else {
 return
 }
 if direction.contains(.left) || direction.contains(.right) {
 clearConstraint(with: .width, on: self)
 if direction.contains(.left) {
 clearConstraint(with: .leading, on: superview)
 clearConstraint(with: .left, on: superview)
 }
 if direction.contains(.right) {
 clearConstraint(with: .right, on: superview)
 clearConstraint(with: .trailing, on: superview)
 }
 }
 if direction.contains(.top) || direction.contains(.bottom) {
 clearConstraint(with: .height, on: self)
 if direction.contains(.top) {
 clearConstraint(with: .top, on: superview)
 }
 if direction.contains(.bottom) {
 clearConstraint(with: .bottom, on: superview)
 }
 }
 case .invisible:
 self.isHidden = true
 }
 }

 private func clearConstraint(with attribute: NSLayoutAttribute, on view: UIView) {
 guard let constraints = findConstraints(in: view, attribute: attribute) else {
 return
 }

 constraints.forEach { constraint in
 constraint.clear()
 }
 }

 private func restoreConstraint(with attribute: NSLayoutAttribute, on view: UIView) {
 guard let constraints = findConstraints(in: view, attribute: attribute) else {
 return
 }

 constraints.forEach { constraint in
 constraint.restore()
 }
 }

 private func findConstraints(in view: UIView, attribute: NSLayoutAttribute) -> [NSLayoutConstraint]? {
 return view.constraints.filter { constraint in
 guard let firstItem = constraint.firstItem as? NSObject else {
 return false
 }

 if firstItem == self && constraint.firstAttribute == attribute {
 return true
 }

 guard let secondItem = constraint.secondItem as? NSObject else {
 return false
 }

 return secondItem == self && constraint.secondAttribute == attribute
 }
 }
}
  1. 設(shè)置一個UIView的分類诈豌,方便調(diào)用

  2. 聲明邊距類型:Direction, 分別為上抒和、下矫渔、左、右摧莽、 全部

  3. 聲明顯示隱藏類型:visible(direction: Direction) 顯示 && 顯示的邊距蚌斩, invisible 只隱藏, gone(direction: Direction) 隱藏且隱藏邊距

  4. 聲明方法 func setVisible(_ visible: VisibleType)范嘱, 內(nèi)部根據(jù)visible類型送膳,進行filter約束 且 重置為0或者恢復(fù)

  5. 為什么private func findConstraints(in view: UIView, attribute: NSLayoutAttribute) -> [NSLayoutConstraint]? 返回的是約束數(shù)組, 因為以下兩種情況:

    1. 當一個帶有intrinsicContentSize UIView或者子類丑蛤,比如Label叠聋、 Button, 系統(tǒng)會自動增加NSLayoutConstraintsIntrinsicWidth 和 NSLayoutConstraintsIntrinsicHeight, 如果你還增加了高度寬度約束受裹,這時候其實寬度高度各兩個碌补。

    2. 相關(guān)聯(lián)的約束可能有多個

總結(jié)

感謝開源庫https://github.com/MotokiMiyagi/UIViewVisibility/tree/master 提供的思路虏束。

當前實現(xiàn)的為swift版本,如果需要使用Objective-c的可以使用以上庫厦章,但是需要注意實現(xiàn)中的第5點镇匀,此庫是有問題的。

需要使用三方庫的童鞋袜啃,可以直接到https://github.com/HaoXianSen/HRViewVisible 集成使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汗侵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子群发,更是在濱河造成了極大的恐慌晰韵,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熟妓,死亡現(xiàn)場離奇詭異雪猪,居然都是意外死亡,警方通過查閱死者的電腦和手機起愈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門只恨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抬虽,你說我怎么就攤上這事官觅。” “怎么了斥赋?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長产艾。 經(jīng)常有香客問我疤剑,道長,這世上最難降的妖魔是什么闷堡? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任隘膘,我火速辦了婚禮,結(jié)果婚禮上杠览,老公的妹妹穿的比我還像新娘弯菊。我一直安慰自己,他們只是感情好踱阿,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布管钳。 她就那樣靜靜地躺著,像睡著了一般软舌。 火紅的嫁衣襯著肌膚如雪才漆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天佛点,我揣著相機與錄音醇滥,去河邊找鬼黎比。 笑死,一個胖子當著我的面吹牛鸳玩,可吹牛的內(nèi)容都是我干的阅虫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼不跟,長吁一口氣:“原來是場噩夢啊……” “哼颓帝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躬拢,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤躲履,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后聊闯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體工猜,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年菱蔬,在試婚紗的時候發(fā)現(xiàn)自己被綠了篷帅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拴泌,死狀恐怖魏身,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚪腐,我是刑警寧澤箭昵,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站回季,受9級特大地震影響家制,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泡一,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一颤殴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼻忠,春花似錦涵但、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至塑娇,卻和暖如春芥永,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钝吮。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工埋涧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留板辽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓棘催,卻偏偏與公主長得像劲弦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子醇坝,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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