CocoaPods: Mix Objective-C and Swift - ios

I'm working on a custom CocoaPod to be deployed privately. Most of the code in this pod is existing code written in Objective-C.
All of the Objective-C code is perfectly accessible from both Objective-C and Swift in implementing projects, but I don't seem to be able to access any of the Swift code from either language in the implementing projects. I would like to be able to use and update the existing Objective-C code, but use Swift for anything new and for Swift specific features.
How can I use Swift code from a primarily Objective-C CocoaPod in Swift files in my implementing project?
My CocoaPod is called XibisFrameworkPod, and I've tried using the following import statements in my implementing project Swift files:
import XibisFrameworkPod
import XibisFrameworkPod.Swift
Here's my redacted podspec file:
Pod::Spec.new do |s|
s.name = 'XibisFrameworkPod'
s.version = '0.1.0'
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '7.0'
s.source_files = 'XibisFrameworkPod/Classes/**/*'
end

Turns out I was just missing the public modifier on the class/function declarations:
public class SomeClass : NSObject

Related

Sharing CocoaPod using UIApplication.shared between iOS Extension and app

I have a custom built private CocoaPod that I wrote. I'm trying to use it in my iOS application, which is working fine. But when I add it to my iMessage App or Share Extension it fails and gives me an error 'shared' is unavailable: Use view controller based solutions where appropriate instead. when trying to use UIApplication.shared.
My first thought of how to fix this was to add a Swift Flag IN_EXTENSION or something like that. Then wrap the code in an #if block.
Problem is the target for the CocoaPod source is in some type of framework. The source is not part of the app or extensions directly. So adding that flag doesn't really help.
Below is an example of my 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
If I comment out the line pod 'MyCustomSwiftPackage', '1.0.0' under MyApp Share Extension it works fine. But if I leave it uncommented it fails.
I do need this package in my share extension tho.
I've thought about writing a separate pod that just handles the UIApplication.shared logic and adding that pod to the MyApp. But that seems like a real pain. Especially since I'm not aware of a way to deploy 2 CocoaPods in 1 project that rely on the same source files.
If that is the only solution it almost seems better to use Git Submodules and have the source directly in the app, so I can have it part of those targets directly and the #if SHOULD work then. Problem with that is the dependancies of the CocoaPod wouldn't be handled if I use Git Submodules. So I really have to use CocoaPods somehow.
I'd prefer a simple solution that doesn't feel as hacky as those ones. So is there a better way to handle this and fix that error without resorting to rewriting a TON of code, and that isn't a super hacky solution?
In the comments it was mentioned to use NSSelectorFromString with UIApplication.responds and UIApplication.perform. Problem with that is if Apple ever changes the API, the code will break, even for previous versions of the application since it is being called dynamically with no API future proofing. Although that solution sounds easy, it seems like a really bad decision.
The answer below looks very promising. Sadly after a few changes outlined in the comments, it still isn’t working, with the main application having both the Core subspec along with the AppExtension subspec.
Say you’re owner of MyLibrary:
Pod::Spec.new do |s|
s.name = "MyLibrary"
# Omitting metadata stuff and deployment targets
s.source_files = 'MyLibrary/*.{m,h}'
end
You use unavailable API, so the code conditionally compiles some parts based on a preprocessor macro called MYLIBRARY_APP_EXTENSIONS. We declare a subspec, called Core with all the code, but the flag off. We make that subspec the default one if user doesn’t specify one. Then we’ll declare an additional subspec, called AppExtension including all the code, but setting the preprocessor macro:
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
Then in your application Podfile you’ll link against Core in your main app target, and against AppExtension in your extension, like so:
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
That’s it!
Since you need access to UIApllication.shared only to get topViewController. You can't make it as dependency of your framework, that user needs to provide.
Let's declare provider and with precondition ensure that developer will not forget to setup this property:
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!
}
}
}
Then in your app you could do:
class AppDelegate: UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplication) {
...
TopViewController.provider = self
}
}
extension AppDelegate: TopViewControllerProvider { ... }
In extension you can just always return self.
Another way to get topViewController by any view:
extension UIView {
func topViewController() -> UIViewController? {
/// But I not sure that `window` is accessible in extension
let root = window?.rootViewController
....
}
}

Wrong Module-Swift.h header is generated with import to itself

