2015-09-29 11 views
23

私のアプリ用のGenericListControllerを作成しようとしています。
ストーリーボードを使用したswift 2.0の汎用コントローラ

私はUIViewControllerを拡張する汎用コントローラを拡張するProductListControllerを持っています。私は、ストーリーボードにProductListControllerを接続し、2つの店舗を作ったが、私は常に、このエラーが表示されている:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIViewController 0x7c158ca0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchBar.' 

私はそれが動作GenericListControllerからジェネリックTを削除する場合、私は、私のすべてのコンセントのためにこのエラーが発生しました。私はストーリーボードがジェネリックのスーパーを読み込むことはできないと思います。どのように私はそれを動作させることができますか?

マイコード:私はジェネリッククラスを拡張し、ストーリーボードのXcodeにクラスを追加しようとすると文句を言わないだろう(クラス名を自動補完することを見出した

class GenericListController<T> : UIViewController { 

    var list : [T] = [T]() 
    var filteredlist : [T] = [T]() 

    func getData(tableView : UITableView) { 
    ..... 
    } 

    func setData(list : [T], tableView : UITableView) { 
    ..... 
    } 

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

class ProductListController : GenericListController<ProductModel> { 
     @IBOutlet weak var searchBar: UISearchBar! 
     @IBOutlet weak var tableView: UITableView! 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     getData(tableView) 
    } 
} 

--EDIT--

それは)

+3

なぜあなたはのUIViewControllerで、一般的なモデルを追加する必要がありますか? –

+1

リスト・コントローラのロジックは常に同じなので、オブジェクトのタイプを変更するだけです。私はproductList、saleList、userListを持ち、それらはすべて同じメソッドとロジックを共有しています。 DetailsController、AddControllerと同じです。ジェネリックスを使用できない場合は、私が作成した新しいコントローラにgetData関数をコピーして貼り付ける必要があります(その内部に型が必要なDAOメソッドがあります)。 – sagits

+0

@sagitsジェネリック型(この場合はProductModel)の属性にアクセスすることができないので、これがどれほど有用なのか不思議でした。あなたの方法ではなく、私のものではありませんか?すべてのTが準拠する基本クラスも作成していますか? –

答えて

14

をクラスを検出カントので、これはそれができない理由を答え:use a generic class as a custom view in interface builder

Interface Builder "talks" to your code through the ObjC runtime. As such, IB can can access only features of your code that are representable in the ObjC runtime. ObjC doesn't do generics

このヒントを回避するには:generics in obj-c おそらくobj-cで汎用ViewControllerを作成し、それを受け入れることができますか?

プロトコルの使用を検討しましたか?これはストーリーボードを動揺させない。簡単にテストできるようにコードを少し変更しました。これの欠点は、プロトコルにプロパティを格納することができないことです。だからあなたはまだそれらを貼り付ける必要があります。それはうまく動作することです。

protocol GenericListProtocol {  
    typealias T 
    var list : [T] { get set } 
    var filteredlist : [T] { get set } 
    func setData(list : [T])   
}  
extension GenericListProtocol {   
    func setData(list: [T]) { 
     list.forEach { item in print(item) } 
    }   
} 

class ProductModel {   
    var productID : Int = 0   
    init(id:Int) { 
     productID = id 
    }   
}  

class ProductListController: UIViewController, GenericListProtocol { 

    var list : [ProductModel] = [ProductModel(id: 1),ProductModel(id: 2),ProductModel(id: 3),ProductModel(id: 4)] 
    var filteredlist : [ProductModel] = [] 

    override func viewDidLoad() {    
     super.viewDidLoad()    
     setData(list)    
    } 
} 

更新: ジェネリッククラスの属性にいくつかのアクセスを許可します。 Playgroundで簡単にテストするために基本クラスに変更しました。 UIViewControllerのものは上のコードにあります。

