Maintaining a (private) Cocoa Pod

Development Pods in XCode

Intro

As a follow-up post to my previous article about creating a private Cocoa Pod, this time I will write about the challenges and gotchas that I discovered while creating and maintaining several private pods for the projects I was working on. Since I wrote my previous article, the official documentation of CocoaPods improved significantly, but there are still some aspects that it does not cover.

Extracting commonly used code to libraries is a common use case for companies and other development groups, but it can also be useful for a lone or indie developer maintaining several projects. Of course, code sharing is possible by copying the source files between projects, but if any changes are required in the shared code, it is not a sustainable method. A common way to include and maintain shared code dependencies is so important, that even Apple is working on an official way with Swift Package Manager. Unfortunately, it is still far from being finished and in the meantime, CocoaPods is still the best alternative in my opinion.

If you decide to use this solution and create a private pod, there are several tasks and difficulties that you may encounter while developing and maintaining your pod. We will look at the following topics in detail:

  • Creating a demo project for the Pod
  • Developing the Pod side-by-side with an app
  • Using other private Pod dependencies in the Pod
  • Ignoring warnings when pushing the Pod
  • Storyboards and Xibs in a Pod
  • Localized strings in a Pod

Please note that if you are not familiar with creating a pod, you should read my aforementioned article, or one of the official guides first.

Creating a demo project for the Pod

Ok, let's say you created the pod, but if you work with other developers, you may want to demonstrate the usage of it by providing a demo application. The simplest way is to add it to the same project as the pod, enabling users of your library to both browse the code and try it out with the demo app easily.

To do this, you have to add the demo app as a new target to the XCode project, and then you have to add the framework as an Embedded Library to it. You can do this in the General tab of the Project settings.

You have to add your framework as an Embedded Library

You have to add your framework as an Embedded Library (I used our recently open-sourced library as an example)

Developing the Pod side-by-side with an app

When developing a certain feature of the Pod, you may need to do it side-by-side with one of the apps that use it. Of course, you can use the demo app in the same project if you have such (I detailed this in the previous section), but sometimes you really want to use a real application which is in a separate project and normally use your pod via the online repository. Pushing the pod up after every change and updating the dependencies of the app using it is not very efficient, but luckily CocoaPods provide a way to use a Pod via a local path.

To do this, you can specify the dependency in your Podfile like this:

...
  pod '[Your Pod Name]', :path => '~/Documents/Libraries/[Your Pod Name]'
...

(Of course, you can specify any path where the Pod is located in your drive)

For your private Pods, you can even leave the local usage in and just comment it out when you don't need it (but make sure to only use it via one way at a time):

...
  # pod '[Your Pod Name]', :path => '~/Documents/Libraries/[Your Pod Name]'
  pod '[Your Pod Name]', '[Your Pod Version]'
...

This way you almost never need to invoke pod install or pod update (well, sometimes XCode does not recognize a newly added class in the framework, which can only be solved with a pod install or pod update), and you can even modify the code of the Pod in the same XCode window that contains your app using it. You can find the Pod under the Development Pods group in the Pod target of your project. XCode even displays the group structure of the Pod contrary to when you use it via the online repository (in which case, it just displays all of the files that are contained in it in one list).

Development Pods in XCode

Development Pods in XCode

Using other private Pod dependencies in the Pod

For a lot of use cases, you can get away without using any external dependencies for the Pod itself. Sometimes however, you have to use other libraries. If it is a public pod, it is easy to include via CococaPods, you only have to do the same steps that you do with an app project using pods. The only difference is that you have to specify the Pod as a dependency in the Podspec file:

...
s.dependency '[Pod Name]'  
...

If you use other private pods you also have to tell CocoaPods where the source files are located when pushing the pod (the Git URL of your private Specs repository to be exact). You have to use the --sources option of the pod spec lint or the pod repo push commands. For example to check the correctness of podspec use:

pod spec lint --sources=[Your Pod Specs Git URL],master [Your Pod Name].podspec  

I used the master branch in the example, but of course you can use any other branch too. Or another example for pushing the Podspec up:

pod repo push --sources=[Your Pod Specs Git URL],master [Your Pod Name].podspec  

Ignoring warnings when pushing the Pod

I know the saying that you should treat warnings as error when compiling your projects, but sometimes it is very hard to avoid having some warnings in your project. Even the XCode compiler can have false positives, and currently there is no way to silence a warning in Swift. Even worse - if you use third party dependencies, they can also have warnings which you cannot fix. By default, CocoaPods does not allow pushing Pods if they produce warnings, but luckily there is an option to ignore them: --allow-warnings. For example:

pod spec lint --allow-warnings [Your Pod Name].podspec  

And:

pod repo push --allow-warnings [Your Pod Name].podspec  

Storyboards and Xibs in a Pod

When you are creating a new View, or anything with a UI in your library, you may like to use Storyboards and/or Nibs/Xibs too. These won't be included in the Pod by default, you have to specify where they are in the Podspec, as you do with any other resources via their path. You can do it with one line (you can add other types of resources too as you can see in the example):

...
s.resources = "[Your Project Name]/**/*.{png,jpeg,jpg,storyboard,xib}"  
...

Localized strings in a Pod

Sometimes you have to use localized strings in your Pod. Unfortunately it is not straightforward, as by default the items in the .strings files won't be included in the main Bundle of the application that uses the Pod, so the string keys will be displayed on the UI (which is very unfortunate and can be hard to discover during development). The solution is simple, yet I had a hard time finding out anything about it on the web.

You have to specify a new Resource Bundle in the Podspec:

...
s.resource_bundles = { "[The Name of the Bundle]" => ["[Your Project Name]/*.lproj/*.strings"] }  
...

And load resources from it:

var bundleName = "[The Name of the Bundle]"  
var bundlePath = Bundle(for: [A Class in the Pod project].self)  
    .path(forResource: bundleName, ofType: "bundle")
var bundle = bundlePath != nil ? Bundle(path: bundlePath!) : Bundle.main  
var string = NSLocalizedString("[String Key]", bundle: bundle ?? Bundle.main, comment: "")  

We use the Main Bundle as a fallback if the Bundle cannot be found. You also need a reference to a class in the Pod project file to load the Bundle as you can see in the example above.

This way you can use localized strings in your Pod that will appear in the apps that use your Pod.

Conclusions

Using private Pods is a great way to develop shared code for your applications, but there are definitely some difficulties that you have to overcome while maintaining it. I collected the ones me and my colleagues encountered, but if you have other examples, please share them in the comment section below, so others can benefit from them too. If you have any questions regarding this topic, feel free to ask them here and I will answer them if I can.