2

Here's the code,

protocol TestType : AnyObject {

}

class Generic<TestType> : NSObject {

    private let filterFunction : (TestType,String) -> Bool

    init(filter: @escaping (TestType,String) -> Bool) {
        filterFunction = filter
    }
}

class Parent : UIViewController {
    public var generic : Generic<TestType>!
}

class Child : Parent {

    override func viewDidLoad() {
        // ERROR THIS LINE
        generic = Generic<Object>(filter: { (str1, str2) -> Bool in
            return true
        })
    }
}

class Object : TestType {

}

The error is:

Cannot assign value of type 'Generic<Object>' to type 'Generic<TestType>!'

I tried many things, like typealias, but can't compile the code. The problem is that i don't want a Parent<TestType> or Child<TestType> class, since i want to be able to use it in IB.

How can i store a reference of Generic in the Parent, and initialize it in Child (dynamically, by setting the concrete TestType like Object or another)

Raz-X
  • 51
  • 1
  • 5
  • Compare [Swift generic coercion misunderstanding](http://stackoverflow.com/q/41976844/2976878) & [How do I store a value of type Class in a Dictionary of type \[String:Class\] in Swift?](http://stackoverflow.com/q/38590548/2976878) – a `Generic` is not convertible to a `Generic`. – Hamish Apr 05 '17 at 21:40
  • Why not just create a `Generic` instance? – Hamish Apr 05 '17 at 21:42
  • Thx @Hamish, your posts are very helpful. In my code, Parent Class is a extended TableviewController with some search capabilities. I want my Child Controller to inherit these capabilities, and just give a list of items and filters depending on these items to the Parent ViewController to work well. – Raz-X Apr 05 '17 at 23:53
  • Okay, but my point is why not create a `Generic` in `Child`? Assuming your filter function should discount arguments that aren't of type `Object`, you could always just add a `guard let object = str1 as? Object else { return false }` to your predicate. It would help if you gave us an idea of what you're actually using `Generic` for. – Hamish Apr 06 '17 at 15:03

1 Answers1

0

I finally succeed doing what i wanted!

Not perfect at all, feel free to comment for architecture improvements (especially on the asBaseProtocol() part...)

Here's my complete code (Swift 3.0)

DataFilter

protocol DataFilterDelegate : class {
    func didFilter()
    func didUpdateItems()
}

class DataFilter<T> {
    public weak var delegate : DataFilterDelegate?

    private var items : [SelectableItem<T>]?
    private var filteredItems : [SelectableItem<T>]?
    var source: [SelectableItem<T>]? {
        get {
            if filteredItems != nil {
                return filteredItems
            }
            return items
        }
    }

    var filter : (T,String) -> Bool
    var populateCell : (T) -> UITableViewCell

    init(filter : @escaping (T,String) -> Bool, populateCell: @escaping (T) -> UITableViewCell) {
        self.filter = filter
        self.populateCell = populateCell
    }

    func updateItems(_ items: [T]) {
        self.items = [SelectableItem<T>]()
        for item in items {
            self.items?.append(SelectableItem(item))
        }
        delegate?.didUpdateItems()
    }

    func filterItems(text : String) {
        filteredItems = (text == "") ? nil : items?.filter { item in
            filter(item.item, text)
        }
        delegate?.didFilter()
    }

    func selectedItems() -> [T]? {
        guard let items = items else {
            return nil
        }
        var selectedItems = [T]()
        for item in items {
            if item.isSelected {
                selectedItems.append(item.item)
            }
        }
        return selectedItems
    }
}

extension DataFilter where T : FIRDataObject {
    func asBaseProtocol() -> DataFilter<FIRDataObject> {
        return DataFilter<FIRDataObject>(filter: filterAsBaseProtocol(), populateCell: populateCellAsBaseProtocol())
    }

    private func filterAsBaseProtocol() -> ((FIRDataObject,String) -> Bool) {
        return { (object, text) -> Bool in
            self.filter(object as! T, text)
        }
    }

    private func populateCellAsBaseProtocol() -> ((FIRDataObject) -> UITableViewCell) {
        return { (object) -> UITableViewCell in
            self.populateCell(object as! T)
        }
    }
}

ParentViewController Class

class ParentViewController : UIViewController {
    public var dataFilter : DataFilter<FIRDataObject>? {
        didSet {
            dataFilter!.delegate = self
        }
    }

    // Some Functions using dataFilter
}

ChildViewController Class

class ChildViewController : Parent {

    // Stored as a variable to not have to cast objects to the good type everytime I access dataFilter
    private var patientDataFilter = DataFilter<Patient>(filter: { patient, text in
        patient.firstname.contains(text) ||
            patient.lastname.contains(text)
    }
        , populateCell: { patient in
            let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Patient")
            cell.textLabel?.text = patient.lastname + " " + patient.firstname
            cell.detailTextLabel?.text = patient.postalCode + " " + patient.city
            return cell
    })

    override func viewDidLoad() {
        super.viewDidLoad()

        dataFilter = patientDataFilter.asBaseProtocol()
    }

    func someFunc() {
        let patient1 = patientDataFilter.source[0].item
        // OR
        let patient2 = dataFilter.source[0].item as! Patient
    }
}
Raz-X
  • 51
  • 1
  • 5