OCiOS开发:表格视图实现腾讯好友列表展开收缩效果

前言

这几天刚刚给学生讲到了表格视图的使用,有学生问我,如果要实现类似于腾讯好友列表展开收缩的效果该怎么实现呢?以前我写过有关这样效果的demo,但是一直没有发表成文,今天刚好有时间,于是写出来,分享给各位,当然要实现这种效果方法还是有很多,这里就讲解下我的实现方式,不到之处,还往各位提点建议,我会及时改进。

效果

这里写图片描述

为了简化数据,所以每个分组里面的好友数据都是一样的。

实现思路

1、用一个字典来判断,将section作为key,如果字典对应key有值,则展开组,反之则关闭组。

2、自定义头部视图,并且给头部视图添加点击事件(UITapGestureRecognizer)监测用户点击分组。

3、在主页为表视图创建头部视图的时候,为其设置tag值,其值为当前所在的组(section),并且在自定义头部视图类中,通过委托模式,将其tag值传递到主页,为其获取到对应头部视图的section,设为字典key。

4、涉及技术:委托模式、自定义单元格、自定义头部视图、plist文件读取、点击手势以及协议传值。

数据处理

好友列表展示的数据,我已预先存到plist文件中,包括图片素材,也一并导入了工程,如下所示:

这里写图片描述

素材下载地址:http://download.csdn.net/detail/hierarch_lee/9412991

代码实现

由于代码相对较简单,并且关键部分我也详细注明了代码解释,因此,就不做具体的讲解。
涉及文件

1、LHY_HomeViewController:主页,继承于UIViewController,用于展示好友列表。

2、LHY_FriendsListCell:自定义单元格,继承于UITableViewCell,用于布局好友列表信息;

3、LHY_HeaderView:自定义头部视图,继承于UITableViewHeaderFooterView,用于布局分组显示数据;

代码块1:为主页添导航栏,这里我是手动创建UIWindow,首先在Info.plist将Main字段删除,然后在AppDelegate.m文件中,导入LHY_HomeViewController,添加如下代码:

  • AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    LHY_HomeViewController *lhyVc = [[LHY_HomeViewController alloc] init];

    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:lhyVc];

    _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

    _window.backgroundColor = [UIColor blackColor];

    _window.rootViewController = nav;

    [_window makeKeyAndVisible];

    return YES;
}

代码块2:自定义单元格,LHY_FriendsListCell,主要布局好友信息包括头像、昵称以及签名,具体实现如下:

  • LHY_FriendsListCell.h
#import <UIKit/UIKit.h>

@interface LHY_FriendsListCell : UITableViewCell

@property (strong, nonatomic) UIImageView *headPortraitImageView; /**< 头像 */
@property (strong, nonatomic) UILabel     *nicknameLabel;         /**< 昵称 */
@property (strong, nonatomic) UILabel     *signatureLabel;        /**< 签名 */

@end
  • LHY_FriendsListCell.m
#import "LHY_FriendsListCell.h"

@implementation LHY_FriendsListCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        // 加载视图
        [self.contentView addSubview:self.headPortraitImageView];
        [self.contentView addSubview:self.nicknameLabel];
        [self.contentView addSubview:self.signatureLabel];
    }
    return self;
}

#pragma mark *** Getters ***
- (UIImageView *)headPortraitImageView {
    if (!_headPortraitImageView) {

        _headPortraitImageView = [[UIImageView alloc] init];
        _headPortraitImageView.bounds = CGRectMake(0, 0, 60, 60);
        _headPortraitImageView.center = CGPointMake(40, 40);
        // 设置文本现实模式
        _headPortraitImageView.contentMode = UIViewContentModeScaleAspectFit;
        // 设置圆角
        _headPortraitImageView.layer.cornerRadius = 30;
        // 设置图层超出父视图部分不显示
        _headPortraitImageView.layer.masksToBounds = YES;
    }
    return _headPortraitImageView;

}
- (UILabel *)nicknameLabel {
    if (!_nicknameLabel) {
        _nicknameLabel = [[UILabel alloc] initWithFrame:CGRectMake(80, 5, 310, 30)];
        _nicknameLabel.font = [UIFont boldSystemFontOfSize:18];
        _nicknameLabel.textColor = [UIColor redColor];
    }
    return _nicknameLabel;
}

- (UILabel *)signatureLabel {
    if (!_signatureLabel) {
        _signatureLabel = [[UILabel alloc] initWithFrame:CGRectMake(80, 50, 310, 20)];
        _signatureLabel.font = [UIFont systemFontOfSize:15];
    }
    return _signatureLabel;
}

@end

代码块3:自定义头部视图,LHY_HeaderView,本类主要布局组标题以及指示(展开/关闭)控件,其次,通过添加点击手势结合委托模式,传递tag值。

  • LHY_HeaderView.h
