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方法)。