Bear:タイポグラフィファーストのライティング
Bearのタイポグラフィファーストデザインがいかにしてアップルデザインアワードを受賞したか:ネストタグ、テーマシステム、フォーカスモード、インラインMarkdown。Swift実装パターン付き。
Bear:タイポグラフィファーストのライティング
「Bearを使えば、再びAppleコンピュータを使っているような感覚になります。スピナーもスケルトンローディング画面もトーストメッセージもありません。あるのはスムーズなアニメーションと、常にアクションを待ち受けるコンテンツだけです。」
Bearは集中を妨げないデザインのマスタークラスです。タイポグラフィからタグシステムまで、すべての決定が、管理ではなく思考したい書き手のために設計されています。
重要なポイント
- ローディング状態ゼロ - コンテンツは常に準備完了、同期はバックグラウンドで見えないところで行われる
- タグがフォルダを置き換える - 執筆中のインライン
#tagsは後からのフォルダ管理に勝り、ノートは複数の場所に存在できる - タイポグラフィコントロールは読者を尊重 - フォント、サイズ、行の高さ、幅のコントロールでユーザーは自分の目に最適化可能
- すべてを一度にテーマ化 - 28以上のキュレートされたテーマが細切れのカラーピッカーに勝る
- フォーカスモードは逃げ道 - 集中モードをさらに高める必要があるとき、ワンジェスチャーですべてを消せる
Bearが重要な理由
Bearは2017年のApple Design Awardと複数のEditor's Choice賞を受賞し、メモアプリがパワフルかつ美しくあり得ることを証明しました。
主な実績: - 非開発者にも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 1つのノートに複数のタグを付けられる:
│ └── 📁 Q2 #work/meetings #action-items #q1
└── 📁 Projects
単一の場所 複数の場所(仮想)
移動 = ファイル操作 タグ = 入力するだけ
ファイルブラウザに表示 サイドバー + ノート本文に表示
タグ構文:
単一タグ: #ideas
ネストタグ: #work/meetings/2025
深いネスト: #journal/2025/01/17
サイドバー表示:
├─ 📁 work
│ └─ 📁 meetings
│ └─ 📄 2025
├─ 📁 journal
│ └─ 📁 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
}
// 例:レッドグラファイト(デフォルトのライトテーマ)
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 {
// Signature accent
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をレンダリングします。分割ビューは不要です。
入力内容: 表示内容:
───────────────────────────────────────────────────────
# 見出し 見出し(大きく、太字)
**太字テキスト** 太字テキスト(スタイル適用、** は非表示)
- リスト項目 • リスト項目(箇条書きが表示)
`code` code(等幅、ハイライト表示)
[link](url) link(スタイル適用、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. タイポグラフィはUXである
フォント、サイズ、行の高さ、幅をユーザーがコントロールできるようにすることは、読書体験への敬意を示している。
4. テーマですべてを一度に設定
ユーザーに12色を選ばせるのではなく、完成されたテーマをキュレーションする。
5. 非常口としてのフォーカスモード
集中モードでもまだ気が散るとき、ワンジェスチャーですべてを消せる。
よくある質問
Bearのネストタグはどのように機能しますか?
ノートのどこかに#親/子/孫と入力するだけです。Bearは自動的にサイドバーに階層を作成します。フォルダと異なり、1つのノートに複数のタグを付けられるため、同時に複数の「場所」に存在させることができます。タグはメニューを操作するのではなく、入力することで作成されます。
Bearが個別の色設定ではなくテーマを使用するのはなぜですか?
テーマは視覚的な統一感を保証します。ユーザーが個別に色を選ぶと、コントラストが悪かったり、色調が合わない組み合わせになりがちです。Bearの28以上のキュレーションされたテーマは、すべてのUI要素で読みやすく、美的に一貫したカラースキームを保証します。
BearのMarkdownが他のエディタと異なる点は何ですか?
Bearは入力中にMarkdownをインラインでレンダリングします。
構文文字(**、#、バッククォート)は入力後に非表示になり、スタイル適用後の結果のみが表示されます。別のプレビューペインなしで、編集中に最終的な見た目を確認できます。
Bearはどのようにしてゼロローディング状態を実現しているのか?
Bearはすべてのコンテンツをローカルに保存し、キャッシュから瞬時に読み込みます。iCloud同期はバックグラウンドで行われ、スピナーや進捗インジケーターは表示されません。オフラインでBearを開いても、すべてが機能します。接続が回復すると、同期は静かに完了します。
Bearのノートを他の形式にエクスポートできますか?
BearはMarkdown、PDF、HTML、DOCX、プレーンテキストにエクスポートできます。ノートはMarkdownソースを保持しているため、データの所有権はあなたにあります。タグシステムは、形式に応じてフロントマターまたはファイル構造としてエクスポートされます。
参考リンク