iOS開發中的單選與多選
在前端開發中如果要擁有一個單選或者多選功能十分簡單,因為HTML中有現成的標籤可以很方便的實現單選或者多選效果,比如這樣寫上幾句程式碼就能擁有最原始的選擇效果。
但是在iOS開發中就沒有這麼方便的控制元件了,如果要完成單選或者多選的功能還需要一些邏輯編碼,並且可以選擇的方案還是有很多的。
在iOS開發中經常用於實現單選或者多選功能的控制元件是UITableView
,並且,通過這個控制元件可以完成一個App80%的介面。而微信則是一個全部使用UITableView構建的App。下面將會針對於單選和多選進行一個探究學習,學習SDK中提供有哪些API能夠使用,並且結合這些API如何總結出合適的方法完成單選多選功能。
單選
單選其實還是很簡單的,在將UITableView新增到控制器中的時候選擇任意一個cell都會看到被選擇的cell變為灰色,這是系統提供的,不需要開發者進行設定。談不上好看,但正是由於不是很好看,才留給了開發者很大的自定義的空間。
這個選擇樣式系統是根據設定的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”];\
}\
}|
效果如圖,
這種做法說起來也是比較取巧,在需求不是很複雜的情況下使用還是可以的,如果一旦牽扯到很多的業務邏輯就不要使用這種辦法了。
上面說的都是在一個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對應的檢視中都用到了那個具有特殊狀態的按鈕,並且在每個類中都寫了重複的程式碼,寫三遍的程式碼就要考慮重構。同時,這個工程僅僅提供一個展示,沒有具體的單選以及多選或者全選功能,這些都會在接下來進行實現。
先看看初始工程長什麼樣子吧,而且最終的工程也是長這個樣子的,不過功能部分是看不出來的,體驗才能察覺出來。
感覺給自己挖了個大坑!!!!!!
在實際的開發中,一個購物車要考慮的情形比這複雜的多了,這裡僅僅是簡化到了最簡單的購物車功能。