【问题标题】:UISplitViewController will not correctly collapse at launch on iPad iOS 13UISplitViewController 在 iPad iOS 13 上启动时不会正确折叠
【发布时间】:2020-01-02 02:30:20
【问题描述】:

我正在将我的应用程序转换到 iOS 13,并且 UISplitViewController 在启动时折叠到详细视图,而不是主视图 - 仅在 iPad 上。此外,没有显示后退按钮 - 就好像它是根视图控制器一样。

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

在拆分视图viewDidLoad 中,代理设置为selfpreferredDisplayMode 设置为.allVisible

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

iPhoneiPad 上的 iOS 12 中,splitViewController(_:collapseSecondary:onto:) 方法在启动时会在 application(didFinishLaunchingWithOptions) 和 @ 之间正确调用987654334@.

iPhone 上的 iOS 13 中,splitViewController(_:collapseSecondary:onto:) 方法在启动时被正确调用,介于 scene(willConnectTo session:)sceneWillEnterForeground 之间。

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

【问题讨论】:

  • 请向 Apple 提交错误报告。 iOS 13 自第一个测试版以来就出现了拆分视图问题,Apple 仍未修复这些问题。时间不多了。
  • @rmaddy 你能说得更具体点吗? iOS 13 中新增了哪些拆分视图问题?
  • @rmaddy 谢谢。我很难想象为什么有人会这样做(替换现有拆分视图控制器的两个视图控制器)。一种解决方法是创建和配置一个新的拆分视图控制器并将其替换为旧的(作为窗口的根视图控制器)。
  • @matt 如果 Apple 不修复该错误,那是我的后备计划。只调用setViewControllers 比创建和配置一个全新的拆分视图要简单得多。真正奇怪的是它在 iOS 13 beta 4 中被修复,然后在 beta 5 中再次被破坏。我还没有尝试 13.1。

标签: ios swift ipad uisplitviewcontroller ios13


【解决方案1】:

出于某种原因,在 iOS 13 上,特别是在 iPad 上的紧凑 traitCollections 中,在 UISplitViewController 上调用 viewDidLoad 之前,对委托的调用以查看它是否应该折叠发生,因此当它进行该调用时,您的委托未设置,并且该方法永远不会被调用。

如果您以编程方式创建 splitViewController,这很容易解决,但如果您使用 Storyboards 就不那么容易了。您可以通过将委托设置为 awakeFromNib() 而不是 viewDidLoad() 来解决此问题

使用原始帖子中的示例,代码示例如下

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 而尚未填充的变量。

【讨论】:

  • 这似乎仍然不适合我。即使我在awakeFromNib 中设置了delegate,也不会调用collapseSecondary 方法
【解决方案2】:

我有一个 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

但是拆分视图控制器委托的问题仍然存在。

我记录了对 Apple 的反馈,这是他们编辑后的回复...

...问题是您正在设置 UISplitViewController 的委托 覆盖viewDidLoad。有可能是 UISplitViewController 决定在任何原因之前崩溃 它的视图被加载。当它这样做时,它会检查它的委托,但是 因为代理仍然是 nil 因为你还没有设置它,你的 代码不会被调用。

由于视图是按需加载的,所以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
    }

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

我对@9​​87654335@ 是否为collapseSecondary 的检查是基于来自五个详细视图控制器中的每一个的唯一值 - 计算属性。由于这种独特的检查,在我的自定义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。

【讨论】:

    【解决方案3】:

    嗯,我想答案现在应该涵盖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
      }
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2020-04-20
        • 2015-10-02
        • 2017-01-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-29
        • 1970-01-01
        相关资源
        最近更新 更多