iOS开发-QQ聊天布局实现(聊天机器人应用)

移动端H5输入框被软键盘顶飞的系统化解决方案 在移动端Web开发中,视口(Viewport)是CSS布局和用户视觉交互的核心区域。其原理涉及布局视口与视觉视口的动态博弈,当软键盘弹起时,浏览器会通过收缩视口高度或平移视口位置来确保输入框可见,这一行为直接影响了基于视口单位(如vh)和固定定位(position: fixed)的布局稳定性,导致输入框被遮挡或页面布局错乱。该问题的技术价值在于,它深刻揭示了移动端浏览器视口管理、CSS布局流与原生输入法交互间的复杂关系,是构建高质量、强交互H5应用(如AI对话、在线客服、表单填写)必须攻克的核心体验难题。针 阅读详情

今天和同学们讲一下QQ的聊天布局,以及如何实现一个聊天机器人,很多细节以及分类的处理!那么老规矩废话不多说上代码!

 

#pragma mark - 分类

//

//  UIImage+ZZ.h

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import <UIKit/UIKit.h>

 

@interface UIImage (ZZ)

+ (UIImage *)resizableImage:(NSString *)name;

@end

 

//

//  UIImage+ZZ.m

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import "UIImage+ZZ.h"

 

@implementation UIImage (ZZ)

/**

 *  返回一张可以随意拉伸不变形的图片

 *

 *  @param name 图片名字

 */

+ (UIImage *)resizableImage:(NSString *)name

{

    UIImage *normal = [UIImage imageNamed:name];

    CGFloat w = normal.size.width *0.5;

    CGFloat h = normal.size.height *0.5;

    return [normal resizableImageWithCapInsets:UIEdgeInsetsMake(h, w, h, w)];

}

@end

 

//

//  NSString+ZZ.h

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>

 

@interface NSString (ZZ)

/**

 *  返回字符串所占用的尺寸

 *

 *  @param font    字体

 *  @param maxSize 最大尺寸

 */

- (CGSize)sizeWithFont:(UIFont *)font maxSize:(CGSize)maxSize;

@end

 

//

//  NSString+ZZ.m

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import "NSString+ZZ.h"

 

@implementation NSString (ZZ)

- (CGSize)sizeWithFont:(UIFont *)font maxSize:(CGSize)maxSize

{

    NSDictionary *attrs = @{NSFontAttributeName : font};

    

    return [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;

    

}

@end

 

#pragma mark - 模型model

 

//

//  ZZMessage.h

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import <Foundation/Foundation.h>

typedef enum {

    ZZMessageTypeMe = 0,//自己发的

    ZZMessageTypeOther   // 别人发的

} ZZMessageType;

#pragma mark - 那么我们先来根据plist解决模型的问题首先提供一个最基本的模型

@interface ZZMessage : NSObject

/**

 *  聊天内容

 */

@property (nonatomic,copy)NSString *text;

/**

 *  发送时间

 */

@property (nonatomic,copy)NSString *time;

/**

 *  信息的类型

 */

@property (nonatomic,assign)ZZMessageType type;

 

/**

 *  是否隐藏时间

 */

@property (nonatomic,assign)BOOL hideTime;

 

+ (instancetype)messageWithDict:(NSDictionary *)dict;

- (instancetype)initWithDict:(NSDictionary *)dict;

@end

 

//

//  ZZMessage.m

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import "ZZMessage.h"

 

@implementation ZZMessage

+ (instancetype)messageWithDict:(NSDictionary *)dict

{

    return [[self alloc]initWithDict:dict];

}

 

- (instancetype)initWithDict:(NSDictionary *)dict

{

    if (self = [super init]) {//直接通过kvc赋值

        [self setValuesForKeysWithDictionary:dict];

    }

    return self;

}

@end

 

//

//  ZZMessageFrame.h

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

#pragma mark - 再提供一个ZZMessageFrame的模型内部承载ZZMessage模型

// 正文的字体

#define ZZTextFont [UIFont systemFontOfSize:15]

 

// 正文的内边距

#define ZZTextPadding 20

#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>

 

@class ZZMessage;

 

@interface ZZMessageFrame : 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)ZZMessage *message;

@end

 

//

//  ZZMessageFrame.m

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import "ZZMessageFrame.h"

#import "ZZMessage.h"

#import "NSString+ZZ.h"

 

