2015-09-29 2 views
23

Im пытается создать GenericListController для моего приложения.
Общий контроллер в swift 2.0 с помощью раскадровки

У меня есть ProductListController, которые расширяют этот общий контроллер, который расширяет UIViewController. Я подключил 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.' 

Я получаю эту ошибку для всех моих торговых точек, если я удалить общий T из GenericListController он работает. Я думаю, что раскадровка не может загружать супер с помощью дженериков. Как я могу заставить его работать?

Мой код:

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--

Я обнаружил, что если я направить общий класс и попытаться добавить класс к раскадровке Xcode обыкновение автозаполнение имя класса (вероятно, потому что не могу обнаружить класс)

+3

почему вам нужно добавить общую модель в UIViewController? –

+1

Поскольку моя логика для контроллера списка всегда одна и та же, она просто изменяет тип объекта. У меня есть productList, saleList, userList, и все они используют одни и те же методы и логику. То же самое для DetailController, AddController. Если я не могу использовать generics, мне придется скопировать и вставить мою функцию 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 Возможно, вы можете создать общий ViewController в obj-c, а затем 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)    
    } 
} 

Update: Разрешить некоторый доступ к атрибутам для общего класса. Изменил его на базовый класс, чтобы легко протестировать его на игровой площадке. Материал 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 был T, где T: MyModel (этот класс содержит метод getId), возможно ли это? Я знаю, что это кажется странным, но мне нужно передать точный classType в мой getData, чтобы он мог анализировать json из webservice и возвращать типизированный список (который необходим для реализации других общих методов). – sagits

+0

@sagits Я думаю, что вы используете не общие , Дженерики являются удивительными при манипулировании X, который находится в объектах, которые соответствуют Y. Нецелесообразно заменять все подклассы. В вашем случае подкласс все равно будет идти. Но обновил ответ, чтобы получить некоторый доступ к атрибутам, которые унаследованы от суперкласса (ProductModel). –

+0

Спасибо, я скоро отправлю свой код, это будет иметь больше смысла. Это сработало, но я использовал typealias T: MyModel. Теперь я пытаюсь обновить значение списка из getData, я попытался обновить список протоколов и передать список из подкласса (getData (myList: [T]) {myList = listFromServer}. Но ни один из них не работал, как я должен сделайте это? – sagits

2

Почтовый код, который я забронировал с помощью пользователя R menke и других. Моя цель состоит в том, чтобы иметь GenericListProtocol, который может обрабатывать UISearchBarDelegate, UITableViewDelegate и мой метод GetData (который нужен класс типа, чтобы иметь возможность правильно разобрать JSON.

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 с некоторыми вспомогательными методами

8

Как @ г-Менке указанных Abov е:

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

Это верно,

В моем опыте, однако, мы можем обойти вопрос следующим образом (YMMV).

Мы можем сделать надуманный пример здесь и посмотреть, как это не удается:

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")) 

Мы получаем это:

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 9, поэтому YMMV. Но сейчас я делаю это до 10.11 для приложения, над которым я работаю, и не сталкивался с какими-либо серьезными проблемами. Это не означает, что я не буду сталкиваться с какой-либо проблемой в будущем или что это даже уместно сделать, я не могу заявить, что знаю все последствия этого. Все, что я могу сказать, это то, что на данный момент, похоже, проблема.

Я зарегистрировал radr на этой спине 4 августа: # 22133133 Я не вижу его в открытом RADR, но под bugreport.apple.com он по крайней мере указан под моей учетной записью, что бы это ни стоило.

+0

Awesome, u петь это, вы смогли повторно использовать список ([SomeGeneric2])? А что касается IBOutlets, могли ли вы повторно использовать те, которые были объявлены в MyNSViewController? Спасибо за ваш ответ – sagits

+0

Это прекрасно работает и не соответствует требованиям «невозможного». Благодаря! – hariseldon78

+0

Это в основном работает, но по какой-то нечетной причине он ломается при попытке расширить подклассы – PeejWeej

2

В качестве обходного пути вы можете просто загрузить свой ProductListController в среду выполнения ObjC (то есть AppDelegate?), Прежде чем создавать его с помощью раскадровки.

ProductListController.load() 

Приветствия

+0

Лучшая вещь когда-либо! – shelll

+0

Согласен, отличный ответ, спасибо :) Одно предостережение: Это не работает для начального viewController в раскадровке, установленной как Основной интерфейс. Похоже, что iOS пытается загрузить viewController слишком рано в этом случае. Обход проблемы: просто создайте Window и rootViewController вручную после вызова load(). – nils

+0

На самом деле, у меня были другие мысли о вызове load(), поскольку этот метод должен быть вызван автоматически. Хорошей новостью является то, что мы можем зарегистрировать класс со временем выполнения, совершая с ним любую произвольную вещь. Вот почему я бы просто напечатал имя класса, которое имеет такой же эффект: print («Загрузка \ (ProductListController.self) class») – nils

Смежные вопросы