しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

iOSのSceneやWindowなどを整理する

SceneDelegateやWindowなどについて改めて調べてみました

SceneDelegateはiPadのマルチウィンドウで使われます
具体的にはSplit ViewやSlide overなどの時に活躍します

検証のためにマルチウインドウ対応のアプリを作ってみます
変更自体は簡単でInfo.plistでEnable Multiple WindowsをYESに変える事で変更ができます

アプリを起動すると下のようにSplit Viewでアプリを表示できました

ログを見たところ、SceneDelegateのwillConnectToメソッドはアプリ起動時とSplit Viewで表示する時の2回呼ばれている事を確認できました

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        print(#function)
        guard let _ = (scene as? UIWindowScene) else { return }
    }
}

現在表示されているUIScene一覧の取得方法は下の通りです

UIApplication.shared.connectedScenes

SwiftUIではSceneDelegateは使わない事が増えています
代わりに下のようにscenePhaseを使います
AppDelegateの代わりはAppのinitなどが使われています

@main
struct MyAppApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            ContentView().onChange(of: scenePhase) { oldPhase, newPhase in
                switch newPhase {
                case .active: print("アクティブ")
                case .inactive: print("非アクティブ")
                case .background: print("バックグラウンド")
                @unknown default: break
                }
            }
        }
    }
    
    init() {
        print("App init")
    }
}

引き続きUIKitについて見ていくと、UIWindowはSceneDelegateごとに作られるものになります
UIWindowは明示的に作成しなくても自動的にセットされます

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        print(window) // → <UIWindow: 0x105313870; frame = (0 0; 1024 1366); hidden = YES; gestureRecognizers = <NSArray: 0x60000174f340>; layer = <UIWindowLayer: 0x600000c84630>>
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
    }

    func sceneWillResignActive(_ scene: UIScene) {
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
    }
}

下のようにする事で自分でUIWindowの初期化ができます

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        window = UIWindow(windowScene: windowScene)
        window?.rootViewController = TestViewController()
        window?.makeKeyAndVisible()
    }

    func sceneDidDisconnect(_ scene: UIScene) {
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
    }

    func sceneWillResignActive(_ scene: UIScene) {
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
    }
}

class TestViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        let label = UILabel()
        label.text = "TEST2"
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)

        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

UISceneではなく画面全体のサイズを取りたい時についてはUIScreenを利用します

UIScreen.main.bounds