# Building a WeChat-style iOS app, part 7

Language: en
Canonical: [Building a WeChat-style iOS app, part 7](<https://allluckly.cn/en/journal/2016-03-15-building-a-wechat-style-ios-app-part-7/>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao31>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-03-15-building-a-wechat-style-ios-app-part-7/>)
Published: 2016-03-15T00:00:00+08:00
Modified: 2026-10-23T10:34:14+08:00
Author: [WzxJiang](<http://www.jianshu.com/users/389c20d5a244/latest_articles>)

Breaks down the chat input bar and message cells, using blocks to send submission and visibility events to the controller. Message ownership determines avatars and stretchable bubble backgrounds, while model-side sizing accommodates messages of different lengths.

Originally credited to WzxJiang; edited and archived by Bison.

[Source listed in the original](<http://www.jianshu.com/users/389c20d5a244/latest_articles>)

English translation of the [Chinese original](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao31>), translated on 2026-10-23. The article body uses automated translation; app descriptions, historical reading notes and video subtitles receive separate editorial checks. Original publication dates, attribution, code and version notes are retained.

**Before you read** This technical note dates from 2015–2017. The examples have not been rerun as part of this archive update. Read API, dependency and tool details in the context of the original publication date.

Editor: [Bison](<https://allluckly.cn/en/>)  
 Contributor: [WzxJiang](<http://www.jianshu.com/users/389c20d5a244/latest_articles>)

> "I built the chat page a few days ago, which took considerable work. Using RongCloud IM and a LeanCloud server meant implementing several normally server-side algorithms on the client. Today I will explain the chat UI rather than the algorithms. Questions and better approaches are welcome in the comments."

![1](<https://allluckly.cn/images/blog/tuogao/tougao12/1.png>)

Result:

![1](<https://allluckly.cn/images/blog/tuogao/tougao31/1.gif>)

1. Building the bottom chat bar

![1](<https://allluckly.cn/images/blog/tuogao/tougao31/2.png>)

I decouple the interface using a custom View. Its ShowBlock, HideBlock and SendBlock let the VC respond to showing, hiding and sending from the chat bar. The preceding article explained observing keyboard show/hide events and moving the bar smoothly with it.[Build WeChat Yourself, Part 6: problems encountered so far](<https://allluckly.cn/en/journal/2016-03-14-building-a-wechat-style-ios-app-part-6/>).

2. Creating cells

This is how I create chat cells in WWeChat:

![1](<https://allluckly.cn/images/blog/tuogao/tougao31/3.png>)

There are two ImageViews here. I hide one depending on whether the message is mine or the other person’s. Because height varies, anchor constraints from the upper-left or upper-right corner.

3. Creating message bubbles

The bubble’s starting X coordinate, width and height vary, so creating it in a Xib would be troublesome.  
 So I handle it as follows:  
 In Cell.m, create self.bubbleView manually according to whether the message is mine.  
 //如果是你 if (model.isMe) { \[\_AiConView removeFromSuperview\];

```
    [_BiConView setImageWithURL:[NSURL URLWithString:[[UserInfoManager manager]avaterUrl]] placeholderImage:[UIImage imageNamed:@"avater.jpg"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {

    }];
    self.bubbleView.frame = CGRectMake([UIScreen mainScreen].bounds.size.width - 40 - 10 - 5 - width,10,width,height);
    UIImage * bubbleImg = [UIImage imageNamed:@"SenderTextNodeBkg"];
    UIEdgeInsets insets = UIEdgeInsetsMake(30, 15,30,15);
    UIImage *insetImage = [bubbleImg resizableImageWithCapInsets:insets];
    self.bubbleView.image = insetImage;
}
else
{
    [_BiConView removeFromSuperview];

    self.bubbleView.frame = CGRectMake(10 + 40 + 5,10,width,height);
    UIImage * bubbleImg = [UIImage imageNamed:@"ReceiverTextNodeBkgHL"];
    UIEdgeInsets insets = UIEdgeInsetsMake(30, 15,30,15);
    UIImage *insetImage = [bubbleImg resizableImageWithCapInsets:insets];
    self.bubbleView.image = insetImage;
}
[self.contentView addSubview:_bubbleView];
```

resizableImageWithCapInsets controls which parts of the bubble do not stretch. Its values depend on the image’s actual pixels.

```
UIEdgeInsets insets = {top, left, bottom, right};
```

Then add the Label manually.

```
UILabel * messageLabel = [[UILabel alloc]initWithFrame:CGRectMake(20, 3, _bubbleView.frame.size.width - 40, _bubbleView.frame.size.height - 12)];
messageLabel.font = [UIFont systemFontOfSize:14];

messageLabel.text = model.message;

messageLabel.numberOfLines = 0;

[_bubbleView addSubview:messageLabel];
```

3. Automatic cell sizing  
 I used a shortcut to make the cell size adapt automatically.  
 I added a bubbleSize property to MessageModel.  
 - (void)setMessage:(NSString \*)message { \_message = message; self.bubbleSize = \[self giveSizeForStr:self.message\]; }

```
- (CGSize)giveSizeForStr:(NSString *)str
{
    UIFont * font = [UIFont systemFontOfSize:14.0];
    NSDictionary * dic = [NSDictionary dictionaryWithObjectsAndKeys:font,NSFontAttributeName,nil];
    CGSize size = [str boundingRectWithSize:CGSizeMake(200 - 20 - 20, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:dic context:nil].size;

    return CGSizeMake(size.width + 40, size.height + 20);
}
```

When assigning message in the model, calculate the cell’s height. Adding 40 to width and 20 to height accounts for margins between the text and bubble.  
 Then, in the VC:

```
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary * dic = _dataArr[indexPath.section];
    NSArray * arr = dic[@"messages"];
    MessageModel * model = arr[indexPath.row];
    return model.bubbleSize.height > 40 ? model.bubbleSize.height + 20: 40+20;
}
```

The bubble needs at least 40px. Adding 20 compensates for distortion when stretching it.

4. Creating time labels  
 My algorithm groups messages less than 60 seconds apart into one array. A message already assigned to one array cannot enter another. Then set each group’s header view. P.S. For the algorithm, see the method in WWeChatApi:

```
- (void)getMessagesWithConversationID:(NSString *)conversationID andNum:(int)num andType:(RCConversationType)type AndSuccess:(void (^)(NSArray * messageArr))successBlock
andFailure:(void(^)())failureBlock
andError:(void(^)(NSError * error))errorBlock;
```

Method for setting the header view

```
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UIView * headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 40)];

    UILabel *timeLabel = [[UILabel alloc]initWithFrame:CGRectMake((self.view.frame.size.width - 60)/2.0, 10, 60, 20)];

    NSDictionary * dic = _dataArr[section];

    timeLabel.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3];
    timeLabel.layer.cornerRadius = 5;
    timeLabel.clipsToBounds = YES;

    double times = [dic[@"timestamp"]doubleValue];

    timeLabel.text = [[WZXTimeStampToTimeTool tool]compareWithTimeDic:[[WZXTimeStampToTimeTool tool]timeStampToTimeToolWithTimeStamp:times andScale:3]];

    timeLabel.font = [UIFont systemFontOfSize:12];

    timeLabel.textColor = [UIColor whiteColor];

    timeLabel.textAlignment = NSTextAlignmentCenter;

    [headerView addSubview:timeLabel];

    return headerView;
}
```

WZXTimeStampToTimeTool is a utility I wrote to compare a timestamp with local time and convert it into a string quickly.  
 I use double for the NSTimeInterval timestamp. Other types, such as NSInteger, can produce an incorrect value on the 5 and 5s, showing a date 46 years ago in 1970. double is NSTimeInterval’s underlying type: typedef double NSTimeInterval;

That is all for today. If you have questions about other areas, comment and I will explain them next time.  
1

### Complete code

[Download here—GitHub: WzxJiang](<https://github.com/Wzxhaha/WWeChat>)

If you like the project, please Star or Fork it!

---

Recommended reading: [A beginner developer's review of 2015](<https://allluckly.cn/en/journal/2016-02-01-a-beginner-developer-s-review-of-2015/>)

