iOS学习程序员IOS开发学习笔记

传智播客第八天练习总结-QQ聊天UI界面

2019-03-18  本文已影响13人  COMMA_迷途知返

摘要:本练习是传智播客IOS UI基础班的第八天课程练习,对练习的知识点和重点进行总结。实现效果如图所示

QQ聊天界面UI练习

基本思路

  1. 导入素材,创建主界面
  2. 实现字典转模型(可以暂时不计算控件的frame),并实现懒加载
  3. 创建自定义Cell,实现其创建单元格各控件的类方法和对象方法,重写数据模型的set方法,在该方法中对各控件的内容和frame进行赋值
  4. 实现tableView的datasource方法
  5. 在frame模型中计算各控件的frame
  6. 修正tableView的细节:分割线、不可选中、背景色、正文的文字颜色和背景图片等(拉伸图片)
  7. 实现对自定义Cell中时间的判断,如果与上一条消息发送时间相同,则不显示
  8. 实现键盘弹出时整个View的随动效果(通知)
  9. 把键盘的return键变成send键,实现新的消息的发送和回复(UITextField的delegate方法)

1、导入素材,创建主界面

素材主要有一个的plist文件和相关的图片,plist文件总体是一个数组,每条消息是一个Dictionary,包含正文、事件和类型条信息,类型中0表示自己发送的消息,1表示别人发送的消息

plist文件

2、实现字典转模型和懒加载

由于要在懒加载时就计算控件的Frame和单元格行高,所以创建两个Model,一个存储消息模型,一个存储Frame信息

个人认为可以直接在一个model中存储message和messageFrame信息,但是结构不够清晰,维护相对复杂

由于消息的类型只有0和1两种类型,为了更加直观,可以将其存储为一个枚举形式

typedef enum {
    LJMessageTypeMe = 0,//表示自己发的消息
    LJMessageTypeOther = 1//表示对方发的消息
}LJMessageType;
@implementation LJMessage
-(instancetype)initWithDic:(NSDictionary *)dic
{
    if (self = [super init]) {
            //注意类成员的名称要与字典的键名称一致
        [self setValuesForKeysWithDictionary:dic];
    }
    return self;
}
+(instancetype)messageWithDic:(NSDictionary *)dic
{
    return [[self alloc] initWithDic:dic];
}
@end

Frame模型类中将message模型作为自己的成员之一,还包括时间、正文、头像的Frame,以及单元格的行高。.m文件中需要重写message的set方法,在这个方法中计算各控件的Frame和行高。

注意:计算Frame需要先引入<UIKit/UIKit.h>,否则无法计算文字高度

懒加载的实质是重写模型对象的get方法,当程序需要调用这个成员的get方法时,在方法内部判断模型对象是否为空,如果为空则加载对应的数据

懒加载通常的模式为:1、获取plist文件路径;2、根据文件路径创建一个由NSDictionary组成的NSArray;3、创建一个空的NSMutableArray;4、遍历每一个NSDictionary,使用模型对象的类方法或对象方法将其转换为模型对象;5、将该模型对象加入NSMutableArray;6、循环结束后将这个可变数组赋值给模型数据集;

注意:因为后续要实现消息的发送和自动回复,因此创建的模型数据集应该是一个可变数组

-(NSMutableArray *)messageFrames
{
    if (_messageFrames == nil) {
        NSString * path_plist = [[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil];
        NSArray * messages_dic = [NSArray arrayWithContentsOfFile:path_plist];
        NSMutableArray * messageFrames_model = [NSMutableArray array];
        for (NSDictionary * dic in messages_dic) {
            LJMessageFrame * messageFrame_model_temp = [[LJMessageFrame alloc] init];
            LJMessage * currentMessage = [LJMessage messageWithDic:dic];
            messageFrame_model_temp.message= currentMessage;
            [messageFrames_model addObject:messageFrame_model_temp];
        }
        _messageFrames = messageFrames_model;
    }
    return _messageFrames;
}

3、创建自定义Cell

创建一个基于UITableViewCell的类,定义三个私有成员,lbl_time,img_icon,btn_text分别表示时间、头像和正文控件

重写自定义Cell的initWithStyle: reuseIdentifier:方法

#define myFont [UIFont systemFontOfSize:18]
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        //时间
        UILabel * lbl_time = [[UILabel alloc] init];
        lbl_time.font = myFont;
        lbl_time.textAlignment = NSTextAlignmentCenter;
        [self.contentView addSubview:lbl_time];
        self.lbl_time = lbl_time;
        
        //头像
        UIImageView * img_icon = [[UIImageView alloc] init];
        [self.contentView addSubview:img_icon];
        self.img_icon = img_icon;
        
        //正文
        UIButton * btn_text = [[UIButton alloc] init];
        btn_text.titleLabel.font = myFont;
        btn_text.titleLabel.numberOfLines = 0;
        [self.contentView addSubview:btn_text];
        self.btn_text = btn_text;
    }
    return self;
}

