您的位置 首页 kreess

iOS開發–Swift:佈局庫——SnapKit

如果你是隻從事過iOS開發,覺得使用SnapKit(OC中的Masonry)很方便,甚至xib拖拉也不錯。可以說,這些都是iOS開發稀疏平常的日常。但一旦你學過Flutte

如果你是隻從事過iOS開發,覺得使用SnapKit(OC中的Masonry)很方便,甚至xib拖拉也不錯。

可以說,這些都是iOS開發稀疏平常的日常。

但一旦你學過Flutter/Vue寫過UI組件,那麼iOS的UI編寫真的是有種不忍直視的感覺,可以說是原始社會。

雖然隔壁Android的UI寫起來也不會特別友好,但是還是比iOS好。

為啥,因為其他的UI編寫基本上都可以既見既所得瞭,就算犯瞭錯,邊看邊邊調試就行瞭

隻有iOS的需要編譯調試。。。編譯調試。。。編譯調試。。。

而且其他傢的UI編寫基本上都是一脈相承,前端裡面的CSS,在Flutter中可以找到一些命名相同的組件,我們來舉個例子來說明一下隔壁構建UI的簡單和同化:

下面這個UI如何Flutter、H5、iOS來實現:

如果你正在面試,或者正準備跳槽,不妨看看我精心總結的面試資料:https://gitee.com/Mcci7/i-oser 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障

Flutter:

Wrap( children: model.children .map( (topic) => Padding( padding: EdgeInsets.all(3.0), child: Chip( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, key: ValueKey<String>(topic.name), backgroundColor: _getChipBgColor(topic.name), label: Text( topic.name, style: TextStyle(fontSize: 14.0), ), ), ), ) .toList(), );

H5:

<view class="flex-wrap" v-for="(item, index) in list" :key="index"> <u-tag :text="item.name" :index="item.name" @click="click" /> </view> <style scoped lang="scss"> .flex-wrap { display: flex; display: -webkit-flex; flex-wrap: wrap; width: auto; height: auto; margin: 16rpx; } </style>

iOS:

我需要的代碼太多瞭,這裡就貼出來瞭 T_T

Flutter和H5都是用的wrap作為佈局思路,Flutter有wrap組件,而H5直接用flex-wrap的CSS就可以瞭,而iOS中卻沒有,雖說很多iOS中的組件Flutter和H5需要自定義,而iOS開箱即用。

不過現在這個情況是,就算設計稿與UI元素都和iOS靠攏,但實際上Flutter和H5在組件上已經有非常成熟的官方組件或者第三方,而iOS卻沒有。。。加上沒有熱重載。。。

不過雖然說iOS的佈局不好用,甚至不好使,但是SnapKit至少給你一點點曙光,此話怎講,下面我們來看看同一個佈局,使用iOS原生的佈局和SnapKit的佈局代碼量吧。

iOS原生佈局 VS SnapKit佈局

ios原生佈局:

contentView.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false addConstraint(NSLayoutConstraint(item: imageView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 0)) addConstraint(NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 0)) addConstraint(NSLayoutConstraint(item: imageView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 0)) addConstraint(NSLayoutConstraint(item: imageView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 0))

SnapKit佈局:

contentView.addSubview(imageView) imageView.snp.makeConstraints { make in make.edges.equalTo(contentView) }

使用原生佈局還是SnapKit,不用我多說瞭。

SnapKit的使用、註意事項與疑惑

就我目前做過的項目,就算裡面包含著大量的xib,也基本上會使用SnapKit或者Masonry,雖然說它隻是原生Api的一層封裝,但其鏈式調用、函數式編程的思想非常值得我們學習和借鑒,下面說幾個使用SnapKit的經驗談:

使用SnapKit前,一定要先將子控件添加到父視圖中

parentView.addSubview(subview)

一定一定一定在佈局前將子控件添加到父視圖中,否則會直接崩潰!!!

leading和left、trailing和right

