Custom String Interpolation in Swift
When creating an app, almost everyone will have to display texts to their customers. This normally requires lots of string formatting and can be quite messy. Recently, I stumbled upon a neat trick that can make your code much cleaner: custom string interpolation.
In Swift, it is pretty common to use string interpolation in the following way:
let number = 1
print("the number is: \(number)")
What I found is that it is possible to create custom interpolation functions in the String.StringInterpolation struct to "transform" objects into strings in specific ways. Let's imagine you would like to display a text that includes a currency object. Normally, it would look something like this:
import Foundation
import SwiftUI
struct Currency: Identifiable {
let id: Int
let symbol: String
let amount: Double
func formattedCurrency() -> String {
"\(symbol) \(amount)"
}
}
struct CurrencyView: View {
let currencies = [Currency(id: 1, symbol: "NDZ", amount: 2.0),
Currency(id: 2, symbol: "BRL", amount: 5.0)]
var body: some View {
VStack {
ForEach(currencies) { currency in
Text("You will pay: \(currency.formattedCurrency())")
}
}
}
}
If we make a custom string interpolation function, then we can get rid of the formattedCurrency function. To achieve this, we can make an extension of String.StringInterpolation and create a custom function for our currency struct, like so:
import Foundation
import SwiftUI
struct Currency: Identifiable {
let id: Int
let symbol: String
let amount: Double
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ currency: Currency) {
appendLiteral(currency.symbol)
appendLiteral(" \(currency.amount)")
}
}
struct CurrencyView: View {
let currencies: [Currency] = [Currency(id: 1, symbol: "NDZ", amount: 2.0),
Currency(id: 2, symbol: "BRL", amount: 5.0)]
var body: some View {
VStack {
ForEach(currencies) { currency in
Text(verbatim: "You will pay: \(currency)")
}
}
}
}
This way, we can directly insert currency into our string. Both of the above examples would look like this:
Taking it one step further, we can also create an interpolation function for an array of currencies, which can be quite handy:
import Foundation
import SwiftUI
struct Currency: Identifiable {
let id: Int
let country: String
let symbol: String
let amount: Double
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ currencies: [Currency]) {
currencies.forEach { currency in
appendInterpolation("In \(currency.country) ")
appendLiteral("you will pay: \(currency.symbol)")
appendLiteral(" \(currency.amount)")
appendLiteral("\n")
}
}
}
struct CurrencyView: View {
let currencies: [Currency] = [Currency(id: 1, country: "New Zealand", symbol: "NDZ", amount: 2.0),
Currency(id: 2, country: "Brazil", symbol: "BRL", amount: 5.0)]
var body: some View {
Text(verbatim: "For your items:\n\(currencies)")
}
}
Now we do not have to call any formatting functions.
This can be quite helpful when using protocols, especially when we don't care about the specific implementation details of classes that conform to the protocol. Instead, we want to have a consistent description for each object:
import Foundation
import SwiftUI
protocol Currency {
var country: String { get }
var symbol: String { get }
var amount: Double { get }
func format() -> String
}
struct BRL: Currency {
func format() -> String {
"In \(country) you will pay: \(symbol) \(amount)"
}
let country = "Brazil"
let symbol = "BRL"
let amount: Double
}
struct NZD: Currency {
func format() -> String {
"In \(country) you will pay: \(symbol) \(amount)"
}
let country = "New Zeland"
let symbol = "NZD"
let amount: Double
let exchangeRateToUSD: Double
}
struct CurrencyView: View {
let currencies: [any Currency] = [BRL(amount: 5.0),
NZD(amount: 2.0, exchangeRateToUSD: 1.5)]
var body: some View {
VStack {
Text("For your item")
ForEach(currencies, id:\.country) { currency in
Text(currency.format())
}
}
}
}
In this case, if we don't care about the exchangeRateToUSD field in the NZD currency, we can use string interpolation to simplify our code. By implementing a single custom string interpolation function, we can handle all cases without the need for specific functions for each currency.
Here's an example of how you can achieve this:
import Foundation
import SwiftUI
protocol Currency {
var country: String { get }
var symbol: String { get }
var amount: Double { get }
}
struct BRL: Currency {
let country = "Brazil"
let symbol = "BRL"
let amount: Double
}
struct NZD: Currency {
let country = "New Zeland"
let symbol = "NZD"
let amount: Double
let exchangeRateToUSD: Double
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ currency: any Currency) {
appendInterpolation("In \(currency.country) ")
appendLiteral("you will pay: \(currency.symbol)")
appendLiteral(" \(currency.amount)")
}
}
struct CurrencyView: View {
let currencies: [any Currency] = [BRL(amount: 5.0),
NZD(amount: 2.0, exchangeRateToUSD: 1.5)]
var body: some View {
VStack {
Text("For your item")
ForEach(currencies, id:\.country) { currency in
Text(verbatim: "\(currency)")
}
}
}
}
Both of these views look like this:
In conclusion, string interpolation is a powerful and handy feature in Swift that allows you to transform objects into string descriptions. It can make your code more readable and concise. I hope you found this information helpful and enjoyable to read! If you have any further questions or need assistance with anything else, feel free to ask. Additionally, I appreciate any feedback you have to offer, so feel free to share your thoughts. :)