Общий контроллер в swift 2.0 с использованием раскадровки
Я пытаюсь создать GenericListController для своего приложения.
У меня есть 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 не будет автозаполнять имя класса (возможно, потому, что он не может определить класс)
Ответы
Ответ 1
Это объясняет, почему это невозможно: использовать общий класс как пользовательский вид в построителе интерфейса
Интерфейс Builder "ведет переговоры" с вашим кодом через среду выполнения ObjC. Таким образом, IB может иметь доступ только к функциям вашего кода, которые могут быть представлены в среде выполнения ObjC. ObjC не выполняет дженерики
Этот намек на возможную работу: generics в 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)
}
}
Обновление:
Разрешить некоторый доступ к атрибутам для общего класса.
Изменил его на базовый класс, чтобы легко протестировать его на игровой площадке. Материал 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()
Ответ 2
Как указано выше @r-menke:
Интерфейс Builder "ведет переговоры" с вашим кодом через среду выполнения ObjC. Таким образом, IB может иметь доступ только к функциям вашего кода, которые могут быть представлены в среде выполнения ObjC. ObjC не выполняет дженерики
Это верно,
Однако, по моему опыту, мы можем обойти проблему следующим образом (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)
Работает так, как вы ожидали. В моем случае my CanvasController
объявляется следующим образом:
class CanvasController: MyNSViewController<SomeGeneric1, SomeGeneric2>
Теперь я столкнулся с некоторыми проблемами в iOS, используя этот метод с общим подклассом UITableView. Я не пробовал это под iOS 9, поэтому YMMV. Но сейчас я делаю это до 10.11 для приложения, над которым я работаю, и не сталкивался с какими-либо серьезными проблемами. Это не означает, что я не буду сталкиваться с какой-либо проблемой в будущем или что это даже уместно сделать, я не могу заявить, что знаю все последствия этого. Все, что я могу сказать, это то, что на данный момент похоже на проблему.
Я подал рад на это обратно 4 августа: # 22133133 Я не вижу его в открытом RADR, но под bugreport.apple.com он по крайней мере указан под моей учетной записью, что бы это ни стоило.
Ответ 3
Сообщение о некотором коде, которое я сделал с помощью пользователя 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 с некоторыми вспомогательными методами
Ответ 4
В качестве обходного пути вы можете просто загрузить свой ProductListController
в среду выполнения ObjC (то есть AppDelegate?), прежде чем создавать его с помощью раскадровки.
ProductListController.load()
Приветствия