So I am mixing swift and objc everywhere.
I have development pod called Renetik.
It has some extensions written in swift but it's mostly objective c code.
Now, I wrote some class and used it in main project fine and wanted to move it to Renetik development pod. When I do it somehow in Renetik-Swift.h wrong import is generated and project won't compile.
#import <Renetik/Renetik.h>
Then I experimented a lot. And found out that wrong import is generated when I actually return from swift class function type defined in pod itself. It is happening just when I try to make it in Development Pod where is mostly objective-c. Other swift extension and classes works juts when I try to modify some class to return objc class defined in pod itself.
I will write example when everything is OK. This compiles fine and I can call function testMe from main project:
#objc public class ReplaceMe: NSObject {
#objc public func testMe() {
let variable = CSResponse<NSObject>()
variable.cancel()
}
}
Just this small change and wrong header is generated as I stated before:
#objc public class ReplaceMe: NSObject {
#objc public func testMe() -> CSResponse<NSObject> {
let variable = CSResponse<NSObject>()
variable.cancel()
return variable
}
}
I use use_frameworks! in podfile as use_modular_headers! don't work for other reasons. I m able to setup branch in Github project where this happens as this is open source.
So it seems only not hack possibility is to create other pod just for Swift files. Define in podspec dependency to released verison, and in host application podfile add dependencies with :path => .

Using a cocoaPod written in Swift in an Object-C project

I have a problem with the import of an external framework within a Swift class.
My project uses cocoaPod to manage dependencies and is configured as follows:
Main project in OBject-C which uses a pod written in Swift (MyPod).
The Pod Swift (MyPod) must use an external framework (ExternalFramework) written in OBject-C.
There is no way to show ExternlFramewrok inside MyPod. I receive "Use of undeclared type" when I try to use any class of ExternlFramework in MyPod.
I attach the part of the MyPod .podspec:
s.source_files = 'MyPod/Sources/**/*', 'MyPod/LibrariesHeaders/**/*.h'
s.ios.vendored_library = 'MyPod/Libraries/*.a'
s.private_header_files = 'MyPod/MyPod/headers/*.h'
s.ios.vendored_frameworks = 'ExternalFramework.framework'
s.frameworks ='ExternlFramework'
s.prefix_header_contents = <<EOC
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#endif
EOC
Logically I can not modify ExternlFramework in any way but I can change MyPod as desired.
Any suggestions?
Thanks!

Mixed CocoaPods project: ObjC functions are visible from Swift, class variables are not

I have set up a Swift iOS project with CocoaPods. I want to use pods based on both Swift and Objective C. Let's say I want to use KeychainAccess (Swift-based) and CocoaLumberjack (Objective C-based). Here's the Podfile I use:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
pod 'KeychainAccess'
pod 'CocoaLumberjack'
Now, I try to use these pods:
import UIKit
import KeychainAccess
import CocoaLumberjack
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// this works fine
let keychain = Keychain(service: "access-token")
// this works as well
DDLog.addLogger(DDTTYLogger.sharedInstance())
// 'DDLog.Type' does not have a member named 'logLevel'
DDLog.logLevel = .Verbose
// 'DDLog.Type' does not have a member named 'logAsync'
DDLog.logAsync = false
}
}
Am I missing something? How do I make CocoaLumberjack work? (or any other ObjC pod, I assume I will have the same issue with other pods as well).
I have created a project which reproduces this issue.

No autocomplete on cocapods for AppCode 3.1

I have recently set up a project from a base XCode Project with the cocoapods installation, and when importing the project into AppCode 3.1, I am not getting any autocompletion for my swift frameworks that are installed through CocoaPods
Here is my Podfile
# Uncomment this line to define a global platform for your project
platform :osx, '10.10'
use_frameworks!
target 'Main' do
pod "SwiftyJSON", ">= 2.1.3"
pod 'BrightFutures', '~> 1.0.0-beta.3'
end
target 'MainTests' do
end
As you can see, I have 2 pods defined (one is SwiftyJson and one is BrightFutures). Both of these pods are installed as Swift Frameworks (hence why use_frameworks!) is there.
Everything appears to be imported and working correctly for AppCode (I have two projects, one is the main project, and the other one is Pods, which is the exact same as it is in XCode), however it appears that AppCode doesn't index any of the Pod frameworks, so I am not getting autocompletion status on using those frameworks, at all. As an example, in the following code
import Cocoa
import BrightFutures
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification) {
let f = future { 5 }.map{f in f + 7}
f.onSuccess{callback in println(callback)}
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
I get no autocompletion on the following
import BrightFutures (importing anything else that isn't from CocoaPods works fine)
Auto complete on anything on this line let f = future { 5 }.map{f in f + 7} or this one f.onSuccess{callback in println(callback)}
Autocomplete works fine on anything else (i.e. aNotification) which is an argument on the applicationDidFinishLaunching function
Is there some setting in AppCode that I am missing, or is this something that hasn't been implemented yet? In XCode, autocompletion works completely correctly.
EDIT: I have also noticed that in AppCode, the .framework files are in red (not sure if this means anything?)

Resources