使用iOS扩展程序和应用程序之间共享的UIApplication.shared CocoaPod

时间:2019-02-19 19:21:38

标签: ios swift cocoapods

我写了一个自定义的私人CocoaPod。我正在尝试在我的iOS应用程序中使用它,效果很好。但是,当我将其添加到iMessage应用或共享扩展程序时,它会失败,并在尝试使用'shared' is unavailable: Use view controller based solutions where appropriate instead.时出现错误UIApplication.shared

我首先想到的解决方法是添加一个Swift Flag IN_EXTENSION或类似的东西。然后将代码包装在#if块中。

问题是CocoaPod源的目标在某种类型的框架中。来源不直接属于应用程序或扩展程序。因此,添加该标志并没有真正的帮助。

下面是我的Podfile的示例。

source 'https://github.com/CocoaPods/Specs.git'
source 'git@github.com:CUSTOMORG/Private-CocoaPods-Spec.git'
platform :ios, '9.0'
use_frameworks!
inhibit_all_warnings!

target 'MyApp' do
  pod 'MyCustomSwiftPackage', '1.0.0'
end

target 'MyApp Share Extension' do
  pod 'MyCustomSwiftPackage', '1.0.0'
end

如果我将pod 'MyCustomSwiftPackage', '1.0.0'下的MyApp Share Extension行注释掉,则可以正常工作。但是,如果我不加注释,它将失败。

我的共享扩展名中确实需要此软件包。

我已经考虑过编写一个单独的pod来处理UIApplication.shared逻辑,并将该pod添加到MyApp中。但这似乎是一种真正的痛苦。尤其是因为我不知道一种方法可以在1个依赖相同源文件的项目中部署2个CocoaPods。

如果这是唯一的解决方案,那么使用Git子模块并直接在应用程序中拥有源代码似乎更好,因此我可以将其直接作为那些目标的一部分,然后#if应该可以工作。问题是如果我使用Git子模块,将无法处理CocoaPod的依赖关系。所以我真的必须以某种方式使用CocoaPods。

我希望有一个简单的解决方案,它不会像那些解决方案那样笨拙。那么,有没有更好的方法来解决这个问题并修复该错误,而不必诉诸重写大量的代码,那不是一个超级hacky解决方案吗?


在评论中提到将NSSelectorFromStringUIApplication.respondsUIApplication.perform一起使用。问题在于,如果Apple曾经更改过API,即使对于以前版本的应用程序,代码也会中断,因为它是在没有API将来证明的情况下动态调用的。尽管该解决方案听起来很简单,但似乎是一个错误的决定。


answer below看起来很有希望。遗憾的是,在注释中概述了一些更改之后,它仍然无法正常运行,主应用程序同时具有Core子规范和AppExtension子规范。

2 个答案:

答案 0 :(得分:1)

假设您是 MyLibrary 的所有者:

Pod::Spec.new do |s|
  s.name             = "MyLibrary"
  # Omitting metadata stuff and deployment targets

  s.source_files = 'MyLibrary/*.{m,h}'
end

您使用了不可用的API,因此该代码有条件地基于名为 MYLIBRARY_APP_EXTENSIONS 的预处理器宏来编译某些部分。我们声明一个名为Core的子规范,其中包含所有代码,但标志关闭。如果用户未指定子规范,则将其指定为默认子规范。然后,我们将声明一个名为AppExtension的附加子规范,其中包括所有代码,但会设置预处理器宏:

Pod::Spec.new do |s|
  s.name             = "MyLibrary"
  # Omitting metadata stuff and deployment targets
  s.default_subspec = 'Core'

  s.subspec 'Core' do |core|
    core.source_files = 'MyLibrary/*.{m,h}'
  end

  s.subspec 'AppExtension' do |ext|
    ext.source_files = 'MyLibrary/*.{m,h}'
    # For app extensions, disabling code paths using unavailable API
    ext.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => 'MYLIBRARY_APP_EXTENSIONS=1' }
  end
end

然后在应用程序Podfile中,您将链接至主应用程序目标中的Core,并链接至扩展名中的AppExtension,如下所示:

abstract_target 'App' do
  # Shared pods between App and extension, compiled with same preprocessor macros
  pod 'AFNetworking'

  target 'MyApp' do
    pod 'MyLibrary/Core'
  end

  target 'MyExtension' do
    pod 'MyLibrary/AppExtension'
  end
end

就这样!

答案 1 :(得分:0)

因为您仅需要访问UIApllication.shared才能获得topViewController。您不能将其作为用户需要提供的框架的依赖项。

让我们声明提供程序并使用precondition确保开发人员不会忘记设置此属性:

protocol TopViewControllerProvider: class {
    func topViewController() -> UIViewController
}

enum TopViewController {
    static private weak var _provider: TopViewControllerProvider?
    static var provider: TopViewControllerProvider {
        set {
            _provider = newValue
        }
        get {
            precondition(_provider != nil, "Please setup TopViewController.provider")
            /// you can make provider optional, or handle it somehow
            return _provider!
        }
    }
}

然后在您的应用中可以执行以下操作:

class AppDelegate: UIApplicationDelegate {
    func applicationDidFinishLaunching(_ application: UIApplication) {
        ...
        TopViewController.provider = self
    }
}
extension AppDelegate: TopViewControllerProvider { ... }

在扩展中,您可以随时返回自己。

通过任何视图获取topViewController的另一种方法:

extension UIView {
    func topViewController() -> UIViewController? {
        /// But I not sure that `window` is accessible in extension
        let root = window?.rootViewController
        ....
    }
}