JSQMessagesViewController 常见问题实战指南:TabBar 适配、弹性气泡、头像与 Cell 及工具栏定制(FAQ 全解)
2026/9/23 15:34:17 网站建设 项目流程
  • UI组件
  • 即时通讯

【免费下载链接】JSQMessagesViewController

An elegant messages UI library for iOS

项目地址:https://gitcode.com/gh_mirrors/js/JSQMessagesViewController
点击查看免费下载

导读

本文基于 JSQMessagesViewController 官方 FAQ 整理而成,围绕 iOS 聊天界面开发中最常遇到的五类问题展开:UITabBarController/UITabBar兼容性、实验性的弹性气泡(springy bubbles)、头像移除、消息 Cell 的两种定制路线、输入工具栏按钮的换位与替换。文中所有结论均与当前仓库源码(JSQMessagesCollectionViewFlowLayout、JSQMessagesViewController、JSQMessagesInputToolbar)及 Demo(DemoMessagesViewController.m)相互印证。读完本文,你将掌握上述五类问题的可直接复制的 Objective-C 解决方案,并理解每段代码背后的布局、委托与工具栏机制。

一、在 UITabBar / UITabBarController 中使用本库

1.1 问题背景

FAQ 明确指出:库与UITabBarController/UITabBar的兼容性是"是又不是"(yes and no),存在历史性的布局争议。核心原因在于JSQMessagesViewControllerviewDidLoad阶段会主动将自身 view 扩展到整个屏幕(edgesForExtendedLayout的默认行为),当嵌入 TabBar 容器时,聊天视图底部会被 TabBar 遮挡,导致最后一条消息或输入工具栏显示不全。

1.2 官方推荐 Workaround

FAQ 给出的最稳妥方案是在JSQMessagesViewController子类的viewDidLoad中关闭边缘延伸:

- (void)viewDidLoad { [super viewDidLoad]; self.edgesForExtendedLayout = UIRectEdgeNone; }

1.3 原理补充(源码依据)

  • viewDidLoad必须调用[super viewDidLoad]:该方法是 JSQMessagesViewController.h 中标注为NS_REQUIRES_SUPER的生命周期方法之一(viewWillAppear:viewDidAppear:等同样如此),跳过 super 调用会导致内部布局逻辑失效。
  • 关闭edgesForExtendedLayout后,控制器视图的自动布局将基于安全区域之外的内容矩形(即 TabBar 顶部)来计算,聊天 collection view 与输入工具栏即可完整落在 TabBar 之上。

提示:若你在viewDidAppear:中开启了弹性气泡(见下文第二节),请一并注意该时序与布局属性的配合。

二、开启"弹性气泡"(Springy Bubbles)——实验特性

2.1 开启方式

FAQ 给出了最小启用代码,并标注该特性仍处于实验阶段

- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; self.collectionView.collectionViewLayout.springinessEnabled = YES; }

关键时序约束springinessEnabled必须viewDidAppear:中设置,而不是viewDidLoad。原因见下节源码分析。

2.2 底层原理(源码证据)

弹性气泡由布局对象 JSQMessagesCollectionViewFlowLayout 实现,它继承自UICollectionViewFlowLayout,并在内部使用UIDynamicAnimator驱动:

  • 属性声明见 JSQMessagesCollectionViewFlowLayout.h:springinessEnabled默认值为NOspringResistanceFactor(阻力系数)默认值为1000,数值越大阻力越大、气泡越"不弹",调小则更"弹"。
  • 初始化时这两个默认值在jsq_configureFlowLayout中设定(见 JSQMessagesCollectionViewFlowLayout.m)。
  • 布局通过UIDynamicAnimator+UIAttachmentBehavior(吸附行为)模拟弹簧:prepareLayout会为可见区域内的 item 创建/移除吸附行为(JSQMessagesCollectionViewFlowLayout.m),并在滚动时依据手指位置与springResistanceFactor动态调整每个 item 的 center(jsq_adjustSpringBehavior:forTouchLocation:,见 JSQMessagesCollectionViewFlowLayout.m)。
  • 关闭springinessEnabled时布局会移除所有动力学行为并清空可见 indexPath 缓存(JSQMessagesCollectionViewFlowLayout.m)。
  • Demo 中同样在viewDidAppear:里根据用户设置开启/关闭该特性(DemoMessagesViewController.m),并在注释中强调"必须在viewDidAppear:中设置,且此特性大多稳定但仍是实验性的"。

为什么不建议在viewDidLoad开启?此时 collection view 的 bounds 尚未完成布局,UIDynamicAnimator无法正确计算可见 item 集合,会出现抖动或行为失效。

三、移除头像(Avatars)

3.1 两步移除法

FAQ 要求同时完成两件事:把布局中的入站/出站头像尺寸清零,并在数据源方法中返回nil

