Featured image of post TTGSnackbar 2.0.0:从简单提示条到现代消息反馈组件

TTGSnackbar 2.0.0:从简单提示条到现代消息反馈组件

前言

TTGSnackbar 最早只是我用 Swift 写的一个 Android Snackbar 风格提示条:显示一段文案,带一个 Action,几秒后自动消失。

这次整理到 2.0.0,目标变成了一个更完整的 iOS 消息反馈组件:不仅能显示顶部/底部消息,还要支持语义样式、队列管理、自定义 View、Swift concurrency、无障碍、触觉反馈和现代 Apple 平台要求。

TTGSnackbar Poster

Github: https://github.com/zekunyan/TTGSnackbar

更新

这次 2.0.0 可以理解成几个方向的补齐:

  • 平台升级:当前开发目标为 Swift 5.9、Xcode 15+、iOS 16.0+。
  • 语义样式:内置 .default.info.success.warning.error.loading
  • 配置对象:新增 TTGSnackbarConfiguration,更适合现代 Swift 调用。
  • Swift Concurrency:可以 await 用户点击、滑动、dismiss、展示失败等结果。
  • 队列管理TTGSnackbarManager 支持排队、替换当前、相同消息去重。
  • 体验细节:Dynamic Type、VoiceOver、Reduce Motion、haptics、生命周期暂停/恢复计时器。
  • 自定义能力:支持自定义内容 View,也可以放到自定义容器里展示。

Architecture

快速使用

最简单的用法还是没有变:

import TTGSnackbar

let snackbar = TTGSnackbar(message: "TTGSnackbar!", duration: .short)
snackbar.show()

Simple snackbar

如果消息需要一个恢复入口,比如删除文件后的 Undo:

let snackbar = TTGSnackbar(
    message: "File deleted",
    duration: .middle,
    actionText: "Undo"
) { snackbar in
    restoreFile()
    snackbar.dismiss()
}

snackbar.show()

Action snackbar

这个 API 继续保留,是为了兼容老项目,也因为它确实足够直接。对一个提示控件来说,简单场景不应该被复杂 API 绑架。

语义样式

以前做不同状态的提示,通常是自己设置背景色、文字色、icon。写多了之后就会发现,业务里其实反复出现的是几类固定语义:成功、警告、错误、加载中。

所以新版加了 TTGSnackbarStyle

let snackbar = TTGSnackbar(message: "Saved successfully", duration: .middle)
snackbar.style = .success
snackbar.show()

Semantic snackbar

加载中的场景也可以直接用 .loading,配合 .forever 手动结束:

let snackbar = TTGSnackbar(message: "Uploading...", duration: .forever)
snackbar.style = .loading
snackbar.actionText = "Cancel"
snackbar.actionBlock = { snackbar in
    cancelUpload()
    snackbar.dismiss()
}
snackbar.show()

Loading snackbar

Configuration API

属性式配置比较直观,但是当参数多起来以后,会出现“创建对象、设置一堆属性、最后 show”的散落代码。2.0.0 里增加了 TTGSnackbarConfiguration,把一次展示需要的信息收拢到一个 value object:

let snackbar = TTGSnackbar(configuration: .init(
    message: "File deleted",
    duration: .long,
    style: .warning,
    actionText: "Undo",
    actionBlock: { snackbar in
        undoDelete()
        snackbar.dismiss()
    }
))

snackbar.show()

这不是为了替代旧 API,而是给复杂一点的调用场景一个更稳定的入口。比如产品里做统一消息中心、错误处理器、网络状态提示时,用 configuration 传递会清楚很多。

async / await

Snackbar 其实是一个临时 UI,但它经常和业务决策有关:用户点了 Undo 还是直接忽略?展示失败要不要降级?被队列丢弃算不算完成?

新版可以直接等待结果:

let result = await TTGSnackbar.show(configuration: .init(
    message: "File deleted",
    duration: .long,
    style: .warning,
    actionText: "Undo"
))

switch result {
case .action:
    undoDelete()
case .dismissed:
    break
case .dropped:
    print("A manager policy dropped the snackbar")
case .failedToPresent:
    print("No window or container was available")
default:
    break
}

这块实现上用的是 continuation,把 action、second action、tap、swipe、dismiss、manager drop、presentation failure 都统一成 TTGSnackbarResult。调用方不用再在几个 block 之间跳来跳去,读起来会顺很多。

自定义内容

有些提示不只是“一行文字 + 一个按钮”,比如同步状态、表单错误、网络诊断,就需要自己塞一个 View 进去:

let contentView = UIStackView()
contentView.axis = .vertical
contentView.spacing = 4
contentView.isLayoutMarginsRelativeArrangement = true
contentView.layoutMargins = UIEdgeInsets(top: 16, left: 18, bottom: 16, right: 18)

let titleLabel = UILabel()
titleLabel.text = "Custom content view"
contentView.addArrangedSubview(titleLabel)

let snackbar = TTGSnackbar(customContentView: contentView, duration: .forever)
snackbar.shouldActivateLeftAndRightMarginOnCustomContentView = true
snackbar.show()

Custom snackbar

这个设计里,库继续负责安全区域、展示位置、动画和 dismiss,内容内部怎么布局交给业务。边界要分清,不然控件会越来越像一个小页面 =。=

总结

TTGSnackbar 2.0.0 最大的变化不是“样式变好看了”,而是从一个简单提示条,补成了一个更适合现代 iOS 项目的反馈组件。

简单场景仍然可以一行 show();复杂场景可以用 configuration、manager 和 async result 做统一管理。这个平衡比较重要:提示控件要轻,但也不能在真正接到业务流程时马上露怯。