In my opinion, one of the best features of both Swift and Objective-C is extensions. They allow you to add new methods to any class, and the entire project will be able to call them without inheriting and overloading.
As a mobile developer, I work with both iOS and Android, and I often see Android functions and methods shorter, cleaner, and easier to understand in Swift. Along with some new methods (extensions), we get a brief, clean, and easy to maintain code snippet in Swift.
I’ll be using Swift, but all of these extensions can be converted to Objective-C or used with Objective-C directly without conversion.
String.trim () and Swift.trimmed
In 99% of cases when I cut a String in Swift, I want to remove spaces and other similar symbols (e.g. new lines and tabs).
This simple extension does the trick:
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="token keyword">import</span> <span class="token builtin">Foundation</span> <span class="token keyword">extension</span> <span class="token builtin">String</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> trimmed <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">{</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token function">trimmingCharacters</span> <span class="token punctuation">(</span> <span class="token keyword">in</span> <span class="token punctuation">:</span> <span class="token punctuation">.</span> whitespacesAndNewlines <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function">trim</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span> <span class="token operator">=</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> trimmed <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Use:
1 2 3 4 |
var str1 = " a b c d e n" var str2 = str1.trimmed str1.trim() |
Int.toDouble () and Double.toInt ()
These methods can be helpful if you work with options. If you have no Int option, you can convert it with Double (a), where “a” is an integer variable. But if “a” is optional, you can’t do it.
Let’s add extensions to Int and Double:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import Foundation extension Int { func toDouble() -> Double { Double(self) } } extension Double { func toInt() -> Int { Int(self) } } |
Use:
1 2 3 |
let a = 15.78 let b = a.toInt() |
String.toDate (…) and Date.toString (…)
Retrieving Datet from String and formatting Date to display or send to API is common practice. The standard conversion method requires three lines of code. Let’s see how to make it shorter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import Foundation extension String { func toDate(format: String) -> Date? { let df = DateFormatter() df.dateFormat = format return df.date(from: self) } } extension Date { func toString(format: String) -> String { let df = DateFormatter() df.dateFormat = format return df.string(from: self) } } |
Use:
1 2 3 4 |
let strDate = "2020-08-10 15:00:00" let date = strDate.toDate(format: "yyyy-MM-dd HH:mm:ss") let strDate2 = date?.toString(format: "yyyy-MM-dd HH:mm:ss") |
Int.centsToDollars ()
Some payment APIs (e.g. Stripe) prefer to use currency (cents) to process payments. It allows to avoid inaccuracies of Float and Double. At the same time, it would be more comfortable to use these types to display the value.
This extension does this conversion:
1 2 3 4 5 6 7 8 |
import Foundation extension Int { func centsToDollars() -> Double { Double(self) / 100 } } |
Use:
1 2 3 |
let cents = 12350 let dollars = cents.centsToDollars() |
String.asCoferences ()
The coordinates of a place on Earth have at least two numbers – latitude and longitude. Another one is the height, but that only makes sense in 3D, which isn’t very common in software development.
From the API we get two separate fields, or one with comma separated values. This extension allows to perform conversion of such strings into CLLocationCoordinate2D.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import Foundation import CoreLocation extension String { var asCoordinates: CLLocationCoordinate2D? { let components = self.components(separatedBy: ",") if components.count != 2 { return nil } let strLat = components[0].trimmed let strLng = components[1].trimmed if let dLat = Double(strLat), let dLng = Double(strLng) { return CLLocationCoordinate2D(latitude: dLat, longitude: dLng) } return nil } } |
Use:
1 2 3 |
let strCoordinates = "41.6168, 41.6367" let coordinates = strCoordinates.asCoordinates |
String.asURL ()
iOS and macOS use type URLs to handle the links. It’s more flexible, allowing for different types of URLs to be grabbed and handled. At the same time, we usually import it or get it from the String API. It’s pretty easy to convert one to another, but this extension allows us to handle these options or conversion strings:
1 2 3 4 5 6 7 8 |
import Foundation extension String { var asURL: URL? { URL(string: self) } } |
Use:
1 2 3 |
let strUrl = "https://medium.com" let url = strUrl.asURL |
UIDevice.vibrate ()
IPhone vibrations can be a great effect for button presses and other feedback from the device. For iPhone vibrations, there is a special sound that is handled by AudioToolbox framework.
For the AudioToolbox with all UIViewControllers vibrating is annoying and logically, vibration is a function of the device (it is not coming from the speaker but from the device itself) rather than playing the sound.
This extension allows to simplify it to one line:
1 2 3 4 5 6 7 8 9 |
import UIKit import AudioToolbox extension UIDevice { static func vibrate() { AudioServicesPlaySystemSound(1519) } } |
Use:
1 2 |
UIDevice.vibrate() |
String.width (…) and String.height (…)
iOS can UILabelt automatically compute the size using the constraints provided, but sometimes it’s important that you set the size yourself.
This extension allows us to calculate String width and height using UIFont:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import UIKit extension String { func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat { let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil) return ceil(boundingBox.height) } func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat { let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height) let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil) return ceil(boundingBox.width) } } extension NSAttributedString { func height(withConstrainedWidth width: CGFloat) -> CGFloat { let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil) return ceil(boundingBox.height) } func width(withConstrainedHeight height: CGFloat) -> CGFloat { let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height) let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil) return ceil(boundingBox.width) } } |
Use:
1 2 3 |
let text = "Hello, world!" let textHeight = text.height(withConstrainedWidth: 100, font: UIFont.systemFont(ofSize: 16)) |
String.containsOnlyDigits
The extension below is useful when you need to limit user input or validate data from the API. It checks if the String has only digits:
1 2 3 4 5 6 7 8 9 |
import Foundation extension String { var containsOnlyDigits: Bool { let notDigits = NSCharacterSet.decimalDigits.inverted return rangeOfCharacter(from: notDigits, options: String.CompareOptions.literal, range: nil) == nil } } |
Use:
1 2 3 |
let digitsOnlyYes = "1234567890".containsOnlyDigits let digitsOnlyNo = "12345+789".containsOnlyDigits |
String.isAlphanumeric
Like the previous extension, this extension checks the contents of the String. It returns true if the string is not empty and contains only alphanumeric characters. A reverse version of this extension can be helpful for confirming that the password contains non-alphanumeric characters.
1 2 3 4 5 6 7 8 |
import Foundation extension String { var isAlphanumeric: Bool { !isEmpty && range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil } } |
Use:
1 2 3 |
let alphanumericYes = "asd3kJh43saf".isAlphanumeric let alphanumericNo = "Kkncs+_s3mM.".isAlphanumeric |
Sign up for the chain
Swift 5 has a way to register String. Computing the indices and offsets is annoying if you want to get the characters 5 through 10. This extension allows simple Int to be used for this purpose:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import Foundation extension String { subscript (i: Int) -> Character { return self[index(startIndex, offsetBy: i)] } subscript (bounds: CountableRange<Int>) -> Substring { let start = index(startIndex, offsetBy: bounds.lowerBound) let end = index(startIndex, offsetBy: bounds.upperBound) if end < start { return "" } return self[start..<end] } subscript (bounds: CountableClosedRange<Int>) -> Substring { let start = index(startIndex, offsetBy: bounds.lowerBound) let end = index(startIndex, offsetBy: bounds.upperBound) if end < start { return "" } return self[start...end] } subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring { let start = index(startIndex, offsetBy: bounds.lowerBound) let end = index(endIndex, offsetBy: -1) if end < start { return "" } return self[start...end] } subscript (bounds: PartialRangeThrough<Int>) -> Substring { let end = index(startIndex, offsetBy: bounds.upperBound) if end < startIndex { return "" } return self[startIndex...end] } subscript (bounds: PartialRangeUpTo<Int>) -> Substring { let end = index(startIndex, offsetBy: bounds.upperBound) if end < startIndex { return "" } return self[startIndex..<end] } } |
Use:
1 2 3 |
let subscript1 = "Hello, world!"[7...] let subscript2 = "Hello, world!"[7...11] |
UIImage.squared
When you ask users to take their photo or choose an existing photo as their profile photo, it will be difficult for them to provide a square photo. At the same time, most UIs use either square or circle. This extension cuts the UIImage offered, turning it into a perfect square:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import UIKit extension UIImage { var squared: UIImage? { let originalWidth = size.width let originalHeight = size.height var x: CGFloat = 0.0 var y: CGFloat = 0.0 var edge: CGFloat = 0.0 if (originalWidth > originalHeight) { // landscape edge = originalHeight x = (originalWidth - edge) / 2.0 y = 0.0 } else if (originalHeight > originalWidth) { // portrait edge = originalWidth x = 0.0 y = (originalHeight - originalWidth) / 2.0 } else { // square edge = originalWidth } let cropSquare = CGRect(x: x, y: y, width: edge, height: edge) guard let imageRef = cgImage?.cropping(to: cropSquare) else { return nil } return UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation) } } |
Use:
1 2 3 |
let img = UIImage() // Must be a real UIImage let imgSquared = img.squared |
UIImage.resize (…)
Before uploading images to the server, you must ensure that the image is small enough. iPhone and iPad have very good cameras and photos from their gallery are of unlimited size. To make sure UIImage that size is not larger than a certain size, for example: 512 pixels or 1024 pixels use this extension:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import UIKit extension UIImage { func resized(maxSize: CGFloat) -> UIImage? { let scale: CGFloat if size.width > size.height { scale = maxSize / size.width } else { scale = maxSize / size.height } let newWidth = size.width * scale let newHeight = size.height * scale UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight)) draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } } |
Use:
1 2 3 |
let img2 = UIImage() // Must be a real UIImage let img2Thumb = img2.resized(maxSize: 512) |
The last two extensions can be chained:
1 2 3 |
let img = UIImage() // Must be a real UIImage let imgPrepared = img.squared?.resized(maxSize: 512) |
Int.toString ()
One of Java’s most useful features is the toString () method. It is a method of all classes and types. Swift allows to do something similar using the inference string: “(someVar)”. But there is one difference – your variable is optional. Swift will add the word optional to the output. Java will only crash, but Kotlin will handle the options nicely: someVar? .ToString () will return an Option String, which is null (nil) if someVarl and null (nil) or String contain a value. .
Unfortunately, Swift doesn’t allow Any extension, so add the least toString () method to Int:
1 2 3 4 5 6 7 8 |
import Foundation extension Int { func toString() -> String { "(self)" } } |
Use:
1 2 3 |
let i1 = 15 let i1AsString = i1.toString() |
Double.toString ()
As in the previous example, converting Double to String can be very helpful. But in this case, we will limit the output to two fractional digits. I can’t say this extension will be useful for every situation, but for most use cases it should work fine:
1 2 3 4 5 6 7 8 |
import Foundation extension Double { func toString() -> String { String(format: "%.02f", self) } } |
Use:
1 2 3 |
let d1 = 15.67 let d1AsString = d1.toString() |
Double.toPrice ()
Creating Strings for a price is just another way to format Doubles. This algorithm is uncommon, it depends on the regional settings. But you can use it as a general idea and make adjustments to your application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import Foundation extension Double { func toPrice(currency: String) -> String { let nf = NumberFormatter() nf.decimalSeparator = "," nf.groupingSeparator = "." nf.groupingSize = 3 nf.usesGroupingSeparator = true nf.minimumFractionDigits = 2 nf.maximumFractionDigits = 2 return (nf.string(from: NSNumber(value: self)) ?? "?") + currency } } |
Use:
1 2 3 |
let dPrice = 16.50 let strPrice = dPrice.toPrice(currency: "€") |
String.asDict
JSON is a popular format for exchanging or storing structured data. Most APIs prefer to use JSON. JSON is a JavaScript structure. Swift has the same data type – dictionary.
Converting one into a trick is quite simple:
1 2 3 4 5 6 7 8 9 |
import Foundation extension String { var asDict: [String: Any]? { guard let data = self.data(using: .utf8) else { return nil } return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] } } |
Use:
1 2 3 |
let json = "{" hello ": " world "}" let dictFromJson = json.asDict |
String.asArray
This extension is similar to the previous one, but it converts the JSON array into a Swift array:
1 2 3 4 5 6 7 8 9 |
import Foundation extension String { var asArray: [Any]? { guard let data = self.data(using: .utf8) else { return nil } return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Any] } } |
Use:
1 2 3 |
let json2 = "[1, 2, 3]" let arrFromJson2 = json2.asArray |
String.asAttributedString
Sometimes we need some kind of background-independent simple text. A fairly common way is to use plain HTML for this purpose.
UILabel can display text with bold (<strong>) sections, underlined text, larger and smaller paragraphs, etc.You just need to convert the HTML to NSAttributedString and assign it to UILabel.attributedText.
This extension will help you to do the first task:
1 2 3 4 5 6 7 8 9 |
import Foundation extension String { var asAttributedString: NSAttributedString? { guard let data = self.data(using: .utf8) else { return nil } return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) } } |
Use:
1 2 3 |
let htmlString = "<p>Hello, <strong>world!</string></p>" let attrString = htmlString.asAttributedString |
Bundle.appVersion
The last extension in this collection allows the application version to be obtained from the Info.plist file. It may be useful for:
- Submit the version of the application to the API.
- Check for available updates.
- Display the application version on the device screen.
- Include the application version in support email. The extension below allows you to load the application version (or nil if it isn’t available) in one line of code:
1 2 3 4 5 6 7 8 9 10 11 12 |
import Foundation extension Bundle { var appVersion: String? { self.infoDictionary?["CFBundleShortVersionString"] as? String } static var mainAppVersion: String? { Bundle.main.appVersion } } |
Use:
1 2 |
let appVersion = Bundle.mainAppVersion |
Conclusion
I hope these extensions have helped you make your code shorter and clearer. Please modify them to meet your requirements and include them in your projects.
Link article references: https://medium.com/better-programming/24-swift-extensions-for-cleaner-code-41e250c9c4c3?fbclid=IwAR1KzYlRt0MzLbF-QlFQGyy-Fsiga1DCnKsMQuGoCvf2gnaS530nMA1Y7e
Have a good time and see you next time!