2017-08-29 18 views
2

我的应用程序具有用于创建自定义视图控制器的类层次结构。自定义视图控制器类未在故事板的类菜单中列出

第一个类是AppViewController。它扩展了NSViewController并包含了所有视图控制器的通用方法,例如显示警报,从数据库中检索数据等等。它没有定义任何变量。

class AppViewController: NSViewController 
{ 
    ... 
} 

下一个类是ListViewController和通用于所有的我的“名单”的意见。这些视图包含一个NSTableView,其中包含关联数据库表中所有记录的列表。它扩展了AppViewController并且符合通常的协议。

请注意,此类是通用的,因此它可以正确处理不同的视图和数据模型。

class ListViewController<Model: RestModel>: AppViewController, 
              NSWindowDelegate, 
              NSTableViewDataSource, 
              NSTableViewDelegate 
{ 
    ... 
} 

ListViewController定义了一些变量,包括一个NSTableView的IBOutlet。该插座没有连接到故事板中的任何内容。计划是在运行时设置它。

ListViewController还定义了各种函数,包括viewDidLoad(),viewWillAppear(),一些特定于应用程序的函数等等。

最后一类特定于数据库模型和视图,在这种情况下是Customers视图。它扩展了ListViewController。

class Clv: ListViewController<CustomerMaster> 
{ 
    ... 
} 

CustomerMaster是符合RestModel协议的具体类。

问题:
奇怪的是,最后一堂课,CLV,不至情节板中的自定义类显示:类下拉菜单,这意味着我不能将它指定为自定义类的我视图。

我想只是打字它,但导致运行时错误

未知类_TtC9Inventory3Clv在Interface Builder文件...

如果我删除<型号:RestModel >从ListViewController类定义,并从CLV类定义中移除< CustomerMaster>,以CLV类,然后出现在类菜单(当然,这并不能真正帮助,只是一个观察的)。

AppViewController和ListViewController都出现在该菜单中

我不知所措。

+0

您已经自己解释了它。 Objective-C无法看到Swift泛型。 – matt

回答

0

@vikingosegundo在解释Xcode的抱怨并且通常信息量非常丰富时的回答并没有帮助我解决特定的问题。我的项目是从Xcode 8.3.3开始的,我已经在故事板中有很多窗口和视图,所以我不想放弃或者绕过故事板/通用的问题。这就是说,我做了一些更多的研究,并意识到许多人更喜欢委托给类继承,所以我决定探索这种方法。我能够找到满足我需求的工作。

我在这里介绍一个简化但功能强大的方法。

首先,协议,我们的数据模型必须符合:

protocol RestModel 
{ 
    static var entityName: String { get } 
    var id: Int { get } 
} 

接下来,数据模型:

/// 
/// A dummy model for testing. It has two properties: an ID and a name. 
/// 
class ModelOne: RestModel 
{ 
    static var entityName: String = "ModelOne" 
    var id: Int 
    var name: String 

    init(_ id: Int, _ name: String) 
    { 
    self.id = id 
    self.name = name 
    } 
} 

然后,协议,这是所有类,扩展我们的基类必须符合:

/// 
/// Protocol: ListViewControllerDelegate 
/// 
/// All classes that extend BaseListViewController must conform to this 
/// protocol. This allows us to separate all knowledge of the actual data 
/// source, record formats, etc. into a view-specific controller. 
/// 
protocol ListViewControllerDelegate: class 
{ 
    /// 
    /// The actual table view object. This must be defined in the extending class 
    /// as @IBOutlet weak var tableView: NSTableView!. The base class saves a weak 
    /// reference to this variable in one of its local variables and uses that 
    /// variable to access the actual table view object. 
    /// 
    weak var tableView: NSTableView! { get } 

    /// 
    /// This method must perform whatever I/O is required to load the data for the 
    /// table view. Loading the data is assumed to be asyncronous so the method 
    /// must accept a closure which must be called after the data has been loaded. 
    /// 
    func loadRecords() 

