Совместное использование CocoaPod с помощью UIApplication.shared между расширением iOS и приложением - PullRequest
0 голосов
/ 19 февраля 2019

У меня есть свой собственный закрытый CocoaPod, который я написал.Я пытаюсь использовать его в своем приложении для iOS, которое работает нормально.Но когда я добавляю его в приложение iMessage или в расширение для общего доступа, он не работает и выдает ошибку 'shared' is unavailable: Use view controller based solutions where appropriate instead. при попытке использовать UIApplication.shared.

Моей первой мыслью о том, как это исправить, было добавление флага Swift.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, она будет работать нормально.Но если я оставлю его без комментария, он потерпит неудачу.

Мне нужен этот пакет в моем расширении общего ресурса, хотя.

Я подумал о написании отдельного модуля, который просто обрабатывает логику UIApplication.shared идобавив этот модуль в MyApp.Но это похоже на настоящую боль.Тем более, что я не знаю, как развернуть 2 CocoaPods в 1 проекте, которые полагаются на одни и те же исходные файлы.

Если это единственное решение, то кажется, что почти лучше использовать субмодули Git и иметь источник напрямуюв приложении, так что я могу включить его в эти цели напрямую, и тогда #if ДОЛЖНО работать.Проблема в том, что зависимости CocoaPod не будут обработаны, если я буду использовать Git Submodules.Поэтому мне действительно нужно как-то использовать CocoaPods.

Я бы предпочел простое решение, которое не выглядит таким же хакерским, как те.Так есть ли лучший способ справиться с этим и исправить эту ошибку, не прибегая к переписыванию TON кода, и это не супер хакерское решение?


В комментариях упоминается использование NSSelectorFromString с UIApplication.responds и UIApplication.perform.Проблема в том, что если Apple когда-либо изменит API, код будет поврежден, даже для предыдущих версий приложения, поскольку он вызывается динамически, без возможности проверки API в будущем.Хотя это решение звучит легко, оно кажется очень плохим решением.


Ответ ниже выглядит очень многообещающе.К сожалению, после нескольких изменений, изложенных в комментариях, он все еще не работает, так как основное приложение имеет как подспец Core, так и подспецификацию AppExtension.

Ответы [ 2 ]

0 голосов
/ 26 марта 2019

Поскольку вам нужен доступ к 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
        ....
    }
}
0 голосов
/ 27 февраля 2019

Допустим, вы являетесь владельцем 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

Затем в вашем подфиле приложения вы будете ссылаться на 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

Вот и все!

...