我写了一个自定义的私人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解决方案吗?
在评论中提到将NSSelectorFromString
与UIApplication.responds
和UIApplication.perform
一起使用。问题在于,如果Apple曾经更改过API,即使对于以前版本的应用程序,代码也会中断,因为它是在没有API将来证明的情况下动态调用的。尽管该解决方案听起来很简单,但似乎是一个错误的决定。
answer below看起来很有希望。遗憾的是,在注释中概述了一些更改之后,它仍然无法正常运行,主应用程序同时具有Core子规范和AppExtension子规范。
答案 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
....
}
}