Attributed strings are a fantastic way to work with styled text on iOS and OS X. NSAttributedString is an incredibly full-featured API, but because of this, simple tasks sometimes require a fair amount of boilerplate code. Today we'll look at BonMot, a library from Raizlabs that simplifies the process of composing attributed strings.
BonMot takes the form of a set of chainable functions that we can use to compose attributed strings:
letfancyQuote="Traveling through hyperspace ain't"+"like dusting crops, farm boy.\n"+" β Han Solo"quoteLabel.attributedText=BONChain().fontNameAndSize("AmericanTypewriter",17.0).lineHeightMultiple(1.8).string(fancyQuote).attributedString
BonMot will do all the heavy lifting of applying attributes and return a fully-formed NSAttributedString ready for use wherever we need.
We can use BonMot to concatenate multiple composed attributed strings:
BonMot even supports the NSTextAttachment parts of the NSAttributedString API. We can generate an βimage next to a labelβ like this:
chain.appendLink(BONChain().image(ackbar))chain.appendLink(BONChain().fontNameAndSize("Futura-MediumItalic",17.0).string("It's a trap!"),separator:" ")
We've only scratched the surface here, BonMot has a ton of features to offer. More info about BonMot can be found at git.io/bonmot
Today we're looking at SwiftyBeaver, a library from Sebastian Kreutzberger that aims to improve Xcode's logging capabilities. Let's dive in.
After adding the library to our project we'll need to import the library in our AppDelegate. We'll also need to create a global log reference we can use throughout our app:
importSwiftyBeaverletlog=SwiftyBeaver.self
Then we'll need to tell SwiftyBeaver to log somewhere, let's set it up to log to Xcode's console and a log file:
SwiftyBeaver is built around the concept of "destinations". Here we're adding one for the Xcode console, then another to log to a file in our app's documents directory.
Now, we can use our global log reference to log messages. There's functions for each of the different levels. Each will log its level in its own color:
log.verbose("Nothing really happened")// graylog.debug("A thing happened")// bluelog.info("A good thing happened")// greenlog.warning("A probably bad thing happened")// yellowlog.error("A definitely bad thing happened")// red
Both built-in destinations have a ton of configuration options allowing us to customize how we'd like our logs to behave. We can customize the format of log statements or change a file destination's location, we can even define our own destinations if needed!
We're continuing our look at Xcode Playgrounds today with CocoaPods. Let's see what it takes to import a CocoaPod into a Playground.
We'll begin by creating a new Xcode Project. Next, we'll head into our project's directory and run pod init to generate our Podfile. We'll open it up and configure it just as we usually would:
Then we'll run pod install to generate the Workspace file for our project and install our pods. We're almost done, next we need create a new Playground and add it to our project.
Finally, we need to add our Playground to our Podfile'slink_with directive (only its name, not the .playground extension):
link_with'Spaceships','scratch-pad'
We'll run pod install one more time and we're done. We can now import the pod we installed into our Playground and try it out. Neat!
Formatting strings is one of the most common tasks in building software. It's usually not that complex, but it can be tempting to cut corners.
When working with things like currency or street addresses, having a robust formatting system in place can be crucial in ensuring our app works well all over the world.
Today we'll look at Format, a library from Roy Marmelstein that can help us achieve all this with ease.
Format starts by extending Swift's number types to add a format function. We can call this on any number (even literals) and it will return a String with our desired format:
134.format(Decimals.Two)// => "134.00"
Format makes localizing a breeze. It uses the device's current locale by default, or we can render a specific one:
Ordinal numbers can really help class up the joint:
134.format(General.Ordinal)// => "134th"
Format can format in all sorts of interesting ways, for example:
10.11.format(General.SpellOut)// => "ten point one one"
Numbers are cool, but what about addresses? Almost every nation has a slightly different convention for how they format street addresses.
Format wraps CNPostalAddressFormatter from the Contactsframework (Bite #24) to make formatting addresses quite simple:
AddressFormatter().format("1 Infinite Loop",city:"Cupertino",state:"CA",postalCode:"95014",country:"USA",ISOCountryCode:"US")// => "1 Infinite Loop\nCupertino CA 95014\nUSA"
Format also extends CLPlacemark, adding a function to format that class's addressDictionary property. Users expect things to look familiar. Never underestimate the power of good localization.
More info about Format can be found at git.io/format
Building a good authentication system is a lot of work. Instead of starting from scratch, it'd be great if we could build on top of some existing popular service, and allow our users to log in with their existing account there.
Today we'll check out SimpleAuth from Caleb Davenport. It provides an extremely easy-to-use way to implement social sign-in inside our apps. Let's take a look:
SimpleAuth is built around the concept of Providers. In this context, a provider contains all the code needed to talk to individual services like Twitter, Facebook, etc.
Let's add support for signing in with Twitter to an app. We'll start by adding the pod to our Podfile, then run pod install.
pod'SimpleAuth/Twitter'
Then, we'll need a consumer key and secret from Twitter. We can get these by creating a new app on Twitter's developer portal.
Back in our Application Delegate, we'll configure SimpleAuth'sTwitter provider with our info:
Then we can grab their Twitter username out of a user dictionary. (Which contains keys like uid, image, etc.) SimpleAuth uses these names to abstract away the different attribute names each service uses to represent these values. For example, the field uid always holds a unique user identifier and is present on almost all providers.
SimpleAuth ships with a ton of built-in providers (Twitter, Facebook, Instagram, Tumblr, Dropbox, Foursquare, etc.). It also makes it very straightforward to create our own providers, just in case we ever need to. Neat!
We've covered Auto Layout quite bit, but so far we've only been using the classes and APIs that Apple ships. Today we'll start checking out some third-party libraries that can improve our experience when working with Auto Layout.
We can pass in up to 5 UIView or NSView instances at once, then a closure. In that closure, we'll use ==, >=, or <=operators to define constraints upon the attributes of the view instances. This can really help improve readability:
constrain(viewA){ain// fixed sizes:a.height==44// centering inside a parent view:a.centerX==a.superview!.centerX// inequalities:a.top>=a.superview!.top+10}
But wait, there's more! We can capture the created constraints like this:
It's another fastlane friday here on LBOC. Today we'll be looking at another awesome tool in the fastlane suite called scan. It provides an easy way to run the tests of our iOS or OS X app. Let's dive in.
Before we begin, let's look at why a tool like scan can be helpful.
Xcode ships with a great command line tool called xcodebuild that allows to do all sorts of interesting things to our projects from the command line. It can be a bit verbose to configure though, and its output isn't very readable at a glance.
There's other tools like xcpretty that can help improve this output, but they take a fair amount of configuration as well.
That's where scan comes in. It takes care of all of this (plus a lot more) in one simple command: scan.
This is all we need for basics usage. scan will auto-detect things like our workspace, but we can always configure things as well:
scan --scheme "app-store"
Like other fastlane tools, we can run scan init to generate a new Scanfile, where we can store all our configuration options:
scheme"Spaceships"cleantrueoutput_types"html"
Other Features
π Displays nice output, stores original xcodebuild log in ~/Library/Logs/scan
π Can generate HTML, JSON or JUnit reports
π£ Can send well-formatted test results to Slack. Check out the slack_only_on_failure configuration option to only report failed tests.
scan also helps with resolving common Xcode oddities like duplicated simulators or simulators that stop responding. Finally, scan works great with tools continuous integration tools like Jenkins and services like Travis. Happy testing!
Today we'll take our first look at ReactiveCocoa. It's a huge topic, so we'll start with why we might want to use it. Let's dive in. π
Traditionally, we'd wire up the different components in our app declaratively. We'd use common Cocoa techniques like Delegates, KVO, NSNotificationCenter notifications, target/actions, etc.
At first our app might have just a text field and delegate. But then we add a button, and maybe a segmented control. Things can quickly get messy. We inevitably end up with code like this sprinkled throughout our view controllers:
We're also using a ton of different mechanisms to accomplish the same conceptual task: Updating our UI to reflect state changes. Managing all of this in a small app might not seem like a big issue, but in even moderately complex codebases, this can be a big source of bugs. π
Enter ReactiveCocoa (also commonly called "RAC"). It's a library for iOS and OS X that helps us write code that works with streams of values over time. It also allows us to write code that's closer to how we think about our app while building it:
"The Create Crew Member button should only be enabled: if the crew name is valid, and a rank has been selected, and the ship is not already full."
For example, in RAC, we can get a reference to a signal producer, which will send along our field's text, every time it changes:
letcrewNameValid=crewNameTextField.rac_textSignal().toSignalProducer().map{$0as!String}crewNameValid.startWithNext{textin/* do something with text */}
We'll do this for our rank control too. Next, we can merge both of these signal producers into one, then use the values thatsignal producer sends along to set our createButton's enabled property.
ReactiveCocoa is a giant departure from how we've traditionally built our apps. Don't worry if it doesn't make sense right away. In the future, we'll dive deeper into RAC, and look at all the different ways we can use it.
More info about ReactiveCocoa can be found at git.io/RAC
Regular Expressions are a fantastic way to search through text. Apple provides support for them via the NSRegularExpression class. It has great support for matching, extracting, etc. Its API can be a bit verbose for simple matches though. Today we'll look at Regex, a tiny little library from Adam Sharp that makes writing regular expressions in Swift much more friendly and expressive. Let's check it out:
Regex has a bunch of great features, but at its core it allows us to take regular expression code like this:
...and turn it into something just a tad more readable:
Regex("star (wars|trek)").matches(stringToMatch)
Regex is backed by NSRegularExpression under the hood, and it does a great job of "swift-ifying" its API. In addition to simple Boolean checks, we can also use Regex in a few other rather Swifty ways. For example, pattern matching:
switchstarThing{caseRegex("gate$"):print("dial the gate!")caseRegex("wars$"):print("the force is strong")caseRegex("trek$"):print("set phasers to stun")default:break}
Last but not least, we can easily grab any captured strings:
Teaching users how to use our apps is incredibly important. We should strive to do this through familiar UI patterns, intuitive flows, good error handling, etc. Sometimes though, we just need to explain what's going on.
Next, we'll extend our view controller so it conforms to the CoachMarksControllerDataSourceprotocol. Instructions is highly customizable. It has support for custom body and arrow views, positions, highlights, and more. Let's keep things simple here and add a regular coach mark for a button in our app.
funcnumberOfCoachMarksForCoachMarksController(coachMarksController:CoachMarksController)->Int{return1}funccoachMarksController(coachMarksController:CoachMarksController,coachMarksForIndexindex:Int)->CoachMark{returncoachMarksController.coachMarkForView(launchButton)}funccoachMarksController(coachMarksController:CoachMarksController,coachMarkViewsForIndexindex:Int,coachMark:CoachMark)->(bodyView:CoachMarkBodyView,arrowView:CoachMarkArrowView?){letcoachViews=coachMarksController.defaultCoachViewsWithArrow(true,arrowOrientation:coachMark.arrowOrientation)coachViews.bodyView.hintLabel.text="This button launches the spaceship, proceed with caution!"coachViews.bodyView.nextLabel.text="Got it"return(bodyView:coachViews.bodyView,arrowView:coachViews.arrowView)}
It's good practice to let users who don't need our coach marks skip them. Instructions has us covered. We can setup a "skip view" on our controller. We'll use the default one, which shows in the nav bar: