【IOS】模仿windowsphone清單索引的控制項YFMetroListBox,vblistbox索引
有沒有覺得UITableView內建的右側索引很難用,我一直覺得WindowsPhone中的清單索引非常好用。
所以呢,我們來實作類別似Windows Phone中的清單索引(這就是信仰)。
最終實現:
1.完整的首字母索引 2.Header名稱索引
想法:這個控制項是該繼承UITableView還是UIView,抑或其他的呢?
想要寫的這個控制項,只是在UITableView的基礎上增加-點擊HeaderView事件-來彈出索引。
彈出索引的大小為控制項的大小,並直接添加到父視圖中。
所以覺得直接繼承UITableView會更加方便,而繼承UIView雖然說在寫法上更簡單些,但是總覺得不太好。
碰巧看到一個實現UITableView動畫的例子,於是參照著實現了HeaderView的點擊事件。
https://github.com/applidium/ADLivelyTableView
原理是通過增加一個中間代理,只在控制項中實現了willDisplayCell協議來控制滑動時的動畫,
如果在VC中使用控制項時也實現了該協議,則讓控制項中的代理髮送該訊息給VC。這樣的話相當
於UITableView的實現部分都不變,動畫都交給控制項來實現。
-------------------------------------我是分割線----------------------------------------------
1.繼承UITableView - 增加中間代理
普通流程:UIViewController<UITableViewDelegate> ---> UITableView
tableView.delegate = self;
控制項流程:繼承UITableView : YFMetroListBox : UITableView
增加私人屬性: id<UITableViewDelegate> _selfDelegate;
UIViewController<UITableViewDelegate> ---> YFMetroListBox<UITableViewDelegate> ---> UITableView
tableView.delegate = self; 重寫YFMetroListBox代理見下面代碼
重寫YFMetroListBox設定代理的方法:(這個是繼承自UITableView的屬性)
-(void)setDelegate:(id<UITableViewDelegate>)delegate{//這裡的形參即VC對象 _selfDelegate = delegate; //讓本類(YFMetroListBox)對VC進行監聽 [super setDelegate:self]; //讓父類(UITableView)對子類的監聽
}
//重寫方法: YFMetroListBox或者父類是否實現了協議- (BOOL)respondsToSelector:(SEL)aSelector { return [super respondsToSelector:aSelector] || [_selfDelegate respondsToSelector:aSelector]; } //轉寄訊息 - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([_selfDelegate respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:_selfDelegate]; } else { [super forwardInvocation:anInvocation]; } }
2.YFMetroListBox實現willDisplayHeaderView協議,增加添加HeaderView的點選手勢
彎路1 :剛開始想使用viewForHeaderInSection添加自訂View,之後添加手勢,
但是這樣的話就得在此HeaderView內容,因此直接在初始化的時候傳入了NSArray(感覺不好,但沒其他方案)。
除了上述協議,還有titleForHeaderInSection也是能夠直接設定預設的標題內容的,那麼如何擷取headerView?
再者,上面兩個協議都是UITableViewDataSource中的,要在控制項中這樣做會更加的混亂了
解決 :不管VC中如何設定標題,在HeaderView將要顯示的時候,添加手勢就行了
#pragma mark -m UITableViewDelegate- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section{ //如果VC也實現了該協議,則發送此訊息 if([_selfDelegate respondsToSelector:@selector(tableView:willDisplayHeaderView:forSection:)]){ [_selfDelegate tableView:tableView willDisplayHeaderView:view forSection:section]; }
//增加手勢 UITapGestureRecognizer *tapRegesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapHeaderViewInSection)]; view.gestureRecognizers = @[tapRegesture];}
3. 在YFMetroListBox中擷取標題內容數組
彎路2 : 存在headerViewForSection:方法擷取某一section的headerView。由此想到,
既然在VC中設定好了頭標題,那麼我肯定可以在內部動態擷取全部標題。(錯誤的想法)
-(NSArray *)getHeaderViewTextArray{ NSMutableArray *array = [NSMutableArray array]; for (int i = 0 ; i < [self numberOfSections]; i++) {//迴圈section數 NSArray *subviews = [[self headerViewForSection:i] subviews];//取得UIlabel的內容 for (id subview in subviews) { if([subview isKindOfClass:[UILabel class]]){ UILabel *label = (UILabel *)subview; [array addObject:label.text]; break; } } } return array;}
但是通過這個方法擷取的的始終只有螢幕中的可見部分的頭標題,而不是全部的頭標題。
原因:因為使用了重用機制,只有顯示的部分被建立了,所以想要擷取全部的內容
是不可能的。這類問題 應該從資料來源觸發。
解決 :像UITableViewDataSource協議中sectionIndexTitlesForTableView一樣通過協議來實現。
自訂一個協議,在VC中實現該協議,給YFMetroListBox頭標題數組資料。
@protocol YFMetroListBoxDelegate <NSObject>@required- (NSArray<NSString *> *)sectionIndexTitlesForYFMetroListBox:(YFMetroListBox *)metroListBox;//返回的標題頭數組有兩種格式 1.自訂標題頭 2.("#ABCD...Z@"),#表示數字 @暫時表示除了數字 字母 英文 以外的字元。@end
具體的將一組包含數字、中文、英文、符號的資料分組排序成#ABC...Z✿"格式我們在下一篇隨筆給出。
4. 畫出點擊後介面
介面有兩種,通過metroListBoxType枚舉屬性來進行設定
分別對應最上面的兩幅圖。這點沒什麼說的,通過上一點擷取標題頭數組。來展示。
如果為YFMetroListBoxTypeAllAlphabet,則將#.A.B.C...Z.@全部畫出,給標題頭數組存在的添加點擊事件。
點擊後通過 [self scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]
atScrollPosition:UITableViewScrollPositionTop animated:NO]進行跳轉。
5.重寫 插入、刪除、重新載入資料等方法
到目前為止 基本上就做好了。只不過在添加更改資料來源之後進行reloadData、
或者對section進行insertSections、deleteSections、moveSection等操作,索引列表是不會改變的。
因此要重寫他們,手動的調用更新索引介面的方法。
//Data-(void)reloadData{ [self updateZoomOutView]; [super reloadData];}-(void)reloadSectionIndexTitles{ [self updateZoomOutView]; [super reloadSectionIndexTitles];}// only call insert/delete/reload calls or change the editing state inside an update block. otherwise things like row count, etc. may be invalid.- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation{ [self updateZoomOutView]; [super insertSections:sections withRowAnimation:animation];}- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation{ [self updateZoomOutView]; [super deleteSections:sections withRowAnimation:animation];}- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation{ [self updateZoomOutView]; [super reloadSections:sections withRowAnimation:animation];}- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection{ [self updateZoomOutView]; [super moveSection:section toSection:newSection];}
這樣就大功告成了。
GitHub地址: YFMetroListBox