UISplitViewController 在 iPad iOS 13 上启动时无法正确折叠

18

我正在将我的应用程序过渡到iOS 13,但是在iPad上,UISplitViewController在启动时会折叠到详细视图而不是主视图。同时,“返回”按钮没有显示出来,就像它是根视图控制器一样。

我的应用程序由 UISplitViewController 组成,已被子类化并符合 UISplitViewControllerDelegate。分割视图包含两个子项——都是 UINavigationControllers,并嵌入在一个 UITabBarController (子类化的 TabViewController) 中。

在分割视图的 viewDidLoad 中,将代理设置为 self,并将 preferredDisplayMode 设置为 .allVisible

由于某种原因,方法 splitViewController(_:collapseSecondary:onto:) 没有被调用。

iOS 12iPhoneiPad 上,在 application(didFinishLaunchingWithOptions)applicationDidBecomeActive 之间正确调用了方法 splitViewController(_:collapseSecondary:onto:)

iOS 13iPhone 上,在 scene(willConnectTo session:)sceneWillEnterForeground 之间正确调用了方法 splitViewController(_:collapseSecondary:onto:)

然而,在 iOS 13iPad 上,如果窗口在启动时具有紧凑的宽度,例如作为拆分视图创建的新场景,则根本不会调用 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上却没有!我是一名新开发者,这是我的第一篇帖子,如果我的代码没有提供足够的细节或格式不正确,请谅解!


1
请向苹果公司提交错误报告。自从第一个测试版以来,iOS 13一直存在分屏视图问题,而苹果公司仍未解决这些问题。时间正在流逝。 - rmaddy
2
@matt 请查看http://www.openradar.me/radar?id=4969975819272192 - rmaddy
@rmaddy 谢谢。我有些难以想象为什么会这样做(替换现有分割视图控制器的两个视图控制器)。一个解决方法是创建和配置一个新的分割视图控制器,并将其替换为旧的视图控制器(作为窗口的根视图控制器)。 - matt
@matt 如果苹果不修复这个 bug,那就是我的备选方案。只需调用 setViewControllers 就比创建和配置一个全新的分屏视图简单得多。真正奇怪的是,在 iOS 13 beta 4 中已经修复了它,但在 beta 5 中又出现了问题。我还没有尝试过 13.1。 - rmaddy
谢谢,感谢您分享这个例子。希望您能随时通知我。顺便说一下,在我的 beta 7 上测试您的 Github 项目时,在旋转后并没有奇迹般地恢复分割视图控制器。 - matt
显示剩余5条评论
5个回答

7
由于某些原因,在iOS 13上,特别是在紧凑的traitCollections下的iPad上,在调用委托方法查看是否应该折叠之前,会先调用UISplitViewController中的viewDidLoad方法。因此,当它进行调用时,您的代理未设置,该方法将永远不会被调用。
如果您以编程方式创建splitViewController,则可以轻松解决此问题,但如果您使用Storyboard,则不是那么容易。您可以通过在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尚未被调用。


6
对我来说这仍然无法工作。即使在我已经在awakeFromNib中设置了delegatecollapseSecondary方法也从未被调用。 - sudoExclaimationExclaimation

2

我认为答案应该涵盖iOS14。

如果您发现委托方法没有被调用。

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

也许你应该考虑使用iOS14的。
  @available(iOS 14.0, *)
  func splitViewController(_ svc: UISplitViewController, topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column) -> UISplitViewController.Column {
        return .primary
  }

1
在浏览了许多不同的答案后,这就是帮助我的东西。谢谢! - Samuel Yanez

1
我有一个Xcode项目,现在是针对iOS 13的,它使用了一个带有与五个分割视图控制器相关联的选项卡控制器,每个控制器都有自己的主详细信息(表格)视图和控制器。
以前 - 在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
然而,分割视图控制器代理的问题仍然存在。
我向苹果公司反馈了此问题,以下是他们编辑后的回复...
“...问题在于您正在覆盖viewDidLoad方法中的UISplitViewController代理设置。可能是UISplitViewController在加载其视图之前决定折叠。当它这样做时,它会检查其代理,但由于代理仍为空,因为您尚未设置它,您的代码将不会被调用。”
由于视图是按需加载的,因此viewDidLoad方法的时间是不可预测的。通常最好尽早设置视图控制器代理。在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,但显然只适用于第一个分割主细节视图控制器组合。

我修改了代码,尝试使所有五个分割视图控制器都能成功实现这一点...

    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
    }

这段代码也可以运行……几乎……

我对于是否在collapseSecondaryreturn true的检查是基于每个五个详细视图控制器的一个唯一值——一个计算属性。由于这个唯一的检查,似乎很难在我的自定义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 {
        ...
    }
}

到目前为止,每个五分屏视图控制器在应用启动时都会折叠详细视图,无论是 iPhone 还是 iPad。

0
override func awakeFromNib() {
      super.awakeFromNib()
      splitViewController?.delegate = self
      splitViewController?.preferredDisplayMode = .allVisible
}

//MARK: Split View Controller Delegate
    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        print("splitview collapse method")
        return false
    }

在 Xcode 12.4 模拟器 iPhone 上启动时调用该方法,如果您设置:Storyboard -> Split View Controller -> Style -> Unspecified (Discouraged) not Double Column


目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

0
你需要在类“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

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接