David James
David James

Reputation: 2466

Swift extension to MutableCollectionType not working with array of generic types

I have an extension on UIView that returns an array of sub-views based on meta type. This way I can query the view hierarchy for example for all UITextFields, etc.

extension UIView {
    func subviewsOfType<T:UIView>(_:T.Type) -> [T]? {
        ...
    }
}

This can be called like so:

let fields = loginView.subviewsOfType(UITextField.self)

Now, I would like to act on that array [UITextField] with an interface that matches the generic passed in, in other words whatever is available on UITextField, example:

fields.alpha = 0.3 // via UIView
fields.enabled = false // via UIControl

I have created extensions on MutableCollectionType to provide these overrides:

extension MutableCollectionType where Generator.Element == UIView {

    var alpha:CGFloat {
        get {
            if let first = self.first {
                return first.alpha
            } else {
                return 1.0
            }
        }
        set {
            for view in self {
                view.alpha = newValue
            }
        }
    }
}

extension MutableCollectionType where Generator.Element == UIControl {

    var enabled:Bool {
        get {
            if let first = self.first {
                return first.enabled
            } else {
                return true
            }
        }
        set {
            for view in self {
                view.enabled = newValue
            }
        }
    }
}

However, when I try to use these extensions they do not work as expected. :(

Here are the variations of calling code I have tried and their errors:

Ambiguous reference to member alpha

Cannot assign to property fields is let constant

Cannot assign to immutable expression of type

Ambiguous reference to member enabled

Alternatively, I tried using methods instead of computed properties, e.g.

func alpha(alpha:CGFloat) -> Self {
    for view in self {
        view.alpha = alpha
    }
    return self
}

This "works" but includes ugly casting:

Using setters

I would prefer to use regular properties on those arrays via the MutableCollectionType extensions:

loginView.subviewsOfType(UITextField.self).enabled = true

What am I missing??

Upvotes: 3

Views: 286

Answers (2)

Martin R
Martin R

Reputation: 539815

There are two problems. As Rob already said, you have to restrict the extension methods to subclasses of UIView/UIControl.

But then the compiler still complains on

if let fields = self.view.subviewsOfType(UITextField.self) {
    fields.alpha = 0.3
    fields.enabled = false
}

with

error: cannot assign to property: 'fields' is a 'let' constant

The reason is that setting a property is considered a mutation of the array (which is a value type), and you cannot mutate constants.

However, the array elements itself in your case are reference types (instances of a class). The setter methods mutate only the referenced objects, and leave the array itself unchanged. Therefore you can declare the setter method as nonmutating:

var alpha:CGFloat {
    get { ... }
    nonmutating set {
        for view in self {
            view.alpha = newValue
        }
    }
}

And now the above code compiles and works without problems.

Upvotes: 4

Rob Napier
Rob Napier

Reputation: 299355

You've constrained to exactly UIView, not subclasses of UIView. You meant this:

extension MutableCollectionType where Generator.Element: UIView { ... }
extension MutableCollectionType where Generator.Element: UIControl { ... }

Note the : rather than ==.

And in this particular case, you'll need fields to be a var since you mutate it:

if var fields = loginView.subviewsOfType(UITextField.self) {
    fields.alpha = 0.3
    fields.enabled = true
}

Upvotes: 4

Related Questions