アプリを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 1でiPadの場合、ただし、起動時にウィンドウの幅がコンパクトである場合、たとえば分割ビューとして作成された新しいシーンでは、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では呼び出されていない理由がわかりません。私は新しい開発者で、これが私の最初の投稿です。コードが十分な詳細を提供していないか、正しくフォーマットされていない場合はお詫びします!
何らかの理由で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がまだ呼び出されていないためにまだ入力されていない変数を参照していないことを確認する必要があります。
私はXcodeプロジェクトを持っています-iOS 13になりました-それぞれが独自のマスター詳細(テーブル)ビューとコントローラーを持つ5つの分割ビューコントローラーとの関係を持つタブバーコントローラーを使用しています。
以前-iOS 12.x以前、実際にはObjective-Cを作成していたとき-分割ビューコントローラーデリゲートは、各(親)分割ビューコントローラーのマスタービューコントローラーのコードで設定されていました-デリゲートはサブクラスで設定しましたUITableViewController
のviewDidLoad
メソッド。これは、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:)
は呼び出されず(ブレークポイントを使用してテストされます)、結果として次のように動作します。
これは新しいクラス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の両方で、アプリの起動時に詳細ビューを折りたたみます。
これをクラス "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
}