#import <UIKit/UIKit.h>

@class LHY_HeaderView;

// 协议声明
@protocol LHY_HeaderViewDelegate <NSObject>

- (void)lhyHeaderView:(LHY_HeaderView *)lhyHeaderView didSelectedWithTag:(NSInteger)tag;

@end

@interface LHY_HeaderView : UITableViewHeaderFooterView

@property (nonatomic, strong) UILabel                *sectionTitleLabel; /**< 组标题 */
@property (nonatomic, strong) UIImageView            *indictorImageView; /**< 指示图片 */

@property (nonatomic, weak) id <LHY_HeaderViewDelegate> delegate;        /**< 代理 */

@end
  • LHY_HeaderView.m
#import "LHY_HeaderView.h"

@implementation LHY_HeaderView

- (instancetype)initWithReuseIdentifier:(nullable NSString *)reuseIdentifier {
    if (self = [super initWithReuseIdentifier:reuseIdentifier]) {

        self.contentView.backgroundColor = [UIColor clearColor];
        self.layer.borderWidth = 0.34;
        self.layer.borderColor = [UIColor lightGrayColor].CGColor;

        // 加载控件
        [self.contentView addSubview:self.indictorImageView];
        [self.contentView addSubview:self.sectionTitleLabel];

        // 添加点击手势
        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapGesture:)];
        [self addGestureRecognizer:tapGesture];

    }
    return self;
}



#pragma mark *** Gestures ***
- (void)respondsToTapGesture:(UITapGestureRecognizer *)gesture {
    // 判断代理是否存并且遵守协议,如果满足条件,则让代理执行协议方法,并且将对应组以及tag值传递给代理;
    if (_delegate && [_delegate conformsToProtocol:@protocol(LHY_HeaderViewDelegate)]) {
        [_delegate lhyHeaderView:self didSelectedWithTag:self.tag];
    }
}

#pragma mark *** Getters ***
- (UIImageView *)indictorImageView {
    if (!_indictorImageView) {
        _indictorImageView = [[UIImageView alloc] init];
        _indictorImageView.bounds = CGRectMake(0, 0, 20, 20);
        _indictorImageView.center = CGPointMake(15, 22);
    }
    return _indictorImageView;
}

- (UILabel *)sectionTitleLabel {
    if (!_sectionTitleLabel) {
        _sectionTitleLabel = [[UILabel alloc] init];
        _sectionTitleLabel.bounds = CGRectMake(0, 0, 200, 30);
        _sectionTitleLabel.center = CGPointMake(CGRectGetMaxX(self.indictorImageView.frame) + CGRectGetMidX(_sectionTitleLabel.bounds) + 10, 22);
        _sectionTitleLabel.font = [UIFont boldSystemFontOfSize:18];
    }
    return _sectionTitleLabel;
}

@end

代码块4:加载主页,LHY_HomeViewController,本类主要配置表格视图显示,以及处理组展开/关闭状态。

  • LHY_HomeViewController.h
#import <UIKit/UIKit.h>

@interface LHY_HomeViewController : UIViewController

@end
  • LHY_HomeViewController.m
#import "LHY_HomeViewController.h"
#import "LHY_HeaderView.h"
#import "LHY_FriendsListCell.h"

static NSString *const kLHY_ViewControllerTitle         = @"好友列表";
static NSString *const kReusableCellIdentifier          = @"a1b1c1";
static NSString *const kHeaderFooterViewReuseIdentifier = @"a2b2c2";

@interface LHY_HomeViewController () <UITableViewDataSource, UITableViewDelegate, LHY_HeaderViewDelegate>

{
    NSArray *_sectionTitles;   /**< 分组标题集合 */
    NSArray *_friendsListInfo; /**< 好友信息集合 */

    NSMutableDictionary *_openDict; /**< 用于设置当前分组展开或关闭 */
}

@property (nonatomic, strong) UITableView *tableView; /**< 表格视图 */

- (void)initializeDataSource;    /**< 初始化数据源 */
- (void)initializeUserInterface; /**< 初始化用户界面 */

@end

@implementation LHY_HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self initializeDataSource];
    [self initializeUserInterface];
}

#pragma mark *** Initialize methods ***
- (void)initializeDataSource {

    // 初始化分组数据
    _sectionTitles = @[@"同事", @"家人", @"朋友", @"同学", @"陌生人", @"黑名单"];

    // 根据plist文件获取好友列表数据
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"FriendsList" ofType:@"plist"];
    _friendsListInfo = [NSArray arrayWithContentsOfFile:plistPath];
}

- (void)initializeUserInterface {

    self.title = kLHY_ViewControllerTitle;
    // 关闭系统自动偏移
    self.automaticallyAdjustsScrollViewInsets = NO;
    self.view.backgroundColor = [UIColor whiteColor];

    // 加载表格视图
    [self.view addSubview:self.tableView];
}