@implementation ZZMessageFrame

- (void)setMessage:(ZZMessage *)message

{

    _message =  message;

    // 间距

    CGFloat padding = 10;

    // 屏幕的宽度

    CGFloat screenW =  [UIScreen mainScreen].bounds.size.width;

    

    // 1.时间

    if (message.hideTime == NO) {//显示时间

        CGFloat timeX = 0;

        CGFloat timeY = 0;

        CGFloat timeW = screenW;

        CGFloat timeH = 40;

        _timeF = CGRectMake(timeX, timeY, timeW, timeH);

    }

    

    // 2.头像

    CGFloat iconY =  CGRectGetMaxY(_timeF) + padding;

    CGFloat iconW =  40;

    CGFloat iconH =  40;

    CGFloat iconX;

    if (message.type == ZZMessageTypeOther) {//别人发的

        iconX = padding;

    } else {//自己的发的

        iconX = screenW - padding - iconW;

    }

    _iconF = CGRectMake(iconX, iconY, iconW, iconH);

    

    // 3.正文

    CGFloat textY =  iconY;

    // 文字计算的最大尺寸

    CGSize textMaxSize = CGSizeMake(200,MAXFLOAT);

    // 文字计算出来的真实尺寸(按钮内部label的尺寸)

    CGSize textRealSize =  [message.text sizeWithFont:ZZTextFont maxSize:textMaxSize];

    // 按钮最终的真实尺寸

    CGSize textBtnSize = CGSizeMake(textRealSize.width +ZZTextPadding *2, textRealSize.height +ZZTextPadding *2);

    CGFloat textX;

    if (message.type == ZZMessageTypeOther) {//别人发的

        textX = CGRectGetMaxX(_iconF) + padding;

    } else {//自己的发的

        textX = iconX - padding - textBtnSize.width;

    }

    //    _textF =  CGRectMake(textX, textY, textSize.width + 40, textSize.height + 40);

    _textF = (CGRect){{textX, textY}, textBtnSize};

    

    // 4.cell的高度

    CGFloat textMaxY =  CGRectGetMaxY(_textF);

    CGFloat iconMaxY =  CGRectGetMaxY(_iconF);

    _cellHeight = MAX(textMaxY, iconMaxY) + padding;

}

@end

 

#pragma mark - 自定义cell & view

//

//  ZZInputView.h

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import <UIKit/UIKit.h>

 

@protocol ZZInputViewDelegate <NSObject>

@optional

 

- (void)inputViewDidSendMsg:(NSString *)msgStr;

 

@end

 

@interface ZZInputView : UIView

+ (instancetype)inputView;

 

@property (nonatomic, weak) id <ZZInputViewDelegate> delegate;

@end

 

//

//  ZZInputView.m

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import "ZZInputView.h"

#import "UIImage+ZZ.h"

 

@interface ZZInputView()<UITextFieldDelegate>

@property (nonatomic, weak) UIImageView *bgImg;

@property (nonatomic, weak) UIButton *voiceBtn;

@property (nonatomic, weak) UITextField *msgTf;

@property (nonatomic, weak) UIButton *smileBtn;

@property (nonatomic, weak) UIButton *addBtn;

@end

 

@implementation ZZInputView

+ (instancetype)inputView

{

    return [[self alloc] init];

}

 

- (instancetype)initWithFrame:(CGRect)frame

{

    if (self = [super initWithFrame:frame]) {

        [self setUpSubViews];

    }

    

    return self;

}

 

- (instancetype)initWithCoder:(NSCoder *)decoder

{

    if (self = [super initWithCoder:decoder]) {

        [self setUpSubViews];

    }

    

    return self;

}

 

- (void)setUpSubViews