    /// 
    /// This method must simply return the number of rows in the data set. 
    /// 
    func numberOfRows() -> Int 

    /// 
    /// This method must return the text that is to be displayed in the specified 
    /// cell. 
    /// - parameters: 
    /// - row: The row number (as supplied in the call to tableView(tableView:viewFor:row:). 
    /// - col: The column identifier (from tableColumn.identifier). 
    /// - returns: String 
    /// 
    func textForCell(row: Int, col: String) -> String 

} // ListViewControllerDelegate protocol 

现在实际的基类:

class BaseListViewController: NSViewController, 
           NSTableViewDataSource, 
           NSTableViewDelegate 
{ 
    // 
    // The instance of the extending class. Like most delegate variables in Cocoa 
    // applications, this variable must be set by the delegate (the extending 
    // class, in this case). 
    // 
    weak var delegate: ListViewControllerDelegate? 

    // 
    // The extending class' actual table view object. 
    // 
    weak var delegateTableView: NSTableView! 

    // 
    // Calls super.viewDidLoad() 
    // Gets a reference to the extending class' table view object. 
    // Sets the data source and delegate for the table view. 
    // Calls the delegate's loadRecords() method. 
    // 
    override func viewDidLoad() 
    { 
    super.viewDidLoad() 
    delegateTableView = delegate?.tableView 
    delegateTableView.dataSource = self 
    delegateTableView.delegate = self 
    delegate?.loadRecords() 
    delegateTableView.reloadData() 
    } 


    // 
    // This is called by the extending class' table view object to retreive the 
    // number of rows in the data set. 
    // 
    func numberOfRows(in tableView: NSTableView) -> Int 
    { 
    return (delegate?.numberOfRows())! 
    } 


    // 
    // This is called by the extending class' table view to retrieve a view cell 
    // for each column/row in the table. We call the delegate's textForCell(row:col:) 
    // method to retrieve the text and then create a view cell with that as its 
    // contents. 
    // 
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? 
    { 
    if let col = tableColumn?.identifier, let text = delegate?.textForCell(row: row, col: col) 
    { 
     if let cell = delegate?.tableView.make(withIdentifier: (tableColumn?.identifier)!, owner: nil) as? NSTableCellView 
     { 
     cell.textField?.stringValue = text 
     return cell 
     } 
    } 
    return nil 
    } 
} // BaseListViewController{} 

最后,延伸类:

/// 
/// A concrete example class that extends BaseListViewController{}. 
/// It loadRecords() method simply uses a hard-coded list. 
/// This is the class that is specified in the IB. 
/// 
class ViewOne: BaseListViewController, ListViewControllerDelegate 
{ 
    var records: [ModelOne] = [] 

    // 
    // The actual table view in our view. 
    // 
    @IBOutlet weak var tableView: NSTableView! 

    override func viewDidLoad() 
    { 
    super.delegate = self 
    super.viewDidLoad() 
    } 

    func loadRecords() 
    { 
    records = 
    [ 
     ModelOne(1, "AAA"), 
     ModelOne(2, "BBB"), 
     ModelOne(3, "CCC"), 
     ModelOne(4, "DDD"), 
    ] 
    } 

    func numberOfRows() -> Int 
    { 
    return records.count 
    } 

    func textForCell(row: Int, col: String) -> String 
    { 
    switch col 
    { 
    case "id": 
     return "\(records[row].id)" 

    case "name": 
     return records[row].name 

    default: 
     return "" 
    } 
    } 
} // ViewOne{} 

这是当然的简化原型。在真实世界的实现中,加载记录和更新表格会在异步加载数据库,Web服务等数据后发生在闭包中。

我的完整原型定义了扩展BaseListViewClass的两个模型和两个视图控制器。它按需要工作。基类的生产版本将包含许多其他方法(这就是为什么首先希望它成为基类的原因:-)

+0

