Bear: 排版优先的写作
Bear的排版优先设计如何赢得Apple Design Award:嵌套标签、主题系统、专注模式和内联Markdown。包含Swift实现模式。
Bear:以排版为先的写作体验
"使用 Bear 时,你会感觉自己又在使用真正的 Apple 电脑了。没有加载动画,没有骨架屏,没有弹窗提示。只有流畅的动画和随时待命的内容。"
Bear 是无干扰设计的典范之作。从排版到标签系统,每一个决策都服务于那些想要专注思考、而非管理工具的写作者。
核心要点
- 零加载状态 - 内容始终就绪;同步在后台悄然进行
- 标签取代文件夹 - 写作时随手添加
#标签胜过事后整理文件夹,笔记还可以同时存在于多个位置 - 排版控制尊重读者 - 字体、字号、行高和宽度控制让用户为自己的眼睛优化阅读体验
- 一键切换整体主题 - 28 种以上精心策划的主题胜过零散的颜色选择器
- 专注模式作为终极方案 - 当无干扰还不够彻底时,一个手势即可移除一切
Bear 为何重要
Bear 荣获 2017 年 Apple 设计大奖及多项编辑精选奖,证明了笔记应用可以同时兼具强大功能与精美设计。
主要成就: - 让非开发者也能轻松使用 Markdown - 发明了嵌套标签,作为文件夹的灵活替代方案 - 为不同写作场景创建了 28 种以上主题 - 设计了OLED专属主题(Dieci),实现纯正黑色显示 - 零加载状态:内容始终就绪
核心设计理念
零阻力原则
Bear 消除了思想与文字之间的所有障碍:
阻力模式(其他应用) BEAR 的方式
───────────────────────────────────────────────────────────────────
创建前弹出文件夹对话框 直接开始输入
格式工具栏遮挡视野 Markdown 内联,无形融入
同步加载动画打断心流 后台同步,无任何指示器
设置分散在各个菜单中 排版选项触手可及
需要颜色选择器来高亮 主题一键切换所有样式
核心洞察:每个 UI 元素都是潜在的干扰。尽可能减少它们。
模式库
1. 无限嵌套标签
Bear 的标签系统用灵活的内联组织方式取代了僵化的文件夹层级结构。
传统文件夹 vs Bear 标签:
文件夹方式 BEAR 的标签方式
───────────────────────────────────────────────────────────────────
📁 Work 笔记包含: #work/meetings
├── 📁 Meetings 笔记包含: #work/meetings/q1
│ ├── 📁 Q1
│ │ └── standup-2025-01.md 一条笔记可以有多个标签:
│ └── 📁 Q2 #work/meetings #action-items #q1
└── 📁 Projects
单一位置 多个位置(虚拟)
移动 = 文件操作 标签 = 直接输入即可
在文件浏览器中可见 在侧边栏 + 笔记正文中可见
标签语法:
单个标签: #ideas
嵌套标签: #work/meetings/2025
深层嵌套: #journal/2025/01/17
侧边栏结果:
├─ 📁 工作
│ └─ 📁 会议
│ └─ 📄 2025
├─ 📁 日记
│ └─ 📁 2025
│ └─ 📁 01
│ └─ 📄 17
核心洞察:标签是通过内联输入的,而非从菜单中选择。书写的过程即创建组织结构的过程。
2. 排版控制系统
Bear 提供了其他笔记应用所隐藏的精细排版控制:
┌─ 排版设置 ──────────────────────────────────────────────────────────┐
│ │
│ 字体 │
│ [Avenir Next ▼] ← 系统字体 + 自定义字体 │
│ │
│ 字号 │
│ [─────●────────] 16pt │
│ │
│ 行高 │
│ [────────●─────] 1.6 │
│ │
│ 行宽 │
│ [──●───────────] 窄幅 ← 最佳阅读宽度 │
│ │
│ 段落间距 │
│ [─────●────────] 中等 │
│ │
└────────────────────────────────────────────────────────────────────┘
Swift 实现方法:
struct TypographySettings: Codable {
var fontName: String = "Avenir Next"
var fontSize: CGFloat = 16
var lineHeightMultiple: CGFloat = 1.6
var lineWidth: LineWidth = .comfortable
var paragraphSpacing: CGFloat = 12
enum LineWidth: String, Codable {
case narrow = "narrow" // 约60个字符
case comfortable = "medium" // 约75个字符
case wide = "wide" // 全宽
}
}
// 应用于编辑器
func applyTypography(_ settings: TypographySettings, to textView: UITextView) {
let style = NSMutableParagraphStyle()
style.lineHeightMultiple = settings.lineHeightMultiple
style.paragraphSpacing = settings.paragraphSpacing
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont(name: settings.fontName, size: settings.fontSize)!,
.paragraphStyle: style
]
textView.typingAttributes = attributes
}
3. 主题系统
Bear 的主题会同时影响所有元素——无需逐个挑选颜色。
主题结构:
struct BearTheme {
// 背景层级
let sidebarBackground: Color
let noteListBackground: Color
let editorBackground: Color
// 文本层次
let textPrimary: Color
let textSecondary: Color
let textMuted: Color
// 语义高亮
let tagColor: Color
let linkColor: Color
let codeBackground: Color
let headingColor: Color
// 选择和焦点
let selectionColor: Color
let cursorColor: Color
}
// 示例:Red Graphite(默认浅色主题)
let redGraphite = BearTheme(
sidebarBackground: Color(hex: "#F7F7F7"),
noteListBackground: Color(hex: "#FFFFFF"),
editorBackground: Color(hex: "#FFFFFF"),
textPrimary: Color(hex: "#333333"),
textSecondary: Color(hex: "#888888"),
textMuted: Color(hex: "#BBBBBB"),
tagColor: Color(hex: "#D14C3E"), // 标志性红色
linkColor: Color(hex: "#B44B41"),
codeBackground: Color(hex: "#F5F5F5"),
headingColor: Color(hex: "#333333"),
selectionColor: Color(hex: "#FFE4E1"),
cursorColor: Color(hex: "#D14C3E")
)
// 示例:Dieci(OLED 优化)
let dieci = BearTheme(
sidebarBackground: Color(hex: "#000000"), // 纯黑色
noteListBackground: Color(hex: "#000000"), // 纯黑色
editorBackground: Color(hex: "#000000"), // 纯黑色
textPrimary: Color(hex: "#FFFFFF"),
textSecondary: Color(hex: "#888888"),
textMuted: Color(hex: "#555555"),
tagColor: Color(hex: "#FF9500"),
linkColor: Color(hex: "#FF9500"),
codeBackground: Color(hex: "#1C1C1C"),
headingColor: Color(hex: "#FFFFFF"),
selectionColor: Color(hex: "#3A3A3C"),
cursorColor: Color(hex: "#FF9500")
)
主题分类: - 浅色主题:Red Graphite、High Contrast、Solarized Light - 深色主题:Dark Graphite、Dracula、Nord - OLED 主题:Dieci、Charcoal(纯黑色,节省电量) - 特色主题:Shibuya Jazz、Everforest(氛围定制)
4. 专注模式
Bear 的专注模式移除一切干扰,只留下文字——连光标都变得不显眼。
普通模式
┌────────┬────────────┬───────────────────────────────────────────┐
│ │ │ │
│侧边栏 │ 笔记列表 │ 编辑器 │
│ │ │ │
│ #work │ 会议 │ # 会议笔记 │
│ #ideas │ 想法 │ │
│ #books │ 草稿 │ 今天我们讨论了... │
│ │ │ │
└────────┴────────────┴───────────────────────────────────────────┘
专注模式(键盘快捷键或滑动)
┌─────────────────────────────────────────────────────────────────┐
│ │
│ │
│ # 会议笔记 │
│ │
│ 今天我们讨论了... │
│ │
│ │
│ │
└─────────────────────────────────────────────────────────────────┘
一切消失。只剩文字。
实现原则: - 单一手势或快捷键触发 - 动画快速响应(无冗长过渡效果) - 光标闪烁柔和,不会分散注意力 - 边距为文本周围提供呼吸空间
5. TagCons(可视化标签图标)
Bear 会自动为常用标签分配图标,使侧边栏一目了然。
带有 TagCons 的侧边栏:
├─ 💡 ideas
├─ 📚 books
├─ ✏️ writing
├─ 📝 journal
├─ 🏃 fitness
├─ 💼 work
│ ├─ 📅 meetings
│ └─ 📋 projects
└─ 🎯 goals
图标分配逻辑:
enum TagConCategory {
static let mappings: [String: String] = [
"ideas": "💡",
"books": "📚",
"reading": "📖",
"writing": "✏️",
"journal": "📝",
"diary": "📓",
"work": "💼",
"meetings": "📅",
"projects": "📋",
"goals": "🎯",
"fitness": "🏃",
"health": "❤️",
"recipes": "🍳",
"travel": "✈️",
"music": "🎵",
"code": "💻",
]
static func icon(for tag: String) -> String? {
let normalized = tag.lowercased()
return mappings[normalized]
}
}
核心要点:图标自动分配但可自定义。智能默认值减少设置摩擦。
视觉设计系统
调色板(红色石墨主题)
extension Color {
// 标志性强调色
static let bearRed = Color(hex: "#D14C3E")
// 背景
static let sidebarBg = Color(hex: "#F7F7F7")
static let editorBg = Color(hex: "#FFFFFF")
// 文本
static let textPrimary = Color(hex: "#333333")
static let textSecondary = Color(hex: "#888888")
// 代码块
static let codeBg = Color(hex: "#F5F5F5")
static let codeText = Color(hex: "#333333")
}
排版
struct BearTypography {
// 编辑器字体
static let bodyFont = Font.custom("Avenir Next", size: 16)
static let headingFont = Font.custom("Avenir Next", size: 24).weight(.semibold)
static let monoFont = Font.custom("SF Mono", size: 14)
// 行高
static let bodyLineHeight: CGFloat = 1.6
static let headingLineHeight: CGFloat = 1.3
// 最佳阅读宽度
static let maxLineWidth: CGFloat = 680 // 约75个字符
}
动画设计理念
无加载状态
Bear 的核心动画原则:内容始终处于就绪状态。
// 反模式:加载指示器
struct LoadingNote: View {
var body: some View {
ProgressView() // Bear 从不这样做
}
}
// Bear 的方式:乐观更新,即时响应
struct NoteEditor: View {
@State private var note: Note
var body: some View {
TextEditor(text: $note.content)
.onAppear {
// 内容已从本地缓存中获取
// 同步在后台无感进行
}
}
}
流畅的面板过渡
// 侧边栏折叠/展开
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
sidebarVisible.toggle()
}
// 专注模式过渡
withAnimation(.easeInOut(duration: 0.2)) {
focusMode = true
}
Markdown 体验
实时预览(内联样式)
Bear 在您输入时即时渲染 Markdown,无需分屏视图。
您输入的内容: 您看到的效果:
───────────────────────────────────────────────────────
# Heading 标题(大号,粗体)
**粗体文本** 粗体文本(带样式,** 隐藏)
- list item • list item(显示项目符号)
`code` code(等宽字体,高亮显示)
[link](url) 链接(带样式,URL 隐藏)
实现思路:
class MarkdownTextStorage: NSTextStorage {
private var backingStore = NSMutableAttributedString()
override func replaceCharacters(in range: NSRange, with str: String) {
beginEditing()
backingStore.replaceCharacters(in: range, with: str)
edited(.editedCharacters, range: range, changeInLength: str.count - range.length)
endEditing()
}
override func processEditing() {
super.processEditing()
applyMarkdownStyling()
}
private func applyMarkdownStyling() {
// 根据 Markdown 模式应用样式
// 隐藏语法字符(**、`、# 等)
// 在保留纯文本源的同时进行内联渲染
}
}
对我们工作的启示
1. 零加载状态
如果内容存在于本地,就立即显示。同步在后台进行。
2. 标签 > 文件夹
写作时内联添加标签比事后整理文件夹更快。
3. 排版即用户体验
让用户控制字体、大小、行高和宽度,体现了对阅读体验的尊重。
4. 一次性主题化所有元素
不要让用户挑选 12 种颜色,而是提供精心策划的完整主题。
5. 专注模式作为终极方案
当无干扰模式还不够无干扰时,一个手势就能移除所有界面元素。
常见问题
Bear 的嵌套标签是如何工作的?
在笔记中的任意位置输入 #parent/child/grandchild,Bear 会自动在侧边栏中创建层级结构。与文件夹不同,一条笔记可以有多个标签,同时出现在多个"位置"。标签通过输入来创建,而非通过菜单导航。
为什么 Bear 使用主题而不是单独的颜色设置?
主题确保视觉的一致性。当用户单独选择颜色时,往往会创建出对比度差或色调冲突的组合。Bear 提供的 28+ 款精心策划的主题,保证了所有 UI 元素具有可读性和美学上的一致性。
Bear 的 Markdown 与其他编辑器有何不同?
Bear 在您输入时实时渲染 Markdown。
语法字符(**、#、反引号)在输入后会自动隐藏,只显示样式化的结果。你在编辑时就能看到最终效果,无需单独的预览面板。
Bear 如何实现零加载状态?
Bear 将所有内容存储在本地,并从缓存即时加载。iCloud 同步在后台进行,没有转圈或进度指示器。即使离线打开 Bear,一切都能正常使用。连接恢复后,同步会静默完成。
我可以将 Bear 笔记导出为其他格式吗?
Bear 支持导出为 Markdown、PDF、HTML、DOCX 和纯文本格式。笔记保留其 Markdown 源文件,因此你拥有数据的所有权。标签系统会根据格式导出为 frontmatter 或文件结构。
参考链接