{

    UIImageView *bgImg = [[UIImageView alloc] init];

    bgImg.image = [UIImage resizableImage:@"chat_bottom_bg"];

    [self addSubview:bgImg];

    self.bgImg = bgImg;

    

    UIButton *voiceBtn = [[UIButton alloc] init];

    [voiceBtn setBackgroundImage:[UIImage imageNamed:@"chat_bottom_voice_nor"] forState:UIControlStateNormal];

    [voiceBtn setBackgroundImage:[UIImage imageNamed:@"chat_bottom_voice_press"] forState:UIControlStateHighlighted];

    [self addSubview:voiceBtn];

    self.voiceBtn = voiceBtn;

    

    UITextField *msgTf = [[UITextField alloc] init];

    msgTf.delegate = self;

    msgTf.borderStyle = UITextBorderStyleNone;

    msgTf.background = [UIImage imageNamed:@"chat_bottom_textfield"];

    msgTf.adjustsFontSizeToFitWidth = YES;

    msgTf.returnKeyType = UIReturnKeySend;

    msgTf.font = [UIFont systemFontOfSize:14.0f];

    [self addSubview:msgTf];

    self.msgTf = msgTf;

    

    UIButton *smileBtn = [[UIButton alloc] init];

    [smileBtn setBackgroundImage:[UIImage imageNamed:@"chat_bottom_smile_nor"] forState:UIControlStateNormal];

    [smileBtn setBackgroundImage:[UIImage imageNamed:@"chat_bottom_smile_press"] forState:UIControlStateHighlighted];

    [self addSubview:smileBtn];

    self.smileBtn = smileBtn;

    

    UIButton *addBtn = [[UIButton alloc] init];

    [addBtn setBackgroundImage:[UIImage imageNamed:@"chat_bottom_up_nor"] forState:UIControlStateNormal];

    [addBtn setBackgroundImage:[UIImage imageNamed:@"chat_bottom_up_press"] forState:UIControlStateHighlighted];

    [self addSubview:addBtn];

    self.addBtn = addBtn;

}

 

- (void)layoutSubviews

{

    [super layoutSubviews];

    

    CGFloat bgImgX = 0;

    CGFloat bgImgY = 0;

    CGFloat bgImgW = self.frame.size.width;

    CGFloat bgImgH = 44;

    self.bgImg.frame = CGRectMake(bgImgX, bgImgY, bgImgW, bgImgH);

    

    CGFloat voiceBtnX = 5;

    CGFloat voiceBtnY = bgImgY;

    CGFloat voiceBtnW = bgImgH;

    CGFloat voiceBtnH = bgImgH;

    self.voiceBtn.frame = CGRectMake(voiceBtnX, voiceBtnY, voiceBtnW, voiceBtnH);

    

    CGFloat msgTfX = voiceBtnX + bgImgH;

    CGFloat msgTfY = 1;

    CGFloat msgTfW = self.frame.size.width - 3 * (bgImgH + voiceBtnX) - voiceBtnX;

    CGFloat msgTfH = bgImgH - msgTfY;

    self.msgTf.frame = CGRectMake(msgTfX, msgTfY, msgTfW, msgTfH);

    

    CGFloat smileBtnX = self.frame.size.width - 2 * (bgImgH + voiceBtnX);

    CGFloat smileBtnY = bgImgY;

    CGFloat smileBtnW = bgImgH;

    CGFloat smileBtnH = bgImgH;

    self.smileBtn.frame = CGRectMake(smileBtnX, smileBtnY, smileBtnW, smileBtnH);

    

    CGFloat addBtnX = self.frame.size.width - (bgImgH + voiceBtnX);

    CGFloat addBtnY = bgImgY;

    CGFloat addBtnW = bgImgH;

    CGFloat addBtnH = bgImgH;

    self.addBtn.frame = CGRectMake(addBtnX, addBtnY, addBtnW, addBtnH);

}

#pragma mark - 文本框代理

/**

 *  点击了return按钮(键盘最右下角的按钮)就会调用

 */

- (BOOL)textFieldShouldReturn:(UITextField *)textField

{

    // 1.通知代理

    if ([self.delegate respondsToSelector:@selector(inputViewDidSendMsg:)]) {

        [self.delegate inputViewDidSendMsg:textField.text];

    }

    

    // 2.清空文字

    textField.text = nil;

    

    // 返回YES即可

    return YES;

}

@end

 

//

//  ZZMessageCell.h

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#pragma mark - 这里写出自定义cell

#import <UIKit/UIKit.h>

 

@class ZZMessageFrame;

@interface ZZMessageCell : UITableViewCell

+ (instancetype)cellWithTableView:(UITableView *)tableView;

 

@property (nonatomic,strong)ZZMessageFrame *messageFrame;

@end

 

//

//  ZZMessageCell.m

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

#import "ZZMessageCell.h"

#import "ZZMessageFrame.h"

#import "ZZMessage.h"

#import "UIImage+ZZ.h"

 

@interface ZZMessageCell()

