Journey before destination, tests before production

Custom @Environment Keys in SwiftUI

July 08, 2019

environment

@Environment is a property wrapper that gives views access to shared dependencies—Calendar, Locale, ColorScheme, and others built into SwiftUI.

struct CalendarView: View {
    @Environment(\.calendar) var calendar: Calendar

    var body: some View { }
}

But what about app-specific dependencies? Say you want an ImageFetcher available to any view that displays remote images, with caching across screens.

SwiftUI supports this through custom environment keys:

struct MovieCell: View {
    @Environment(\.imageFetcher) var fetcher: ImageFetcher
    var movie: Movie

    private var poster: AnyPublisher<UIImage, Never> {
        fetcher.image(for: movie.posterURL)
    }

    var body: some View { }
}

Implementation

Two pieces are required: an EnvironmentKey and an extension on EnvironmentValues.

Define the key

EnvironmentKey requires a default value for your dependency:

public protocol EnvironmentKey {
    associatedtype Value
    static var defaultValue: Self.Value { get }
}
struct ImageFetcherKey: EnvironmentKey {
    static let defaultValue = ImageFetcher()
}

The defaultValue is instantiated lazily on first access via @Environment.

defaultValue

Expose the property

Extend EnvironmentValues to provide the key path used with @Environment:

extension EnvironmentValues {
    var imageFetcher: ImageFetcher {
        get { self[ImageFetcherKey.self] }
        set { self[ImageFetcherKey.self] = newValue }
    }
}

That’s it. Any view can now access @Environment(\.imageFetcher).

Full implementation available here.


Serg Dort

Written by Serg Dort. Say one thing for him: he ships code.