iOS实现搭建聊天页面的实例代码
发布时间 - 2026-01-11 02:17:36 点击率:次由于工作需要,需要用到ios聊天页面,在网上搜了半天没有想要的,果断自己写一个,发个笔记

功能分析,模仿QQ聊天页面
输入框失去第一响应的情况:
1:点击页面
2:下滑页面
输入框成为第一响应的情况:
1:开始输入
2:上滑页面最底部
控制器
//
// WDPersonMessageDetailVC.m
// WestDevelopment
//
// Created by wangtao on 2017/6/23.
// Copyright © 2017年 xikaijinfu. All rights reserved.
//
#import "WDPersonMessageDetailVC.h"
#import "WDPersonMessageDetailCell.h"
#import "WDPersonMessageFooterCell.h"
#import "WDPersonMessageDetailModel.h"
#import <IQKeyboardManager.h>
@interface WDPersonMessageDetailVC ()
@property (nonatomic, weak) WDPersonMessageFooterCell *textfieldView;
@end
@implementation WDPersonMessageDetailVC
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat contentOffsetY = scrollView.contentOffset.y;
// 页面下滑,并且输入框还是第一响应的时候,控制器要失去第一响应
if (contentOffsetY > 10) {
if (self.textfieldView.isFirst) {
[self clickSelf];
}
}
// 页面上滑,控制器成为第一响应
if (contentOffsetY < - 10) {
self.textfieldView.isFirst = YES;
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// 关闭IQ键盘
[IQKeyboardManager sharedManager].enable = NO;
[IQKeyboardManager sharedManager].enableAutoToolbar = NO;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.view endEditing:YES];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[IQKeyboardManager sharedManager].enableAutoToolbar = YES;
[IQKeyboardManager sharedManager].enable = YES;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)loadView
{
UIScrollView *view = [[UIScrollView alloc] init];
view.frame = CGRectMake(0, 0, kMainScreenWidth, kMainScreenHeight);
self.view = view;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[IQKeyboardManager sharedManager].enable = NO;
[IQKeyboardManager sharedManager].enableAutoToolbar = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
// 旋转tableView
self.tableView.transform = CGAffineTransformMakeScale (1, -1);
self.tableView.tableHeaderView.transform = CGAffineTransformMakeScale (1, -1);
self.tableView.tableFooterView.transform = CGAffineTransformMakeScale (1, -1);
self.view.backgroundColor = WTHexColor(0xeaeaea);
self.tableView.backgroundColor = WTHexColor(0xeaeaea);
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, 0);
[self.tableView registerClass:[WDPersonMessageDetailCell class] forCellReuseIdentifier:WDPersonMessageDetailCellID];
[self.tableView registerClass:[WDPersonMessageFooterCell class] forHeaderFooterViewReuseIdentifier:WDPersonMessageFooterCellID];
[self.tableView wt_addTapTarget:self action:@selector(clickSelf)];
[self addFooter];
}
//键盘弹出时把消息列表tableView的高度设为(屏幕高度 - 输入框高度 - 键盘高度),同时输入框上移;
//键盘消失时再把tableView的高度设为(屏幕高度 - 输入框的高度),同时输入框下移。
//这样可以完美解决聊天列表的上面的消息无法显示问题和键盘遮挡问题。
- (void)keyboardWillShow:(NSNotification*)notification
{
// 0.取出键盘动画的时间
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// 1.取得键盘最后的frame
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
// 2.计算控制器的view需要平移的距离
CGFloat transformY = keyboardFrame.origin.y - self.view.frame.size.height;
// 3.执行动画
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
WTWS(weakSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:duration animations:^{
weakSelf.tableView.frame = CGRectMake(0, 0, kMainScreenWidth, kMainScreenHeight - keyboardFrame.size.height - 64);
weakSelf.inputView.transform = CGAffineTransformMakeTranslation(0, transformY);
}];
});
}
- (void)keyboardWillHide:(NSNotification*)notification
{
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:duration animations:^{
self.tableView.frame = CGRectMake(0, 0, kMainScreenWidth, kMainScreenHeight);
self.view.transform = CGAffineTransformIdentity;
}];
}
//失去第一响应
- (void)clickSelf
{
[[NSNotificationCenter defaultCenter] postNotificationName:kMessageState object:@(YES)];
}
- (void)addHeader
{
__unsafe_unretained __typeof(self) weakSelf = self;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[weakSelf loadData];
}];
[self.tableView.mj_header beginRefreshing];
}
//关闭下拉和上拉控件的文字展示
- (void)addFooter
{
// [self addHeader];
[self loadData];
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)];
[footer setTitle:@"" forState:MJRefreshStateIdle];
[footer setTitle:@"" forState:MJRefreshStatePulling];
[footer setTitle:@"" forState:MJRefreshStateRefreshing];
[footer setTitle:@"" forState:MJRefreshStateWillRefresh];
[footer setTitle:@"" forState:MJRefreshStateNoMoreData];
self.tableView.mj_footer = footer;
}
- (void)loadData
{
self.page = 1;
NSDictionary *par = @{
kToken : [WTAccount shareAccount].token,
kUserId : [WTAccount shareAccount].uid,
kCurrentPage : @(self.page),
kFriendId : self.friendId,
};
[WDNetwork postkMyMessageDetailPhoneWithParameters:par modelClass:[WDPersonMessageDetailModel class] responseBlock:^(id dataObject, NSError *error) {
if (!error && [[dataObject class] isSubclassOfClass:[NSArray class]]) {
NSArray* reversedArray = [[dataObject reverseObjectEnumerator] allObjects];
self.dataArray = [NSMutableArray arrayWithArray:reversedArray];
[self.tableView reloadData];
self.page ++;
if ([dataObject count] < 20) {
[self.tableView.mj_header endRefreshing];
[self.tableView.mj_footer endRefreshingWithNoMoreData];
} else {
[self.tableView.mj_header endRefreshing];
[self.tableView.mj_footer endRefreshing];
}
} else {
[self.tableView.mj_header endRefreshing];
[self.tableView.mj_footer endRefreshingWithNoMoreData];
}
}];
}
- (void)loadMoreData
{
NSDictionary *par = @{
kToken : [WTAccount shareAccount].token,
kUserId : [WTAccount shareAccount].uid,
kCurrentPage : @(self.page),
kFriendId : self.friendId,
};
[WDNetwork postkMyMessageDetailPhoneWithParameters:par modelClass:[WDPersonMessageDetailModel class] responseBlock:^(id dataObject, NSError *error) {
if (!error && [[dataObject class] isSubclassOfClass:[NSArray class]]) {
NSArray* reversedArray = [[dataObject reverseObjectEnumerator] allObjects];
[self.dataArray addObjectsFromArray:reversedArray];
[self.tableView reloadData];
self.page ++;
if ([dataObject count] < 20) {
[self.tableView.mj_footer endRefreshingWithNoMoreData];
} else {
[self.tableView.mj_footer endRefreshing];
}
} else {
[self.tableView.mj_footer endRefreshingWithNoMoreData];
}
}];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 50;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return CGFLOAT_MIN;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
WDPersonMessageFooterCell *footer = [tableView dequeueReusableHeaderFooterViewWithIdentifier:WDPersonMessageFooterCellID];
self.textfieldView = footer;
footer.contentView.transform = CGAffineTransformMakeScale (1, -1);
WTWS(weakSelf);
footer.clickSenderText = ^(NSString *text) {
NSDictionary *par = @{
kToken : [WTAccount shareAccount].token,
kUserId : [WTAccount shareAccount].uid,
kComment : text,
kFlag : @(11),
kFriendId : weakSelf.friendId,
};
[WDNetwork postkAddCommentPhoneWithParameters:par modelClass:[NSNull class] responseBlock:^(id dataObject, NSError *error) {
if (!error && ([[dataObject objectForKey:kCode] integerValue] == 200)) {
[weakSelf loadData];
weakSelf.textfieldView.sendSucceed = YES;
} else if (!error && [dataObject objectForKey:kMsg]) {
}
}];
};
footer.resignFirstRes = ^{
weakSelf.tableView.frame = CGRectMake(0, 0, kMainScreenWidth, kMainScreenHeight - 64);
weakSelf.view.transform = CGAffineTransformIdentity;
};
return footer;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataArray.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
WDPersonMessageDetailModel *model = self.dataArray[indexPath.row];
return model.height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
WDPersonMessageDetailCell *cell = [tableView dequeueReusableCellWithIdentifier:WDPersonMessageDetailCellID forIndexPath:indexPath];
cell.model = self.dataArray[indexPath.row];
cell.contentView.transform = CGAffineTransformMakeScale (1, -1);
return cell;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
输入框 UITableViewHeaderFooterView
// // WDPersonMessageFooterCell.h // WestDevelopment // // Created by wangtao on 2017/6/26. // Copyright © 2017年 xikaijinfu. All rights reserved. // #import "WDBaseTVHeaderFooterView.h" typedef void(^ClickSender_t)(NSString *text); typedef void(^ResignFirstResponder)(); @interface WDPersonMessageFooterCell : WDBaseTVHeaderFooterView @property (nonatomic, copy) ClickSender_t clickSenderText; @property (nonatomic, copy) ResignFirstResponder resignFirstRes; @property (nonatomic, assign) BOOL isFirst; @property (nonatomic, assign) BOOL sendSucceed; @end
//
// WDPersonMessageFooterCell.m
// WestDevelopment
//
// Created by wangtao on 2017/6/26.
// Copyright © 2017年 xikaijinfu. All rights reserved.
//
#import "WDPersonMessageFooterCell.h"
@interface WDPersonMessageFooterCell () <UITextFieldDelegate>
@property (nonatomic, weak) UITextField *textField;
@property (nonatomic, weak) UIView *line;
@end
@implementation WDPersonMessageFooterCell
@synthesize isFirst = _isFirst;
- (void)setupAll
{
self.contentView.backgroundColor = WTHexColor(0xf2f2f2);
UITextField *textField = [[UITextField alloc] init];
textField.backgroundColor = kWhiteColor;
[self.contentView addSubview:textField];
textField.delegate = self;
self.textField = textField;
textField.layer.cornerRadius = 3;
textField.layer.masksToBounds = YES;
textField.returnKeyType = UIReturnKeySend;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(messageState:) name:kMessageState object:nil];
UIView *line = [[UIView alloc] init];
line.backgroundColor = WTHexColor(0xdddddd);
[self.contentView addSubview:line];
self.line = line;
}
- (void)messageState:(NSNotification *)noti
{
NSInteger state = [[noti object] boolValue];
if (state) {
[self.textField resignFirstResponder];
if (self.resignFirstRes) {
self.resignFirstRes();
}
}
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (self.clickSenderText) {
self.clickSenderText(self.textField.text);
}
return YES;
}
- (void)setIsFirst:(BOOL)isFirst
{
_isFirst = isFirst;
if (isFirst) {
[self.textField becomeFirstResponder];
} else {
[self.textField resignFirstResponder];
}
}
- (BOOL)isFirst
{
if ([self.textField isFirstResponder]) {
return YES;
}
return NO;
}
- (void)setSendSucceed:(BOOL)sendSucceed
{
self.textField.text = @"";
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat padding = 10;
self.textField.frame = CGRectMake(padding, padding, self.wt_width - padding * 2, self.wt_height - padding * 2);
self.line.frame = CGRectMake(0, 0, self.wt_width, .5);
}
@end
消息cell
//
// WDPersonMessageDetailCell.m
// WestDevelopment
//
// Created by wangtao on 2017/6/23.
// Copyright © 2017年 xikaijinfu. All rights reserved.
//
#import "WDPersonMessageDetailCell.h"
#import "WDPersonMessageDetailModel.h"
@interface WDPersonMessageDetailCell ()
@property (nonatomic, weak) UILabel *time;
@property (nonatomic, weak) UIImageView *icon;
@property (nonatomic, weak) UILabel *detail;
@property (nonatomic, weak) UIView *baseView;
@end
@implementation WDPersonMessageDetailCell
- (void)setupAll
{
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.backgroundColor = WTHexColor(0xeaeaea);
self.contentView.backgroundColor = WTHexColor(0xeaeaea);
UILabel *time = [UILabel labelWithText:@""
textColor:WTHexColor(0xaaaaaa)
textAlignment:NSTextAlignmentCenter
font:12
backgroundColor:kClearColor];
[self.contentView addSubview:time];
self.time = time;
UIImageView *icon = [[UIImageView alloc] init];
[self.contentView addSubview:icon];
icon.image = [UIImage imageNamed:kDefault];
self.icon = icon;
self.icon.layer.cornerRadius = 35 / 2;
self.icon.layer.masksToBounds = YES;
UIView *baseView = [[UIView alloc] init];
[self.contentView addSubview:baseView];
self.baseView = baseView;
baseView.layer.masksToBounds = YES;
baseView.layer.cornerRadius = 4;
UILabel *detail = [UILabel labelWithText:@""
textColor:kBlackColor
textAlignment:NSTextAlignmentLeft
font:13
backgroundColor:kClearColor];
[baseView addSubview:detail];
self.detail = detail;
detail.numberOfLines = 0;
}
- (void)setModel:(WDPersonMessageDetailModel *)model
{
_model = model;
if ([model.isShow isEqualToString:@"1"]) {
self.time.text = model.addTime;
self.time.hidden = NO;
self.time.frame = CGRectMake(0, 0, kMainScreenWidth, 20);
} else {
self.time.text = @"";
self.time.hidden = YES;
self.time.frame = CGRectZero;
}
self.time.text = model.addTime;
[self.icon wt_setImageWithUrlString:model.headImg placeholderString:@"me_icon"];
self.detail.text = model.comment;
if ([model.userId isEqualToString:[WTAccount shareAccount].uid]) {
self.detail.textColor = kBlackColor;
self.baseView.backgroundColor = kWhiteColor;
self.icon.frame = CGRectMake(kPadding, self.time.wt_bottom + kPadding, 35, 35);
self.baseView.frame = CGRectMake(self.icon.wt_right + kPadding, self.icon.wt_top, model.commentW, model.commentH);
self.detail.frame = CGRectMake(kPadding, kPadding, model.commentW - kPadding * 2, model.commentH - kPadding * 2);
} else {
self.detail.textColor = kWhiteColor;
self.baseView.backgroundColor = kHomeColor;
self.icon.frame = CGRectMake(kMainScreenWidth - 35 - kPadding, self.time.wt_bottom + kPadding, 35, 35);
self.baseView.frame = CGRectMake(self.icon.wt_left - kPadding - model.commentW, self.icon.wt_top, model.commentW, model.commentH);
self.detail.frame = CGRectMake(kPadding, kPadding, model.commentW - kPadding * 2, model.commentH - kPadding * 2);
}
}
@end
模型
//
// WDPersonMessageDetailModel.m
// WestDevelopment
//
// Created by wangtao on 2017/6/23.
// Copyright © 2017年 xikaijinfu. All rights reserved.
//
#import "WDPersonMessageDetailModel.h"
@implementation WDPersonMessageDetailModel
- (CGFloat)commentW
{
if (_commentW == 0) {
_commentW = [self.comment wt_calculateStringSizeWithFontOfSize:13 maxWidth:kMainScreenWidth / 2].width + 20;
}
return _commentW;
}
- (CGFloat)commentH
{
if (_commentH == 0) {
CGFloat textH = [self.comment wt_calculateStringSizeWithFontOfSize:13 maxWidth:kMainScreenWidth / 2].height;
// 一行字体是15高,一行的情况就和头像一样高
_commentH = (textH < 20) ? 35 : (textH + 20);
}
return _commentH;
}
- (CGFloat)height
{
if (_height == 0) {
_height = self.commentH + 20;
if ([self.isShow isEqualToString:@"1"]) {
_height += 20;
}
}
return _height;
}
@end
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# iOS聊天页面
# iOS聊天页面实现
# iOS搭建聊天页面
# iOS输出手机系统版本号
# iOS利用UIScrollView实现图片的缩放实例代码
# React-Native实现ListView组件之上拉刷新实例(iOS和Android通用)
# iOS实现按钮点击选中与被选中切换功能
# IOS中多手势之间的冲突和解决办法
# IOS 开发之UILabel 或者 UIButton加下划线链接
# 输入框
# 设为
# 半天
# 弹出
# 发个
# 大家多多
# 时再
# 在网上
# userInfo
# keyboardFrame
# CGRectValue
# UIKeyboardFrameEndUserInfoKey
# doubleValue
# CGRect
# UIKeyboardAnimationDurationUserInfoKey
# indexPathForRow
# indexPath
# inSection
# atScrollPosition
# scrollToRowAtIndexPath
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
如何快速生成ASP一键建站模板并优化安全性?
Android自定义listview布局实现上拉加载下拉刷新功能
网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
如何在建站之星网店版论坛获取技术支持?
Google浏览器为什么这么卡 Google浏览器提速优化设置步骤【方法】
Laravel如何实现RSS订阅源功能_Laravel动态生成网站XML格式订阅内容【教程】
如何快速辨别茅台真假?关键步骤解析
如何打造高效商业网站?建站目的决定转化率
郑州企业网站制作公司,郑州招聘网站有哪些?
Laravel如何生成PDF或Excel文件_Laravel文档导出工具与使用教程
制作无缝贴图网站有哪些,3dmax无缝贴图怎么调?
Python图片处理进阶教程_Pillow滤镜与图像增强
php485函数参数是什么意思_php485各参数详细说明【介绍】
消息称 OpenAI 正研发的神秘硬件设备或为智能笔,富士康代工
详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)
常州企业网站制作公司,全国继续教育网怎么登录?
php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】
BootStrap整体框架之基础布局组件
Laravel中的Facade(门面)到底是什么原理
香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧
Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】
*服务器网站为何频现安全漏洞?
Laravel如何使用Telescope进行调试?(安装和使用教程)
Laravel如何处理文件下载请求?(Response示例)
Swift中swift中的switch 语句
高防网站服务器:DDoS防御与BGP线路的AI智能防护方案
如何用免费手机建站系统零基础打造专业网站?
php打包exe后无法访问网络共享_共享权限设置方法【教程】
青岛网站建设如何选择本地服务器?
百度浏览器如何管理插件 百度浏览器插件管理方法
Laravel如何使用软删除(Soft Deletes)功能_Eloquent软删除与数据恢复方法
Android仿QQ列表左滑删除操作
Mybatis 中的insertOrUpdate操作
Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】
Laravel如何使用Service Provider服务提供者_Laravel依赖注入与容器绑定【深度】
浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】
如何快速登录WAP自助建站平台?
QQ浏览器网页版登录入口 个人中心在线进入
如何在腾讯云服务器快速搭建个人网站?
原生JS获取元素集合的子元素宽度实例
js代码实现下拉菜单【推荐】
Laravel如何实现API版本控制_Laravel API版本化路由设计策略
广州网站制作公司哪家好一点,广州欧莱雅百库网络科技有限公司官网?
HTML5打空格有哪些误区_新手常犯的空格使用错误【技巧】
jQuery中的100个技巧汇总
Win11应用商店下载慢怎么办 Win11更改DNS提速下载【修复】
大学网站设计制作软件有哪些,如何将网站制作成自己app?
Laravel API资源(Resource)怎么用_格式化Laravel API响应的最佳实践
Laravel如何实现模型的全局作用域?(Global Scope示例)