/**

 *  时间

 */

@property (nonatomic,weak) UILabel *timeView;

/**

 *  头像

 */

@property (nonatomic,weak) UIImageView *iconView;

/**

 *  正文

 */

@property (nonatomic,weak) UIButton *textView;

@end

 

@implementation ZZMessageCell

+ (instancetype)cellWithTableView:(UITableView *)tableView

{

    static NSString *ID =@"message";

    ZZMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    if (cell ==nil) {

        cell = [[ZZMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];

    }

    return cell;

}

 

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier

{

    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

    if (self) {

        // 子控件的创建和初始化

        // 1.时间

        UILabel *timeView = [[UILabel alloc]init];

        timeView.textAlignment = NSTextAlignmentCenter;

        timeView.textColor = [UIColor grayColor];

        timeView.font = [UIFont systemFontOfSize:13];

        [self.contentView addSubview:timeView];

        self.timeView = timeView;

        

        // 2.头像

        UIImageView *iconView = [[UIImageView alloc]init];

        [self.contentView addSubview:iconView];

        self.iconView = iconView;

        

        // 3.正文

        UIButton *textView = [[UIButton alloc]init];

        textView.titleLabel.numberOfLines = 0;//自动换行

        textView.titleLabel.font = ZZTextFont;

        textView.contentEdgeInsets = UIEdgeInsetsMake(ZZTextPadding, ZZTextPadding, ZZTextPadding, ZZTextPadding);

        [textView setTitleColor:[UIColor blackColor]forState:UIControlStateNormal];

        [self.contentView addSubview:textView];

        self.textView = textView;

        

        // 4.设置cell的背景色

        self.backgroundColor = [UIColor clearColor];

    }

    return self;

}

 

- (void)setMessageFrame:(ZZMessageFrame *)messageFrame

{

    _messageFrame = messageFrame;

    

    ZZMessage *message = messageFrame.message;

    

    // 1.时间

    self.timeView.text = message.time;

    self.timeView.frame = messageFrame.timeF;

    

    // 2.头像

    NSString *icon = (message.type == ZZMessageTypeMe) ?@"me" :@"other";

    self.iconView.image = [UIImage imageNamed:icon];

    self.iconView.frame = messageFrame.iconF;

    

    // 3.正文

    [self.textView setTitle:message.text forState:UIControlStateNormal];

    self.textView.frame = messageFrame.textF;

    

    // 4.正文的背景

    if (message.type == ZZMessageTypeMe) {//自己发的,蓝色

        [self.textView setBackgroundImage:[UIImage resizableImage:@"chat_send_nor"] forState:UIControlStateNormal];

    } else {//别人发的,白色

        [self.textView setBackgroundImage:[UIImage resizableImage:@"chat_recive_nor"] forState:UIControlStateNormal];

    }

}

 

@end

 

#pragma mark - controller

 

 

//

//  ZZMessageController.h

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

 

#import <UIKit/UIKit.h>

 

@interface ZZMessageController : UIViewController

 

@end

 

 

 

 

//

//  ZZMessageController.m

//  03-QQ聊天布局

//

//  Created by 周昭 on 2017/3/14.

//  Copyright © 2017年 ZZ. All rights reserved.

//

 

 

#import "ZZMessageController.h"

#import "ZZMessage.h"

#import "ZZMessageFrame.h"

#import "ZZMessageCell.h"

#import "ZZInputView.h"

 

@interface ZZMessageController ()<UITextFieldDelegate,UITableViewDelegate,UITableViewDataSource,ZZInputViewDelegate>

@property (nonatomic,strong) NSMutableArray *messageFrames;

@property (nonatomic,strong) NSDictionary *autoreply;

@property (nonatomic, weak) UITableView *tableView;

@property (nonatomic, weak) UITextField *inputView;

@end

 

@implementation ZZMessageController

 

- (void)viewDidLoad {

    [super viewDidLoad];

    

    // 1.表格的设置

    [self setUpTableView];

    

    // 2.监听键盘的通知

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];

    

    // 3.底部的输入view

    [self setUpInputView];

}

 

#pragma mark - setUp 初始化

/**

 *  表格的设置

 */

- (void)setUpTableView

