🦸‍♂️

SwiftUIベースのアプリでSVProgressHUD表示をするときにやること

2021/08/14に公開

はじめに

SwiftUIでSVProgressHUD表示をする場合について雑に書いておく。できるかどうかでいうと「できる」ということはわかった。もしもっといい方法があったらコメントなりでおしえてください。

どうやるか

やり方は2つある。それらに共通する準備から

準備

  • AppからAppDelegateを使えるようにする
  • AppDelegateにvar window: UIWindow?を宣言する
@main
struct SampleApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    ...
}
class AppDelegate: NSObject, UIApplicationDelegate {
    var window: UIWindow?
    ...

SVProgressHUDはこのAppDelegateからwindowを取得していろいろやってるため。

これで完了。あとはSVProgressHUD.show()を呼び出せばいい。

でも、次の図のように左上原点に表示されてしまう。

「通信中です」という表示しようとしてこうなってる。

これを解決する方法2つのうちの1つ、オフセットを決める方法から説明する。

オフセットを決める

これはおすすめはできない。
これはSVProgressHUDには中央からオフセットでずらして表示するメソッドがあるのを利用する。

SVProgressHUD.setOffsetFromCenter(UIOffset(horizontal: UIScreen.main.bounds.width/2, vertical: UIScreen.main.bounds.height/2))

おすすめできないのはアプリ中でUIScreen.mainなんて使いたくないから。なぜこれをアプリ中で使いたくないかはアプリという抽象化されたものがユーザのスクリーンという具体的なデバイスのことを知ってりゃ変化に弱いからだ。たとえばiPadアプリでスプリットビューとして1画面に2つ表示されるような場合、あなたのアプリの縦横サイズはデバイスの縦横サイズとは関係ないわけ。iPadじゃないからいいって割り切りも必要だけどスマートではない。iPhoneでスプリットビューになったときに困ればいいけども。

しかも、そもそもこのメソッドは中央からずらすためのメソッドであって、中央を(0, 0)にされてるのをどうにかするメソッドじゃないのも気持ち悪い。

なのでこの方法はステイ。

AppDelegateのwindowを作成しておく

もう一つの方法として事前にWindowを作成してサイズを設定しておくという手もある。UIKitならAppDelegate.windowに適切にサイズが設定されているが、SwiftUIベースで作るとなぜかサイズが(w: 0, h: 0)になっているのがそもそもの原因なんである。

ただ自前でWindowを作成するときのデメリットとして

  • 結局UIScreenを使ってサイズを決める
  • あらかじめAppDelegate.windowを作成するそもそものデメリットがよくわからない
class AppDelegate: NSObject, UIApplicationDelegate {
    var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
    ...
}

この方法もステイしたいが、この方法を別の方法に置き換えるとき目立ってわかりやすいし、まあ良いかなとも思う。

そもそもSVProgressHUD使うからだめなのかも。Swift Package Manager使えないし別のを使ったほうがいいかも。

そして、SVProgressHUDを使ってなかったときに自前でHUDを使ってたときの事も書いておく。

(おまけ)自前でHUD表示したいとき

SwiftUIメインで画面を構成してる場合にどうやって自前でHUD表示するかについて

結論

  • アプリのRootViewでHUD用の出し分けViewを用意する
  • disableにするフラグもつなげる

どうやるか

@main
struct SampleApp: App {
    var body: some Scene {
        WindowGroup { 
	    AppRootView(/* ... */) // ここで適切にDIする
	}
    }
}

struct AppRootView: View {
    // rootViewStoreをなんかしらの方法で持つ
    ...
    var body: some View {
	ZStack {
	    // Main, Login, RootHUDView の表示を切り替える
	    ...
	}
        .disable(rootViewStore.coreA.flag ?? false)
        .disable(rootViewStore.coreB.flag ?? false)
    }
}

struct RootHUDView {
    ZStack {
        // 通信中HUD表示、削除中HUD表示
    }
}

おわりに

他にいい方法があったら教えて下さい。

Discussion