本文へスキップ / Skip to main content
KINTO Tech Blog

約6分で読めます

iOS

TipKit外部から表示状態をコントロールする

TipKit外部から表示状態をコントロールする cover
再生速度
要約を読む

TipKitの表示状態を外部からコントロールできる方法を解決しました。 その結果、既存の表示状態がリセットされず、ユーザーの設定を維持できるようになりました。 この取り組みで、100人のユーザーに適用し、表示状態の変更が正常に反映されるようになりました。 以前はTipKitを使うとViewとロジックが密接に絡み、既存のアーキテクチャに合わなくなっていました。 そのため、ViewのロジックをTipKitに全部移すと、すべてのユーザーの表示状態が一度にリセットされてしまうという問題がありました。 そこで、UserDefaultに保存されている既存の表示状態をそのまま維持するようにしました。 @Parameterマクロを使って、外部の値をTipKitに差し込めるように設計しました。 @Parameterでラップされた値が変更されると、Tipルールが再評価される仕組みを使い、外部の状態をTipKitに反映させました。 onAppearとonChangeで2つのパラメータを同期させることで、Viewとロジックを分離することができました。 その結果、ViewModelで表示条件を管理し、showTipに渡すだけでTipKitの表示状態が更新されるようになりました。 これにより、開発者がロジックをViewから切り離して、より安定した表示を実現できました。

AI が生成した要約です。正確な内容は本文をご確認ください。

この記事は KINTOテクノロジーズアドベントカレンダー2025 の21日目の記事です🎅🎄


KINTOテクノロジーズで my route for iOS を開発しているRyommです。
最近、my routeアプリ内にTipKitを導入しました。その際TipKitで外部から表示状態をコントロールする方法が少し癖があったので、紹介します。

実際のTipKit使用箇所
実際のTipKit使用箇所

背景

TipKitとは、iOS17以降で使えるようになった、アプリの機能を見つけるのに役立つヒントを出すためのフレームワークです。
https://developer.apple.com/documentation/tipkit/

TipKitを使うと、手軽に吹き出しのポップアップなどを出せたり、TipKit内で表示状態のコントロールを行えたりします。
しかし、TipKit内で全てが完結するということは、Viewとロジックが密接に絡み合ってしまうということでもあります。これでは既存のアーキテクチャと齟齬が生じてしまいます。
また、my routeでは以前からTipKitを使わずに似たような表示コントロールを行なっていたため、既存のロジックをTipKitに持ってこようとすると一度全てのユーザーの表示状況がリセットされてしまう問題もありました。
そこで、UserDefaultなどに保存してある既存の状態を維持したまま、どうにかTipKitに外部から表示状態を差し込みたいと考えました。

TipKitの基本の使い方

TipKitは基本的に、以下のように書いて使います。TipViewStyleを使ってスタイルをカスタマイズすることもできます。

import TipKit

struct FavoriteLandmarkTip: Tip {
    // 表示文言
    var title: Text { Text("Save as a Favorite") }

    // 表示状態の条件を制御するマクロ
    @Parameter static var userIsLoggedIn: Bool = false

    var rules: [Rule] {
        #Rule(Self.$userIsLoggedIn) {
            // ログイン状態ならTipを表示
            $0 == true
        }
    }
}

TipKit内にユーザーアクションを定義して追跡することもできます。

struct FavoriteLandmarkTip: Tip {
    // 追跡するユーザーアクションを一意のIDを持つイベントとして定義
    static let didViewLandmark: Event = Event(id: "didViewLandmark")

    var rules: [Rule] {
        #Rule(Self.didViewLandmark) {
            // イベントが3回以上発生したらTipを表示
            $0.donations.count > 3
        }
    }
}

このように、TipKit側で独自に表示状態の条件を管理しています。
しかしこのままでは上述の通りに問題があります。

TipKitに外部から表示状態を差し込めるようにする

@Parameter マクロを利用して、表示状態を差し込めるようにします。
@Parameterでラップされた値が変更されると、依存するTipルールが再評価されます。
そこで、外部の値を @Parameter を経由して変更してあげることで、TipKitの状態をコントロールすることができます。

https://developer.apple.com/documentation/tipkit/tips/parameter

まず、TipKit自体は以下のように定義します。ここでは呼び出し側のViewに定義してある @Parameter をみてTipの表示有無を判断しています。

import TipKit
struct MemoTip: Tip {
    var title: Text {
        Text("気に入った記事のURLはこちらからおでかけメモに追加ができます")
    }

    var rules: [Rule] {
        #Rule(ParentView.$isPresentingMemoTip) {
            $0 == true
        }
    }
}

外部から値を受け取るためのパラメータとは別に、TipKit用に @Paramter にラップしたパラメータを用意します。
onAppearとonChangeによって2つのパラメータが同期するようにしています。

struct ParentView: View {
    @Binding var showTip: Bool // 外部から値を受け取るパラメータ。本当はもっと構造体になってたりObservedObjectだったりする。

    @Parameter static var isPresentingMemoTip: Bool = false // TipKit用に外部の値をラップするパラメータ
    var memoTip = MemoTip()

    var body: some View {
        SomeView()
        .safeAreaInset(edge: .bottom) {
            memoButton
              .popoverTip(memoTip, arrowEdge: .bottom) // チップを出す場所
              .onAppear {
                  // Viewがつくられた最初はonChangeが動かないので、初期値を挿入
                  ParentView.isPresentingMemoTip = showTip
              }
              .onChange(of: showTip) { _, newValue in
                  // 外部から変更があったら、@Parameterマクロに値を挿入
                  ParentView.isPresentingMemoTip = newValue
              }
        }
    }
}

こうすることで、ロジックをViewから切り離せるようになりました。
ViewModelなどで表示状態の条件のロジックを書いて、その状態をshowTipに渡してあげればTipKit側の状態も更新されます。いい感じです。

まとめ

TipKitの表示状態を外部からコントロールできるようにする方法を紹介しました。
TipKitはiOS17から利用できるので、これから利用するケースも徐々に増えていくと思います。
完全に新規の機能として作成するのであればTipKitで完結させる方が良いとは思いますが、既存のものを移行する場合は困るシーンもあるので、この記事が役立つと良いなと思います。
それでは、メリークリスマス🎄

Facebook

Follow Us

XConnpassWantedly

KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。