アプリのGenericListControllerを作成しようとしています。
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を削除すると、すべてのアウトレットでこのエラーが発生します。ストーリーボードはジェネリックでスーパーをロードできないと思います。どうすればそれを機能させることができますか?
私のコード:
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)
}
}
-編集-
ジェネリッククラスを拡張してストーリーボードにクラスを追加しようとすると、xcodeがクラス名をオートコンプリートしないことがわかりました(おそらくクラスを検出できないため)
これは、それが不可能な理由に答えます: インターフェイスビルダーのカスタムビューとしてジェネリッククラスを使用します
Interface Builderは、ObjCランタイムを介してコードと「対話」します。そのため、IBは、ObjCランタイムで表現可能なコードの機能にのみアクセスできます。 ObjCはジェネリックを行いません
考えられる回避策のこのヒント: obj-cのジェネリック obj-cでジェネリックViewControllerを作成すると、IBがそれを受け入れますか?
プロトコルの使用を検討しましたか?これはストーリーボードを混乱させることはありません。簡単にテストできるようにコードを少し変更しました。これの欠点は、プロトコルにプロパティを保存できないことです。したがって、それらをコピーして貼り付ける必要があります。利点は、それが機能することです。
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()
@ r-menkeが上で述べたように:
Interface Builderは、ObjCランタイムを介してコードと「対話」します。そのため、IBは、ObjCランタイムで表現可能なコードの機能にのみアクセスできます。 ObjCはジェネリックを行いません
これは本当です、
しかし、私の経験では、次のように問題を回避できます(YMMV)。
ここで不自然な例を作成し、これがどのように失敗するかを確認できます。
_class C<T> {}
class D: C<String> {}
print(NSClassFromString("main.D"))
_
ここで実行例:
nil
が出力されることがわかります
これを少し微調整して、もう一度試してみましょう。
http://swiftstub.com/346544378
_class C<T> {}
class D: C<String> {}
print(NSClassFromString("main.D"))
let _ = D()
print(NSClassFromString("main.D"))
_
これを取得します:
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)
_
期待どおりに機能します。私の場合、私のCanvasController
は次のように宣言されています。
_class CanvasController: MyNSViewController<SomeGeneric1, SomeGeneric2>
_
今、私は一般的なUITableViewサブクラスでこのテクニックを使用してiOSでいくつかの問題に遭遇しました。私はiOS9で試したことがないので、YMMVです。しかし、私は現在、作業中のアプリに対して10.11でこれを行っており、大きな問題は発生していません。それは、私が将来問題にぶつからないということではなく、これが適切であるということでもありません。私はこれの完全な影響を知っていると主張することはできません。私が言えるのは、今のところ、問題を回避しているように見えるということだけです。
私は8月4日にこれにradrを提出しました:#22133133オープンRADRには表示されませんが、bugreport.Apple.comの下では、少なくとも私のアカウントの下にリストされています。
ユーザーRmenkeなどの助けを借りてアーカイブしたコードを投稿します。私の目標は、UISearchBarDelegate、UITableViewDelegate、およびgetDataメソッド(jsonを正しく解析できるようにするために型クラスが必要)を処理できる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メソッドを実装できましたが、デフォルトのクラスに2つ実装する必要があります。
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は、いくつかのヘルパーメソッドを備えた単なるUIViewControllerです。
回避策として、ストーリーボードでインスタンス化する前に、ProductListController
をObjCランタイム(つまり、AppDelegate?)にロードすることができます。
ProductListController.load()
乾杯