1. 程式人生 > >iOS開發中的單選與多選

iOS開發中的單選與多選

在前端開發中如果要擁有一個單選或者多選功能十分簡單,因為HTML中有現成的標籤可以很方便的實現單選或者多選效果,比如這樣寫上幾句程式碼就能擁有最原始的選擇效果。

但是在iOS開發中就沒有這麼方便的控制元件了,如果要完成單選或者多選的功能還需要一些邏輯編碼,並且可以選擇的方案還是有很多的。

在iOS開發中經常用於實現單選或者多選功能的控制元件是UITableView,並且,通過這個控制元件可以完成一個App80%的介面。而微信則是一個全部使用UITableView構建的App。下面將會針對於單選和多選進行一個探究學習,學習SDK中提供有哪些API能夠使用,並且結合這些API如何總結出合適的方法完成單選多選功能。

單選

單選其實還是很簡單的,在將UITableView新增到控制器中的時候選擇任意一個cell都會看到被選擇的cell變為灰色,這是系統提供的,不需要開發者進行設定。談不上好看,但正是由於不是很好看,才留給了開發者很大的自定義的空間。

simple

這個選擇樣式系統是根據設定的selectionStyle屬性決定的,這是一個列舉型別,預設的是UITableViewCellSelectionStyleBlue,但是通過選擇代理方法獲取cell並且打印出來的selectionStyle屬性卻是UITableViewCellSelectionStyleDefault!!

|

@property (nonatomic) UITableViewCellSelectionStyle selectionStyle;\
// default is UITableViewCellSelectionStyleBlue.

typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) {\
UITableViewCellSelectionStyleNone,\
UITableViewCellSelectionStyleBlue,\
UITableViewCellSelectionStyleGray,\
UITableViewCellSelectionStyleDefault NS_ENUM_AVAILABLE_IOS(7_0)\
};

|

除此之外,UIKit中還為UITableView提供了單選、多選、在編輯狀態下的單選、在編輯狀態下的多選的布林值屬性,以及其他的選擇操作API

|

@property (nonatomic) BOOL allowsSelection;

@property (nonatomic) BOOL allowsSelectionDuringEditing;

@property (nonatomic) BOOL allowsMultipleSelection;

@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing;

@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow

@property (nonatomic, readonly, nullable) NSArray

及簡方法實現選擇操作

單選和多選

要達到單選或者多選操作,需要做的是在UITableView的代理方法-tableView:didSelectRowAtIndexPath:中對選擇的cell進行選中標記,以及-tableView:didDeselectRowAtIndexPath中對取消選中的cell取消選中標記,這裡使用UITableViewCell的accessoryType屬性進行選中與否的設定

|

-tableView:didSelectRowAtIndexPath:

UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];

if (cell.selected) {\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}else{\
cell.accessoryType = UITableViewCellAccessoryNone;\
}

-tableView:didDeselectRowAtIndexPath

UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];

if (!cell.selected) {\
cell.accessoryType = UITableViewCellAccessoryNone;\
}

|

如果這樣做了的話,在進行滑動表檢視的時候會出現cell重用的現象,可以在要進行重用的時候根據cell的selected屬性進行決定accessoryType顯示樣式

|

-tableView:cellForRowAtIndexPath:

UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];

if (cell.selected) {\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}else{\
cell.accessoryType = UITableViewCellAccessoryNone;\
}

|

做完了這些,可以在對UITableView進行設定屬性的地方決定是要單選allowsSelection還是多選allowsMultipleSelection

|

_tableView.allowsSelection = YES;\
// or\
_tableView.allowsMultipleSelection = YES;

|

單選回退

前面說了,如果是多選狀態,可以回到一個都沒有選中的狀態,但是如果是單選狀態,一旦選擇就回不到原始一個都沒有選中的狀態。如果要實現一個單選可以回退到原始狀態的效果怎麼辦呢?

這時候需要藉助一個標記位,用來標記選中的cell所在的位置,這個標記位使用NSIndexPath的例項物件來表示,當點選cell的時候對該cell所處的位置和標記位進行比較,如果相同的話就取消掉cell的選中效果,並且將標記位置為nil,同時要對UITableView進行取消選中當前indexPath下的cell操作,這樣做的目的是為了在使用indexPathForSelectedRow屬性獲取選中cell的時候能夠獲取到正確的資料;如果和標記位不相同就新增選中狀態,並重新賦值標記位。將-tableView:didSelectRowAtIndexPath:方法使用一下程式碼進行替換,同時對當前控制器新增一個selectedIndexPath屬性作為標記位

|

-tableView:didSelectRowAtIndexPath:

UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];

if (_selectedIndexPath == indexPath) {\
cell.accessoryType = UITableViewCellAccessoryNone;\
[tableView deselectRowAtIndexPath:_selectedIndexPath animated:YES];\
_selectedIndexPath = nil;\
}else{\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
_selectedIndexPath = indexPath;\
}

|

其他地方還是和單選一樣的,這樣就可以實現單選回退操作了。

以上的程式碼

藉助標記位實現單選操作

當然也可以使用一個NSIndexPath的例項物件作為標記位來實現單選操作,在點選每一個cell的時候進行標記的重新賦值,在cellForRow方法中根據標記位來決定哪一個cell顯示選中狀態。

|

-tableView:cellForRowAtIndexPath:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];\
if (_selectedIndexPath && _selectedIndexPath == indexPath) {

cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}else{\
cell.accessoryType = UITableViewCellAccessoryNone;\
}

-tableView:didSelectRowAtIndexPath:

UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];

