BWiOSUIComponents
iOS UI組件的開(kāi)發(fā)
Github: https://github.com/bobwongs/BWiOSUIComponents
內(nèi)容
- 概述
- UI組件的開(kāi)發(fā)經(jīng)驗(yàn)
- 仿京東地址選擇器
- Reference
概述
熟練實(shí)用的UI組件的開(kāi)發(fā),是iOS開(kāi)發(fā)者必備的基礎(chǔ)技能之一,要掌握扎實(shí)這方面的技能,才能更高效、輕松地勝任實(shí)踐項(xiàng)目中的開(kāi)發(fā)工作。
UI組件的開(kāi)發(fā)經(jīng)驗(yàn)
復(fù)雜的/高度自定制的/高可移植性的UI組件的封裝經(jīng)驗(yàn)
理解UI組件的設(shè)計(jì),一定要多花時(shí)間在理解和設(shè)計(jì)上,不然只會(huì)需要更多的不確定的時(shí)間去解決問(wèn)題,而且還不一定能解決
設(shè)計(jì):架構(gòu)/類結(jié)構(gòu)、流程、接口
做接口設(shè)計(jì),面向接口的編程,而不面向?qū)崿F(xiàn)
減少依賴,如庫(kù)、常量、宏定義、媒體資源
對(duì)多次使用的常量、功能代碼進(jìn)行封裝,方便進(jìn)行統(tǒng)一的維護(hù)、減少不必要的代碼冗余、提升代碼質(zhì)量
對(duì)大段的功能代碼,進(jìn)行抽出封裝成方法,不影響對(duì)功能代碼主流程的閱讀,提升代碼的可讀性
開(kāi)發(fā)實(shí)現(xiàn)的可視化,把設(shè)計(jì)、實(shí)現(xiàn),記錄下來(lái)
注釋的書(shū)寫(xiě),包括:核心框架,復(fù)雜流程、邏輯、核心參數(shù),提高代碼的可讀性
開(kāi)發(fā)步驟
- 熟悉和理解目標(biāo)設(shè)計(jì)組件,UI和交互邏輯
- UML設(shè)計(jì),UI層、數(shù)據(jù)源層、操作層的設(shè)計(jì),從整體到局部,先從整體進(jìn)行設(shè)計(jì),捋清、解決和重設(shè)計(jì)不合理的地方
- 開(kāi)發(fā),搭框架 -> 具體實(shí)現(xiàn)
- 自測(cè)
- 完成
仿京東地址選擇器
效果

