创建实现特定协议的对象数组

时间:2017-02-07 12:33:38

标签: ios swift uiviewcontroller

TL; DR

我正在寻找一种数组类型(var array = [TheTypeImLookingFor]()),例如,UIViewController 子类实现协议MyProtocol

解释

我正在构建一种带有容器视图和嵌入式子视图(控制器)的向导视图。没问题,这将有效,因为我只有一个base类型的子视图控制器。

由于屏幕的内容,我现在有一堆MyTableViewController类型的视图控制器,它是UITableViewController的子类和其他以常规UIViewControllers为基础的视图控制器。

所有视图控制器都有一个共同点。默认数据属性myData: MyObject

我创建了一个包含此属性的协议MyProtocol

现在,我必须将所有这些视图控制器组合到一个数组中,以将其用作向导步骤。只要我只需访问视图控制器方法(数组项的类型为UIViewController)我就可以使用var viewControllers = [UIViewController]()或者我只想访问myData属性,我将数组项类型更改为MyObject

但问题是,我必须从UIViewController和协议中访问方法。

这就是为什么我要寻找类似于'所有子类UIViewController 的对象实现协议MyProtocol的原因。

我试过了:

  • var viewControllers = [UIViewController: MyProtocol]() //是一个词典
  • `var viewControllers = UIViewController,其中MyProtocol
  • `var viewControllers = UIViewController.conforms(to:MyProtocol)
  • ...

但没有任何事情能像预期的那样发挥作用。

5 个答案:

答案 0 :(得分:2)

据我所知,目前无法输入内容,因此它描述了从给定类继承的任何内容符合给定的协议。

一种可能的hacky解决方法是只需创建一个包装器类型,以便在需要将实例视为MyProtocol的情况下为您执行类型转换。

struct MyProtocolViewController {

    let base: UIViewController

    init<T : UIViewController>(_ base: T) where T : MyProtocol {
        self.base = base
    }

    func asMyProtocol() -> MyProtocol {
        return base as! MyProtocol
    }
}

现在您可以创建[MyProtocolViewController],并可以将元素视为UIViewControllerMyProtocol

// given that ViewController and AnotherViewController conform to MyProtocol.
let viewControllers = [MyProtocolViewController(ViewController()),
                       MyProtocolViewController(AnotherViewController())]

for viewController in viewControllers {
    print(viewController.asMyProtocol().myData)
    print(viewController.base.prefersStatusBarHidden)
}

答案 1 :(得分:1)

您可以将协议组合与占位符协议一起用于该类:

protocol UIViewControllerClass {}
extension UIViewController: UIViewControllerClass {}

protocol MyProtocol:class {}

class MySpecialVC:UIViewController,MyProtocol {}    

var viewControllers = [UIViewControllerClass & MyProtocol]()

viewControllers.append( MySpecialVC() )   

这涵盖了类型安全部分,但不允许您在没有类型转换的情况下访问UIViewController方法。您可以通过向协议添加类型属性来减少类型转换丑陋(适用于基类时)

extension MyProtocol where Self: UIViewControllerClass
{
   var vc:UIViewController { return self as! UIViewController }
}

// accessing the view controller's methods would then only require insertion of a property name.
viewControllers.first!.vc.view

或者,您可以定义需要在占位符协议中调用的UIViewController方法,但如果您要使用其中许多方法,这很快就会变得无聊和冗余。

答案 2 :(得分:0)

为什么不简单地创建:

为什么不创建:

class ObservingViewController : UIViewController, MyProtocol {


}

var viewControllers : [ObservingViewController] = []

答案 3 :(得分:0)

您还可以创建一个protocol来定义您需要的所有UIViewController个功能。确保复制方法签名,否则您将不得不再次实现这些功能。

protocol UIViewControllerInteractions {
    //copy the signature from the methods you want to interact with here, e.g.
    var title: String? { get set }
}

然后,您可以扩展现有协议。

protocol MyProtocol: UIViewControllerInteractions { }

或者创建一个扩展UIViewControllerInteractionsMyProtocol的新协议。

protocol MyProtocolViewController: UIViewControllerInteractions, MyProtocol { }

现在,当您扩展SubclassUIViewController时,您仍然只需添加myData,因为UIViewControllerInteractions中的方法已由UIViewController实施(这就是我们为什么复制方法签名)

class SubclassUIViewController: MyProtocol {
    var myData ...
}

您现在可以拥有array MyProtocolMyProtocolViewController,并且还可以调用UIViewControllerInteractions中定义的方法,这些方法将调用UIViewController方法。

var viewController: [MyProtocol] = [...]
viewController.forEach { (vc) in
    print(vc.myData)
    print(vc.title)
}

答案 4 :(得分:0)

我遇到了类似的问题,并使用自定义基类解决了这个问题。想象一下如下数组:

var viewControllers: [MapViewController]

所有这些都应该从UIViewController扩展并实现以下协议:

protocol MapViewControllerDelegate {
    func zoomToUser()
}

然后我宣布了一个基类:

class MapViewController: UIViewController {
    var delegate: MapViewControllerDelegate?
}

警告:此类不实现上述协议,但保留了提供所需功能的属性。下一步是定义一个将添加到数组中的UIViewController

class GoogleMapsViewController: MapViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
}

extension GoogleMapsViewController: MapViewControllerDelegate {
    func zoomToUser() {
        // Place custom google maps code here
    }
}

重要的部分位于viewDidLoad方法中。视图控制器将自己指定为委托。

<强>用法:

let googleMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "GoogleMapsViewController") as! GoogleMapsViewController
let mapboxMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MapboxMapsViewController") as! MapboxMapsViewController
let mapViewControllers: [MapViewController] = [googleMapsViewController, mapboxViewController]
for mapVC in mapViewControllers {
    mapVC.delegate?.zoomToUser()
}

好处:

  • MapViewController就像一个抽象类,如果我更改MapViewControllerDelegate,编译器会强制我在GoogleMapsViewControllerMapboxMapsViewController中实现更改。
  • 如果我需要第二个协议,我可以实现第二个委托属性。
  • 不需要像其他答案那样进行类型转换。每个UIViewController仍然是UIViewController并提供其所有方法。