網站首頁 編程語言 正文
前言
通過Protocol去封裝入參,抹平了入參之間的差異。
今天這篇依然圍繞一個我遇到的業務場景,給大家提供一種思路——使用enum抹平數組元素差異。
業務場景
我先說明一下業務場景:
頁面是一個有限可以滑動的的頁面(后面我們會分析到其實無限或者有限都無所謂)
頁面的每一個子View都是和后臺返回的數據綁定,其JSON大致可以理解為下面這種形式:
{ "aPart":{ "some":"" }, "bPart":[ { "label":"", "value":"" }, { "label":"", "value":"" } ], "cPart":[ { "iconUrl":"", "text":"", "functionType":"" }, { "iconUrl":"", "text":"", "functionType":"" } ], "dPart":{ "name":"", "age":"" }, "deviceType":"", "serviceAble":"" }
業務需求如下:
- aPart對應aView,bPart對應bView,cPart對應cView,dPart對應dView,可以如此窮舉下去
- 每個part的JSON結構可能都不相同
- 后臺下發JSON的時候,會根據用戶賬號的情況,返回不同數據,比如aPart的業務沒有,后臺會返回
"aPart":null
,App的aView需要隱藏,存在可能多個part都返回為null的情況,比如"bPart"
和"cPart"
都為null的情況,那么App的bView和cView需要隱藏。
那么問題來了,iOS端如何構建一個可以靈活配置的界面?
用什么控件
使用UIScrollView的分析
作為開發App頁面的第一點,選取合適的控件是非常重要,因為控件決定了最終數據源的形式。
因為后臺數據返回的并不是一個JSON數組,同時又是有限的數據,很多人優先會考慮通過UIScrollView
去進行頁面的構建,我一開始也是這么想的,但是麻煩的是這一點:
后臺下發JSON的時候,會根據用戶賬號的情況,返回不同數據,比如aPart的業務沒有,后臺會返回"aPart":null
,App的aView需要隱藏。
也就是說你把aView
、bView
、cView
、dView
貼在了scrollView上面之后,需要根據后臺的數據隱藏頁面,甚至更新布局邏輯,每一個view都有2種情況,假設后臺有4個業務數據,那么那就是2的4次方——32種可能。
其實僅隱藏還是顯示非常簡單,但是如果是使用SnapKit
布局,那么更新view的布局,可能就并不是特別好了,同時如果這個頁面后續還有新的業務數據,那么就子view會繼續增加,維護成本也會越來越高。
所以,到此使用UIScrollView的方案,別否決了,并不是說它不能構建,而是成本有些高,而且不夠靈活。
所以剩下的只剩下一種選擇了——使用通過數據源綁定UI的UITableView
。(備注:其實使用UICollectionView
和UITableView
都一樣,只是多一個瀑布流布局而已)。
使用UITableView的分析
使用UITableView
的優勢:
完完全全通過數據去驅動頁面,頁面是否顯示完全通過有無數據決定。
但是數據源相較普通模型有著更加嚴格的數據格式——數組!
而且數組的每個元素最好都是相同的數據類型,因為如果使用[Any]
這樣去表達一個數據源,成本太高。
好了,既然我們定下了使用UITableView
來進行頁面構建,那么剩下的難點也就來了——如何將后臺的數據加工成為一個好用的數據源?
加工數據
將后臺數據加工成為一個數組并不難,關鍵是統和數據類型才是難點。
在上一篇文章里面,我通過Protocol去統和了Model
與[String: String]
,在這次情況下面可不可行呢?
protocol EraserConvertible {} struct Response: Codabel { let aPart: APart? let bPart: BPart? let cPart: CPart? let dPart: DPart? } struct APart: Codabel, EraserConvertible {} struct BPart: Codabel, EraserConvertible {} struct CPart: Codabel, EraserConvertible {} struct DPart: Codabel, EraserConvertible {}
我們回頭看看這個JSON,你不得不認清這樣個現實,每個Part都是獨立的,完全看不到任何關聯,如果想要做類型一致,EraserConvertible
這個協議很難滿足。
不如做向上類型統和,也就是說數據源變成let dataSource = [Response]()
這種形式,在tableView的數據源方法中獲取單個數據,然后在進行分析:
let dataSource = [Response]() func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item = dataSource[indexPath.row] if let aPart = item.aPart { /// 構建aCell let cell = tableView.dequeueReusableCell(withIdentifier: aCell.className)! /// 將aPart賦值給aCell cell.aPart = aPart return cell } if let bPart = item.bPart { /// 構建bCell, let cell = tableView.dequeueReusableCell(withIdentifier: bCell.className)! /// 將bPart賦值給bCell cell.bPart = bPart return cell } . . . }
嗯,這樣看起來非常不錯,只是每個item都包含了aPart到dPart,方案的是可行。讓我們想想有沒有更加優雅的思路呢?
數據源的類型不同決定了不同的Cell。我們可不可以用狀態來表示?
于是乎我寫下了這樣的代碼:
enum BusinessPart { case a case b case c case d }
數據源變成let dataSource = [BusinessPart]()
這種形式,那么如何區分每個case
不同的數據呢?
Swift的enum是可以帶參數的,而且單個case帶不帶參數,帶什么類型的參數都很自由。
于是乎,我們接著改造BusinessPart
:
enum BusinessPart { case a(APart) case b(BPart) case c(CPart) case d(DPart) }
這樣我現在就通過enum
抹平的數組元素的差異,接下來只要把后臺數據架構成為我想要的[BusinessPart]格式就好了,這里放處理邏輯:
private func process(model: Response) -> [BusinessPart] { var array: [BusinessPart] = [] /// 非空才加入數組,后臺返回null,此時aPart為nil,那么aCell也不會出現 if let aPart = model.aPart { array.append(.a(aPart)) } if let bPart = model.bPart { array.append(.b(bPart)) } if let cPart = model.cPart { array.append(.c(cPart)) } if let dPart = model.dPart { array.append(.d(dPart)) } return array }
在TableView
的數據源方法中這么使用:
let dataSource = [BusinessPart]() func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item: BusinessPart = dataSource[indexPath.row] switch item { case .a(let aPart): /// 構建aCell let cell = tableView.dequeueReusableCell(withIdentifier: aCell.className)! /// 將aPart賦值給aCell cell.aPart = aPart return cell case .b(let bPart): /// 構建bCell let cell = tableView.dequeueReusableCell(withIdentifier: bCell.className)! /// 將bPart賦值給bCell cell.bPart = bPart return cell case .c(let cPart): /// 構建cCell let cell = tableView.dequeueReusableCell(withIdentifier: cCell.className)! /// 將cPart賦值給cCell cell.cPart = cPart return cell . . . } }
至于這個[BusinessPart]
的dataSource怎么在UITableViewDataSource
中如何處理,亦或者在BusinessPart
的分類中怎么處理,這個就是一個仁者見仁智者見智的問題了。
總結
到最后,這個頁面的數據加工,最后還是變成了向上還是向下統和的思考。
其實就是對于一個差異性特別大的數據,如何比較好的整合為一個在iOS中合適的數組問題,通過字段的全覆蓋達到模型的統合,抑或通過Swift中枚舉帶參的特點,抹平差異。
從代碼層面上看,兩者的代碼量差不多,但是使用enum抹平數組元素差異為我們提供一個新的解題思路,至少這是我自己思考的成果。
另外一個角度就是通過布局控件來展開,因為我看Android就是直接用一個ScrollView擼起的:
Android開發中,大部分控件都有visibility這個屬性,其屬性有3個分別為“visible ”、“invisible”、“gone”。主要用來設置控制控件的顯示和隱藏。
invisible當控件visibility屬性為invisible時,界面保留了view控件所占有的空間;而控件屬性為gone時,界面則不保留view控件所占有的空間。
因為Android這個三個屬性可以非常便利的完成隱藏還是不隱藏,以及是否保留控件所占有的空間。
而iOS可能需要不僅改變isHidden屬性,甚至連view的frame也要進行改變,成本太大了。在使用UITableView的分析已經提到。
但是也不是不可能,比如使用FlexLib應該可以實現。(備注:我自己沒用過FlexLib庫,這個有待考證哈??),但是也增加了一定的學習成本與引入了更多的庫。
參考文檔
Swift:enum你會用嗎?
原文鏈接:https://juejin.cn/post/7168670695256227854
相關推薦
- 2023-05-23 深入了解React中的合成事件_React
- 2022-06-10 C語言?模擬實現strlen函數詳解_C 語言
- 2022-08-15 常見哈希算法、Hmac算法和BouncyCastle
- 2022-12-29 React引入css的三種方式小結_React
- 2022-08-26 Python中def()函數的實戰練習題_python
- 2022-11-26 利用Python讀取Excel表內容的詳細過程_python
- 2024-04-05 @Version樂觀鎖配置mybatis-plus使用(version)
- 2022-10-31 一文詳解如何使用Redis實現分布式鎖_Redis
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支