Custom @Environment value for share actions

3 min read––– views
Custom @Environment value for share actions
📢

Sponsored

Sponsor This Blog

Connect with a targeted audience of iOS developers and Swift engineers. Promote your developer tools, courses, or services.

Learn More →

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 use openURL 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://artemnovichkov.com")!)
        }
    }
}

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

Keys and values

At first we should create a custom key, conform to EnvironmentKey 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 extend EnvironmentValues to make ourShareAction be available from the environment:

extension EnvironmentValues {
 
    var share: ShareAction {
        self[ShareActionEnvironmentKey.self]
    }
}

The last thing is adding callAsFunction in ShareAction struct to use the same syntax:

struct ShareAction {
 
    func callAsFunction(_ activityItems: [Any]) {
        let vc = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
        let windowScenes = UIApplication.shared.connectedScenes
            .compactMap { $0 as? UIWindowScene }
        let activeScene = windowScenes
            .filter { $0.activationState == .foregroundActive }
        let firstActiveScene = activeScene.first
        let keyWindow = firstActiveScene?.keyWindow
        keyWindow?.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://artemnovichkov.com")!])
        }
    }
}

Since iOS 16.0, you can use ShareLink view to share activities. It's a more SwiftUI-way to share activities and it's more convenient to use.

ShareLink(item: URL(string: "https://artemnovichkov.com")!) {
    Text("Share")
}

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