Search code examples
iosswiftgenericsswift-extensions

Generic `getText()` function for UI elements in Swift


For a lot of actions, I need to check if the content of label.text, textView.text, textField.text etc. is nil.

So I created some extensions:

extension UILabel {
    func getText() -> String {
        return self.text ?? ""
    }
}

extension UITextField {
    func getText() -> String {
        return self.text ?? ""
    }
}

extension UITextView {
    func getText() -> String {
        return self.text ?? ""
    }
}

The extensions are very redundant. A similar case is when I need to cast an Int, Double, Float etc. to another number format. What I want is a simple toInt() with a return of -1 or 0 when something went wrong.

So how can I create a generic function for the toString() or toInt()? I read about extensions and generics in the Apple documentation, but I didn't see a solution for my problem.

Finally, I tried to expand UIView with the same extension, because it's a superclass of UILabel etc., but I can't call getText().

So what is a good method to create generic extensions?


Solution

  • There are a couple of dubious things here: I wouldn't want any toInt() function to just return -1 or 0 when things went wrong. Swift has a good optionality system for a reason, and there are several obvious pitfalls introduced by returning -1 or 0. I also don't know what you plan to do for implementing getText() on a UIView. Lots of views don't have text. I don't know what this implementation would mean or do.

    So I'll ignore those two details and focus on the primary question, which seems to be the redundancy of your extensions. There is a cleaner way, via protocol extensions, to cut down on duplication.

    protocol TextProviding {
        var text: String? { get }
    }
    
    extension TextProviding {
        // Following your example here. I would prefer calling
        // this a `var textOrEmpty: String` but same idea.
        func getText() -> String {
            return text ?? ""
        }
    }
    
    // To conform additional types, you just need 1 line
    extension UILabel: TextProviding { }
    extension UITextField: TextProviding { }
    

    EDIT: as some have pointed out, using the above code with extension UITextView: TextProviding { } will not work, because UITextView's text is a String!, not a String?. Unfortunately, this means if you want this to work for UITextView as well, you should rename the var text requirement to something else (and this means you will need a couple extra lines to manually conform UILabel and UITextField).

    protocol TextProviding {
        var string: String? { get }
    }
    
    extension TextProviding {
        var stringOrEmpty: String {
            return string ?? ""
        }
    }
    
    extension UILabel: TextProviding { 
        var string: String? { return text }
    }
    extension UITextField: TextProviding { 
        var string: String? { return text }
    }
    extension UITextView: TextProviding {
        var string: String? { return text }
    }