Neil Macy

Property Wrappers in Swift

It's really easy to make a property wrapper in Swift to automatically modify a property.

@NonEmpty Example

Say I want to ensure an optional String can't be empty - it's either nil, or it's got a value, but that value can't be "" (because then it's got the same meaning as nil, and I don't want to have to check both values when I'm using it). I can create a @NonEmpty property wrapper which makes sure a String matches that requirement.

Usage in Code

@NonEmpty var myString = "" // gets changed to `nil`
@NonEmpty var myString = nil // keeps its value as `nil`
@NonEmpty var myString = "Killie won the Championship in 2021/22" // 🏆 - keeps its original value

The Code

@propertyWrapper public struct NonEmpty {
    public var wrappedValue: String? {
        didSet {
            wrappedValue = getNonEmptyStringOrNil(from: wrappedValue)
        }
    }

    public init(wrappedValue: String?) {
        self.wrappedValue = getNonEmptyStringOrNil(from: wrappedValue)
    }

    private func getNonEmptyStringOrNil(from inputString: String?) -> String? {
        if let inputString = inputString, !inputString.isEmpty {
            return inputString
        } else {
            return nil
        }
    }
}

The Tests

class NonEmptyPropertyWrapperTests: XCTestCase {
    func testNonEmpty_givenNilString_returnsNilString() {
        @NonEmpty var testString = nil
        XCTAssertNil(testString)
    }

    func testNonEmpty_givenEmptyString_returnsNilString() {
        @NonEmpty var testString = ""
        XCTAssertNil(testString)
    }

    func testNonEmpty_givenWhitespaceString_returnsOriginalString() {
        @NonEmpty var testString = " "
        XCTAssertEqual(testString, " ")
    }

    func testNonEmpty_givenNonEmptyString_returnsOriginalString() {
        @NonEmpty var testString = "Test"
        XCTAssertEqual(testString, "Test")
    }
}

Links

There's usually a Swift By Sundell link for anything Swift-related and this is no different. Here's a great example of how to make a @Capitalized wrapper for a String, a generic @UserDefaultsBacked wrapper, and much more info on how it works:

Published on 25 May 2022