简体   繁体   中英

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:

对成员alpha的模糊引用

无法分配给属性字段是让常量

无法分配给类型的不可变表达式

启用了对成员的模糊引用

Alternatively, I tried using methods instead of computed properties, eg

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

This "works" but includes ugly casting:

使用setter

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

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

What am I missing??

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
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM