6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

【SwiftUI】滑らかに変化するテキストアニメーションを作る方法

Last updated at Posted at 2023-06-26

はじめに

テキストを滑らかに変化させるアニメーションを作る方法を紹介します。

作るもの

このようなテキストのアニメーションを作ります。

やり方

コード

import SwiftUI

struct AnimatedText: View {
    let text: String

    // テキストの各文字と、その文字のIDを格納します。
    @State private var characters: [(character: String.Element, id: String)] = []

    // matchedGeometryEffectを用いたアニメーションを行うために必要な変数です。
    @Namespace private var namespace

    var body: some View {
        HStack(spacing: 0) {
            // charactersをForEachで回します。
            ForEach(characters, id: \.id) { character, id in
                // Textを表示します。matchedGeometryEffectのidに、charactersのidを指定します。
                Text(String(character))
                    .frame(height: .zero)
                    .matchedGeometryEffect(id: id, in: namespace)
            }
        }
        .animation(.easeIn(duration: 0.3), value: characters.map(\.character)) // アニメーションを設定します。
        .onAppear {
            setCharacters(text: text)
        }
        .onChange(of: text) { newValue in
            setCharacters(text: newValue)
        }
    }

    private func setCharacters(text: String) {
        // 文字列を文字に分割します。

        var oldCharacters = characters
        var newCharacters: [(character: String.Element, id: String)] = []
        
        for character in text {
            if let oldCharacter = oldCharacters.first(where: { $0.character == character }) {
                // characterがoldCharactersに含まれている場合は、oldCharacterのidを指定してnewCharactersに追加します。
                newCharacters.append((character: character, id: oldCharacter.id))

                // oldCharactersからoldCharacterを削除します。
                if let index = oldCharacters.firstIndex(where: { $0.character == character }) {
                    oldCharacters.remove(at: index)
                }
            } else {
                // characterがoldCharactersに含まれていない場合は、新しいidを指定してnewCharactersに追加します。
                let id = UUID().uuidString
                newCharacters.append((character: character, id: id))
            }
        }
        
        characters = newCharacters
    }
}

使い方

import SwiftUI

struct ContentView: View {
    private let texts: [String] = [
        "しりとり",
        "りんご",
        "ごりら",
        "らっぱ",
        "ぱんだ",
        "だり",
    ]
    @State private var selectedIndex = 0
    
    var body: some View {
        VStack(spacing: 60) {
            AnimatedText(text: texts[selectedIndex])
                .font(.system(size: 48).bold())
                .padding()
            
            Button("Change") {
                if selectedIndex == texts.count - 1 {
                    selectedIndex = 0
                } else {
                    selectedIndex += 1
                }
            }
            .buttonStyle(.bordered)
        }
    }
}

まとめ

matchedGeometryEffectを用いることで、滑らかに変化するViewを作ることができます。今回は、文字列を文字に分割して、それぞれの文字にidを割り当てることで、テキストの滑らかなアニメーションを実現しました。

使用例

このテクニックを使った例です。

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?