if (_selectedIndexPath) {

// 取消上一次選擇的accessoryType\
cell = [tableView cellForRowAtIndexPath:self.selectedIndexPath];\
cell.accessoryType = UITableViewCellAccessoryNone;\
}

// 設定這一次選擇的accessoryType\
cell = [tableView cellForRowAtIndexPath:indexPath];\
if (cell.accessoryType) {\
cell.accessoryType = UITableViewCellAccessoryNone;\
} else {\
cell.accessoryType = UITableViewCellAccessoryCheckmark;\
}

// 儲存indexPath\
self.selectedIndexPath = indexPath;

[tableView performSelector:@selector(deselectRowAtIndexPath:animated:) withObject:indexPath afterDelay:0.5];

// 或者用短一點的,將該方法中以上寫的程式碼替換成下面這樣,但是要考慮一下這樣有可能會出現的後果\
_selectedIndexPath = indexPath;

[tableView reloadData];

|

這些做法比較基礎,也比較簡單。如果是在沒有進行自定義Cell,並且對選擇樣式也沒什麼要求,一個對號也能行的那種情況下,這樣做就行了,沒必要多折騰了。但是,往往一個App的區域性樣式跟整體的樣式是相關的,比如微信主色調是一種綠色的,並且設計師由於美觀要使用一個帶有圓圈的對號來表示選中狀態,這樣的話一個簡單的對號顯然已經不能滿足需求了。在進行自定義Cell的時候,需要考慮將選中和非選中的樣式進行遷移,不要在控制器中進行,換到在Cell中進行。

自定義cell中實現單選操作

使用自定義的Cell,需要在實現類裡的-setSelected:animated:方法進行判斷,如果不需要有Cell的選中樣式可以在-tableView:cellForRowAtIndexPath:中將選中樣式設為none。

|

  • (void)setSelected:(BOOL)selected animated:(BOOL)animated {

    [super setSelected:selected animated:animated];\
    if (selected) {\
    self.checkoutImageView.image = [UIImage imageNamed:@”CheckBox_HL”];\
    }else{\
    self.checkoutImageView.image = [UIImage imageNamed:@”CheckBox”];\
    }\
    }

    |

效果如圖,

selected

這種做法說起來也是比較取巧,在需求不是很複雜的情況下使用還是可以的,如果一旦牽扯到很多的業務邏輯就不要使用這種辦法了。

以上的程式碼

上面說的都是在一個Section下進行單選,如果換個前提條件,是要求在多個Section下進行全部Cell的單選呢?那如果又增加了難度,要求是在多個Section下進行每一個Section內部的單選呢?

首先,這個在多個Section下全域性單選功能和單個Section下的單選功能是一樣的,重要的是下面這個在每一個Section下進行單選操作。

多個Section下每個Section內部的單選功能

以上的程式碼

多選

和單選一樣,如果多選任務要求的是多個Section下的全域性多選呢?如果是多個Section下的每個Section內部的多選呢?這樣的話又要如何實現呢?思考一下。

當認真思考之後你會發現,這樣的做法實在是無聊。

一個購物車

在做了這麼多的單選和多選操作之後,用一個實實在在的例子進行一下演練,涉及到一個很實際的應用場景:挑選購物車中的物品進行結賬。

在你手機中某一家電商類App的購物車中會有同一個商家的多種物品,也會出現所有的商品都不是同一個商家這種情況。當你有錢的時候,你可以全選購物車中的所有物品進行結賬;當你不是很有錢的時候,只能夠買其中的一部分,你可以選擇同一個商家下面的所有商品,同時你又比較糾結,你也可以挑選多個商家的某一些商品進行結賬;當你實在是沒有錢了,但又想買買買,所有錢只能夠買一件的時候,你在購物車中挑選了一件進行結賬。

使用者的行為就是最直觀的專案需求,翻譯成編碼需求大概是這樣:

  • 表檢視是由許多Sections的,每一個Section下又有若干Rows
  • 可以對全域性的Row單選、多選、全選
  • 可以多某一個Section進行單選、多選、全選
  • 可以跨Section進行多選
  • 當有至少一個Row沒有被選中的時候,全選按鈕就不應該是選中狀態
  • 當某一個Section下僅僅有一個Row的時候,並且選中了該Row的時候,當前Section也應該被選中
  • 當點選全選按鈕的時候,所有的Row都應該進入全選狀態,並且對應的Section的HeaderView也要變成選中狀態

應該就這些了吧。。。

初始工程已經寫好了,這個初始工程使用了plist檔案提供了假資料供表檢視使用,同時使用了一些Xib進行構建區域性檢視的樣式,這在開發中會成為便捷開發的部分,同時也會在進行小功能demo的時候節省大量的時間用來寫無聊至極的佈局程式碼。但是,這個初始工程還是有很多問題的,比如,沒有很好地解決Cell重用問題,當點選一個靠上部分或者靠下部分的按鈕,然後滑動表格,會發現,其他地方的按鈕別選中了!!典型的cell重用問題,這個會在後面進行解決重用問題;還有裡面在每一個xib對應的檢視中都用到了那個具有特殊狀態的按鈕,並且在每個類中都寫了重複的程式碼,寫三遍的程式碼就要考慮重構。同時,這個工程僅僅提供一個展示,沒有具體的單選以及多選或者全選功能,這些都會在接下來進行實現。

先看看初始工程長什麼樣子吧,而且最終的工程也是長這個樣子的,不過功能部分是看不出來的,體驗才能察覺出來。

shoppingList

感覺給自己挖了個大坑!!!!!!

在實際的開發中,一個購物車要考慮的情形比這複雜的多了,這裡僅僅是簡化到了最簡單的購物車功能。

參考資料: