KINTO Tech Blog
iOS

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

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

この記事は 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 にラップしたパラメータを用意します。
onAppearonChangeによって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

関連記事 | Related Posts

We are hiring!

【UI/UXデザイナー】クリエイティブ室/東京・大阪・福岡

クリエイティブGについてKINTOやトヨタが抱えている課題やサービスの状況に応じて、色々なプロジェクトが発生しそれにクリエイティブ力で応えるグループです。所属しているメンバーはそれぞれ異なる技術や経験を持っているので、クリエイティブの側面からサービスの改善案を出し、周りを巻き込みながらプロジェクトを進めています。

【フロントエンドエンジニア(リードクラス)】KINTO中古車開発G/東京

KINTO開発部KINTO中古車開発グループについて◉KINTO開発部 :58名 - KINTOバックエンドG:17名 - KINTO開発推進G:8名 - KINTOフロントエンドG:19名 - KINTOプロダクトマネジメントG:5名  - KINTO中古車開発G:19名★  ←こちらの配属になります。

イベント情報