Search code examples
swiftstringstring-interpolation

Swift Conditional String Interpolation


I often trap myself writing code like this when I need to construct a string depending on some condition (in this case isFavorite):

let me = Contact(name: "Stefan", isFavorite: true)

var message = "Contact \(me.name)"
if me.isFavorite {
    message.append(" is a favorite contact")
}

These are 4 lines or alternatively a complex ternary operator (if ? then : else) for such a simple task. I always have a bad conscience with this...

Is there a way to do this more elegantly with Swift?


Solution

  • Swift 5

    Indeed there is - I found the answer at the UIKonf 2019 where I heard from Erica Sadun that there is a way by using Swift 5 String Interpolation to achieve this in one single line. All you need is this reusable extension:

    extension String.StringInterpolation {
        mutating func appendInterpolation(if condition: @autoclosure () -> Bool, _ literal: StringLiteralType) {
            guard condition() else { return }
            appendLiteral(literal)
        }
    }
    

    It enables you to transform your 4 lines into just one:

    "Contact: \(me.name)\(if: me.isFavorite, " is a favorite contact")"
    

    You can read about the different possibilities of Swift's string interpolation in in Erica's article: https://ericasadun.com/2018/12/12/the-beauty-of-swift-5-string-interpolation/

    Playground

    Start getting your hands dirty with this:

    import Foundation
    
    // *****************************************************************************
    // Many thanks to Erica Sadun for describing the new possibilities of string
    // interpolation in Swift 5 👍
    // *****************************************************************************
    
    
    // *****************************************************************************
    // Conditional Interpolation
    
    struct Contact {
        let name: String
        let isFavorite: Bool
    }
    let me = Contact(name: "Stefan", isFavorite: true)
    
    // ***************************************************************** Swift 4
    
    var message = "Contact \(me.name)"
    if me.isFavorite {
        message.append(" is favorite")
    }
    message // we need 4 lines to construct this message!!!
    "Contact: \(me.name)\(me.isFavorite ? " is favorite" : "")" // or a complex ternary operator
    
    // ***************************************************************** Swift 5
    
    extension String.StringInterpolation {
    
        mutating func appendInterpolation(if condition: @autoclosure () -> Bool, _ literal: StringLiteralType) {
            guard condition() else { return }
            appendLiteral(literal)
        }
    }
    "Contact: \(me.name)\(if: me.isFavorite, " is favorite")" // simple and clean - no extras
    
    // *****************************************************************************
    // Optional Interpolation
    
    let optionalMe: Contact? = Contact(name: "Stefan", isFavorite: true)
    
    // ***************************************************************** Swift 4
    
    "Contact: \(optionalMe?.name)" // shows warning
    
    // ***************************************************************** Swift 5
    
    // Interpolate nil value
    extension String.StringInterpolation {
        /// Provides `Optional` string interpolation without forcing the
        /// use of `String(describing:)`.
        public mutating func appendInterpolation<T>(_ value: T?, default defaultValue: String) {
            if let value = value {
                appendInterpolation(value)
            } else {
                appendLiteral(defaultValue)
            }
        }
    }
    let nilContact: Contact? = nil
    "Contact: \(nilContact?.name, default: "nil")"
    
    // Strip `Optional`
    extension String.StringInterpolation {
        /// Interpolates an optional using "stripped" interpolation, omitting
        /// the word "Optional" from both `.some` and `.none` cases
        public mutating func appendInterpolation<T>(describing value: T?) {
            if let value = value {
                appendInterpolation(value)
            } else {
                appendLiteral("nil")
            }
        }
    
    }
    "Contact: \(describing: optionalMe?.name)"