{

    UITableView *tableView = [[UITableView alloc] init];

    tableView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 44);

    tableView.backgroundColor = [UIColor colorWithRed:235/255.0 green:235/255.0 blue:235/255.0 alpha:1.0];

    tableView.separatorStyle = UITableViewCellSeparatorStyleNone; //去除分割线

    tableView.allowsSelection = NO; //不允许选中

    tableView.delegate = self;

    tableView.dataSource = self;

    [self.view addSubview:tableView];

    self.tableView = tableView;

}

 

/**

 *  底部的输入view

 */

- (void)setUpInputView

{

    ZZInputView *inputView = [ZZInputView inputView];

    inputView.frame = CGRectMake(0, self.view.frame.size.height - 44, self.view.frame.size.width, 44);

    inputView.delegate = self;

    [self.view addSubview:inputView];

}

 

#pragma mark - ZZInputViewDelegate 方法

- (void)inputViewDidSendMsg:(NSString *)msgStr

{

    // 1.自己发一条消息

    [self addMessage:msgStr type:ZZMessageTypeMe];

    

    // 2.自动回复一条消息

    NSString *reply = [self replayWithText:msgStr];

    [self addMessage:reply type:ZZMessageTypeOther];

}

 

/**

 *  发送一条消息

 */

- (void)addMessage:(NSString *)text type:(ZZMessageType)type

{

    // 1.数据模型

    ZZMessage *msg = [[ZZMessage alloc] init];

    msg.type = type;

    msg.text = text;

    // 设置数据模型的时间

    NSDate *now = [NSDate date];

    NSDateFormatter *fmt = [[NSDateFormatter alloc]init];

    fmt.dateFormat =@"HH:mm";

    // NSDate  --->  NSString

    // NSString ---> NSDate

    //    fmt.dateFormat = @"yyyy-MM-dd HH:mm:ss";

    //  2014-08-09 15:45:56

    // 09/08/2014  15:45:56

    msg.time = [fmt stringFromDate:now];

    

    // 看是否需要隐藏时间

    ZZMessageFrame *lastMf = [self.messageFrames lastObject];

    ZZMessage *lastMsg = lastMf.message;

    msg.hideTime = [msg.time isEqualToString:lastMsg.time];

    

    // 2.frame模型

    ZZMessageFrame *mf = [[ZZMessageFrame alloc] init];

    mf.message = msg;

    [self.messageFrames addObject:mf];

    

    // 3.刷新表格

    [self.tableView reloadData];

    

    // 4.自动滚动表格到最后一行

    NSIndexPath *lastPath = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0];

    [self.tableView scrollToRowAtIndexPath:lastPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

}

 

/**

 *  根据自己发的内容取得自动回复的内容

 *

 *  @param text 自己发的内容

 */

- (NSString *)replayWithText:(NSString *)text

{

//    for (int i = 0; i < text.length; i++) {

//        NSString *word = [text substringWithRange:NSMakeRange(i,1)];

//        

//        if (self.autoreply[word]) return self.autoreply[word];

//    }

    if ([text isEqualToString:@"1"]) {

        return @"110";

    } else if ([text isEqualToString:@"2"]) {

        return @"120";

    } else if ([text isEqualToString:@"3"]) {

        return @"911";

    } else if ([text isEqualToString:@"4"]) {

        return @"119";

    } else

        return@"滚蛋";

}

 

/**

 *  当键盘改变了frame(位置和尺寸)的时候调用

 */

- (void)keyboardWillChangeFrame:(NSNotification *)note

{

    // 设置窗口的颜色

    self.view.window.backgroundColor = self.tableView.backgroundColor;

    

    // 0.取出键盘动画的时间

    CGFloat duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey]doubleValue];

    

    // 1.取得键盘最后的frame

    CGRect keyboardFrame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];

    

    // 2.计算控制器的view需要平移的距离

    CGFloat transformY = keyboardFrame.origin.y -self.view.frame.size.height;

    

    // 3.执行动画

    [UIView animateWithDuration:duration animations:^{

        

        self.view.transform = CGAffineTransformMakeTranslation(0, transformY);

    }];

}

 

/*

 UIKeyboardAnimationCurveUserInfoKey = 7;  // 动画的执行节奏(速度)

 UIKeyboardAnimationDurationUserInfoKey = "0.25"; // 键盘弹出\隐藏动画所需要的时间

 UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";

 UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 588}";

 UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";

 UIKeyboardFrameChangedByUserInteraction = 0;

 

 // 键盘弹出

 UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";//键盘刚出来那一刻的frame

 UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}"; // 键盘显示完毕后的frame

 

 // 键盘隐藏

 UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";

 UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";

 */