将整个自定义Cell的创建过程封装在一个类方法中,方便用户调用

+(instancetype)messageViewCellWithTableView:(UITableView *)tableView reuseidentifier:(NSString *)reuseID
{
    LJMessageViewCell * cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
    if (!cell) {
        cell = [[LJMessageViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseID];
    }
    
    return cell;
}

自定义Cell中的控件信息必须是根据其模型数据来决定的,教程中的方法是为Cell添加一个Frame模型数据的成员变量,通过重写set方法的方式来为各控件赋值。这样比较容易理解,使用也方便。个人认为也可以为自定义Cell定义一个方法,该方法使用Frame模型作为参数,通过参数将模型数据信息传入,然后为各控件赋值。

@interface LJMessageViewCell : UITableViewCell
@property(nonatomic, strong)LJMessageFrame * messageFrameModel;
@property(nonatomic, strong)UILabel * lbl_time;
@property(nonatomic, strong)UIImageView * img_icon;
@property(nonatomic, strong)UIButton * btn_text;
@end
  
@implementation LJMessageViewCell
-(void)setMessageFrameModel:(LJMessageFrame *)messageFrameModel
{
    _messageFrameModel = messageFrameModel;
    //时间内容
    self.lbl_time.text = messageFrameModel.message.time;
    self.lbl_time.frame = messageFrameModel.timeFrame;
    
    //头像内容
    if (messageFrameModel.message.type == LJMessageTypeMe) {
        self.img_icon.image = [UIImage imageNamed:@"me"];
    }else{
        self.img_icon.image = [UIImage imageNamed:@"other"];
    }
    self.img_icon.frame = messageFrameModel.iconFrame;
    
    //正文内容
    [self.btn_text setTitle:messageFrameModel.message.text forState:UIControlStateNormal];
    //根据消息类型确定背景图名称和正文字体颜色
    NSString * nor,* highlight;
    if (messageFrameModel.message.type == LJMessageTypeMe) {
        nor = @"chat_send_nor";
        highlight = @"chat_send_press_pic";
        [self.btn_text setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    } else {
        nor = @"chat_recive_nor";
        highlight = @"chat_recive_press_pic";
        [self.btn_text setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    }
    UIImage * img_normal = [UIImage imageNamed:nor];
    UIImage * img_highlight = [UIImage imageNamed:highlight];
    //设置背景图
    [self.btn_text setBackgroundImage:img_normal forState:UIControlStateNormal];
    [self.btn_text setBackgroundImage:img_highlight forState:UIControlStateHighlighted];
    //设置frame
    self.btn_text.frame = messageFrameModel.textFrame;        
}
@end

4、实现tableView的datasource方法

主要需要实现四个方法:

5、计算各控件的Frame

实现各控件的frame的计算,主要的知识点在于对文字高度的计算,对文本高度的计算方法有很多种,教程中使用的是boundingRectWithSize:的方法:

#import <UIKit/UIKit.h>
#define myFont [UIFont systemFontOfSize:18]
-(void)setMessage:(LJMessage *)message
{
    _message = message;
    //获取屏幕宽度
    CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
    static CGFloat margin = 5;
    NSDictionary * attr = @{NSFontAttributeName:myFont};
    NSString * text_string = message.text;
    CGSize text_size = [text_string boundingRectWithSize:CGSizeMake(200.0, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:attr context:nil].size;
        CGFloat textX = 0;
    CGFloat textY = iconY;
    CGFloat textW = text_size.width;
    CGFloat textH = text_size.height;
    if (message.type == LJMessageTypeOther) {
        textX = CGRectGetMaxX(self.iconFrame);
    } else {
        textX = iconX - textW;
    }
    self.textFrame = CGRectMake(textX, textY, textW, textH);
}

注意:计算Frame使用的文本属性应与实际显示的文本属性相同,否则会出现错误,另外文本框的numberOfLines应设置为0,否则无法换行。

6、修正tableView的细节

该部分主要包括:取消分割线,使单元格不可选中,设定tableview和cell的背景色,设定当拖动tableView时收回键盘,根据消息类型设置正文颜色和背景图。主要的知识点在于背景图的拉伸方式的设定,教程中使用的方法是

- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;

该方法是通过设置左边不拉伸区域的宽度和上面不拉伸区域的高度来达到边角不拉伸的效果

leftCapWidth:左边不拉伸区域的宽度
topCapWidth:上面不拉伸区域的高度
扩展:
rightCapWidth = 图片宽度-leftCapWidth -1
bottomCapWidth = 图片高度-topCapWidth -1

而拉伸区域capInset实际上是(topCapHeight,leftCapWidth,bottomCapWidth,rightCapWidth)所以一般leftCapWidth取图片宽度的一般,topCapHeight取图片高度的一般,来获取拉伸区域1*1的矩阵来复制填充(UIImageResizingModeTile),保持外围的区域不变

与该方法类似的还有一个是resizableImageWithCapInsets

- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode NS_AVAILABLE_IOS(6_0);

7、实现对自定义Cell中时间的判断

判断时间是否需要显示的关键在于判断当前消息的时间与前一条消息的事件是否相同,思路是在懒加载的for循环里,将当前的消息模型与上一条消息模型进行对比,上一条消息模型可以在当前模型尚未加入到NSMutableArray之前通过lastObject获取。然后在Frame模型中定义一个Bool成员变量,用于记录当前消息是否应该显示时间。

如果不需要显示时间,则可以直接不计算时间控件的frame

8、实现键盘弹出时整个View的随动效果

该效果实现的难点主要在于如何知道键盘的尺寸发生了改变,并且获取键盘改变之后的尺寸,教程中使用了通知机制

通知机制

每一个应用程序都有一个通知中心(NSNotificationCenter)实例,专门负责协调不同对象之间的消息通信;任何一个对象都可以向通知中心发布通知(NSNotification),描述自己在做什么。其它感兴趣的对象(Observer)可以申请在某个特定通知发布时(或在某个特定的对象发布通知时)收到这个通知。

9、实现新的消息的发送和回复

实现新消息的发送和回复主要的关键点在于监听文本框的输入操作,并更新tableView的数据源。实现思路如下:

  1. 使用UItextField的delegate方法textFieldShouldReturn:来监听发送按钮的点击操作
  2. 获取当前文本框的内容
  3. 获取当前时间并转换为指定的格式
  4. 根据消息正文和消息类型创建消息模型和Frame模型
  5. 将新的Frame模型加入到模型数据集和中
  6. 刷新tableView并将最后一行滚动到第一行
  7. 清空文本框

如果需要实现自动回复,将发送消息的功能进行封装即可,通过传入一个消息正文和消息类型实现自动发送

-(void)sendMessage:(NSString *)message WithType:(LJMessageType)type
{
    //获取当前时间
    NSDate * nowdate = [NSDate date];
    NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"今天 HH:mm"];
    NSString * nowTime = [dateFormatter stringFromDate:nowdate];
    //根据文本框的内容创建LJMessageFrame对象
    LJMessage * message_send_model = [[LJMessage alloc] init];
    message_send_model.text = message;
    message_send_model.time = nowTime;
    message_send_model.type = type;
    LJMessageFrame * messageFrame_model = [[LJMessageFrame alloc] init];
    messageFrame_model.message = message_send_model;    
    //将创建的对象加入到模型数据集合中
    [self.messageFrames addObject:messageFrame_model];
    //刷新tableView
    [self.messageView reloadData];
}

以上就是QQ聊天UI界面的案例总结,有不足之处欢迎留言,一起学习一起进步!

上一篇 下一篇

猜你喜欢

热点阅读