We started using both CocoaPods and Carthage to manage our dependencies, and we wanted to add a nice little view in our app that shows a list of open-source acknowledgements and licenses. We have around 20 dependencies, and the thought of adding the acknowledgements manually sounded tedious and keeping everything updated every time we added/removed/updated a dependency sounded even worse. 💀
When something is tedious and requires repeating, it’s the worst. (Especially if you are lazy like me!) But the good thing is, with a bit of code we can automate all the things. We can take our laziness and do something fun. 🌟
Best of all, we can do this all in Swift!
Here’s the game plan.
- Get all
LICEN(S|C)Efiles for CocoaPods and Carthage dependencies.
- Convert each license to Markdown.
- Convert the Markdown into HTML.
- Put the HTML file in our Xcode project.
- Load the HTML into a
UIWebViewfor < iOS 8) and show it in the app.
Steps 1-4 can live in a script, and once we write step 5 once, it will be automatically updated everytime we run the script. In our case, we took it one small step further and added it as a task to our
Rakefile so we regenerate the acknowledgements HTML everytime we run
rake (which also installs dependencies) so we can keep our licenses up-to-date when we add, remove, or update dependencies. (Thanks @_eliperkins for your help with this!)
Even if this isn’t the exact solution for the exact problem that you’re trying to solve, the concepts that we will go over here will be applicable to other Swift scripts that you might want to write.
Let’s get started.
Step 1: Get LICEN(S|C)E files for CocoaPods and Carthage dependencies.
When I learned that “license” can be spelled with two C’s as in “licence” I was very surprised. Anyway, let’s first see where we can find all of the licenses (and licences too I suppose).
- CocoaPods LICENSE files live in
- Carthage LICENSE files live in
First, we get the URL’s for all files in
Carthage/Checkouts/ which contain the
LICENSE files for all of our dependencies. Feel free to follow along with the completed script
acknowledge.swift. If you are unfamiliar with
guard, I recommend reading “Optional?” first since
guard will appear everywhere in this post. 💂🏼
Then we filter for just the
… and convert them to
getLicense is defined as:
Step 2: Convert each License to Markdown
How do we convert these
License’s into Markdown? That secret is in how we defined
License which conforms to the
Streamable protocol which we also defined.
License conforms to
Streamable it needs to define a
body. Based on this, the
Streamable protocol provides
writableString via a protocol extension that formats the License as a nice Markdown string with
title as a
h1 header style. (Apple has nice documentation around protocols and protocol extensions if they are new to you!)
Step 3: Convert the Markdown into HTML.
This is where things got a little bit interesting. I didn’t want to write my own Markdown to HTML converter. (Remember, I’m lazy.) @hyperspacemark found an open-source project called
Markingbird 🐦 that does exactly this. But how do we get
Markingbird added to our script? My first attempt was to add it as a Carthage dependency so we can
import Markingbird but I quickly learned that there was no Carthage support for it. (I gave a talk on using Carthage as a dependency manager for scripting in Swift if you’re interested!)
Upon reading the
Markingbird more thoroughly, I saw,
the easiest way to use it is to simply copy that file into your own projects. Knowing that there isn’t a simple way to manage multiple files for Swift scripts, I copy-🍝’d the
LICENSE and all into the top of our script.
And guess what. It looked horrible. I could barely tell where the actual script was which the many many lines of
Markingbird.swift code added. We had to find a way to separate this code out.
After searching around, I found a Stack Overflow answer that suggested making use of the Unix command
cat, which concatenates and prints files (type
man cat into your command line to read the rest of the documentation). 🐱 Using this, we can
cat Markingbird.swift acknowledge.swift which will print out all of
Markingbird.swift followed by all of
acknowledge.swift. We can then take that output and pipe it into
xcrun swift as below:
cat Markingbird.swift acknowledge.swift | xcrun swift -
swift means take
stdin and run that. (If you’re curious you can read more about it.)
Anyway, voilà! It’s a bit weird, but it works and it’s very stable since all it does is use basic Unix commands.
Breaking News: While I was in the middle of writing this post, @hyperspacemark opened a pull-request to add Carthage support to Markingbird.
That means we can use the Carthage method to add Markingbird to the script. ✨ In
.private because we’re not shipping this into our app, check out the documentation), we can add
Markingbird as below:
github "venmo/Markingbird" ~> 1.13
(Until this pull request is merged in and a new release is cut, we can point to
venmo/Markingbird instead of
When you run
carthage bootstrap, Carthage builds the frameworks and outputs them to
tvOS depending on what platforms the framework supports). And the
swift command lets us specify a framework search path with the
-F option. We can run
acknowledge.swift with that option like below:
xcrun swift -F Carthage/Build/Mac acknowledge.swift
acknowledge.swift, we can
I don’t know about you, but I don’t want to type
xcrun swift -F Carthage/Build/Mac acknowledge.swift all the time. 😪 It’s pretty long and it very much defeats the purpose of scripting. The good thing is, we can make it so that we have to only run
./acknowledge.swift with 2 simple steps:
chmod +x acknowledge.swiftto give it executable permissions.
#!/usr/bin/env xcrun swift -F Carthage/Build/Macat the very top of
acknowledge.swift. This is an interpreter directive that specifies how to run this file.
Now, all we have to type is
./acknowledge.swift to run the script. 🎉
But before we do that, let’s finish up the script. Our last step is to convert the Markdown to HTML and write it out to a file. We can do this in three lines of code.
Step 4: Put the HTML file into our Xcode project.
In our script, we output the generated HTML into our
Resources directory. Once you add it to the Xcode project once, it’ll get updated automatically when you run the script. Unless you also commit your
Carthage directories (which is fine, just different philosophies), you probably want to put your HTML file into
.gitignore as well since it’s a build product that is generated automatically.
Step 5: Load the HTML into a
UIWebView for < iOS 8) and show it in the app.
At this point we’re done talking about things that are specific to scripting, but if you’re curious about this web view business, feel free to follow along.
UIWebView have a method called
loadHTMLString(_:baseURL:) that lets you load an HTML string directly in the web view. So in our case, we want to load the entire mega HTML string that we generated into this method.
And that’s it! Once you apply some basic CSS to the HTML, you will end up with something like this. (We’re still tweaking the CSS to make it look better as well.) Because we hooked in the call to
./acknowledge.swift to our
Rakefile to have it get called right after updating any CocoaPods or Carthage dependencies, we never have to worry about about adding/deleting/updating our licenses again. Time to kick back and relax. 😎
That’s it for now. This piece goes a little beyond the basics compared to my other step-by-step posts, but I thought it might be fun to start writing a bit about different applications of Swift as well. If you have any questions, please feel free to leave them in the comments below, or tweet at me @ayanonagon. The biggest reason I’m writing these posts is to get better at explaining concepts and how amazing they are, so any feedback about what was clear vs. not would be much appreciated as well. Also let me know what other topics you want to hear about. Hope you had fun learning, and thanks for reading. 🐣
I’m also incredibly curious what others have been doing for Swift scripting, like what you do for dependency management and what you are automating with the scripts, so please let me know in the comments below! 📩