class ProductModel {   
    var productID : Int = 0   
    init(id:Int) { 
     productID = id 
    }   
} 

class ProductA : ProductModel { 
    var aSpecificStuff : Float = 0 
}  

class ProductB : ProductModel { 
    var bSpecificStuff : String = "" 
} 

protocol GenericListProtocol {   
    typealias T = ProductModel 
    var list : [T] { get set } 
    var filteredlist : [T] { get set } 
    func setData(list : [T])   
} 

extension GenericListProtocol {   
    func setData(list: [T]) { 
     list.forEach { item in 
      guard let productItem = item as? ProductModel else { 
       return 
      } 
      print(productItem.productID) 
     } 
    }   
} 


class ProductListController: GenericListProtocol { 

    var list : [ProductA] = [ProductA(id: 1),ProductA(id: 2),ProductA(id: 3),ProductA(id: 4)] 
    var filteredlist : [ProductA] = [] 

    init() {    
     setData(list)    
    } 
} 

var test = ProductListController() 
+0

こんにちは、私はあなたの答えを実装しようとしていましたが、私はどこT:MyModel(このクラスはgetIdメソッドを保持しています)、Tが必要です、それは可能ですか?私はこれが奇妙に思えるが、私は正確なclassTypeをgetDataに渡す必要があるので、webserviceからjsonを解析し、型付きのリストを返すことができます(これは他の汎用メソッドを実装するために必要です)。 – sagits

+0

@sagitsジェネリックを間違って使用していると思います。ジェネリックは、Yに従うオブジェクトにあるXを操作するときには素晴らしいものです。すべてのサブクラスを置き換えることは有用ではありません。あなたのケースでは、サブクラスはまだ行く方法です。しかし、スーパークラス(ProductModel)から継承された属性へのアクセスを得るための方法で答えを更新しました –

+0

