web-dev-qa-db-ja.com

iPad iOS 13での起動時にUISplitViewControllerが正しく折りたたまれない

アプリをiOS 13に移行しています。UISplitViewControllerは、起動時にマスターではなく詳細ビューに折りたたまれます(iPadのみ)。また、まるでルートビューコントローラであるかのように、戻るボタンは表示されません。

私のアプリは、サブクラス化されたUISplitViewControllerで構成され、UISplitViewControllerDelegateに準拠しています。分割ビューには2つの子(両方UINavigationControllers)が含まれており、UITabBarController(サブクラス化されたTabViewController)に埋め込まれています

分割ビューviewDidLoadでは、デリゲートはselfに設定され、preferredDisplayMode.allVisibleに設定されます。

何らかの理由で、メソッドsplitViewController(_:collapseSecondary:onto:)が呼び出されていません。

iOS 12iPhoneおよびiPadの場合、メソッドsplitViewController(_:collapseSecondary:onto:)は、起動時にapplication(didFinishLaunchingWithOptions)およびapplicationDidBecomeActive

iOS 1iPhoneでは、splitViewController(_:collapseSecondary:onto:)sceneWillEnterForegroundの間のメソッドscene(willConnectTo session:)が起動時に正しく呼び出されます。

iOS 1iPadの場合、ただし、起動時にウィンドウの幅がコンパクトである場合、たとえば分割ビューとして作成された新しいシーンでは、splitViewController(_:collapseSecondary:onto:)メソッドはまったく呼び出されません。メソッドを呼び出すのは、ウィンドウを通常の幅に拡大してから縮小する場合のみです。

class SplitViewController: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        preferredDisplayMode = .allVisible
}

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        print("Split view controller function")
        guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
        guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
        if topAsDetailController.passedEntry == nil {
            return true
        }
        return false
    }
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        // Setup split controller
        let tabViewController = self.window!.rootViewController as! TabViewController
        let splitViewController = tabViewController.viewControllers![0] as! SplitViewController
        let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
        navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
        navigationController.topViewController!.navigationItem.leftBarButtonItem?.tintColor = UIColor(named: "Theme Colour")

        splitViewController.preferredDisplayMode = .allVisible

}
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        if #available(iOS 13.0, *) {
        } else {
            let tabViewController = self.window!.rootViewController as! TabViewController
            let splitViewController = tabViewController.viewControllers![0] as! SplitViewController
            let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
            navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
            navigationController.topViewController!.navigationItem.leftBarButtonItem?.tintColor = UIColor(named: "Theme Colour")

            splitViewController.preferredDisplayMode = .allVisible
        }

        return true
    }

このメソッドがiPhoneでは呼び出されているのに、iPadでは呼び出されていない理由がわかりません。私は新しい開発者で、これが私の最初の投稿です。コードが十分な詳細を提供していないか、正しくフォーマットされていない場合はお詫びします!

15
Belacqua2000

何らかの理由でiOS 13、特にiPadのコンパクトなtraitCollectionsで、デリゲートへの呼び出しが折りたたまれるべきかどうかを確認するために、UISplitViewControllerでviewDidLoadが呼び出される前に呼び出されるため、その呼び出しを行うときにデリゲートが設定されず、メソッド呼び出されることはありません。

プログラムでsplitViewControllerを作成している場合、これは簡単な修正ですが、ストーリーボードをあまり使用していない場合。デリゲートをviewDidLoad()ではなくawakeFromNib()に設定することで、これを回避できます。

元の投稿の例を使用すると、コードのサンプルは次のようになります

class SplitViewController: UISplitViewController, UISplitViewControllerDelegate {
    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
        preferredDisplayMode = .allVisible
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        return true
    }
}

また、collapseSecondary関数で使用しているロジックが、viewDidLoadがまだ呼び出されていないためにまだ入力されていない変数を参照していないことを確認する必要があります。

5
user2898617

私はXcodeプロジェクトを持っています-iOS 13になりました-それぞれが独自のマスター詳細(テーブル)ビューとコントローラーを持つ5つの分割ビューコントローラーとの関係を持つタブバーコントローラーを使用しています。

以前-iOS 12.x以前、実際にはObjective-Cを作成していたとき-分割ビューコントローラーデリゲートは、各(親)分割ビューコントローラーのマスタービューコントローラーのコードで設定されていました-デリゲートはサブクラスで設定しましたUITableViewControllerviewDidLoadメソッド。これは、iPhoneとiPadの両方で何年もうまく機能しました。

例えば.

_class MasterViewController: UITableViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        splitViewController?.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
        splitViewController?.delegate = self
        ...
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        ...
    }
}
_

明確にするために、私はタブバーコントローラーまたは分割ビューコントローラーをサブクラス化していません。

Xcode 11とiOS 13のリリースにより、マスタービューコントローラーのスプリットビューコントローラーデリゲートメソッドは呼び出されなくなりました。