其實在目前國內App中使用leading與left,trailing與right在正常情況下是等價的,這是因為國內的閱讀習慣是從左到右的,不過如果你的App需要在阿拉伯國傢上架,他們的佈局是從右至左時(比如阿拉伯文) 則會對調。

我個人的習慣是使用leading和trailing,這也可能和我做過國際化有關。

給控件添加、更新約束、引用約束、停用、啟用

  • 添加新的約束

contentView.addSubview(imageView) imageView.snp.makeConstraints { (make) in }

  • 刪除控件以前所有約束,添加新約束

由於imageView已經添加到contentView上瞭,所以remake的時候不需要調用addSubview的方法

imageView.snp.remakeConstraints { (make) in }

  • 更新約束,寫哪條更新哪條,其他約束不變

不過有的時候更新一條約束的時候可能會崩潰,我在cell中進行update的時候就遇到過,至於原因正在排查,這個時候如果崩潰瞭,可能考慮使用remakeConstraints已經控件的重新佈局,雖然這樣會消耗一點性能,但總比崩潰好。

imageView.snp.updateConstraints { (make) in }

  • 引用約束,聲明一個局部變量或者類屬性來引用要修改的約束

值得註意的是,topConstraint使用的是可選類型,這樣保證瞭在空時,調用deactivate()和activate()方法不會崩潰。同時設置為全局變量,方便在各種情況進行調用。

var topConstraint: Constraint? = nil override func viewDidLoad() { super.viewDidLoad() let imageView = UIImageView() view.addSubview(imageView) imageView.snp.makeConstraints { (make) in self.topConstraint = make.left.equalToSuperview().offset(100).constraint make.right.equalToSuperview().offset(-100) make.top.equalToSuperview().offset(100) make.bottom.equalToSuperview().offset(-100) } }

停用

topConstraint?.deactivate()

啟用

topConstraint?.activate()

設置約束關系

約束關系 說明

設置控件佈局屬性

約束關系 說明

設置約束偏移

方法 參數 說明

舉兩個例子:

imageView.snp.makeConstraints { (make) in make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.top.equalToSuperview().offset(20) make.bottom.equalToSuperview().offset(-20) }

/// 具體父控件四周都是20間距 imageView.snp.makeConstraints { (make) in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)) }

第2種形式與第1種形式通過left、right、top、bottom寫出來的效果一模一樣,隻是組織語言的形式不一樣,SnapKit的編程方式雖然是Swift進行組織的,但是更多是靠其自身構建的DSL進行編碼。

設置約束優先級

  • SnapKit為我們提供瞭三個默認的方法,required、high、medium、low,優先級最大數值是1000