ありがとう、すぐに私のコードを掲載します。それは機能しましたが、私はT型のMyModelを使用しました。今私はgetDataからリストの値を更新しようとしている、私は、プロトコルリストを更新し、サブクラス(getData(myList:[T]){myList = listFromServer}からリストを渡してtryied。 – sagits

2

私はユーザR menke他の助けを借りて実現しました。 私の目標はUISearchBarDelegate、UITableViewDelegateと正しくJSONを解析することができるように型クラスを必要とする私のgetDataメソッドを(扱うことができるGenericListProtocolを持つことです。

import Foundation 
import UIKit 

protocol GenericListProtocol : UISearchBarDelegate, UITableViewDelegate{ 
    typealias T : MyModel // MyModel is a model i use for getId, getDate... 
    var list : [T] { get set } 
    var filteredlist : [T] { get set } 

    var searchActive : Bool { get set } 

    func setData(tableView : UITableView, myList : [T]) 

    func setData() 

    func getData(tableView : UITableView, objectType : T, var myList : [T]) 

    func filterContentForSearchText(searchText: String) 

} 

extension GenericListProtocol { 

    func setData(atableView : UITableView, myList : [T]) { 
    print("reloading tableView data") 
    atableView.reloadData() 
    } 

    func getData(tableView : UITableView, objectType : T, var myList : [T]) { 

    let dao: GenericDao<T> = GenericDao<T>() 
    let view : UIView = UIView() 

    let c: CallListListener<T> = CallListListener<T>(view: view, loadingLabel: "loading", save: true, name: "ProductModel") 

    c.onSuccess = { (onSuccess: JsonMessageList<T>) in 
     print("status " + onSuccess._meta!.status!) // this is from my ws 

     myList = onSuccess.records 

     self.setData(tableView, myList: myList) 
    } 

    c.onFinally = { (any: AnyObject) in 
     // tableView.stopPullToRefresh() 
    } 

    // my dao saves json list on NSUSER, so we check if its already downloaded 
    let savedList = c.getDefaultList() 
    if (savedList == nil) { 
     dao.getAll(c); 
    } 
    else { 
     myList = savedList! 
     print(String(myList.count)) 
     self.setData(tableView, myList: myList) 

    } 


    } 

    func searchBarTextDidBeginEditing(searchBar: UISearchBar) { 
    searchActive = true; 
    } 

    func searchBarTextDidEndEditing(searchBar: UISearchBar) { 
    searchActive = false; 
    } 

    func searchBarCancelButtonClicked(searchBar: UISearchBar) { 
    searchActive = false; 
    } 

    func searchBarSearchButtonClicked(searchBar: UISearchBar) { 
    searchActive = false; 
    } 

    func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { 
    print("searching") 
    self.filterContentForSearchText(searchText) 
    if(filteredlist.count == 0){ 
     searchActive = false; 
    } else { 
     searchActive = true; 
    } 
    self.setData() 
    } 



} 

私が最もUISearchBarDelegate、UITableViewDelegateのメソッドを実装することができましたが、

import Foundation 
import UIKit 
import EVReflection 
import AlamofireJsonToObjects 

class ProductListController : GenericListController, GenericListProtocol { 

    @IBOutlet weak var searchBar: UISearchBar! 
    @IBOutlet weak var tableView: UITableView! 


    var list : [ProductModel] = [ProductModel]() 
    var filteredlist : [ProductModel] = [ProductModel]() 
    var searchActive : Bool = false 


    override func setInit() { 
    self.searchBar.delegate = self 
    self.listName = "ProductModel" 
    self.setTableViewStyle(self.tableView, searchBar4 : self.searchBar) 
    getData(self.tableView, objectType: ProductModel(), myList: self.list) 
    } 

    // this method hasnt worked from extension, so i just pasted it here 
    func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { 

    self.filterContentForSearchText(searchText) 
    if(filteredlist.count == 0){ 
     searchActive = false; 
    } else { 
     searchActive = true; 
    } 
    self.setData(self.tableView, myList: list) 
    } 

    // this method hasnt worked from extension, so i just pasted it here 
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 

    if searchActive { 
     return self.filteredlist.count 
    } else { 
     return self.list.count 
    } 
    } 

    // self.list = myList hasnt worked from extension, so i just pasted it here 
    func setData(atableView: UITableView, myList : [ProductModel]) { 
    print(String(myList.count)) 
    self.list = myList 
    print(String(self.list.count)) 
    self.tableView.reloadData() 
    } 

    // i decided to implement this method because of the tableView 
    func setData() { 
    self.tableView.reloadData() 
    } 


    // this method hasnt worked from extension, so i just pasted it here 
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 

    let cell:GenericListCell = tableView.dequeueReusableCellWithIdentifier("cell") as! GenericListCell 


    var object : ProductModel 
    if searchActive { 
     object = filteredlist[indexPath.row] 
    } else { 
     object = list[indexPath.row] 
    } 

    cell.formatData(object.name!, subtitle: object.price ?? "valor", char: object.name!) 
    print("returning cell") 


    return cell 

    } 


    override func viewDidLoad() { 
    //   searchFuckinBar.delegate = self 
    super.viewDidLoad() 


    // Do any additional setup after loading the view, typically from a nib. 
    } 


    func filterContentForSearchText(searchText: String) { 
    // Filter the array using the filter method 
    self.filteredlist = self.list.filter({(object: ProductModel) -> Bool in 
     // let categoryMatch = (scope == "All") || (object.category == scope) 
     let stringMatch = object.name!.lowercaseString.rangeOfString(searchText.lowercaseString) 
     return (stringMatch != nil) 
    }) 
    } 

    func formatCell(cell : GenericListCell, object : ProductModel) { 
    cell.formatData(object.name!, subtitle: object.price ?? "valor", char: object.name!) 
    } 

    override func didReceiveMemoryWarning() { 
    super.didReceiveMemoryWarning() 
    // Dispose of any resources that can be recreated. 
    } 
} 