- (BOOL)prefersStatusBarHidden

{

    return YES;

}

 

- (NSDictionary *)autoreply

{

    if (_autoreply ==nil) {

        _autoreply = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"autoreply.plist" ofType:nil]];

    }

    return _autoreply;

}

 

- (NSMutableArray *)messageFrames

{

    if (_messageFrames ==nil) {

        NSArray *dictArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil]];

        

        NSMutableArray *mfArray = [NSMutableArray array];

        

        for (NSDictionary *dict in dictArray) {

            // 消息模型

            ZZMessage *msg = [ZZMessage messageWithDict:dict];

            

            // 取出上一个模型

            ZZMessageFrame *lastMf = [mfArray lastObject];

            ZZMessage *lastMsg = lastMf.message;

            

            // 判断两个消息的时间是否一致

            msg.hideTime = [msg.time isEqualToString:lastMsg.time];

            

            // frame模型

            ZZMessageFrame *mf = [[ZZMessageFrame alloc] init];

            mf.message = msg;

            

            // 添加模型

            [mfArray addObject:mf];

        }

        

        _messageFrames = mfArray;

    }

    return _messageFrames;

}

 

#pragma mark - 数据源方法

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{

    return self.messageFrames.count;

}

 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    // 1.创建cell

    ZZMessageCell *cell = [ZZMessageCell cellWithTableView:tableView];

    

    // 2.给cell传递模型

    cell.messageFrame = self.messageFrames[indexPath.row];

    

    // 3.返回cell

    return cell;

}

 

#pragma mark - 代理方法

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

    ZZMessageFrame *mf = self.messageFrames[indexPath.row];

    return mf.cellHeight;

}

 

/**

 *  当开始拖拽表格的时候就会调用

 */

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView

{

    // 退出键盘

    [self.view endEditing:YES];

}

 

- (void)dealloc

{

    [[NSNotificationCenter defaultCenter] removeObserver:self];

}

@end

 

 

 

 

ios-实现简单的聊天界面.zip 实现简单的QQ聊天界面 代码分离 以frame模型降低对系统的消耗 用MJExtension解析数据 提高代码的简洁程度 立即下载

相关推荐

ios-聊天界面的UI搭建.zip

整个消息UI还是仿照环信的, 基本上大致相同, 只是稍加修改, 因为需求只需要聊天, 文字, 表情, 所以demo也只有这些, 基本比较简单, 代码:https://github.com/No88/LHChat, 喜欢的star一下

iOS聊天室 简单的对话聊天界面(cell自适应高度)

难点 因为聊天长度不一样,需要设置自适应高度 发送信息后,需要使tableView添加一条cell,并更新 cell的所有子视图需要清除,否则会有bug(在最后会附上不清除子视图的效果) 键盘弹出界面上移,点击空白处键盘回收,界面下移 思路 聊天界面的对话其实就是一个tableView,创建一个可变数组记录每句话的高度,根据话语的高度设置单元格高度 按发送键时插入一条新cell在最底端,获取...

吃橘子 4439

ios仿移动QQ聊天页面布局

ios移动聊天布局,移动聊天布局,消息、语音、图片布局

IOS详解TableView——对话聊天布局的实现

上篇博客介绍了如何使用UITableView实现类似QQ的好友界面布局。这篇讲述如何利用自定义单元格来实现聊天界面的布局。 借助单元格实现聊天布局难度不大,主要要解决的问题有两个: 1.自己和其他人说话头像和气泡图像在不同的位置。 找了些类似的例子,有根据不同情况设置不同的自定义类的。这里使用根据说话人的属性来设置不同的位置,在一个单一的单元格类中。 2.像微博等根据说话的内容...

jishublog 370

IOS 聊天界面(自适应文字)的实现

本文主要介绍一个实现聊天界面的思路过程,具有很好的参考价值。下面跟着小编一起来看下吧

iOS实现搭建聊天页面的实例代码

本篇文章主要介绍了iOS实现搭建聊天页面的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

iOS开发 - 第02篇 - UI进阶 - 04 - QQ聊天界面

1、QQ聊天界面2、初步思路3、添加Frame模型4、界面优化5、键盘处理6、自动回复处理

NSRyan的博客 1040

