SwiftUIでdistortionEffectをかけてみる

はじめに

iOS17からSwiftUIのViewに対してシェーダをかけられるモディファイアがいくつか追加されました。

まだBataなので一部変更が入るかもしれませんが、MetalViewを使わずにシェーダーを入れられるのはお手軽なのでリッチなUIが作りやすくなりそうです。

distortionEffect(_:maxSampleOffset:isEnabled:)を使ってみる

さっそくdistortionEffect(_:maxSampleOffset:isEnabled:)を使ってみます。

ドキュメントを読むとMetalシェーダの部分は以下の形の関数と一致させて作成する必要があるようです、何か追加でパラメータを付与したい場合はargsの部分に追加すれば良いようです。

[[ stitchable ]] float2 name(float2 position, args...)

簡単な平滑化をする処理をMSLで作成しています、渡したsizeに応じてpositionの値を丸めています。

#include <metal_stdlib>
#include <SwiftUI/SwiftUI_Metal.h>
using namespace metal;

[[ stitchable ]] float2 pixellate(float2 position, float size) {
    float2 pixellatedPosition = round(position / size) * size;
    return pixellatedPosition;
}

SwiftUI側の処理はこんな感じ

struct ContentView: View {
    @State private var pixellate: CGFloat = 1

    let startDate: Date = .init()
    var body: some View {
        VStack {
            Text("Hello World🐈🐒")
                .font(.largeTitle.bold())
                .distortionEffect(.init(function: .init(library: .default, name: "pixellate"), arguments: [.float(pixellate)]), maxSampleOffset: .zero)

            Slider(value: $pixellate, in: 1...10)
        }
        .padding()
    }
}

https://youtube.com/shorts/GGhK0YlTMCY?feature=share

時間やsin関数を使えばループで動いてくれるのでいい感じのシェーダが作れればリッチなアニメーションをアプリに取り込むことができそうです

struct ContentView: View {
    @State private var pixellate: CGFloat = 1
    let startDate: Date = .init()

    var body: some View {
            TimelineView(.animation) {
                let time = sin($0.date.timeIntervalSince1970 - startDate.timeIntervalSince1970) * 10
                VStack {
                Image(.sample)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(height: 200)
                Text("Hello World🐈🐒")
                    .font(.largeTitle.bold())

            }
            .distortionEffect(.init(function: .init(library: .default, name: "pixellate"), arguments: [.float(time)]), maxSampleOffset: .zero)
        }
        .padding()
    }
}

https://youtube.com/shorts/Gvi1hAU5JA4?feature=share