SwiftUIにGIPHY入れてGIF表示してみた
さいしょに
SwiftUIでGIPHYを使おうと公式のドキュメントを見ると、GIF画像を選択するサンプルしかなくて表示する部分を実装する必要があったので、試行錯誤して分かったことなどをまとめました。
codeまとめ。
import SwiftUI
import GiphyUISDK
struct GiphyMediaView: UIViewRepresentable {
var media: GPHMedia?
func makeUIView(context: Context) -> GPHMediaView {
let mediaView = GPHMediaView()
mediaView.media = media
return mediaView
}
func updateUIView(_ uiView: GPHMediaView, context: Context) {
uiView.media = media
}
}
import SwiftUI
import GiphyUISDK
struct GiphyPicker: UIViewControllerRepresentable {
@Binding var media: GPHMedia?
func makeUIViewController(context: UIViewControllerRepresentableContext<GiphyPicker>) -> GiphyViewController {
Giphy.configure(apiKey: "your api key")
let giphy = GiphyViewController()
giphy.swiftUIEnabled = true
giphy.delegate = context.coordinator
return giphy
}
func updateUIViewController(_ uiViewController: GiphyViewController, context: UIViewControllerRepresentableContext<GiphyPicker>) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, GiphyDelegate {
var parent: GiphyPicker
init(_ parent: GiphyPicker) {
self.parent = parent
}
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
// mediaをviewに渡す
parent.media = media
}
}
}
struct ContentView: View {
@State private var isShow = false
@State var media: GPHMedia? = nil
var body: some View {
VStack {
if let media {
GiphyMediaView(media: media) // 選択したmediaを表示
}
Button("Picker表示") {
isShow.toggle()
}
.sheet(isPresented: $isShow) {
GiphyPicker(media: $media)
}
}
}
}
GIPHY SDKのインストール
-
SDKリポジトリ
ドキュメントやExampleなどはこのリポジトリで管理されてます。
自分はCocoaPodsを使ってるので、CocoaPodsでのやり方を書いておきます。
といってもGiphy
を追加してpod install
するだけです。
pod 'Giphy'
Swift Package Managerなども使えるので詳しくはこちらを参照してください。
別途やること
使うにあたってAPIキーが必要になるので、
GIPHY Developersからアカウントを作成しAPIキーを取得しておいてください。アカウントを作成して、アプリの情報を入力して作るとAPIキーを取得できるようになります。
公式サンプルでPicker実装
リポジトリの画像から拝借
GIFを選択するシートのビューの実装はこの部分に記載されてるので、必要な箇所だけ実装していきます。
import SwiftUI
import GiphyUISDK // ライブラリ読み込み
struct GiphyPicker: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GiphyPicker>) -> GiphyViewController {
Giphy.configure(apiKey:"") // APIキーを入れる
let giphy = GiphyViewController() // GiphyPickerのViewControllerを生成
giphy.swiftUIEnabled = true
return giphy
}
func updateUIViewController(_ uiViewController: GiphyViewController, context: UIViewControllerRepresentableContext<GiphyPicker>) {
}
}
GiphyPickerをButtonを押下した時にsheetで表示できるようにします。
struct ContentView: View {
@State private var isShow = false
var body: some View {
Button("Picker表示") {
isShow.toggle()
}
.sheet(isPresented: $isShow) {
GiphyPicker()
}
}
}
apiKey
と書いてある部分に自分のAPIキーを入れると選択ビューが表示されます。
サンプルはここまでしかなくGIFを選択した際のGIFデータを取得できないので、ここからはワイが実装した部分を書いていきます。
Delegateメソッドで選択したGIFを取得する
SDKではGIFデータをmedia(GPHMedia)に格納していて、GIF表示ビュー(GPHMediaView)にもこのmediaを使います。
そのため、まずはCoordinatorの実装をしてGiphyDelegate
に用意されているdelegateメソッドのdidSelectMedia
で、選択したmediaを取得できるようにします。
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, GiphyDelegate {
var parent: GiphyPicker
init(_ parent: GiphyPicker) {
self.parent = parent
}
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
print(media) // 選択したmediaをデバッグコンソールに表示
}
}
ここらへんの書き方は、大体どのUIViewControllerRepresentableでも同じな気がします。というかワイは「まぁこういうものだよね」ってぐらいの雑な理解しかしてません。
クラスとmakeCoordinator関数が作成できたら、GiphyViewControllerのdelegateに作ったCoordinatorを割り当てていきます。
func makeUIViewController(context: UIViewControllerRepresentableContext<GiphyPicker>) -> GiphyViewController {
// ... 省略
giphy.delegate = context.coordinator // 作成したcoordinatorクラスを割り当て
return giphy
}
これで、Picker内部でGIFをタップするとdidSelectMedia
が発火し、選択したmediaがデバッグメッセージに表示されます。
Bindingで選択したGIFを取得できるようにする
まだ、今の段階だとタップしたmedia情報が取得できるだけなので、取得したmediaをSwiftUI側で使えるようにプロパティを設定していきます。
今回はUIViewControllerRepresentableにmediaプロパティを設定しました。
また、型はGPHMedia
を指定し、選択してない状態があるためoptionalで定義しnilでも使えるようにしました。
Delegateメソッドからプロパティを直接呼び出せないので、先ほどGiphyPickerのプロパティを定義していたparentからparent.mediaでアクセスしmediaを渡します。
struct GiphyPicker: UIViewControllerRepresentable {
@Binding var media: GPHMedia? // Bindingプロパティの設定
// ... 省略
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
// bindingにmediaを渡す
parent.media = media
}
}
}
そして、GiphyPickerを呼び出す側でState定義したmediaを渡します。
@State var media: GPHMedia? = nil
// ... 省略
GiphyPicker(media: $media)
これで、PickerでGIFを選択するとmediaを取得できるようになりました。選択すると@State
のmediaに値が入りView側で扱えるようになります。
まだ表示部分を作ってないので、表示ビューを作成していきますよい。
UIViewRepresentableでGifの表示ビュー実装
表示ビューはGPHMediaViewを使います。
SwiftUIだとそのままでは使えないため、UIViewRepresentableで使える形にラップしていきます。
ラップして表示してるだけなので一気に全部実装していきます
プロパティに同じようにmedia(GPHMedia)を設定し、makeUIViewでGPHMediaViewを生成して選択したmediaを渡して表示させます。
import SwiftUI
import GiphyUISDK
struct GiphyMediaView: UIViewRepresentable {
var media: GPHMedia?
func makeUIView(context: Context) -> GPHMediaView {
let mediaView = GPHMediaView()
mediaView.media = media // mediaViewにmediaデータを設定して表示する
return mediaView
}
func updateUIView(_ uiView: GPHMediaView, context: Context) {
uiView.media = media // 違うGIFデータが来た時にアップデートする
}
}
最後に、選択したmediaを表示できるように呼び出してみます。
struct ContentView: View {
@State private var isShow = false
@State var media: GPHMedia? = nil
var body: some View {
VStack {
if let media {
GiphyMediaView(media: media) // 選択したmediaを表示
}
Button("Picker表示") {
isShow.toggle()
}
.sheet(isPresented: $isShow) {
GiphyPicker(media: $media)
}
}
}
}
完成です。
終わりに
案外GIF扱うの楽で助かりました、やっぱりAPIは楽にアプリの機能を拡張できるので使うの楽しいですね。
初めて、一からUIViewControllerRepresentableやUIViewRepresentableを使って書いてみましたが案外理解が深まったのと意外と簡単に作れたので今後UIKit使うのもいけそうです。
最後に使ったパラメータや、GIPHYのIDからmediaを取得する部分などを書いて終わりにします。
使ったパラメータ
GIPHYのGPHMediaView
、GiphyViewContoller
でワイが使ったパラメータの説明などを軽くしておきます。まだあんまり分かってないので理解したらいつか書きます多分
GPHMediaViewで使ったもの
GPHMediaViewではそんなに設定することはなかったです。
アスペクト比がおかしい場合はcontentModeの設定などできます。
let mediaView = GPHMediaView()
mediaView.media = media
mediaView.contentMode = .scaleAspectFit
return mediaView
ただ、アスペクト比を保つよりも、実装時MediaViewのサイズをSwiftUI側でframe使って変えても画像自体のサイズが変わらずはみ出てしまうなどサイズ調整の方につまづきました。結局解決できてなくて課題です
View側にfixedSize()を指定することで、一応サイズを変更できたので一旦良しとしています。
GiphyMediaView(media: media)
.fixedSize()
.frame(minHeight: 100, maxHeight: 300)
ワイはfixedSizeを設定して、高さを最小100, 最大300設定していい感じに表示できるよう調整してました。(うまくいってるのかは謎です)
注意点
- 幅が固定になることとwidthを使うとうまくいきません。
ここら辺は何でかよく分からないので諦めました。
GiphyViewControllerで使ったもの
-
GiphyViewController.trayHeightMultipiler
: snap pointの高さ
多分GIFを表示している部分の高さが変わるのかな(デフォルトは0.7) -
shouldLocalizeSearch
: スマホの設定言語で検索をするかしないかのフラグ。(デフォルトはfalseで英語) -
theme
: GPHThemeを入れてPickerのデザインを変えられます。Typeを指定することでデザインが変わります。指定できるタイプには.automatic
,.dark
,.lightBlur
,.darkBlur
があります。また、カスタムのデザインも作れるみたいです。 -
mediaTypeConfig
: シートのタブに表示する画像を表示できます。配列でタイプを指定でき、指定できるタイプは以下です。- recents: 最近選択したもの
- gifs
- clips: videoみたいなの
- stickers
- text
- emoji
設定するとこんな感じです。
GiphyViewController.trayHeightMultipiler = 0.7
let giphy = GiphyViewController()
giphy.swiftUIEnabled = true
giphy.shouldLocalizeSearch = true
giphy.mediaTypeConfig = [.gifs, .stickers, .recents]
giphy.theme = GPHTheme(type: .lightBlur)
return giphy
他にもあるみたいですが、ドキュメントになかったり読んでも分からんので一旦このくらいで。
IDからmediaを取得
公式にある実装ではIDからmediaが取得できるようになると、データを送る際もIDを送るだけで表示でき便利そうでした。
そのため、書いてあった部分を勉強ついでにasync/await
で書き換えて実装してみました。
import GiphyUISDK
func getMedia(giphyId: String) async -> GPHMedia? {
await withCheckedContinuation { continuation in
guard let id = giphyId else {
continuation.resume(returning: nil)
return
}
GiphyCore.shared.gifByID(id) { response, error in
if let error {
print(error)
continuation.resume(returning: nil)
}
if let media = response?.data {
continuation.resume(returning: media)
} else {
continuation.resume(returning: nil)
}
}
}
}
await getMedia(giphyId: "abcdefghijklmn") // mediaを取得
GiphyCoreを使うにも、APIキーの指定がいるみたいなので下記を起動時に呼び出しておくなどしておいてください。
GiphyCore.shared.apiKey = "your api key"
HELP
- GIFデータのキャッシュとアプリ開発時のAPIキーの管理ベストプラクティス分かってないので、わかる方いたらコメント欄で是非に教えてください🙇
おわり。
参考になれば幸いです。
Discussion