iOS UI組件開(kāi)發(fā)

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ā)步驟

  1. 熟悉和理解目標(biāo)設(shè)計(jì)組件,UI和交互邏輯
  2. UML設(shè)計(jì),UI層、數(shù)據(jù)源層、操作層的設(shè)計(jì),從整體到局部,先從整體進(jìn)行設(shè)計(jì),捋清、解決和重設(shè)計(jì)不合理的地方
  3. 開(kāi)發(fā),搭框架 -> 具體實(shí)現(xiàn)
  4. 自測(cè)
  5. 完成

仿京東地址選擇器

效果

demo_address_selection

設(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

新浪微博: http://weibo.com/bobwongs

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,733評(píng)論 25 709
  • 本文參加#我的軍訓(xùn)我來(lái)說(shuō)#活動(dòng),本人承諾,文章內(nèi)容為原創(chuàng),且未在其他平臺(tái)發(fā)表過(guò)。 懷著懵懂與憧憬,來(lái)到了我...
    zhouxu閱讀 434評(píng)論 2 3
  • 應(yīng)邀參加了亞歷山大谷酒莊(AlexanderValleyVineyards)一座家族經(jīng)營(yíng)式精品酒莊的品鑒會(huì),讓...
    行走的戰(zhàn)士閱讀 595評(píng)論 0 0
  • 如果說(shuō)雞湯是中老年朋友們的朋友圈的爆款, 那么“喪”很可能正在刷屏年輕人的朋友圈。 愚人節(jié)期間,“喪茶”開(kāi)始走紅微...
    宛若婉君閱讀 1,563評(píng)論 0 3
  • 讀布魯克林有棵樹(shù)34%。 神奇的事情 書(shū)里寫(xiě)的美國(guó)貧民窟的故事,弗蘭西前三輩的家史跟中國(guó)的我家的故事,外公外婆爸媽...
    Turtle1220閱讀 164評(píng)論 0 1

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