設(shè)計(jì)
類結(jié)構(gòu)
BWAddressPickerView 負(fù)責(zé)視圖的展示、用戶交互、根據(jù)數(shù)據(jù)源刷新顯示內(nèi)容
BWAddressPickerManager 負(fù)責(zé)獲取數(shù)據(jù)源刷新視圖,供外部進(jìn)行使用
BWAddressSourceManager 負(fù)責(zé)從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)源,本項(xiàng)目用本地獲取數(shù)據(jù)源,也可以修改為從網(wǎng)絡(luò)獲取
流程
主流程:
從數(shù)據(jù)庫(kù)讀取地址數(shù)據(jù)源 -> 用讀取回來(lái)的數(shù)據(jù)源轉(zhuǎn)換成供地址組件顯示用的數(shù)據(jù)源 ->
顯示地址選擇組件 -> 用戶選擇地址,記錄選中序列 ->
獲取下一級(jí)的數(shù)據(jù)源,用數(shù)據(jù)源刷新顯示內(nèi)容 ->
回到用戶選擇 ->
完成所有選擇,傳出選中的地址模型數(shù)組,供調(diào)用者使用
重新選擇流程:
先移除原來(lái)已選的數(shù)據(jù),包括BWAddressPickerView和BWAddressPickerManager對(duì)應(yīng)需要移除的數(shù)據(jù) ->
再添加用戶新選擇數(shù)據(jù) ->
回到主流程
技術(shù)要點(diǎn)
數(shù)據(jù)結(jié)構(gòu)的選用:數(shù)組。
包括,地址數(shù)據(jù)源為數(shù)組的數(shù)組、用戶選中的地址也是用數(shù)組、UI展示,選中地址的Title和TableView也是用數(shù)組。
選中地址名稱的實(shí)現(xiàn),布局方式:Frame,為了更加方便做動(dòng)畫(huà),每次選中對(duì)原來(lái)添加的顯示名稱組件進(jìn)行移除,然后再重新添加,讓底部的滾動(dòng)條移動(dòng)到對(duì)應(yīng)的位置。
地址列表的TableView,每次新選中,不為最后一個(gè)層級(jí),則添加下一個(gè)TableView,并且滾動(dòng)到下一位置。
接口設(shè)計(jì)
主要在BWAddressPickerView上
@interface BWAddressPickerView : UIView
@property (copy, nonatomic) void(^getDataBlock)(NSUInteger selectedSection, NSUInteger selectedRow); ///< Get data.
@property (copy, nonatomic) void(^didSelectBlock)(NSMutableArray *selectedIndexArray); ///< finish select index array.
@property (copy, nonatomic) void(^removeAddressSourceArrayObjectBlock)(NSRange range); ///< Remove address source array object with given range.
- (void)show;
- (void)dismiss;
/**
* Add next level showed address array to select.
*/
- (void)addNextAddressDataWithNewAddressArray:(NSArray *)newAddressArray;
@end
核心代碼
選擇
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row = indexPath.row;
NSInteger tableViewIndex = tableView.tag - BWAddressPickerFirstTableViewTag;
// ---------- 最后一級(jí)的選擇 && 選擇序列已經(jīng)選滿,先做移除操作 ----------
if (tableViewIndex == BWAddressPickerSelectableCount - 1 && _selectedIndexArray.count == BWAddressPickerSelectableCount) {
[_selectedIndexArray removeLastObject]; // 移除最后一個(gè)序列
[_selectedTitleMutableArray removeObjectAtIndex:_selectedTitleMutableArray.count - 2]; // 移除倒數(shù)第二個(gè)Title
}
// ---------- 若選擇為已選中,則跳轉(zhuǎn)到下一頁(yè)(按這種邏輯設(shè)計(jì),下一頁(yè)肯定是有數(shù)據(jù)的,如果為最后一頁(yè),則已經(jīng)在上一個(gè)流程處理過(guò)了);這里保險(xiǎn)起見(jiàn),再判斷一次下一頁(yè)的數(shù)據(jù) ----------
else if (tableViewIndex < _selectedIndexArray.count && row == _selectedIndexArray[tableViewIndex].integerValue && _addressArrayM.count > tableViewIndex + 1) {
[self makeBarScrollToIndex:tableViewIndex + 1];
[self makeTableViewScrollToIndex:tableViewIndex + 1];
return; // 下一頁(yè)已有數(shù)據(jù),直接做界面跳轉(zhuǎn)操作即可
}
// ---------- 若為重新選擇,而且選擇不為已選中,則移除當(dāng)前選擇序列之后的所有原來(lái)已選擇的,增加新的 ----------
else if (tableViewIndex < _selectedIndexArray.count) {
NSRange removedRange = NSMakeRange(tableViewIndex, _selectedIndexArray.count - tableViewIndex);
NSRange addressArrayRemovedRange = NSMakeRange(tableViewIndex + 1, _addressArrayM.count - tableViewIndex - 1); // 地址數(shù)據(jù)源的移除序列為從下一個(gè)開(kāi)始,addressArrayM的成員對(duì)象個(gè)數(shù)來(lái)計(jì)數(shù)
// 移除和重設(shè)bottomScrollView
[_bottomScrollView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
if (NSLocationInRange(subview.tag - BWAddressPickerFirstTableViewTag, addressArrayRemovedRange)) {
[subview removeFromSuperview];
}
}];
[_bottomScrollView setContentOffset:CGPointMake(tableViewIndex * CGRectGetWidth(_bottomScrollView.frame), 0) animated:YES];
_bottomScrollView.contentSize = CGSizeMake((tableViewIndex + 1) * CGRectGetWidth(_bottomScrollView.frame), _bottomScrollView.contentSize.height);
// 移除Data
[_selectedIndexArray removeObjectsInRange:removedRange];
[_selectedTitleMutableArray removeObjectsInRange:removedRange];
[_addressArrayM removeObjectsInRange:addressArrayRemovedRange];
if (_removeAddressSourceArrayObjectBlock) _removeAddressSourceArrayObjectBlock(addressArrayRemovedRange);
// 重設(shè)Title
[self refreshUIWithCurrentSelectedIndex];
}
[_selectedIndexArray addObject:@(row)];
[self reloadTableViewWithIndex:tableViewIndex];
// 顯示用的Title,加在倒數(shù)第二個(gè)位置,最后一個(gè)為“請(qǐng)選擇”
NSArray *array = _addressArrayM[tableViewIndex];
[_selectedTitleMutableArray insertObject:array[row] atIndex:_selectedTitleMutableArray.count - 1];
if (tableViewIndex == BWAddressPickerSelectableCount - 1) {
// 設(shè)置最后一個(gè)Button
[self setTitleButtonWithTag:tableViewIndex + BWAddressPickerFirstButtonTag title:array[row]];
[self makeBarScrollToIndex:tableViewIndex];
if (self.didSelectBlock) self.didSelectBlock(_selectedIndexArray);
return;
}
if (self.getDataBlock) self.getDataBlock(tableViewIndex, row);
}
優(yōu)化方向
提升UI設(shè)計(jì)和用戶體驗(yàn)效果,例如:使用偽3D做Title的顯示效果
Follow Me
Github: https://github.com/bobwongs
簡(jiǎn)書(shū): http://www.itdecent.cn/u/9d21ec83358a