Подтвердить что ты не робот

Общий контроллер в 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 не будет автозаполнять имя класса (возможно, потому, что он не может определить класс)

4b9b3361

Ответ 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()

Приветствия