实际上你的基本视图控制器和任何其他视图控制器应该有*许多其他方法*。你的回答给人的印象是你寻找一个* pattern *,你应该在这里寻找一个* architecture *。我很乐意提出建议,以深入探讨“轻量级视图控制器”与“Massive View Controller”,对(苹果MVC),VIPER,Bob叔叔的“Clean Architecture”及其变体“Clean Swift”或VIP,MVVM的批评。那就是说:你为什么坚持拥有通用视图控制器,或者根本不用泛型? – vikingosegundo

+0

在视图控制器中,您可以键入一个属性作为协议,并使泛型类实现该属性。或者使用通用数据源(DataSources和Delegates不能在视图控制器中实现)。或通用演示者。 – vikingosegundo

1

今年早些时候,我创建了一个类似的架构的应用程序,我要告诉你:这不能用故事板工作,因为那些不知道实例化过程中的泛型东西。

虽然是用笔尖,因为你比仍可自行初始化您的视图控制器是什么在起作用。

一个例子:

import UIKit 

class ViewController<Model: Any>: UIViewController { 
    var model:Model? 
} 

可以实例化这个视图控制器等

let vc = ViewController<ListItem>(nibName: "ListViewController", bundle: nil) 

或子类它

class ListViewController: ViewController<ListItem> { 
} 

和实例化等

let vc = ListViewController(nibName: "ListViewController", bundle: nil) 

现在它编译并运行,但你还没有获得太多,因为你不能用通用属性连接你的笔尖。

但是你可以做的是在一个非泛型的基本视图控制器中有一个UIView类型的IBOutlet,它具有一个通用视图控制器,它有两个通用契约:一个用于模型,一个用于视图,屁股你很可能希望这适合你的模型。但是现在你必须有一些知道如何将你的模型放在视图上的代码。我称这个渲染器,但你也会发现很多例子是这样的一个类被称为Presenter。

的视图控制器:

class BaseRenderViewController: UIViewController { 
    var renderer: RenderType? 
    @IBOutlet private weak var privateRenderView: UIView! 

    var renderView: UIView! { 
     get { return privateRenderView } 
     set { privateRenderView = newValue } 
    } 
} 


class RenderedContentViewController<Content, View: UIView>: BaseRenderViewController { 

    var contentRenderer: ContentRenderer<Content, View>? { 
     return renderer as? ContentRenderer<Content, View> 
    } 

    open 
    override func viewDidLoad() { 
     super.viewDidLoad() 

     guard let renderer = contentRenderer, let view = self.renderView as? View else { 
      return 
     } 
     do { 
      try renderer.render(on: view) 

     } catch (let error) { 
      print(error) 
     } 
    } 
} 

渲染器:

protocol RenderType {} 

class Renderer<View: UIView>: RenderType { 
    func render(on view: View) throws { 
     throw RendererError.methodNotOverridden("\(#function) must be overridden") 
    } 
} 

class ContentRenderer<Content, View: UIView>: Renderer<View> { 
    init(contents: [Content]) { 
     self.contents = contents 
    } 
    let contents: [Content] 

    override func render(on view: View) throws { 
     throw RendererError.methodNotOverridden("\(#function) must be overridden") 
    } 
} 

您现在可以继承ContentRenderer并覆盖渲染方法来显示对查看内容。

TL;博士

利用我只是说明你可以在任何通用视图控制器不同型号,渲染器和意见相结合的办法。你获得了难以置信的灵活性 - 但是你将无法使用故事板。

+0

尽管它很有意义,但由于我无法将它与故事板一起使用,我该如何使用它?你能提供一个具体的例子吗?请注意,我对Xcode,Swift和Cocoa世界都很陌生,自从我开始使用Xcode 8以来,我只能使用storyboard。 –

+0

正如我所说:如果使用storyboard,则不能使用通用视图控制器。我给你的例子是一个真实世界的例子 - 它具有混凝土的特性。 – vikingosegundo

+0

感谢您的详细解释。经过一些更多的研究后,我认为我至少对发生了什么有了基本的了解。 –

相关问题