0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SwiftUI】ChatGPTアプリのような文章生成アニメーションをサクッと実装する

Last updated at Posted at 2025-01-31

はじめに

こんにちは。
最近ChatGPTのiOSアプリをよく使用しているのですが、文章生成アニメーションと生成開始前後に発生する触覚フィードバック(振動)が心地よく個人的に気に入っています。
このUIってどうやって実装されているのかなと思い色々調べて実装してみたのですが、非常に簡単に実装できたので今回こちらを共有したいと思います!

開発環境は以下の通りです。

  • Swift 5.10
  • Xcode 16.1

実装

今回は以下のような仕様のアプリを実装します。

  • ボタンをタップすると、文章がアニメーションで表示される
  • ボタンのタップ直後に触覚フィードバック(振動)が発生する
  • 文章の表示が完了した直後に触覚フィードバック(振動)が発生する

chatgpt-like_demo.gif

コード全体

まずはコード全体を以下に示します。

import SwiftUI
import Combine

struct ContentView: View {
    let fullText = "こんにちは、これはタイピングアニメーションのテストです。"
    @State private var displayedText = ""
    @State private var index = 0
    @State private var cancellable: AnyCancellable?

    var body: some View {
        VStack(spacing: 20) {
            Text(displayedText)
                .font(.title)
                .fontWeight(.bold)

            Button("スタート") {
                startTypingAnimation()
            }
            .disabled(cancellable != nil) // 途中でボタンを押せないようにする
        }
        .padding()
    }

    private func startTypingAnimation() {
        displayedText = ""
        index = 0
        cancellable?.cancel() // 既存の購読を解除

        // エフェクト開始時の触覚フィードバック
        let generator = UIImpactFeedbackGenerator(style: .light)
        generator.impactOccurred()

        cancellable = Timer.publish(every: 0.05, on: .current, in: .common)
            .autoconnect()
            .sink { _ in
                if index < fullText.count {
                    let nextIndex = fullText.index(fullText.startIndex, offsetBy: index)
                    displayedText.append(fullText[nextIndex])
                    index += 1
                } else {
                    cancellable?.cancel() // タイピングが完了したら購読を解除
                    cancellable = nil

                    // エフェクト終了時の触覚フィードバック
                    let generator = UINotificationFeedbackGenerator()
                    generator.notificationOccurred(.success)
                }
            }
    }
}

以下では、このコードの詳細について、文章性アニメーションと触覚フィードバックに分けて説明します。

文章生成アニメーション

こちらのアニメーションですが、一般的にTyping EffectやTypewriter Effectと呼ばれるようです。

まずはじめにボタンをタップしたときの処理は以下のようになります。

Button("スタート") {
    startTypingAnimation()
}

ここでは後述するstartTypingAnimationメソッドを呼び出しています。

では次にstartTypingAnimationメソッドを実装します。

private func startTypingAnimation() {
    displayedText = ""
    index = 0
    cancellable?.cancel() // 既存の購読を解除

    // エフェクト開始時の触覚フィードバック
    // ...

    cancellable = Timer.publish(every: 0.05, on: .current, in: .common)
        .autoconnect()
        .sink { _ in
            if index < fullText.count {
                let nextIndex = fullText.index(fullText.startIndex, offsetBy: index)
                displayedText.append(fullText[nextIndex])
                index += 1
            } else {
                cancellable?.cancel() // タイピングが完了したら購読を解除
                cancellable = nil

                // エフェクト終了時の触覚フィードバック
                // ...
            }
        }
}

ここでは具体的に以下のような処理を行っています。

  1. 表示する文章と文章のインデックスをリセット、タイマーのサブスクリプションを解除
  2. Timerを使用して0.05秒ごとに文字を表示
  3. 文章の表示が完了したらタイマーのサブスクリプションを解除

またタイマー部分はCombineを使用しています。
タイマーの実装方法としては、FoundationのTimerクラスもあるのですが、公式ドキュメントによると、Combineの方が簡単に実装できるとのことなのでこちらを採用しました。

アニメーションをカスタムしたい場合は、Timerのインターバルを調整したり、TextViewに.animation()を追加することで実現できると思います。

触覚フィードバック

触覚フィードバックは英語ではHaptic Feedbackと呼ばれるようです。
HIGの触覚フィードバックページを見ると、iOSでは以下の3種類の触覚フィードバックがあり、Swiftでは各々に専用のクラスが用意されています。

触覚フィードバック クラス
Notification Feedback UINotificationFeedbackGenerator
Impact Feedback UIImpactFeedbackGenerator
Selection Feedback UISelectionFeedbackGenerator

今回は、以下のようにエフェクト開始時と終了時に触覚フィードバックを発生させます。

// エフェクト開始時の触覚フィードバック
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
// エフェクト終了時の触覚フィードバック
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)

ここでは開始時には.light、終了時には.successを指定しています。1

参考文献

  1. 当初ChatGPTアプリに似せようとしたのですが、難しかったため適当な値を設定しました。。。おそらく終了時は.successに近いのですが、開始時はプリセットのものとは異なる印象をうけたため、.lightを指定しています。触覚フィードバックはカスタムしたい場合は、Core Hapticsを使用すれば可能らしいですが、今回は省略します。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?