public static var required: ConstraintPriority { return 1000.0 } public static var high: ConstraintPriority { return 750.0 } public static var medium: ConstraintPriority { #if os(OSX) return 501.0 #else return 500.0 #endif } public static var low: ConstraintPriority { return 250.0 }

自己設置優先級的值,可以通過priority()方法來設置

imageView.snp.makeConstraints { (make) in make.center.equalToSuperview() make.width.equalTo(100).priority(ConstraintPriority.low) make.height.equalTo(50).priority(800) } 復制代碼

其實這個優先級的使用,我自己在開發過程中很少使用到,還需要更多的學習和探索。

SnapKit與UIScrollView

很多新手將在將子控件放入UIScrollView進行佈局的時候,經常會發現ScrollView滑不動瞭。

首先要理解一點核心思想:UIScrollView是依靠與其子視圖(subview)之間的約束來確定ContentSize的大小。為什麼這麼說呢?

這是因為UIScrollView是個非常特殊的UIView,對於UIScrollView的subview來說,它的leading/trailing/top/bottom的space是相對於UIScrollView的contentSize而不是bounds來確定的,換句話說:UIScrollView與其subview之間相對位置的約束並不會直接用於frame的計算,而是會轉化為對contentSize的計算。當UIScrollView知道瞭上下左右的約束分別指向subview的什麼位置之後,隻要subview的位置固定下來瞭,那麼UIScrollView的contentSize的大小就確定下來瞭。

但是當我們嘗試使用UIScrollView和它subview的leading/trailing/top/bottom來互相決定大小的時候,會出現Has ambiguous scrollable content width/height的 warning。

根據經驗,習慣的做法是在UIScrollView和它原來的subviews之間增加一個contentView,依靠contentView來確定contentSize。

代碼如下圖所示:

class ViewController: UIViewController { lazy let scrollView = UIScrollView() lazy let contentView = UIView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white view.addSubview(scrollView) scrollView.snp.makeConstraints { (make) in make.edges.equalTo(view) } /// 添加容器視圖 scrollView.addSubview(contentView) contentView.snp.makeConstraints { (make) in make.top.bottom.equalTo(scrollView) make.left.right.equalTo(view) // 確定的寬度,因為垂直滾動 } let label1 = UILabel() /// 註意將組件添加到contentView上,而不是scrollView上面 contentView.addSubview(label1) label1.numberOfLines = 0 label1.backgroundColor = .yellow label1.snp.makeConstraints { (make) in make.left.right.equalTo(contentView).inset(20) make.top.equalTo(contentView).offset(20) } let label2 = UILabel() /// 註意將組件添加到contentView上,而不是scrollView上面 contentView.addSubview(label2) label2.numberOfLines = 0 label2.backgroundColor = .red label2.snp.makeConstraints { (make) in make.left.right.equalTo(label1) make.top.equalTo(label1.snp.bottom).offset(20) /// 底部約束一定要添加,用於告訴contentView在哪裡,不然不能夠確定contentSize。 make.bottom.equalToSuperview() } label1.text = """ hi,掘友們! 你們發現沒,為什麼程序員的技術水平差不多,有的升職加薪機會多,有的不去找工作,總有工作找他們?同等“硬件”條件下,為啥競爭力不一樣呢? 很主要的一個原因是,大傢的影響力不一樣。寫博客、做分享、寫開源…… 這些都是提高影響力的方式,很多技術達人,甚至靠輸出自己的技術知識和經驗,把副業做成瞭主業。當然,這樣的影響力不是一天一夕養成的,需要持續輸出,積點成勢。 很多人也立瞭Flag要每天寫點東西,可總是堅持不瞭兩天,Flag立瞭倒,倒瞭立,一直得不到正反饋,最後自己都氣餒瞭。這一次,我們幫你培養長期堅持且正向激勵的好習慣,扶穩Flag! """ label2.text = """ 小王1人更文瞭28天,小王將獲得3千元以內等值獎品,比如Gopro hero8。如果小王同時被幸運大獎砸中,小王將在幸運大獎和一等獎之內選擇其一。 小王和小李共2人完成瞭27天更文挑戰,小王和小李每人獲得1500元等值獎品,比如AirPos2代。 小李更滿30天,除瞭一等獎之外,還將獲得滿勤獎獎勵。 小王等共6人完成瞭27天更文挑戰,小王等6人每人可選833元以內等值獎品。 小王更文瞭23天,可選擇二等獎/三等獎中的一種。 """ } }

如果文本還不夠使得scrollView進行滑動,請在label1和label2中的text中多隨意寫些字符串。

其他選擇

隨著移動端App向著大前端的思路進化,目前iOS和Android的UI開發工具都在向聲明式演進,iOS有SwiftUI,Android有ComposeUI,加上跨平臺的Flutter,其實基本上都是一個模子刻出來的,隻是語言不同而已。

另外iOS的OC中還有一個FlexLib 庫。

該佈局框架基於flexbox模型,這個模型是web端的佈局標準。基於flexbox模型,FlexLib提供瞭強大的佈局能力,並且易於使用。

之前有同事弄過,用過的都說好,我也準備探索一下。

如果你正在面試,或者正準備跳槽,不妨看看我精心總結的面試資料:https://gitee.com/Mcci7/i-oser 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障

明日繼續

終於挨過瞭周末更文,最近工作上因為iOS與H5的交互非常多,所以考慮下周會插播一些工作上的問題,進行存檔。還望大傢海涵。

作者:season_zhu鏈接:https://juejin.cn/post/6970515865003360263

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

返回顶部