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

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?

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 }
}

For the first problem, you should only extend UIView and check whether its a label, a text field or a text view with if let.
extension UIView {
func getText() -> String {
if let label = self as? UILabel {
return label.text ?? ""
} else if let textField = self as? UITextField {
return textField.text ?? ""
} else if let textView = self as? UITextView {
return textView.text ?? ""
}
return ""
}
}
The advantage of this method over creating a protocol is that you can easily extend it, even if the text/title is retrievable with another method, for example, with a UIButton.
[...]
else if let button = self as? UIButton {
return button.title(for: .normal) ?? ""
}

This one-liner reduces redundant code (what you wanted) than all other answers posted here. No need for crazy condition-checks or multiple extensions.
extension UIView {
func getText() -> String? {
return self.responds(to: #selector(getter: UILabel.text)) ?
self.perform(#selector(getter: UILabel.text))?.takeUnretainedValue() as? String : nil
}
}

Create a protocol with an extension and then extend every class to adopt the protocol :)
protocol GetTextProtocol {
var text: String? {get}
func getText() -> String
}
extension GetTextProtocol {
func getText() -> String {
return self.text ?? ""
}
}
extension UILabel: GetTextProtocol {
var text: String? {
return self.text
}
}
extension UITextView: GetTextProtocol {
var text: String? {
return self.text
}
}

Related

How to make Xcode display a string representation for user class?

Consider the following class:
class CalculatorButton: CalculatorButtonProtocol, CustomStringConvertible {
var type: CalculatorButtonType
var description: String {
return label
}
let label: String
init(_ label: String, type: CalculatorButtonType) {
self.label = label
self.type = type
}
func action(n1: String, n2: String?) -> String {
fatalError("Not implemented")
}
func format(_ n: Float) -> String {
if n == floor(n) {
return String(Int(n))
}
return String(n)
}
}
While debugging using breakpoints, it's very useful to see a string representation of a class in the debugger window. For the above, I'd like to see the label. However, Xcode shows the reference of the button instance instead. People are saying that adopting CustomStringConvertible protocol gives a class human-readable representation, but that didn't help.
GitHub link: https://github.com/asarkar/ios-bootcamp/tree/master/Calculator-SwiftUI
Make your class implement CustomDebugStringConvertible, this will print your custom representation instead of the instance address.
class CalculatorButton: CustomDebugStringConvertible {
// ...
var debugDescription: String {
label
}
}

Swift protocol default implementation for optional readonly variables

I have the following piece of code, the protocol MyDisplayable has three optional Strings, and I have a default implementation of the protocol via extension. My question is, since I'm sure the extension returns the three strings, is there a way I can use them as non-optional and is there any risk if some other implementation overwrites it? (see the question points 1 and 2 in code below)
Thanks a lot!
protocol MyDisplayable {
var displayName: String? { get }
var shortDescription: String? { get }
var longDescription: String? { get }
}
protocol MyObject : MyDisplayable, CustomStringConvertible {
}
extension MyObject {
var displayName: String? {
return "noname"
}
var shortDescription: String? {
return "something can't be described"
}
var longDescription: String? {
return "no way to describe it further"
}
var description: String {
// **1. is there a way to use the strings as if they are non-optional?**
// **2. is it a problem if another class implements the protocol and returns `nil` for any of the strings, but here they are force unwrapped?**
return "\(displayName!): \(shortDescription!)\n\(longDescription!)"
}
}
class Something : MyObject {
}
let something = Something()
print("Something: \(something)")
Unfortunately, it's not possible to treat a declared optional as a non-optional.
You have declared those strings as optional in your protocol, thus when you implement that protocol they stay optional.
However, you can use getter-setter to ensure that your variables always store some value even when they are declared as optional.
I'll elaborate with some code :
protocol MyDisplayable {
var displayName: String? { get set }
var shortDescription: String? { get set }
var longDescription: String? { get set }
}
protocol MyObject : MyDisplayable, CustomStringConvertible {
}
extension MyObject {
var displayName: String? {
get {
return "noname"
}
set {
newValue ?? ""
}
}
var shortDescription: String? {
get {
return "something can't be described"
}
set {
newValue ?? ""
}
}
var longDescription: String? {
get {
return "no way to describe it further"
}
set {
newValue ?? ""
}
}
var description: String {
// **1. is there a way to use the strings as if they are non-optional?**
// **2. is it a problem if another class implements the protocol and returns `nil` for any of the strings, but here they are force unwrapped?**
return "\(displayName!): \(shortDescription!)\n\(longDescription!)"
}
}
class Something : MyObject {
}
let something = Something()
print("Something: \(something)")
Now even if some other class overwrites a nil value to those strings they will return empty string "".
They will still be optional as they are declared optional, but now they will always have a non-nil value.
How about conditional unwrapping in your default implementation?
return "\(displayName ?? "" ): \(shortDescription ?? "" )\n\(longDescription ?? "")"

Using generic function in base class

What I'm trying to do...
In my app I have a lot of form fields that look alike with a bunch of custom functionality (change color on highlight e.c.t.).
I want to create a sort of wrapper class, that abstracts all of this code, then inherit from that to implement my different input types such as date input and text input.
The inherited classes will just need to setup the correct input control for it's type.
What i've tried
This is more like pseudo-code. I have been trying for hours but I just don't understand how to achieve what I need
I think i start with a base class, this needs to define a reference to the input control, and a few methods that each one will override such as being able to set or get the current value
class BaseInput<T>: UIView {
let label = UILabel()
let control: T
... A bunch of methods for layout and stuff ...
func setControlValue(_ value: U) {
print("I'm not a real input yet, so i can't do that")
}
}
I then create an inherited class for a date input. This uses a basic label for the control, and internally will use a UIDatePicker to set the value
class DateInput: BaseInput<UILabel> {
override init() {
self.control = UILabel()
}
override func setControlValue(_ value: Date) {
MyGlobalDateFormatter.string(format:value)
}
}
and another for a text input field
class TextInput: BaseInput<UITextField> {
override init() {
self.control = UITextField()
}
override func setControlValue(_ value: String) {
control.textLabel!.text = value
}
}
What i'm ultimately looking for is the ability to initialise a input component, and for the MyInput.control property to be of the correct class for that specific input, and for the setControlValue method to accept the correct kind of Data (i.e. a String, Int or Date depending on the type of control)
I believe this can be solved using generic's but i'm really struggling to understand how. If anyone can point me in the right direction that would be great.
Note: I don't expect or want anyone to write all of the code for me. Pseudo-code would be enough to allow me to work it all out.
Attempt 1:
protocol CustomControl {
associatedtype Control
associatedtype Value
var control : Control { get }
var label : UILabel { get }
func setControlValue(_ value: Value)
}
class AbstractInputField: UIView {
// This class contains all the setup
// for the label and wrapping UI View
}
class TextInputField: AbstractInputField, CustomControl {
typealias Control = UITextField
typealias Value = String
let control = UITextField()
func setControlValue(_ value: String) {
control.text = value
}
}
class DateInputField: AbstractInputField, CustomControl {
typealias Control = UILabel
typealias Value = String
let control = UILabel()
private let picker = UIDatePicker()
func setControlValue(_ value: Date) {
control.text = GlobalDateFormatter.string(from: value)
}
.. Also in this class it's a bunch of date picker methods ..
}
Elsewhere if I do:
override func viewDidLoad() {
let firstInput = makeControl("text")
firstInput.label.text = "First name"
firstInput.setControlValue(myUser.first_name)
let dobInput = makeControl("date")
dobInput.label.text = "Date of birth"
dobInput.setControlValue(myUser.dob)
}
func makeControl(controlType: String) -> CustomControl {
// Im using strings just for testing, i'd probably make this an enum or something
if controlType == "text" {
return TextInputField()
} else {
return DateInputField()
}
}
I get the error: `Protocol 'CustomControl' can only be used as a generic constraint because it has Self or associated type requirements
What i'm ultimately trying to achieve, is a very simple API to my inputs where i can set the label text, and set the input value. The rest of my app doesn't care if it's a textfield, textview or complete custom input type. My app wants to work with the protocol (or a base class of some kind) that says it has these methods & properties.
Maybe i'm being stupid.
I would suggest to use protocols.
protocol CustomControl {
associatedtype Control
associatedtype Value
var control: Control { get }
func setControlValue(_ value: Value)
}
and then create custom classes conform to this protcol
class CustomTextField: CustomControl {
typealias Control = UITextField
typealias Value = String
let control: UITextField
init() {
self.control = UITextField()
}
func setControlValue(_ value: String) {
control.text = value
}
}
Edited:
class CustomLabel: CustomControl {
typealias Control = UILabel
typealias Value = String
let control: UILabel
init() {
self.control = UILabel()
}
func setControlValue(_ value: String) {
control.text = value
}
}
Edit 2: Alternative approach
protocol HasSettableValue: class {
associatedtype Value
var customValue: Value { get set }
}
protocol IsInitializable {
init()
}
extension UITextField: IsInitializable {}
extension UITextField: HasSettableValue {
typealias Value = String?
var customValue: String? {
get {
return text
}
set {
text = newValue
}
}
}
class BaseClass<T> where T: HasSettableValue, T: IsInitializable {
let control: T
init() {
self.control = T()
}
func setControlValue(_ value: T.Value) {
control.customValue = value
}
}
class CustomTextField: BaseClass<UITextField> {
}
let customTextField = CustomTextField()
customTextField.setControlValue("foo")
print(customTextField.control) // prints <UITextField: 0x...; frame = (0 0; 0 0); text = 'foo'; opaque = NO; layer = <CALayer: 0x...>>
Final update:
The problem with protocols having associated types is you can't use them for declaration of variables. You always need to specify the concrete implementation.
I guess I found a solution fitting your needs:
enum ControlType {
case textField, datePicker
}
enum ControlValueType {
case text(text: String)
case date(date: Date)
var string: String {
switch self {
case .text(text: let text):
return text
case .date(date: let date):
// apply custom format
return "\(date)"
}
}
var date: Date {
switch self {
case .date(date: let date):
return date
default:
preconditionFailure("`date` can be only used with `.date` value type")
}
}
}
protocol Control: class {
var controlValue: ControlValueType { get set }
}
class TextInputField: Control {
private let textField = UITextField()
var controlValue: ControlValueType {
get {
return .text(text: textField.text ?? "")
}
set {
textField.text = newValue.string
}
}
}
class DateInputField: Control {
private let picker = UIDatePicker()
var controlValue: ControlValueType {
get {
return .date(date: picker.date)
}
set {
picker.date = newValue.date
}
}
}
func createControl(ofType type: ControlType) -> Control {
switch type {
case .textField:
return TextInputField()
case .datePicker:
return DateInputField()
}
}
let customDatePicker = createControl(ofType: .datePicker)
customDatePicker.controlValue = .date(date: Date())
print(customDatePicker.controlValue.string) // prints 2017-09-06 10:47:22 +0000
let customTextFiled = createControl(ofType: .textField)
customTextFiled.controlValue = .text(text: "Awesome text")
print(customTextFiled.controlValue.string) // prints Awesome text
I hope this helps.
PS: What you are trying to achieve is not very common pattern in iOS so far I know.

Swift extension clause where this or that

I want an extension for two classes UITextField and UITextView and the code is identical, but I have trouble coming up with an extension that will work for them both.
I am using ReactiveCocoa and I currently have this
import UIKit
import ReactiveCocoa
import enum Result.NoError
typealias NoError = Result.NoError
// How to DRY up this code?
extension UITextField {
func textSignalProducer() -> SignalProducer<String, NoError> {
return self.rac_textSignal()
.toSignalProducer()
.map { $0 as! String }
.flatMapError { error in SignalProducer<String, NoError>(value: "") }
}
}
extension UITextView {
func textSignalProducer() -> SignalProducer<String, NoError> {
return self.rac_textSignal()
.toSignalProducer()
.map { $0 as! String }
.flatMapError { error in SignalProducer<String, NoError>(value: "") }
}
}
How would I write an extension that would work for both? I was trying to do something like
protocol TextSignalProducer {}
extension TextSignalProducer where Self: ???? {
// Same code as is duplicated in both current extensions...
}
but I have no idea how to specify Self as either UITextField or UITextView. Something like where Self == UITextField || Self == UITextView would probably make this possible.
Is there a nice way to accomplish what I want to try? Is this really necessary (I don't know the naming conventions for protocols/extensions)
import UIKit
import ReactiveCocoa
import enum Result.NoError
typealias NoError = Result.NoError
protocol TextSignal {
func rac_textSignal() -> RACSignal!
}
extension UITextField: TextSignal, TextSignalProducer {}
extension UITextView: TextSignal, TextSignalProducer {}
protocol TextSignalProducer {}
extension TextSignalProducer where Self: TextSignal {
func textSignalProducer() -> SignalProducer<String, NoError> {
return self.rac_textSignal()
.toSignalProducer()
.map { $0 as! String }
.flatMapError { error in SignalProducer<String, NoError>(value: "") }
}
}
I am using Swift 2.1, Xcode 7.2 and ReactiveCocoa 4.0.1
You can cut down your proposed solution to a single dummy protocol:
protocol TextSignalProducer {
func rac_textSignal() -> RACSignal!
}
extension TextSignalProducer {
func textSignalProducer() -> SignalProducer<String, NoError> {
return self.rac_textSignal()
.toSignalProducer()
.map { $0 as! String }
.flatMapError { error in SignalProducer<String, NoError>(value: "") }
}
}
extension UITextField: TextSignalProducer {}
extension UITextView: TextSignalProducer {}
I don't think there's a more concise way than that, though. UITextField and UITextView's rac_textSignal() implementations have nothing in common.
UITextView and UITextField conform to UITextInput protocol. If rac_textSignal base on this protocol (I'm not sure because I don't have any project with RactiveCocoa at hand :) ) you can do this:
protocol Cos {
func textSignalProducer() -> String
}
extension UITextView: Cos {
}
extension UITextField: Cos {
}
extension Cos where Self: UITextInput {
func textSignalProducer() -> String {
return "dsfsdf"
}
}
let cos = UITextView()
cos.textSignalProducer()
let cos2 = UITextField()
cos2.textSignalProducer()

Generic Function to Output to UITextField or UILabel

I am trying to write a function in Swift 2 for iOS that processes some text and writes it to either a UITextField or a UILabel. Currently, I have the following that works:
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var textLabal: UILabel!
​func writeSomeText(string: String, toOutput: UITextField) {
// do some text processing
toOutput.text = // processed string
}
​func writeSomeText(string: String, toOutput: UILabel) {
// do some text processing
toOutput.text = // processed string
}
As you see, right now I am overloading the function, essentially duplicating it for both UITextField and UILabel, and since I have a bunch of text processing that is exactly the same, I am duplicating code.
Is there any way to write a function using generics to achieve this with one function definition?
try look at this approach ...
import UIKit
protocol P {
func foo(str: String)->String
}
extension P {
func foo(str: String)->String {
// do some processing
let res = str
return res
}
}
extension UILabel:P {
func bar(str: String) {
self.text = foo(str)
}
}
extension UITextField:P {
func bar(str: String) {
self.text = foo(str)
}
}
let l = UILabel()
l.text = "alfa"
l.bar("ALFA")
let t = UITextField()
t.text = "beta"
t.bar("BETA")
print(l.text, t.text) // Optional("ALFA") Optional("BETA")
by the way, you don't need protocol P at all :-), it is there just as a 'namespace' (not to have a global func foo)
another approach is to define some common protocol and extend UILabel and UITextField
import UIKit
protocol P: class {
var text: String? {get set}
}
extension UILabel: P {}
extension UITextField: P {}
func writeSomeText<T:P>(string: String, toOutput: T ){
// do some text processing
toOutput.text = string
}
let l = UILabel()
writeSomeText("label", toOutput: l)
l.text // "label"
let tv = UITextView()
tv.text = "text"
// but
writeSomeText("text view", toOutput: tv) // error: cannot invoke 'writeSomeText' with an argument list of type '(String, toOutput: UITextView)'
later you can extend other classes with text property, if you want ... without changing implementation of func writeSomeText
A Swifty way (without Generics) would be
#objc
protocol UITextOutputProtocol: class {
func setOutputText(text: String)
}
extension UITextField : UITextOutputProtocol {
func setOutputText(text: String) {
self.text = text
}
}
extension UILabel : UITextOutputProtocol {
func setOutputText(text: String) {
self.text = text
}
}
class ViewController: UIViewController {
//An outlet Collection containing Labels and TextField
#IBOutlet var texts: [UITextOutputProtocol]!
//An example function that will be replicating a text in all controls
func setTexts() {
for text in texts {
text.setOutputText("example Text")
}
}
UPDATE:
As UILabel and UITextField are now implementing protocol UITextOutputProtocol the following code will work.
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var textLabel: UILabel!
​func writeSomeText(string: String, toOutput: UITextOutputProtocol) {
// do some text processing
toOutput.setOutputText("example Text") // change the text here for your processed string
}
func doSomeProcessingOnText(text:String) {
writeSomeText(text, textField)
writeSomeText(text, textLabel)
}
}
This is a little bit of a hack since UITextField and UILabel don't conform any shared protocols for text editing, but you could use KVO:
func writeSomeText(string: String, output: AnyObject) {
// do some text processing
if output.respondsToSelector("text") {
output.setValue(string, forKey: "text")
}
}
respondsToSelector checks to make sure the object you're passing in has a property text before continuing setting a value to that key. This check is used to prevent a crash a runtime if you pass in an object that is not KVO compliant for the key text`.

Resources