概述:本文是黑馬程式員UI視訊教程第九天,QQ好友清單案例的總結
該案例中主要涉及的知識點有:改變圖檔在UIButton中的平鋪方式、UITableViewHeaderFooterView、設定按鈕中圖檔和文本的内邊距和對齊方式、tableView中按組進行重載、自定義delegate協定、layoutSubViews方法、巧妙傳遞使用者的單擊資訊等内容
基本思路:
- 使用UITableviewController,拷貝素材、建立界面、字典轉模型,懶加載
- 實作tableView的資料源方法
- 設定headerView
- 實作點選headerView箭頭旋轉效果
- 實作點選headerView好友清單展開效果
- 實作VIP會員名稱顯示為紅色
最終效果如下:
1、拷貝素材、建立界面、字典轉模型及懶加載
- 該案例中僅包含一個tableView,是以使用一個tableViewController開始将更友善。删除原來的viewController,建立新的tableViewController,然後使其成為初始控制器initial View。建立基于UITableViewController的子類,并将其挂在建立的tableViewController下。
- 注意該plist檔案為字典嵌套字典的結構,是以字典轉模型也需要分兩層來寫
2、實作TableView的資料源方法
- 指定tableView的Section,
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- 指定tableView每個Section的行數,
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- 指定tableView每行所使用的Cell,
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
從效果上看單元格的形式與subtitle形式的單元格基本一緻,可以在代理方法内部使用标準subtitle形式的單元格并為内部控件指派。但由于我們還要進行更多細節的調整,将單元格單獨實作更有利于後續的改進,代碼的可讀性高。
因為每個單元格的形式基本一緻,可以考慮使用xib檔案來建立單元格
- 建立xib單元格
- 建立基于UITableViewCell的子類,并與xib檔案進行挂接。該子類中應包含LJFriend成員(或者将LJFriend對象作為參數傳遞給建立單元格的類方法)
- 實作建立單元格的類方法,重寫LJFriend成員的set方法。在set方法中根據是否是vip設定昵稱的字型顔色是否為紅色
#import <UIKit/UIKit.h> #import "LJFriend.h" @interface LJFriendCell : UITableViewCell @property(nonatomic , strong) LJFriend * friendCell_model; +(instancetype)friendCellWithTableView:(UITableView *)tableView; @end
#import "LJFriendCell.h" @interface LJFriendCell () @property (weak, nonatomic) IBOutlet UILabel *lbl_cell_title; @property (weak, nonatomic) IBOutlet UILabel *lbl_cell_subtitle; @property (weak, nonatomic) IBOutlet UIImageView *img_cell_icon; @end @implementation LJFriendCell #pragma mark - 封裝建立單元格的類方法 +(instancetype)friendCellWithTableView:(UITableView *)tableView { static NSString * reuseID = @"friendCell"; LJFriendCell * cell = [tableView dequeueReusableCellWithIdentifier:reuseID]; if (cell == nil) { cell = [[[NSBundle mainBundle] loadNibNamed:@"LJFriendCell" owner:nil options:nil] lastObject]; } return cell; } #pragma mark - 重寫LJFriend成員的set方法 -(void)setFriendCell_model:(LJFriend *)friendCell_model { _friendCell_model = friendCell_model; //設定頭像 self.img_cell_icon.image = [UIImage imageNamed:friendCell_model.icon]; //設定昵稱 self.lbl_cell_title.text = friendCell_model.name; self.lbl_cell_title.textColor = friendCell_model.isVip ? [UIColor redColor]:[UIColor blackColor]; //設定簽名 self.lbl_cell_subtitle.text = friendCell_model.intro; } @end
3、設定headerView
- 理論上任何基于UIView的子類均可以作為tableView的headerView和footerView,但IOS為我們提供了已經整合好的專用類,UITableViewHeaderFooterView,這個類中已經定義了一些屬性和方法,如根據重用ID建立類對象等等。
- 為tableView指定headerView有兩種方式,第一種是直接為tableView的headerView屬性指派;第二種是實作tableView的代理方法
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
,這種方式可以為每一組指定不同的headerView。代理方法内部實作的思路與單元格基本一緻:
a、擷取魔性資訊;
b、建立headerView;
c、為headerView指派;
d、傳回headerView
- UITableViewHeaderFooterView隻能從代碼建立,不能使用storuboard或xib檔案建立,因為控件庫中沒有這個控件
- 建立一個基于UITableViewHeaderFooterView的子類,因為需要接受group的相關資訊,是以需要包含一個LJGroup的類成員,與UITableViewCell類似,也需要封裝建立headerView的類方法,并重寫它的對象方法
-(instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
- 在
initWithReuseIdentifier:
方法中,首先需要調用父對象的同名方法,然後建立對應的控件,為控件指派并設定相關屬性
知識點:
擷取目前螢幕的尺寸可以通過
[UIScreen mainScreen].bounds.size
來擷取
知識點:
UIButton控件的
屬性可以更改控件内容的水準方向的對齊方式,同理contentHorizontalAlignment
UIControlContentVerticalAlignment
屬性可以更改控件内容的垂直方向的對齊方式
知識點:
UIButton控件的contentEdgeInsets屬性可以更改控件内容的内邊距
UIButton控件的titleEdgeInsets屬性可以更改控件文本框内容的内邊距
兩者都是需要傳遞UIEdgeInsets的結構體變量,可以通過UIEdgeInsetsMake( , , , )來建立
知識點:
UIImageView中圖檔的平鋪方式可以通過contentMode屬性來設定,該屬性需要指定一個類型為UIViewContentMode的枚舉量,該枚舉量主要有以下成員
typedef NS_ENUM(NSInteger, UIViewContentMode) { //圖檔拉伸填充至整個UIImageView(圖檔可能會變形),這也是預設的屬性,如果什麼都不設定就是它在起作用 UIViewContentModeScaleToFill, //圖檔拉伸至完全顯示在UIImageView裡面為止(圖檔不會變形),這種方式圖檔完全放置在父容器的範圍内,空白區域顯示背景色 UIViewContentModeScaleAspectFit, //圖檔拉伸至圖檔的的寬度或者高度等于UIImageView的寬度或者高度為止.看圖檔的寬高哪一邊最接近UIImageView的寬高,一個屬性相等後另一個就停止拉伸.這種方式圖檔可能會超出父容器的範圍 UIViewContentModeScaleAspectFill, //調用setNeedsDisplay 方法時,就會重新渲染圖檔 //下面的屬性都是不會拉伸圖檔的,圖檔也不會縮放,如果圖檔原始尺寸過大,可能會嚴重超出父容器的範圍 UIViewContentModeRedraw, //中間模式 UIViewContentModeCenter, //頂部 UIViewContentModeTop, //底部 UIViewContentModeBottom, //左邊 UIViewContentModeLeft, //右邊 UIViewContentModeRight, //左上 UIViewContentModeTopLeft, //右上 UIViewContentModeTopRight, //左下 UIViewContentModeBottomLeft, //右下 UIViewContentModeBottomRight, };
關于UIViewContentMode屬性的更多資訊請參考
知識點:
如果圖檔超出了父容器的範圍,可以通過clipsToBounds屬性來設定超出部分是否需要被裁剪,它接收一個Boolean變量
- 重寫LJGroup類成員的set方法,在方法中對标題和線上人數進行指派
- 為headerView注冊單擊事件
-
實作headerView的單擊事件
**分析:**該單擊事件中主要實作兩個功能,第一是實作箭頭的旋轉,這可以通過setTransform來實作;第二是實作好友清單的折疊和展開,這可以通過改變該section對應的行數然後重新整理單元格來實作,但單擊事件是在headerView中實作的,它無法直接重新整理單元格,是以需要代理。代理方法雖然可以實作tableView的重載,但是它無法知道目前單擊的是哪一個headerView,是以我們需要以單擊事件的sender為媒介,傳遞目前headerView所在的section的索引号,可以通過控件的tag來實作這個目的
//LJGroup類的聲明 #import <Foundation/Foundation.h> #import "LJFriend.h" @interface LJGroup : NSObject @property(nonatomic , strong) NSString * name; @property(nonatomic , strong) NSArray * friends; @property(nonatomic , assign) int online; //用于記錄目前清單是否為展開狀态 @property(nonatomic , assign , getter=isopen) Boolean open; -(instancetype)initWithDic:(NSDictionary *)dic; +(instancetype)groupWithDic:(NSDictionary *)dic; @end
//LJGroupHeaderView的聲明 #import <UIKit/UIKit.h> #import "LJGroup.h" @class LJGroupHeaderView; //聲明LJGroupHeaderView的delegate方法 @protocol LJGroupHeaderViewDelegate <NSObject> @optional -(void)groupHeaderViewDidClicked:(LJGroupHeaderView *)groupHeaderView withSection:(NSInteger)section; @end @interface LJGroupHeaderView : UITableViewHeaderFooterView @property(nonatomic , strong) LJGroup * groupHeaderView_model; //聲明delegate屬性 @property(nonatomic , weak) id<LJGroupHeaderViewDelegate> delegate; +(instancetype)groupHeaderViewWith:(UITableView *)tableView; @end
//在.m檔案中為控件注冊單擊事件并實作 -(void)groupHeaderViewClick:(UIButton *)sender { //反轉目前的Group的展開狀态 self.groupHeaderView_model.open = !self.groupHeaderView_model.open; [UIView animateWithDuration:0.3 animations:^{ //根據目前的展開狀态判斷應該旋轉的角度 CGFloat angle = self.groupHeaderView_model.isopen ? M_PI_2 : 0; //使控件旋轉 [self.groupname.imageView setTransform:CGAffineTransformMakeRotation(angle)]; }]; //重新整理單元格(調用代理方法) //擷取目前headerView的tag NSInteger tagnumber = [sender superview].tag; if ([self.delegate respondsToSelector:@selector(groupHeaderViewDidClicked: withSection:)]) { //調用代理方法 [self.delegate groupHeaderViewDidClicked:self withSection:tagnumber ]; } }
//在controller中實作tableView的代理方法來傳回headerView -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { //擷取模型資訊 LJGroup * group_current = self.groups[section]; //建立headerView LJGroupHeaderView * headerView = [LJGroupHeaderView groupHeaderViewWith:tableView]; //為headerView指派 headerView.groupHeaderView_model = group_current; //指定headerView的delegate屬性 headerView.delegate = self; //将目前section的索引号傳遞給headerView的tag headerView.tag = section; //傳回headerView return headerView; }
//在controller中實作LJGroupHeaderView的代理方法 -(void)groupHeaderViewDidClicked:(LJGroupHeaderView *)groupHeaderView withSection:(NSInteger)section { NSIndexSet * indexsection = [NSIndexSet indexSetWithIndex:section]; //[self.tableView reloadData]太過浪費資源,下面的方法可以進行某個Section的reload,節省資源 [self.tableView reloadSections:indexsection withRowAnimation:UITableViewRowAnimationAutomatic]; }
問題:小箭頭的旋轉是動畫形式執行的,動畫尚未執行完畢的時候,這個section就已經開始執行reload的代碼了,是以會看到箭頭剛開始旋轉就閃一下又恢複了。
解決辦法:将箭頭旋轉的行為放置在layoutSubvies方法中,這個方法在子視圖被重新布局時被自動調用,預設什麼也不做。
-(void)layoutSubviews { [super layoutSubviews]; [UIView animateWithDuration:0.3 animations:^{ //NSLog(@"該動畫被執行了"); CGFloat angle = self.groupHeaderView_model.isopen ? M_PI_2 : 0; [self.groupname.imageView setTransform:CGAffineTransformMakeRotation(angle)]; }]; }
由于本人水準有限,不當之處還請批評指正。初學IOS,希望大家一起交流一起進步~