When integrating your own CocoaPods into your apps you often find some things you’d like to tweak in your component from within the app project/workspace. This would allow you to immediately test the fix in the context of the app. This blog explains how to work with Development Pods which allow you to do exactly that.
Let’s assume that you have an app project and you’d like to add DTFoundation to it. For this example I created a single view app from Xcode’s template and called it DevPodTest.
Integrating a Pod into an App Project
Your initial Podfile would look like this:
platform :ios, '8.0' pod 'DTFoundation'
You run pod install and when you open the newly generated DevPodText.xcworkspace you will see that CocoaPods copied the source code into a Pods subfolder of your project root. The yellow groups you see in this screenshot are the sub-specs of DTFoundation.
You usually want to avoid checking pods source code and meta files into your repository as those files represent a cached local copy which usually can just be retrieved from the original repository. If you want to avoid them being changed you specify a specific version of the pod in your Podfile.
Because of this I added a few lines to my standard .gitignore file which I add to all my projects.
.DS_Store build *.mode1v3 *.pbxuser project.xcworkspace xcuserdata xcshareddata .svn Pods Podfile.local
The Pods entry is where CocoaPods copies all pod source code. The Podfile.local file we’ll get to shortly.
If you peek into the Pods folder you’ll find that there is no separate git repository present besides the one of your app project. This is because there is none. Instead of this being a live copy of the repository this contains all the files that have been referenced by the podspec. The actual repo clones can be found in ~/Library/Caches/CocoaPods/GitHub.
The Podfile references a range of versions of a pod, a specific version or – like in this example – “just give me the latest”. Whenever you run pod update the workspace will be refreshed.
Switching to a Pod’s Development Branch
Continuing our assumption, you’ve been working away on the DevPodTest app and find that there is a tweak you’d like to make in DTFoundation. As mentioned above it would be pointless to edit the source file you are currently seeing in the workspace since it is a dumb copy.
The Podfile mentioned above references the pod’s released versions found in CocoaPods trunk. You want to be able to have a live git clone of DTFoundation which you can modify and push back to the repository on GitHub.
This can be achieved by modifying the Podfile to reference a specific repository (for example your fork) and branch.
platform :ios, '8.0' pod 'DTFoundation', :git => 'https://github.com/Cocoanetics/DTFoundation.git', :branch => 'develop'
Make this change and run pod update to make CocoaPods carry it out. You will find that the workspace has not changed. You still have references to files which CocoaPods copied into the Pods folder structure.
The one difference though is that those files come from the branch you specified in the Podfile as opposed to the version that was tagged as the version via podspec. You have achieved that all people who try to build the app project also get the development version of the component once the run pod install and/or update.
You can leave the reference to the “bleeding edge” version of the component in the Podfile until your changes have made it into a formal release. At that time you can remove the :git and :branch and use the released version.
The point of this exercise is that you don’t end up having changes only locally which your colleagues or a build server would need to be able to build the app. By specifying the develop branch and repo URL you can continue to work with the CocoaPods workspace but access the newer version.
Overriding a Pod via Local Podfile
Change your Podfile to the following:
platform :ios, '8.0' # Allows per-dev overrides local_podfile = "Podfile.local" eval(File.open(local_podfile).read) if File.exist? local_podfile # Core Dependencies pod 'DTFoundation', :git => 'https://github.com/Cocoanetics/DTFoundation.git', :branch => 'develop'
I was taught this neat trick by Ortha Therox. This works for two reasons: 1) the Podfile is Ruby and 2) the first instance of a pod is the one that “wins”. With the above setup you can choose to have a Podfile.local in your project folder (which gets conveniently ignored via our git ignore file).
There you can specify the path to a local clone of a repo you want to “hot edit”. Now, add a Podfile.local with this contents:
pod 'DTFoundation', :path => '~/Documents/Projects/DTFoundation'
The path can be any local path where you have a live clone of the repository. I keep all my project folders parallel in a Projects folder, but you are free to keep them anywhere you prefer. Adapt the path accordingly, the tilde is short for the current user’s home folder.
If you do pod update now, magic happens.
The first difference you’ll notice is that the DTFoundation pod has now moved into a Development Pods group. If you inspect the reference you find that it no longer points into the Pods subfolder but rather into the local pod repository.
Congratulations, you now have a live copy of the pod which you can update inside the app workspace. This is awesome for small changes that are made exclusively on existing source files. Adding or removing source files is out of the question because you have no access to the project file. For bigger changes like that, you should open the local project file.
Once you are done tweaking the component code you should get it onto the Internet so that other people (or machines) who only know the contents of the global Podfile also have access to it. For the workflow, I recommend branching off develop with a feature branch, commit your changes there and once you are happy with it, merge the temporary branch back into develop. When you push changes to your app repo you also push the updated develop branch to the component repo.
If you don’t have push rights to the component repo (because you are not its owner) the same procedure would also work if you reference your fork of the component on GitHub. In this case you’d probably leave the feature branch intact so that you can base a pull request to the master repo maintainer off it.
One way or the other, you need to make sure that your local changes can be accessed. Otherwise somebody might get annoyed by you “breaking the build”, if the app needs something in the component that you only have on your local hard disk.
Moving Back to Global Podfile
You can just as easily disable the development pod by either renaming Podfile.local or by commenting the line by adding a hash as first character. I would recommend the latter because other names for the local pod file would cause it to no longer be ignored by git. Or you can remove the override file altogether.
If you run pod update after this you are back at the initial state with the component showing up in the Pods group. You could now check if the changes to the develop branch have made it. They should have since pod update also refreshes the repo clone.
Knowing how to move between different versions of a CocoaPods is a good skill to have if you like CocoaPods for your dependency management. You might be needing to tweak your own component pod, or work on fixes for an Open Source pod. Either way, the presented method allows you to edit pod source code right in the context of your app’s workspace.
Switching between pod, pod-via-branch and local-development-pod is easy, so you should not be afraid of it, but leverage it to your advantage.