#pragma mark *** <UITableViewDataSource, UITableViewDelegate> ***
// 设置组数
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // 返回分组数据集合的个数
    return _sectionTitles.count;
}

// 设置行数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // 返回好友列表信息集合的个数
    return _friendsListInfo.count;
}

// 设置单元格
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {


    LHY_FriendsListCell *cell = [tableView dequeueReusableCellWithIdentifier:kReusableCellIdentifier forIndexPath:indexPath];

    // 超出父视图不显示,如果不将其值设为YES(默认为NO),那么现实在表格视图上的数据会出现重叠的bug。
    cell.layer.masksToBounds = YES;

    // 获取值
    NSString *nickName = _friendsListInfo[indexPath.row][@"nickName"];
    NSString *headPortrait = _friendsListInfo[indexPath.row][@"headPortrait"];
    NSString *signature = _friendsListInfo[indexPath.row][@"context"];

    // 赋值控件
    cell.nicknameLabel.text = nickName;
    cell.signatureLabel.text = signature;
    cell.headPortraitImageView.image = [UIImage imageNamed:headPortrait];

    return cell;
}


// 设置行高
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    // 根据当前组获取字典key
    NSString *key = [NSString stringWithFormat:@"%ld", indexPath.section];

    // 判断如果字典对应key是否有值,如果有值,说明当前组展开,返回 80 高度;否则为关闭,返回 0 高度;
    if ([_openDict objectForKey:key]) {
        return 80;
    }else {
        return 0;
    }
}

// 设置组高
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 44;
}

// 自定义头部视图
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    // 创建头部视图
    // 头部视图重用
    LHY_HeaderView *lhyHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:kHeaderFooterViewReuseIdentifier];

    // 设置代理
    lhyHeaderView.delegate = self;

    // 配置视图显示
    lhyHeaderView.sectionTitleLabel.text = _sectionTitles[section];
    lhyHeaderView.indictorImageView.image = [UIImage imageNamed:@"flag.png"];

    // 设置tag值
    lhyHeaderView.tag = section;

    // 根据字典对应key(section)是否有值来判断当前组应该展开还是收缩
    // 如果有值,则说明应该展开组,无值则应该关闭组
    NSString *key = [NSString stringWithFormat:@"%ld", section];
    // 通过动画效果实现展开与关闭指示状态的显示
    [UIView animateWithDuration:0.4 animations:^{
        // 如果展开,则将指示图片旋转45°,箭头向下,意为展开;
        // 如果关闭,则移除transform属性,回复原样,箭头向右,意为关闭;
        lhyHeaderView.indictorImageView.transform = [_openDict objectForKey:key] ? CGAffineTransformMakeRotation(M_PI_2) : CGAffineTransformIdentity;
    }];

    return lhyHeaderView;

}

#pragma mark *** LHY_HeaderViewDelegate ***
- (void)lhyHeaderView:(LHY_HeaderView *)lhyHeaderView didSelectedWithTag:(NSInteger)tag {

    // 判断字典是否存在,如果不存在,则新建;
    // 如果对一个未初始化的可变字典作数据操作,将会导致奔溃;
    if (!_openDict) {
        _openDict = [NSMutableDictionary dictionary];
    }

    // 获取tag值,并将其作为字典key
    NSString *key = [NSString stringWithFormat:@"%ld", tag];

    // 模拟展开与关闭
    // 判断当前组是否有值,有值则移除对应key-value对,无值则设置key-value对。
    if (![_openDict objectForKey:key]) {
        [_openDict setObject:key forKey:key];
    }else{
        [_openDict removeObjectForKey:key];
    }

    // 刷新表格视图指定组
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:tag] withRowAnimation:UITableViewRowAnimationFade];
}



#pragma mark *** Getters ***

- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds) - 64) style:UITableViewStylePlain];
        // 设置代理
        _tableView.delegate = self;
        // 设置数据源
        _tableView.dataSource = self;
        // 设置尾部视图(此设置可解决当表格视图在屏幕能完全展示数据,并且有多余空间情况下出现分割线的问题)
        _tableView.tableFooterView = [[UIView alloc] init];

        // 注册单元格
        [_tableView registerClass:[LHY_FriendsListCell class] forCellReuseIdentifier:kReusableCellIdentifier];
        // 注册头部视图
        [_tableView registerClass:[LHY_HeaderView class] forHeaderFooterViewReuseIdentifier:kHeaderFooterViewReuseIdentifier];
    }
    return _tableView;
}


@end

到了这一步,运行工程,你将会看到上述展示的效果。如果还有朋友不太熟悉思路,欢迎加Q:894416347,填好备注信息,或留言,一起探讨。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值