天天看點

TableView實作QQ好友清單效果

概述:本文是黑馬程式員UI視訊教程第九天,QQ好友清單案例的總結
該案例中主要涉及的知識點有:改變圖檔在UIButton中的平鋪方式、UITableViewHeaderFooterView、設定按鈕中圖檔和文本的内邊距和對齊方式、tableView中按組進行重載、自定義delegate協定、layoutSubViews方法、巧妙傳遞使用者的單擊資訊等内容

基本思路:

  1. 使用UITableviewController,拷貝素材、建立界面、字典轉模型,懶加載
  2. 實作tableView的資料源方法
  3. 設定headerView
  4. 實作點選headerView箭頭旋轉效果
  5. 實作點選headerView好友清單展開效果
  6. 實作VIP會員名稱顯示為紅色

最終效果如下:

TableView實作QQ好友清單效果

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檔案來建立單元格

    1. 建立xib單元格
    2. 建立基于UITableViewCell的子類,并與xib檔案進行挂接。該子類中應包含LJFriend成員(或者将LJFriend對象作為參數傳遞給建立單元格的類方法)
    3. 實作建立單元格的類方法,重寫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,希望大家一起交流一起進步~