Rk3568 百兆以太网RMII模式DTS配置详解

本文详细解析了RK3568芯片在百兆以太网RMII模式下的DTS配置方法,包括时钟树配置、PHY接口模式定义及实际工程中的进阶设置。通过实战案例和调试技巧,帮助开发者快速解决RMII模式下的常见问题,提升工业控制设备的网络稳定性。

weixin_29189377的博客 420

《iOS一个完整的聊天UI框架》可发送文本、表情、图片、视频、语音等消息

#效果图 #集成方式 直接导入:#import "WZMChat.h" 文件夹结构如下图: #数据库设计简单描述 从类型上,可分为3个表: 用户表(user)、会话表(session)、消息表(message); 从实际需求上,再进一步细分: 用户表需要两个:用户(user)和群(group); 而为了消息的优化处理,每一个私聊或群聊,都可以新建一个消息表(message)。 处理逻辑如下: 1、添加好友 - 发起聊天; 2、查询对应的消息表(message)是否存在,不存在则创建; 3、向该消息表(m

u013288870的博客 2537

期权专题9:雪球期权(一)(BSM+蒙特卡罗定价)

python;雪球定价;蒙特卡罗

mhmh123789的博客 8100

【Cadence】Virtuoso@LayoutXL 差分电路版图镜像同步技巧详解

本文详细介绍了在Cadence Virtuoso LayoutXL中实现差分电路版图镜像同步的技巧,包括核心需求、环境配置、分步操作指南及高级技巧。通过使用LayoutXL的Group功能,工程师可以高效保持差分电路的对称性,提升版图迭代效率,确保高速信号处理的稳定性。特别适用于深亚微米工艺下的差分放大器设计。

weixin_29215391的博客 323

用Flutter实现小Q聊天机器人(二)

接上篇,我们新建一个Flutter Project: flutter项目中有android、ios、lib等目录,可能刚开始有不少同学建完项目,就直奔android目录准备开发了,但其实,flutter的编程目录是在lib下的,所有编程文件后缀都为.dart,我们也可以把其看做安卓开发时.java以及java目录,可以更好的理解! 其中的pubspec.yaml这个文件很重要,类似于安卓开发时a...

白玉梁的专栏 2015

Zemax新手别慌!一文搞懂几何像差:从球差、慧差到色差的实战识别与优化

本文为Zemax新手提供几何像差实战指南,详细解析球差、彗差和色差的识别与优化方法。通过软件操作关键节点和实际项目决策逻辑,帮助光学设计师快速掌握像差诊断与优化技巧,提升光学设计效率。

weixin_42524708的博客 258

iOS-QQ自动聊天机器人

素材有两个一个消息字典,一个自动回复匹配字典 首先字典转模型,位置和数据都是模型里面弄好的 加了一个工具方法resizeImage来调整聊天界面 然后来写自定义cell 最后在控制器里面写逻辑 // // UIImage+resizeImage.h // QQ聊天 // // Created by YaguangZhu on 15/8/28. /...

weixin_30905981的博客 772

基因树构建全流程:从OrthoFinder到iTOL的保姆级教程(附兰科植物案例)

本文提供了一份从OrthoFinder到iTOL的基因树构建全流程保姆级教程。详细解析了利用OrthoFinder构建物种树、IQ-TREE构建无根基因树、Notung进行树调和生根,以及最终在iTOL中实现专业可视化的每一步操作与实战经验,并以兰科植物NB-ARC基因家族为例进行演示,帮助研究者系统掌握基因树分析方法。

tequila的博客 521

学Simulink——基于电能质量治理的场景实例:静止无功发生器(SVG)与并网逆变器的结合

本文详细介绍了基于Simulink的SVG与光伏并网逆变器联合仿真建模方法。系统包含光伏单元、SVG主电路、控制策略(无功检测+双闭环控制)等模块,通过ip-iq法实现动态无功补偿。仿真结果表明,SVG能快速响应负载变化,将功率因数提升至0.98以上,有效稳定母线电压。该方案为新能源并网系统提供了有功/无功协同控制解决方案,具有10ms级响应速度和±5kvar补偿能力。文章提供了完整的建模流程、控制算法实现和结果分析,可作为电力电子与电能质量治理领域的实践指导。

amy_mhd的博客 167

