RyanCosans
RyanCosans

Reputation: 450

Create swift array extension for a typed array

This question (Create swift array extension for typed arrays [duplicate]) gets very close to answering the question but what is being done within this question can be generic.

This question (How can I extend typed Arrays in Swift?) also gets close but isn't the same question due to the example not restricting the extension to a particular type of array.

Say I have an array of UIView.

[node, node1, node2]

And I want to add an extension func to it like:

[node, node1, node2].hideAll()

extension UIView {
    func hide() { self.hidden = true }
}

extension Array {
    func hideAll() {
        for node in self { (node as! UIView).hide() }
    }
}

I don't want hideAll() to be accessed on anything but an array containing objects of type UIView. Can I and how do I restrict this?

Upvotes: 4

Views: 455

Answers (1)

Milos
Milos

Reputation: 2758

You can try something like:

protocol ShyObject : AnyObject {
    var hidden: Bool { get set }
}

extension SequenceType where Generator.Element : ShyObject {
    func hideAll() { for e in self { e.hidden = true } }
    func showAll() { for e in self { e.hidden = false } }
}

import UIKit

extension UIView : ShyObject {}
// TODO: extend more shy types...

let nodes = Array(count: 5, repeatedValue: UIView())
nodes.hideAll()

If you specifically need hide() method as well, you can provide it generically for all ShyObjects:

extension ShyObject {
    func hide() { hidden = true }
    func show() { hidden = false }
}

And to implement conformance for a type that does not have hidden:

class MyClass {}

extension MyClass : ShyObject {
    var hidden: Bool {
        get { return /* true or */ false }
        set { /* hide me or show me */ }
    }
}

let myObjects = Array(count: 5, repeatedValue: MyClass())
myObjects.hideAll()

We can also have allHidden and allShown properties too:

extension SequenceType where Generator.Element : ShyObject {
    var allShown: Bool { return !contains{ $0.hidden == true } }
    var allHidden: Bool { return !contains{ $0.hidden == false } }
}

extension MutableCollectionType where Generator.Element : ShyObject {
    var allShown: Bool {
        get { return !contains{ $0.hidden == true } }
        set { newValue ? showAll() : hideAll() }
    }
    var allHidden: Bool {
        get { return !contains{ $0.hidden == false } }
        set { newValue ? hideAll() : showAll() }
    }
}

var nodes = Array(count: 5, repeatedValue: UIView()) // note the `var` :(

nodes.allShown = false
nodes.allHidden // true (does not in itself require a mutable collection!)

... which works, but it has a major drawback that the sequence has to be mutable if we want to use a setter. This is because all computed properties with a setter are assumed by compiler to be mutating even when the sequence elements are of a reference type like here... Still, we can at least have getter only allShown and allHidden properties without paying for it with mutability.

Note that, as @dfri commented at one point, this answer is more general than the question explicitly calls for. If this is not desired, @dfri suggests:

protocol Hideable {
    func hide()
}

extension UIView : Hideable  {
    func hide() { self.hidden = true }
}

extension Array where Element: Hideable {
    func hideAll() { for e in self { e.hide() } }
}

Upvotes: 1

Related Questions