明確にするために、iOS 13の場合、デバイスまたはシミュレーターに関係なく、splitViewController(_:collapseSecondary:onto:)は呼び出されず(ブレークポイントを使用してテストされます)、結果として次のように動作します。

  • iPhone-デバイスまたはシミュレーターでアプリを実行すると、詳細ビ​​ューコントローラーが表示されます。
  • iPad-デバイスまたはシミュレーターでアプリを実行すると、戻るボタンなしで詳細ビューコントローラーが表示されるため、詳細ビューを「エスケープ」する明確なメカニズムはありません。この問題を解決する唯一のユーザー回避策は、デバイスの向きを変更することです。その後、分割ビューコントローラーは期待どおりに動作します。

これは新しいクラスSceneDelegateと関係があるのではないかと思いました。

そこで、カスタムSceneDelegateクラスをテストプロジェクトに、次にプライマリプロジェクトに改造しました。

カスタムSceneDelegateクラスが完全に機能しています。 scene(_:willConnectTo:options:)メソッドで_window?.tintColor_を正常に設定したので、これはわかっています。

ただし、分割ビューコントローラーデリゲートの問題は引き続き発生しました。

私はAppleにフィードバックを記録しました、そしてこれは編集された応答です...

...問題は、viewDidLoadのオーバーライドでUISplitViewControllerのデリゲートを設定していることです。ビューが読み込まれる前にUISplitViewControllerが折りたたむことを決定している可能性があります。それを行うと、デリゲートをチェックしますが、まだ設定していないためデリゲートはまだnilなので、コードは呼び出されません。

ビューはオンデマンドで読み込まれるため、viewDidLoadのタイミングは予測できません。一般的には、View Controllerデリゲートなどを先に設定することをお勧めします。 scene(willConnectTo: session)でそれを行う方がうまくいく可能性があります。

このアドバイスは私に大いに役立ちました。

カスタムSceneDelegateクラスで、次のコードをscene(_:willConnectTo:options:)メソッドに追加しました...

_class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewControllerDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        guard let window = window else { return }
        guard let tabBarController = window.rootViewController as? UITabBarController else { return }

        guard let splitViewController = tabBarController.viewControllers?.first as? UISplitViewController else { return }

        splitViewController.delegate = self
        splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
    }

    ...

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        ...
    }

}
_

このコードはiPhoneとiPadの両方で機能しましたが、明らかに最初のスプリットマスター詳細ビューコントローラーの組み合わせでのみ機能しました。

5つのスプリットビューコントローラーすべてでこの成功を達成するようにコードを変更しました...

_    guard let window = window else { return }
    guard let tabBarController = window.rootViewController as? UITabBarController else { return }

    guard let splitViewControllers = tabBarController.viewControllers else { return }

    for controller in splitViewControllers {

        guard let splitViewController = controller as? UISplitViewController else { return }
        splitViewController.delegate = self
        splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
    }
_

このコードも動作します...ほとんど...

collapseSecondaryの_return true_を行うかどうかの私のチェックは、5つの詳細ビューコントローラーのそれぞれからの一意の値(計算されたプロパティ)に基づいています。この一意のチェックのため、カスタムSceneDelegateクラスでこれを判別するのは難しいように思われたので、カスタムSceneDelegateクラスで、代わりに次のコードを記述しました...

_    guard let window = window else { return }
    guard let tabBarController = window.rootViewController as? UITabBarController else { return }

    guard let splitViewControllers = tabBarController.viewControllers else { return }

    for controller in splitViewControllers {

        guard let splitViewController = controller as? UISplitViewController else { return }
        guard let navigationController = splitViewController.viewControllers.first else { return }
        guard let masterViewController = navigationController.children.first else { return }
        splitViewController.delegate = masterViewController as? UISplitViewControllerDelegate
        splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
    }
_

...そして各詳細ビューコントローラをUISplitViewControllerDelegateに準拠させました。

例えば.

_class MasterViewController: UITableViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // the following two calls now in the scene(_:willConnectTo:options:) method...
        // splitViewController?.preferredDisplayMode = UISplitViewController.DisplayMode.allVisible
        // splitViewController?.delegate = self
        ...
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        ...
    }
}
_

これまでのところ、5つの分割ビューコントローラーはそれぞれ、iPhoneとiPadの両方で、アプリの起動時に詳細ビューを折りたたみます。

1
andrewbuilder

これをクラス "SceneDelegate"の関数 "scene"に追加する必要があります。

splitViewController.delegate = self

例えば:

    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    // Setup split controller
    let tabViewController = self.window!.rootViewController as! TabViewController
    let splitViewController = tabViewController.viewControllers![0] as! SplitViewController
    let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
    navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
    navigationController.topViewController!.navigationItem.leftBarButtonItem?.tintColor = UIColor(named: "Theme Colour")

    splitViewController.preferredDisplayMode = .allVisible

    splitViewController.delegate = self//<<<<<<<<add this

    }
0
Richter