swift-composable-architecture
                                
                                 swift-composable-architecture copied to clipboard
                                
                                    swift-composable-architecture copied to clipboard
                            
                            
                            
                        Unable to use TCA in extensions
Description
We are currently taking leverage of Tuist dependencies to handle dependencies in our modular app. Until the release of version 0.41.0, everything worked fine, since then we're unable to update because we're getting errors in OpenURL.swift:
'open(_:options:completionHandler:)' is unavailable in application extensions for iOS
'shared' is unavailable in application extensions for iOS: Use view controller based solutions where appropriate instead.
Checklist
- [x] I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
- [X] If possible, I've reproduced the issue using the mainbranch of this package.
- [X] This issue hasn't been addressed in an existing GitHub issue or discussion.
Expected behavior
We should be able to use TCA in extensions / TCA should compile successfully.
Actual behavior
We are unable to use TCA in extensions / TCA doesn't compile successfully.
Steps to reproduce
- Integrate TCA using Tuist dependencies / use TCA in extension
- Compile the project
The Composable Architecture version information
0.45.0
Destination operating system
iOS 16
Xcode version information
14.0.1
Swift Compiler version information
swift-driver version: 1.62.8 Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50)
Target: arm64-apple-macosx12.0
Hi @IgorRosocha, this was fixed in 0.42.0. Have you tried that? Also there has been many releases since then, so we recommend updating to the newest.
Hi @mbrandonw, as mentioned in the issue, we're getting the error on the latest release - 0.45.0
Oh sorry, I saw your first mention of 0.41 and thought that is what you were using. Are you able to share a sample project that reproduces the error so that we can figure out how to fix it?
Sure, please take a look at minimal example here: https://github.com/IgorRosocha/TCA-Extension-Sample
To build the project:
- Install Tuist (curl -Ls https://install.tuist.io | bash)
- Run tuist fetchto fetch dependencies
- Run tuist generateto generate and open the project
Please let me know in case you run into some problems when generating and building the project.
Hey @IgorRosocha! Would you mind taking my tuist-fix branch for a spin?
https://github.com/tgrapperon/swift-composable-architecture/tree/tuist-fix
I didn't install Tuist on my machine, but I guess you can point to a specific branch instead of a tagged release. I simply marked the APIs @available(iOSApplicationExtension, unavailable) which is the fix I used in the past to share code in dynamic frameworks between my app and its extensions. I can't say if this is the best solution when importing a SPM dependency via Tuist though (nor if it's a solution at all!).
Hey @tgrapperon, sorry for such a late response. Fix introduced in your tuist-fix branch resolves our issue - the only question is if it's safe for you guys to mark openURL as unavailable for iOS application extensions, or maybe introduce some workaround for extensions.
I've tried to implement a workaround, but without success. We don't have any installed UIViewController at hand to extract the extension context, so the best we can do is falling back to SwiftUI's openURL, but I didn't manage to make it build in an app extension context while preserving the current UIKit branch for regular iOS apps.
Currently we use TCA with CocoaPod integration and also faced with same issue. I can suggest only ugly solution:
extension UIApplication {
    static func openIfAvailable(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey: Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil) {
        let sharedApplication = NSSelectorFromString("sharedApplication")
        let openURL = NSSelectorFromString("openURL:options:completionHandler:")
        guard
            responds(to: sharedApplication),
            let application = perform(sharedApplication).takeRetainedValue() as? UIApplication,
            application.responds(to: openURL) else {
            // Or use SwiftUI implementation?
            completion?(false)
            return
        }
        typealias Callback = @convention(c) (NSObject, Selector, URL, NSDictionary, @escaping (Bool) -> Void) -> Void
        let callback = unsafeBitCast(application.method(for: openURL), to: Callback.self)
        callback(application, openURL, url, options as NSDictionary, { completion?($0) })
    }
}
And what will happen in runtime of an App Extension with integrated TCA with SPM? Seems to SPM ignored APPLICATION_EXTENSION_API_ONLY flag in build settings and allow to perform UIApplication.shared and other methods which mark as @available(iOSApplicationExtension, unavailable).
When used with SPM, the current implementation works, and without producing any error.
Besides NSSelectorFromString, there are two ways to fix this issue AFAIK:
- Annotate this dependency with @available(iOSApplicationExtension, unavailable), which makes code harder to reuse
- Base the implementation on SwiftUI's EnvironmentValues.openURL, which raises the availability to iOS 14+ generation.
It is not clear yet what is the best solution, but I'm confident it will be addressed in one way or another.
Fixed by #1714.