雷军回应「小米老是赶潮流」:做机器人小米是最早一批;Meta将裁掉40%外部审核人员;苹果WWDC26定档6月9日 | 极客头条

23日,针对阿里即将推出“养虾”PC与主机硬件,由阿里云无影团队主导,阿里方面回应《科创板日报》称:是有这个计划,基于原有产品改造,但没有发布时间表。届时,苹果预计发布新一代操作系统,3月23日,小米科技有限责任公司创始人雷军宣布,在中汽摩联相关负责人的见证下,小米正式与中国汽车场地职业联赛(CTCC)、中国超级跑车锦标赛(China GT)和中国汽车耐力锦标。最新的报道显示,Meta公司于当地时间3月19日宣布将进行内容审核策略的重大改革,显著减少对外部承包商的依赖,转而使用先进的人工智能(AI)系统。

优快云资讯 1928

vue2学习笔记2-老规矩,从Hello World开始,了解Vue实例和模板

想要把“Hello,World”放置在页面上,首先需要准备一个HTML的块级元素div,来承接内容。

l_q_l的专栏 1256

腾讯ima Copilot与WorkBuddy的区别及应用场景解析

是腾讯推出的类Copilot工具,官网为,最晚于2025年8月已正式上线(收录于2025年8月10日更新的《国内可用AI工具合集》)。它的定位是“会思考的知识库”,核心解决通用大模型知识过时、易产生幻觉、无法适配私有场景的痛点,支持混元、DeepSeek R1满血版等多模型,覆盖Windows、Mac、iOS、鸿蒙、Android客户端及微信小程序、网页版、浏览器插件等多端,稳定性表现为PC端>APP>小程序。目前支持上传PDF、DOC、JPEG、PNG等格式内容,暂不支持压缩包和音视频。

shangxiPBI的博客 5586

HDR 人像肤色稳定实战:AE/AWB/ToneMap 冲突解耦与优先级裁决

人像 HDR 最容易“翻车”的就是**肤色飘**:AE 抢亮度、AWB 拉白点、TM 又在肩部压高光,三方各自“对”却合起来“错”。本文给出一套**工程可落地**的裁决策略:以**肤色 ROI 锚点**为最高约束,AE 先对脸部入射照度收敛,AWB 在**肤色先验 + 光源模型**下缓变,TM 采用**色相保持 + 中灰/肤色双锚**且时域一致化。配套**阈值与回归**(ΔE00、亮度漂移 RMS、频闪注入),把“主观对”的口径落到**可度量**,并给出逆光、多脸、霓虹等极端场景的**例外分流**。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 1198

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

程序员“造人”

点击上方“程序人生”,选择“置顶公众号”第一时间关注程序猿(媛)身边的故事很少有一个职业能像程序员这样,在短短二十年间完成四次更迭。PC、互联网、移动互联网这三次更迭,对...

程序人生的博客 1145

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

IOS开发教程第一季之01UI基础day8合并IOS学习008----控件(UITableView04聊天机器人)

1.加载xib的两种方法

weixin_43745804的博客 278

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

iOS开发之微信聊天页面实现

  在上篇博客(iOS开发之微信聊天工具栏的封装)中对微信聊天页面下方的工具栏进行了封装,本篇博客中就使用之前封装的工具栏来进行聊天页面的编写。在聊天页面中主要用到了TableView的知识,还有如何在俩天中显示我们发送的表情,具体请参考之前的博客:IOS开发之显示微博表情,在这儿就不做赘述啦。在聊天页面用到了三对,六种Cell,不过cell的复杂度要比之前的新浪微博(IOS开发之新浪围脖)简单的...

weixin_34087503的博客 239

iOS 类似微信,QQ聊天界面的气泡聊天简单实现Demo

以下是YYKit组件的源码分析,高级性能优化相关都在里面可以找到YYwebImage超细源码分析YYImage超细源码分析YYModel源码分析YYText源码分析12.27日更新:分析了一个很牛B的聊天UI框架进阶版高级UI实现 帅气的我又来了,是不是帅气逼人。。。。。。        来说说又是早些前,去面试了一些公司,有些喜欢打电话让你说说runtime,runloop什么的,这还好,关键遇...

Deft_MKJing的博客 2万+
上一篇: iOS开发-支付宝手势解锁实现展示
下一篇: iOS开发-九宫格Demo展示
ZZ_iOSDeveloper
博客等级 码龄11年 21粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值