LoginSignup
19
12

'onChange(of:perform:)' was deprecated in iOS 17.0となったので書き方を比較してみた

Last updated at Posted at 2023-11-17

概要

今まで書いていたonChangeが'onChange(of:perform:)' was deprecated in iOS 17.0となったので、公式ドキュメントを読んで解説を書きました。

環境

Xcode 15.0.1
以下の画像のようにプロジェクトのMinimum DeploymentのiOSが17.0以上であること

Group 10568.png


詳細

iOS17.0より前のバージョンではonChangeの元の書き方は以下のように記載されていました。
onChange(of:perform:)

onChange:deprecated
struct MyScene: Scene {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var cache = DataCache()


    var body: some Scene {
        WindowGroup {
            MyRootView()
        }
        .onChange(of: scenePhase) { newScenePhase in
            if newScenePhase == .background {
                cache.empty()
            }
        }
    }
}

Environment値やBinding値が更新されたタイミング(上記の例の場合はscenePhaseが更新されたタイミング)でクロージャ内で定義した処理が動くという内容でした。

しかし、iOS17.0への移行に伴い、上記の書き方はdeprecatedとなってしまいました。
Xcode上では以下のように警告が表示されるようになっているはずです。

'onChange(of:perform:)' was deprecated in iOS 17.0: Use onChange with a two or zero parameter action closure instead.

iOS17.0への移行に伴い、onChangeの新しい記法が2パターンほどAppleの公式ドキュメントに追加されており、

  • onChangeのクロージャ内で受け取る引数が0個,または2個となっている点
  • onChangeの受け取る引数にinitial:Boolが追加された点
    が以前と異なります。
    それぞれの変更後の内容について以下で解説していきます。

クロージャの引数が0個のonChange(of:initial:_:)

クロージャの引数が0個のonChangeは今まで使用していたonChangeと同じような使い方ができます。
以下がAppleの公式ドキュメントに記載されているサンプルコードです。

onChange(of:initial:_:)

PlayerView
/*
func onChange<V>(
    of value: V,
    initial: Bool = false,
    _ action: @escaping () -> Void
) -> some View where V : Equatable
*/


struct PlayerView: View {
    var episode: Episode
    @State private var playState: PlayState = .paused


    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle)
            PlayButton(playState: $playState)
        }
        .onChange(of: playState) {
            model.playStateDidChange(state: playState)
        }
    }
}

上記のサンプルコードのように、クロージャの引数が0個の場合、今まで書いていたクロージャの引数を明示的に書く必要がなくなりました。

DepricatedonChangeを新しいonChangeの記法で書き直した例が以下のようになります。

PlayerView
// 古い書き方
.onChange(of: scenePhase) { newScenePhase in 
            if newScenePhase == .background {
                cache.empty()
            }
        }

// 新しい書き方
.onChange(of: scenePhase) { 
            if scenePhase == .background {
                cache.empty()
            }
        }

こう見ると、今までよりも記述量が減ってシンプルになった印象です。

クロージャの引数が2個のonChange(of:initial:_:)

クロージャの引数が2個のonChangeの場合、of内で追跡している値が変更された時に、追跡している値の変更される前の値変更された後の値の両方を使用したい時に使用できます。

以下がAppleの公式ドキュメントに記載されているサンプルコードです。

onChange(of:initial:_:)

PlayerView
/*
func onChange<V>(
    of value: V,
    initial: Bool = false,
    _ action: @escaping (V, V) -> Void
) -> some View where V : Equatable
*/


struct PlayerView: View {
    var episode: Episode
    @State private var playState: PlayState = .paused


    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle)
            PlayButton(playState: $playState)
        }
        .onChange(of: playState) { oldState, newState in
            model.playStateDidChange(from: oldState, to: newState)
        }
    }
}

こちらは変更される前の値から変更された後の値を引数として受け取る関数を使う場合や、それぞれの値を使用して比較を行う場合に使えます。
元々このような変更前の値を受け取ることはできたのですが、より簡潔に記述できるようになったという印象です。

こちらも古い書き方と新しい書き方を並べてみました。

PlayerView
// 古い書き方
struct PlayerView: View {
    var episode: Episode
    @State private var playState: PlayState = .paused


    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle)
            PlayButton(playState: $playState)
        }
        .onChange(of: playState) { [playState] newValue in
            model.playStateDidChange(from: playState, to: newValue)
        }
    }
}

// 新しい書き方
struct PlayerView: View {
    var episode: Episode
    @State private var playState: PlayState = .paused


    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle)
            PlayButton(playState: $playState)
        }
        .onChange(of: playState) { oldState, newState in
            model.playStateDidChange(from: oldState, to: newState)
        }
    }
}

以前は更新される前の値を使用するにはof:で定義した値をそのままクロージャ内で宣言しなければならなかったので、コード自体の読みやすさが増したと思います。

onChangeの受け取る引数にinitial:Boolが追加された点

上記のような記法の変更に加え、サンプルコードには記載されていませんが、引数としてinitial:Boolが追加されています。
この引数は初回起動時にonChangeが動作するかを制御する引数で、

  • falseで設定した場合にはViewの初回表示時にはonChangeが動作しない
  • trueで設定した場合には初回表示時にonChangeが動作する
    といった設定ができます。
    宣言時に引数を渡さなければデフォルト値のfalseが自動的に渡されます。

例えば、サンプルコードを以下のように修正すればPlayerViewの初回表示時にonChangeが動作するようになります。

PlayerView
// 新しい書き方
struct PlayerView: View {
    var episode: Episode
    @State private var playState: PlayState = .paused


    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle)
            PlayButton(playState: $playState)
        }
        .onChange(of: playState,initial: true) {
            model.playStateDidChange(state: playState)
        }
    }
}

struct PlayerView: View {
    var episode: Episode
    @State private var playState: PlayState = .paused


    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle)
            PlayButton(playState: $playState)
        }
        .onChange(of: playState,initial: true) { oldState, newState in
            model.playStateDidChange(from: oldState, to: newState)
        }
    }
}
19
12
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
19
12