Bear:タイポグラフィファーストのライティング

Bearのタイポグラフィファーストデザインがいかにしてアップルデザインアワードを受賞したか:ネストタグ、テーマシステム、フォーカスモード、インラインMarkdown。Swift実装パターン付き。

5 分で読める 152 語
Bear:タイポグラフィファーストのライティング screenshot

Bear:タイポグラフィファーストのライティング

「Bearを使えば、再びAppleコンピュータを使っているような感覚になります。スピナーもスケルトンローディング画面もトーストメッセージもありません。あるのはスムーズなアニメーションと、常にアクションを待ち受けるコンテンツだけです。」

Bearは集中を妨げないデザインのマスタークラスです。タイポグラフィからタグシステムまで、すべての決定が、管理ではなく思考したい書き手のために設計されています。


重要なポイント

  1. ローディング状態ゼロ - コンテンツは常に準備完了、同期はバックグラウンドで見えないところで行われる
  2. タグがフォルダを置き換える - 執筆中のインライン#tagsは後からのフォルダ管理に勝り、ノートは複数の場所に存在できる
  3. タイポグラフィコントロールは読者を尊重 - フォント、サイズ、行の高さ、幅のコントロールでユーザーは自分の目に最適化可能
  4. すべてを一度にテーマ化 - 28以上のキュレートされたテーマが細切れのカラーピッカーに勝る
  5. フォーカスモードは逃げ道 - 集中モードをさらに高める必要があるとき、ワンジェスチャーですべてを消せる

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ソースを保持しているため、データの所有権はあなたにあります。タグシステムは、形式に応じてフロントマターまたはファイル構造としてエクスポートされます。