Tip:经过xib和storyboard不可能将一个控件做为ImageView的子控件,只能经过代码的addSubview方法实现。缓存
设置图片的细节:若是button比图片大(为了方便对齐),将图片设置为image而不是background,图片不会被拉伸到失真。安全
为了保证在不一样系统上显示的效果同样,能够不使用系统默认样式,用自定义的背景等,例如QQ的聊天框,若是要实现,首先将TextField的BorderStyle选为空:性能优化
而后设置本身的background便可。框架
细节:聊天的type(发送的仍是接受的)应该用什么数据类型?性能
为了下降沟通成本,提升可读性和安全性,应该使用枚举。优化
枚举类型命名规范:类名+属性名(首字母大写)。枚举成员也要类名+属性为前缀。atom
typedef enum{ MessageTypeMe = 0, MessageTypeOther } MessageType ;
@property (nonatomic, assign) MessageType type;Tip:使用KVC的时候,枚举会自动转整形。
注意弱指针不能指向alloc的对象,不然会被直接销毁,应该先用强指针指着alloc的对象,而后加入到父控件,最后再用弱指针指过去:spa
@property (nonatomic, weak) UILabel *timeView;
UILabel *timeView = [[UILabel alloc] init]; [self.contentView addSubview:timeView]; self.timeView = timeView;自定义cell的步骤:
第一步:新建一个继承自UITableViewCell的类指针
第二步:重写initWithStyle:reuseIdentifier方法code
添加全部的子控件,不须要设置数据和frame(声明一个frame属性以便设置),加入到self.contentView中。必定注意弱指针的用法,先用强指针,加入视图后再交给弱指针。
@interface MessageCell : UITableViewCell @property (nonatomic, strong) MessageFrame *messageFrame; + (instancetype)cellWithTableView:(UITableView *)tableView; @end
@interface MessageCell () @property (nonatomic, weak) UILabel *timeView; @property (nonatomic, weak) UIImageView *iconView; @property (nonatomic, weak) UIButton *textView; @end @implementation MessageCell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { //时间:label //头像:imgaeView //正文:button UILabel *timeView = [[UILabel alloc] init]; [self.contentView addSubview:timeView]; self.timeView = timeView; UIImageView *iconView = [[UIImageView alloc] init]; [self.contentView addSubview:iconView]; self.iconView = iconView; UIButton *textView = [[UIButton alloc] init]; [self.contentView addSubview:textView]; self.textView = textView; } return self; } - (void)awakeFromNib { // Initialization code } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; // Configure the view for the selected state } @end
+ (instancetype)cellWithTableView:(UITableView *)tableView{ static NSString *ID = @"message"; MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if(cell == nil){ cell = [[MessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; } return cell; }这样就大大简化了返回cell的代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ MessageCell *cell = [MessageCell cellWithTableView:tableView]; return cell; }
第三步:提供数据模型和frame模型(后者存放数据模型和全部子控件的高度、cell的高度)
Tip:要使用CGRect,首先要引入<UIKit/UIKit.h>。
注意frame应该是只读的,只在模型内计算,没有set方法,因此类内使用下划线访问。
对于数据模型message,主要是提供消息的类型、事件、内容:
typedef enum{ MessageTypeMe = 0, MessageTypeOther } MessageType ; @interface Message : NSObject @property (nonatomic, copy) NSString *text; @property (nonatomic, copy) NSString *time; @property (nonatomic, assign) MessageType type; - (instancetype)initWithDict:(NSDictionary *)dict; + (instancetype)messageWithDict:(NSDictionary *)dict; @end和之前的模型声明和实现彻底同样。
对于messageFrame模型,内部的数据为Cell各部分的尺寸和message模型,最后控制器访问的将是这个模型。
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #define NameFont [UIFont systemFontOfSize:14] #define TextFont [UIFont systemFontOfSize:15] @class Message; @interface MessageFrame : NSObject /** * 头像的Frame */ @property (nonatomic, assign, readonly) CGRect iconF; /** * 时间的Frame */ @property (nonatomic, assign, readonly) CGRect timeF; /** * 正文的Frame */ @property (nonatomic, assign, readonly) CGRect textF; /** * cell的高度 */ @property (nonatomic, assign, readonly) CGFloat cellHeight; /** * 数据模型 */ @property (nonatomic, strong) Message *message; @end
计算Frame的时机应该是在控制器将message信息传入的时候,所以要重写message的set方法来计算尺寸:
计算的过程略为繁琐,总之就是实现相似QQ的聊天起泡效果,这里只是计算了时间、消息框、头像的位置和尺寸,并无加上起泡。
注意一个细节,使用UIScreen的mainScreen方法的bounds.size获得屏幕尺寸。
- (void)setMessage:(Message *)message{ _message = message; CGFloat screenW = [UIScreen mainScreen].bounds.size.width; CGFloat padding = 10; CGFloat timeX = 0; CGFloat timeY = 0; CGFloat timeW = 320; CGFloat timeH = 40; _timeF = CGRectMake(timeX, timeY, timeW, timeH); CGFloat iconX; CGFloat iconY = CGRectGetMaxY(_timeF); CGFloat iconW = 40; CGFloat iconH = 40; if (message.type == MessageTypeMe) { iconX = screenW - padding - iconW; }else{ iconX = padding; } _iconF = CGRectMake(iconX, iconY, iconW, iconH); CGSize textMaxSize = CGSizeMake(150, MAXFLOAT); CGSize textSize = [self sizeWithText:message.text font:TextFont maxSize:textMaxSize]; CGFloat textY = iconY; CGFloat textX; if (message.type == MessageTypeMe) { textX = iconX - padding - textSize.width; }else{ textX = CGRectGetMaxX(_iconF) + padding; } //_textF = CGRectMake(textX, textY, textSize.width, textSize.height); _textF = (CGRect){{textX,textY},textSize}; CGFloat textMaxY = CGRectGetMaxY(_textF); CGFloat iconMaxY = CGRectGetMaxY(_iconF); _cellHeight = MAX(textMaxY, iconMaxY); }
- (CGSize)sizeWithText:(NSString *)text font:(UIFont *)font maxSize:(CGSize)maxSize{ NSDictionary *attrs = @{NSFontAttributeName : font}; return [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size; }
- (void)setMessageFrame:(MessageFrame *)messageFrame{ _messageFrame = messageFrame; Message *msg = messageFrame.message; self.timeView.text = msg.time; self.timeView.frame = messageFrame.timeF; NSString *icon = msg.type == MessageTypeMe ? @"me" : @"other"; self.iconView.image = [UIImage imageNamed:icon]; self.iconView.frame = messageFrame.iconF; [self.textView setTitle:msg.text forState:UIControlStateNormal]; self.textView.frame = messageFrame.textF; }
对于要屡次计算的数据,放在message的set方法内计算,对于一次性的计算(例如Cell的背景色),放在Cell的init方法中计算:
改良后的Cell初始化方法:注意一个细节,clearColor为透明色(无色)。
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { //一次性的修改放到init方法中 //时间:label //头像:imgaeView //正文:button UILabel *timeView = [[UILabel alloc] init]; timeView.textAlignment = NSTextAlignmentCenter; timeView.textColor = [UIColor grayColor]; [self.contentView addSubview:timeView]; self.timeView = timeView; UIImageView *iconView = [[UIImageView alloc] init]; [self.contentView addSubview:iconView]; self.iconView = iconView; UIButton *textView = [[UIButton alloc] init]; textView.titleLabel.numberOfLines = 0; //自动换行 textView.titleLabel.font = TextFont; textView.backgroundColor = [UIColor grayColor]; [self.contentView addSubview:textView]; self.textView = textView; //设置cell的背景色 self.backgroundColor = [UIColor clearColor]; } return self; }
下面总结一下调用过程:
1.控制器加载messageFrames,须要为每个messageFrame的message属性赋值-
2.因为重写了message的set方法,在set方法内部根据message计算获得文字的宽高,进而获得头像、时间位置尺寸,肯定全部的Frame,而且存住message(保存以前先用message的类方法字典转模型)。
3.系统调用获取Cell的方法时,因为调用了被重写的构造方法initWithStyle:style reuseIdentifier: ,在其内部经过性能优化取得Cell,而后建立各个子控件,进行一次性属性的设置,最后返回Cell。
4.Cell内部有messageFrame属性须要设置,在设置该属性时,会调用重写的set方法,在这个方法内部,实现了对各个子控件尺寸根据传入的messageFrame修改的操做。
5.Cell通过以上4步被正确的建立和设置,返回后正确的显示。
调用顺序:Message类(字典转Message模型)->Frame类(设置messageFrame的message时调用set方法)->MessageCell类(初始化Cell)->MessageCell类(设置Cell的messageFrame属性时调用set方法)。