- (void)viewDidLoad { [super viewDidLoad]; self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero; self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero; } - (id<JSQMessageAvatarImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath { return nil; }

3.2 源码依据与隐藏细节

  • 两个属性定义于 JSQMessagesCollectionViewFlowLayout.h:incomingAvatarViewSizeoutgoingAvatarViewSize默认值均为(30.0f, 30.0f),文档明确说明"设为CGSizeZero即移除头像";也可使用常量kJSQMessagesCollectionViewAvatarSizeDefault(值为30.0f,见 JSQMessagesCollectionViewFlowLayout.m)来恢复默认尺寸。
  • 修改尺寸会触发布局失效:setter 内部调用invalidateLayoutWithContext:(JSQMessagesCollectionViewFlowLayout.m),因此放在viewDidLoad中即可生效,无需额外刷新。
  • Demo 正是用这一模式按用户偏好开关入站/出站头像(DemoMessagesViewController.m)。
  • 数据源方法返回nil是第二步:即使尺寸已归零,若仍返回头像对象,一些复用场景下可能出现残留视图;两者配合才能彻底移除。

布局属性负责"留不留空间",数据源返回nil负责"提不提供内容",二者缺一不可。

四、定制消息 Cell:两种路线

FAQ 将定制 cell 归纳为两种路线,按需求复杂度选择:

  1. 定制现有 cell 的外观与行为(简单,推荐多数场景);
  2. 提供完全自定义的 cell 原型(复杂,需要增删 cell 子视图时使用)。

4.1 路线一:定制现有 cell(Easy)

仅需重写cellForItemAtIndexPath:,拿到基类JSQMessagesCollectionViewCell的实例后即可访问其全部属性:

- (UICollectionViewCell *)collectionView:(JSQMessagesCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:collectionView cellForItemAtIndexPath:indexPath]; // Customize the shit out of this cell // See the docs for JSQMessagesCollectionViewCell return cell; }

可操作属性一览(声明于 JSQMessagesCollectionViewCell.h):

属性说明
cellTopLabel钉在 cell 顶部的标签,常用于时间戳
messageBubbleTopLabel气泡上方的标签,常用于发送者名字
cellBottomLabelcell 底部的标签,常用于送达状态
textView承载消息正文的JSQMessagesCellTextView
messageBubbleImageView气泡背景图片视图
messageBubbleContainerView气泡容器(textView 与气泡图的父视图)
avatarImageView/avatarContainerView头像视图及容器
accessoryButtoncell 的附件按钮
mediaView媒体消息内容视图(非空时textViewmessageBubbleImageView为 nil)
delegate遵守JSQMessagesCollectionViewCellDelegate的委托,回调头像/气泡/cell 点击

三个重要雷区(Demo 源码注释明确标注,见 DemoMessagesViewController.m):

  • 不要直接设置cell.textView.font!字体应通过self.collectionView.collectionViewLayout.messageBubbleFontviewDidLoad中统一设置,否则尺寸计算(JSQMessagesBubblesSizeCalculator)与实际渲染不一致,导致气泡高度错误。messageBubbleFont默认取系统UIFontTextStyleBody首选字体(JSQMessagesCollectionViewFlowLayout.m)。
  • 不要手动改 cell 的布局信息(frame 等),应通过布局属性定制。
  • 设置正文颜色、链接颜色等是安全的;Demo 中即按消息方向设置cell.textView.textColorlinkTextAttributes(DemoMessagesViewController.m)。

4.2 路线二:提供自定义 cell 原型(Hard)

此路线给予最大自由度,适合需要增删 cell 子视图的场景。FAQ 给出五步流程:

  1. 提供自己的 cell 子类,仿照库内置的JSQMessagesCollectionViewCellJSQMessagesCollectionViewCellIncomingJSQMessagesCollectionViewCellOutgoing(后两者见 JSQMessagesCollectionViewCellIncoming.h 与 JSQMessagesCollectionViewCellOutgoing.h)。
  2. JSQMessagesViewController子类上设置如下属性(声明见 JSQMessagesViewController.h):
    • outgoingCellIdentifier—— 出站文本消息 cell 复用标识(默认[JSQMessagesCollectionViewCellOutgoing cellReuseIdentifier]
    • outgoingMediaCellIdentifier—— 出站媒体消息 cell 复用标识(默认[JSQMessagesCollectionViewCellOutgoing mediaCellReuseIdentifier]
    • incomingCellIdentifier—— 入站文本消息 cell 复用标识(默认[JSQMessagesCollectionViewCellIncoming cellReuseIdentifier]
    • incomingMediaCellIdentifier—— 入站媒体消息 cell 复用标识(默认[JSQMessagesCollectionViewCellIncoming mediaCellReuseIdentifier]
  3. 用上述标识把自定义 cell 类/nib 注册到 collection view。
  4. 重写collectionView:cellForItemAtIndexPath:且不要调用super——因为是自己提供的 cell,调用 super 会执行大量无用工作。
  5. (可选)模型对象可实现JSQMessageData协议(见 JSQMessageData.h)或继承JSQMessage扩展需求。

注意:这 4 个 cell 标识属性的默认值不建议在未提供自定义 cell 时覆盖;只有走路线二才需要修改它们。

五、定制输入工具栏按钮

5.1 替换 / 移除左右按钮

FAQ 提供了在viewDidLoad中定制工具栏的完整代码:

- (void)viewDidLoad { [super viewDidLoad]; // This button will call the `didPressAccessoryButton:` selector on your JSQMessagesViewController subclass self.inputToolbar.contentView.leftBarButtonItem = /* custom button or nil to remove */ // This button will call the `didPressSendButton:` selector on your JSQMessagesViewController subclass self.inputToolbar.contentView.rightBarButtonItem = /* custom button or nil to remove */ // Swap buttons, move send button to the LEFT side and the attachment button to the RIGHT // For RTL language support self.inputToolbar.contentView.leftBarButtonItem = [JSQMessagesToolbarButtonFactory defaultSendButtonItem]; self.inputToolbar.contentView.rightBarButtonItem = [JSQMessagesToolbarButtonFactory defaultAccessoryButtonItem]; // The library will call the correct selector for each button, based on this value self.inputToolbar.sendButtonOnRight = NO; }
  • leftBarButtonItem/rightBarButtonItem是 JSQMessagesToolbarContentView 上的属性:置nil即可移除对应按钮;按钮高度被忽略(由工具栏高度决定),宽度保留,可用leftBarButtonItemWidth/rightBarButtonItemWidth显式指定宽度,左右留白由leftContentPadding/rightContentPadding控制(默认8.0f)。
  • 若想用库内置样式,直接使用工厂类 JSQMessagesToolbarButtonFactory 的defaultSendButtonItem(文字 "Send"、无图标、蓝色)与defaultAccessoryButtonItem(回形针图标、无文字)。其实现见 JSQMessagesToolbarButtonFactory.m:send 按钮文本取自本地化字符串"send",颜色使用jsq_messageBubbleBlueColor;accessory 图标取自UIImage jsq_defaultAccessoryImage(即 Assets 中的 clip.png 系列)。

5.2 关于sendButtonOnRight的说明

FAQ 示例中的sendButtonOnRight属于 7.x 早期 API。在当前仓库源码中,该语义已演进为 JSQMessagesInputToolbar 的枚举属性sendButtonLocation

typedef NS_ENUM(NSUInteger, JSQMessagesInputSendButtonLocation) { JSQMessagesInputSendButtonLocationNone, // 无发送按钮(或自行接管) JSQMessagesInputSendButtonLocationRight, // 发送按钮在右侧(默认) JSQMessagesInputSendButtonLocationLeft // 发送按钮在左侧 };
  • 默认值为JSQMessagesInputSendButtonLocationRight(见 JSQMessagesInputToolbar.m)。
  • 关键语义:该属性只决定"左右两个按钮中哪个是发送按钮/哪个是附件按钮",从而决定触发哪个回调——并不会物理移动按钮位置(头文件注释明确说明)。你仍需要自己把按钮放到对应的一侧。
  • 回调分派逻辑在 JSQMessagesViewController.m:按下左侧按钮时,若sendButtonLocation == JSQMessagesInputSendButtonLocationLeft则触发didPressSendButton:withMessageText:senderId:senderDisplayName:date:,否则触发didPressAccessoryButton:;右侧按钮同理。
  • 当输入框有文本时,发送按钮的启用/禁用也依据sendButtonLocation自动更新(JSQMessagesInputToolbar.m),由enablesSendButtonAutomatically(默认YES)控制;若关闭自动管理,需自行控制按钮可用状态。

实战建议:做 RTL(从右到左)语言适配时,按 FAQ 的做法把发送按钮放到左侧并同步把sendButtonLocation设为Left,即可保证点击回调仍被正确路由到didPressSendButton:。若当前仓库版本不支持sendButtonOnRight,请改用self.inputToolbar.sendButtonLocation = JSQMessagesInputSendButtonLocationLeft;

六、延伸阅读

  • 从零集成本库,参见 getting_started.md;版本迁移注意点参见 migration.md。
  • 头像工厂与气泡工厂:JSQMessagesAvatarImageFactory.h、JSQMessagesBubbleImageFactory.h。
  • 相关布局与委托协议:JSQMessagesCollectionViewDelegateFlowLayout.h、JSQMessagesCollectionViewDataSource.h。
  • 单元测试覆盖了本 FAQ 涉及的关键行为,可作为行为契约参考:JSQMessagesInputToolbarTests.m(验证sendButtonLocation默认值)、JSQMessagesCollectionViewFlowLayoutTests.m(布局尺寸与失效行为)、JSQMessagesCollectionViewCellTests.m。
  • UI组件
  • 即时通讯

【免费下载链接】JSQMessagesViewController

An elegant messages UI library for iOS

项目地址:https://gitcode.com/gh_mirrors/js/JSQMessagesViewController
点击查看免费下载

相关推荐

上一篇:GitHub_Trending/agen/agentkit安全审计报告:第三方机构验证的98%安全评分
下一篇:从零跑通 WrenAI:用自然语言问数,15 分钟搭好你的 AI 取数助手

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询