简体   繁体   中英

How do you pass a generic class type in a recursive extension (category) function in Swift?

I want to programmatically change the text color of a specific class type ( UILabel , UITextField , etc.) for ALL subviews (not just it's direct children, but also the children of that child and so on) .

I created an extension to UIView that recursively visits all of it's subviews and their subviews. It's currently hard coded to change the color for a single type ( UILabel ).

Here's the code I've got:

import UIKit

extension UIView {
    func setLabelTextColor(color: UIColor) {
        for subview in self.subviews {
            // Visit any subviews of the current subview (this is the recursive part)
            subview.setLabelTextColor(color)

            // Is this a label?  If so, change it's text color.
            if let label = subview as? UILabel {  <-- I WANT TO PASS THIS CLASS
                label.textColor = color
            }
        }
    }
}


class OptimusPrimeViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.setLabelTextColor(UIColor.blueColor())
    }
}

How do I use generics in Swift to pass the class type (like UITextField instead of UILabel ) so I can specify which class' text color I want to change? Maybe there's some block(closure) magic that could do the same thing?

So instead of:

self.view.setLabelTextColor(UIColor.blueColor())

I'd have something like:

self.view.setTextColor(UIColor.blueColor(), forClassType: UITextField.self)

You can define a generic type T in the function definition like this:

extension UIView {
    func setTextColor<T: UIView>(color: UIColor, forClassType _: T.Type) {
        for subview in self.subviews as [UIView] {
            subview.setTextColor(color, forClassType: T.self)

            if let view = subview as? T {
                view.textColor = color
            }
        }
    }
}

However, textColor is not a property of all UIView s, so you need to go a step further. One solution is to define a protocol with a textColor property, then declare protocol conformance for UILabel , UITextField and any other classes you want to use with this function. If the class doesn't already define the textColor property, you'll need to add it in the extension.

So here's the complete code:

protocol HasTextColor {
    var textColor: UIColor! { get set }
}

extension UILabel: HasTextColor {}
extension UITextField: HasTextColor {}

extension UIView {
    func setTextColor<T: UIView where T: HasTextColor>(color: UIColor, forClassType _: T.Type) {
        for subview in self.subviews as [UIView] {
            subview.setTextColor(color, forClassType: T.self)

            if let view = subview as? T {
                view.textColor = color
            }
        }
    }
}

You would call this function exactly as you suggested:

self.view.setTextColor(UIColor.blueColor(), forClassType: UITextField.self)

I think I figured out a way to do this without having to use a protocol and having to explicitly conform every class to that protocol so they can use this function.

Instead It uses introspection with NSObject to see if the view is the right type and has that property. This way I can use it with anything that has textColor and don't have to add as much code.

func setGlobalTextColor<T: NSObject>(color: UIColor, forClassType: T.Type) {
    for subview in self.subviews as [UIView]{

        // Visit every child before applying changes
        subview.setGlobalTextColor(color, forClassType: T.self)

        // Only change text color for the class we called the function with
        if subview.isMemberOfClass(T) {

            // Sanity check to make sure we only set if class has this property
            if subview.respondsToSelector(Selector("textColor")) {

                // Use Key Value Coding to set the textColor        
                subview.setValue(color, forKey: "textColor")
            }
        }
    }
}

So you can call it with different classes that have a UIColor textColor property:

self.view.setTextColor(UIColor.blueColor(), forClassType: UILabel.self)
self.view.setTextColor(UIColor.redColor(), forClassType: UITextField.self)

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