(二十)即時通訊的聊天氣泡的實現I

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

細節:在cell類內寫一個類方法用於實現緩存池的性能優化:

+ (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屬性只在模型內計算,不容許在外部修改,聲明爲readonly,爲了使用CGXxx要引入UIKit框架。

計算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);
    
}

其中在計算文字對應的text尺寸時,使用了以下的方法:

- (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;
}

最後剩下的最重要的問題,是爲cell的子控件設置frame模型的時機,能夠注意到,在經過initWithTableView獲取到cell以後,應該對cell的messageFrame屬性進行設置,因此只要重寫cell的messageFrame的set方法便可在這個時候改變控件的屬性,而後獲得正確的cell。

- (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;
}



下面的步驟就和之前同樣,控制器新建messageFrames,懶加載數據,而後TableView根據數據源和委託加載數據。


下面總結一下調用過程:

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

相關文章
相關標籤/搜索