mercredi 10 mai 2023

Swift Architectural Problem with Protocol and Inheritance

I’m more familiar with C++ than i am with Swift, but I’m struggling with getting the architecture right for my problem.

The App Delegate talks to my custom ‘MenuSystem’ singleton and says ‘load the app storyboard and make key&visible’ and all that good stuff.

However, the type of menu that the ‘MenuSystem’ class will use will vary by device. i.e. on iPhone - just as an example - it could be a drawer type menu, but on iPad it might be a tab bar and on macOS it might be a tool bar or side menu…

So the menu system class has a property called ‘menuController’ and there is a custom protocol that all the actual subclassed View Controllers (i.e. uitableview for drawer, uitabviewcontroller for tab bar etc etc etc)

This protocol defines all the common things that the ‘MenuSystem’ class needs to communicate to the actual, in use, menu.

The protocol is called ‘RootMenuProtocol’

Each of these will be implemented by an actual class e.g. a subclass of UITableViewController that conforms to the ‘RootMenuProtocol’ protocol.

So in the MenuSystem singleton, I need the property ‘self.menuController’ to refer to any one of the 3 possible (or more) classes that conform to the protocol.

The problem with this architecture is that when I try and then assign the type of UIViewController currently assigned to the property ‘self.menuController’ to ‘rootWindow?.rootViewController’ I get an error : Cannot assign value of type '(any RootMenuProtocol)?' to type 'UIViewController?'

Here's the code:

App Delegate

import UIKit

@main class AppDelegate: UIResponder, UIApplicationDelegate
{
  var window: UIWindow?

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
  {
    // Override point for customization after application launch.
    
    // Create Menu system and launch first view controller
    MenuSystem.shared.loadInitialViewControllerAndMakeActiveInto(Window: window, UsingApplicationStoryboardWithName: "Main")
    
    return true
  }
}

RootMenuProtocol

import Foundation

protocol RootMenuProtocol
{
  func transitionTo(StoryboardID: Any, UsingCustomAnimationTransition: Any)
}

MenuSystem Singleton

import Foundation
import UIKit

class MenuSystem
{
  static let shared: MenuSystem = MenuSystem()
  private var menuController: RootMenuProtocol?
    
  private init()
  {
  }
    
  internal func loadInitialViewControllerAndMakeActiveInto(Window rootWindow: UIWindow?, UsingApplicationStoryboardWithName applicationStoryboardName: String)
  {
    if UIDevice.current.userInterfaceIdiom == .phone
    {
      self.menuController = UIStoryboard(name: "PBFMenuSystem", bundle: nil).instantiateViewController(withIdentifier: "drawerRootViewController") as? MenuRootTableViewController
      
      rootWindow?.rootViewController = self.menuController
      rootWindow?.makeKeyAndVisible()
    }
    else if UIDevice.current.userInterfaceIdiom == .pad
    {
      self.menuController = UIStoryboard(name: "PBFMenuSystem", bundle: nil).instantiateViewController(withIdentifier: "tabBarRootViewController") as? MenuRootTabBarViewController
      
      rootWindow?.rootViewController = self.menuController
      rootWindow?.makeKeyAndVisible()
    }
    else if UIDevice.current.userInterfaceIdiom == .mac
    {
      // Tool Bar here...
    }
  }
  
  internal func requestTransitionTo(StoryboardID id: String, UsingCustomAnimationTransition transitionAnimation: UIViewControllerAnimatedTransitioning? = nil)
  {
    self.menuController?.transitionTo(StoryboardID: id, UsingCustomAnimationTransition: transitionAnimation as Any)
  }
}

MenuRootTabBarViewController

import UIKit

class MenuRootTabBarViewController: UITabBarController, RootMenuProtocol
{
  
  override func viewDidLoad()
  {
    super.viewDidLoad()
    
    // Do any additional setup after loading the view.
  }
  
  func transitionTo(StoryboardID: Any, UsingCustomAnimationTransition: Any)
  {
  }
}

MenuRootTableViewController

import UIKit

class MenuRootTableViewController: UITableViewController, RootMenuProtocol
{
  
  override func viewDidLoad()
  {
    super.viewDidLoad()
    
  }
  
  func transitionTo(StoryboardID: Any, UsingCustomAnimationTransition: Any)
  {
  }
  
  // MARK: - Table view data source
  
  override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 0
  }
  
  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return 0
  }
}

Can someone show me what I've misunderstood about Swift architecture and inheritance/protocols?

I just want the property 'self.menuController' to point to whichever UIViewController subclass I've got that also musts conform to the 'RootMenuProtocol'.

Aucun commentaire:

Enregistrer un commentaire