は* GenericListControllerは、@ R-MENKEはabovを述べたように

8

いくつかのヘルパー・メソッドを持っているだけのUIViewControllerです、私はまだ私のデフォルトのクラスにそれらの2を実装する必要がありますE:(YMMV)は次のよう

Interface Builder "talks" to your code through the ObjC runtime. As such, IB can can access only features of your code that are representable in the ObjC runtime. ObjC doesn't do generics

これが真実である、私の経験で

は、しかし、我々はこの問題を回避することができます。

ここでは不自然な例を作成し、これに失敗したかを確認することができます:ここでは例を実行

class C<T> {} 
class D: C<String> {} 

print(NSClassFromString("main.D")) 

http://swiftstub.com/878703680

あなたはそれがnil

今すぐことができますを印刷していることがわかりますこれをわずかに微調整してもう一度試してください:

http://swiftstub.com/346544378

class C<T> {} 
class D: C<String> {} 

print(NSClassFromString("main.D")) 
let _ = D() 
print(NSClassFromString("main.D")) 

私たちは、この取得:ねえ-O

nil Optional(main.D)

を!それはそれが最初に初期化された後にそれを見つけました。

これをストーリーボードに適用します。私は今アプリケーションでこれを正しく(間違ってまたは間違って)実行しています

// do the initial throw away load 
let _ = CanvasController(nibName: "", bundle: nil) 

// Now lets load the storyboard 
let sb = NSStoryboard(name: "Canvas", bundle: nil) 
let canvas = sb.instantiateInitialController() as! CanvasController 

myView.addSubView(canvas.view) 

期待どおり動作します。

class CanvasController: MyNSViewController<SomeGeneric1, SomeGeneric2> 

は今、私は一般的なのUITableViewサブクラスでこの技術を使用してiOSの上でいくつかの問題に遭遇している次のように私の場合は私のCanvasControllerが宣言されています。私はiOS 9の下でそれを試していないので、YMMV。しかし、私は現在、私が取り組んでいるアプリケーションのために10.11の下でこれをやっていて、大きな問題に遭遇していません。それは私が将来的に問題に遭遇しないということを言っているわけではない、あるいはこれが適切であっても私はこれの完全な結果を知ることができないと主張することはできない。私が言うことができるのは、今のところ問題を取り巻くように見えるということだけです。

私は8月4日、この裏にradrを提出しました:#22133133私はRADRには表示されませんが、bugreport.apple.comの下では、

+0

恐ろしい、あなたこれを歌い、あなたはリストを再利用することができました([SomeGeneric2])?そして、IBOutletsについて、あなたはMyNSViewControllerで宣言されたものを再利用できましたか?あなたの応答に感謝します – sagits

+0

これは完全に動作し、間違った "不可能な"主張を証明します。ありがとう! – hariseldon78

+0

これは基本的には動作しますが、サブクラスを拡張しようとすると何らかの奇妙な理由で破損します – PeejWeej

2

回避策として、ストーリーボードでインスタンス化する前にProductListControllerをObjCランタイム(つまり、AppDelegate?)にロードすることができます。

ProductListController.load() 

乾杯

+0

これまで最高のもの! – shelll

+0

合意、素晴らしい答え、ありがとう:) 1つの警告:これは、メインインターフェイスとして設定されたストーリーボードの最初のviewControllerでは機能しません。そのような場合、iOSがviewControllerをあまりにも早くロードしようとしているように見えます。回避策:load()を呼び出した後、WindowとrootViewControllerを手動で作成してください。 – nils

+0

実際、load()メソッドは自動的に呼び出されるはずなので、私はload()を呼び出すことについてもう一度考えました。良いことは、任意のことを実行することによってクラスをランタイムに登録できることです。それで、私は同じ効果を持つクラス名を印刷するだけです:print( "Loading \(ProductListController.self)class") – nils

関連する問題