iOS
February 13, 2021

Custom @Environment value for share actions

The article is now available on my blog:

https://www.artemnovichkov.com/blog/custom-environment-value-for-share-actions

SwiftUI has a lot of modern and useful features. One of my favourite is @Environment property wrapper. It allows you to get system-wide settings, for instance, current locale or color scheme. Since iOS 14.0 you can useopenURL value to open URLs from apps easily.

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension EnvironmentValues {

/// Opens a URL using the appropriate system service.
    public var openURL: OpenURLAction { get }
}

With just a one line of code you can extend behaviour of you views:

import SwiftUI

struct ContentView: View {

    @Environment(\.openURL) var openURL
    
    var body: some View {
        Button("Share") {
            openURL(URL(string: "https://blog.artemnovichkov.com")!)
        }
    }
}

I was wondering how it works under the hood and tried to implement the same trick for sharing activity items withUIActivityViewController. Let's check the result!

Keys and values

At first we should create a custom key, conform toEnvironmentKey protocol and set a default value. It will be an empty struct for now:

struct ShareAction {}

struct ShareActionEnvironmentKey: EnvironmentKey {

    static let defaultValue: ShareAction = .init()
}

Next, we should extendEnvironmentValues to make ourShareAction be available from the environment:

extension EnvironmentValues {

    var share: ShareAction {
        self[ShareActionEnvironmentKey]
    }
}

The last thing is addingcallAsFunction inShareAction struct to use the same syntax:

struct ShareAction {

    func callAsFunction(_ activityItems: [Any]) {
        let vc = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
        UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true, completion: nil)
    }
}
Unfortunately, there is no SwiftUI-way to open this controller. If you use a better way, let me know!

To read more about callable values of user-defined nominal types, check SE-0253 proposal on Github.

That's it, now we can use.share value and share activities from any view:

import SwiftUI

struct ContentView: View {

    @Environment(\.share) var share
    
    var body: some View {
        Button("Share") {
            share([URL(string: "https://blog.artemnovichkov.com")!])
        }
    }
}

Related Resources

The final code is available here, and there is a list of related articles:


Twitter · Telegram · Github