CGPaths are how CoreGraphics describes the bezier paths it uses for drawing to the screen. They can go a long way to unlocking some of the power of CoreGraphics, so let’s take a look:
We're going to create a custom disclosure indicator to use on our UITableView rows. We’ll create a CGPath, and add two lines to it that make up the arrow shape.
This kind of CoreGraphics code is largely imperative. You tell the system what to do in a bunch of steps.
@IBDesignableclassDisclosureIndicatorView:UIView{overridefuncdrawRect(rect:CGRect){letpath=CGPathCreateMutable()// move to top-leftCGPathMoveToPoint(path,nil,bounds.origin.x,bounds.origin.y)// add line to middle-rightCGPathAddLineToPoint(path,nil,bounds.size.width,CGRectGetMidY(bounds))// move to bottom-leftCGPathMoveToPoint(path,nil,bounds.origin.x,bounds.size.height)// add another line to middle-rightCGPathAddLineToPoint(path,nil,bounds.size.width,CGRectGetMidY(bounds))letcontext=UIGraphicsGetCurrentContext()CGContextAddPath(context,path)UIColor.lightGrayColor().setStroke()CGContextSetLineWidth(context,2.0);CGContextDrawPath(context,kCGPathStroke)}}
We tell CoreGraphics to move to the top-left point in our view, then we ask the system to add line, then move again, and so on.
Finally we add the path to our context, and draw the path to the screen using a light gray color, and a 2-pixel wide line.
Pro Tip: Add @IBDesignable to your view so you can preview it in Interface Builder! (shown below)
Gesture Recognizers are an powerful way to build interactions into your app. Let's look at the basics with a couple of example use cases:
Two Finger Long Press
Here we'll build a gesture that fires when the user touches the screen with two fingers for 1 second.
funchandleLongPressGesture(gr:UILongPressGestureRecognizer){sendHeartbeat()}letlongPressGR=UILongPressGestureRecognizer(target:self,action:"handleLongPressGesture:")// we allow user some 'wiggle' so the recognizer// doesn't fail if they move slightly while touchinglongPressGR.allowableMovement=80longPressGR.minimumPressDuration=1// in secondslongPressGR.numberOfTouchesRequired=2digitalTouchView.addGestureRecognizer(longPressGR)
Make a View Draggable
Ever wanted to enable a view to be dragged around the screen? A pan gesture recognizer does the trick:
Protecting data with Touch ID is a great way to improve your app.
Let's take a look at how to do it:
Save to Keychain
First you need to save something to the keychain, and protect it with the User Presence access control flag.
funcsavePassword(username:String,password:String){letpasswordData=password.dataUsingEncoding(NSUTF8StringEncoding)guardletdata=passwordDataelse{return}letaccessControl=SecAccessControlCreateWithFlags(kCFAllocatorDefault,kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,.UserPresence// this part enables Touch ID support,nil)SecItemAdd([String(kSecAttrService):serviceID,String(kSecClass):kSecClassGenericPassword,String(kSecAttrAccessControl):accessControl.takeUnretainedValue(),String(kSecAttrAccount):username,String(kSecValueData):data],nil)}
Retrieve From Keychain
Then when you retrieve, supply a prompt message and iOS will show the familiar Touch ID alert before executing the Keychain query:
funcpasswordForUsername(username:String)->String?{letquery=[String(kSecAttrService):serviceID,String(kSecClass):kSecClassGenericPassword,String(kSecReturnData):true,String(kSecMatchLimit):kSecMatchLimitOne,String(kSecUseOperationPrompt):"Authenticate to Login",String(kSecAttrAccount):username]// Working with the Security Framework is still kind of ugly in Swift:varpasswordData:Unmanaged<AnyObject>?=nilSecItemCopyMatching(query,&passwordData)ifletopaque=passwordData?.toOpaque(){returnNSString(data:Unmanaged<NSData>.fromOpaque(opaque).takeUnretainedValue(),encoding:NSUTF8StringEncoding)as?String}returnnil}
Creating a Today View Widget for your app is a great way to give users a quick way to access timely information or controls. Let's make one!
Add the Widget Target
Implement Regular Table View Controller Stuff
classTodayViewController:UITableViewController,NCWidgetProviding{varbites=[Bite]()// not shown here because boring:// numberOfRowsInSection returns bites.count// cellForRowAtIndexPath just sets cell.textLabel.text = bite.title}
Grand Central Dispatch is a wonderful way to write asynchronous code in your app. It's actual API however, can sometimes be a bit, well, let's call it "dry". Async is a Swift library by Tobias Due Munk that adds some much-welcomed syntactic sugar to Grand Central Dispatch. Async's core API is simple enough to be understood in a single line of code, so let's look at some other fun things it can do:
Chain Blocks
Async.background{// Runs on the background queue}.main{// Runs on the main queue, but only after// the previous block returns}
Delayed Execution
Async.main(after:0.5){// executed on the main queue after 500 ms}.background(after:0.4){// executed on the background queue// 400 ms after the first block completes}
Cancel Blocks
letcomputeThings=Async.background{computeSomethingBig()}letcomputerOtherThings=computeThings.background{// this sucker is about to get cancelled}Async.main{computeThings.cancel()// this won't do anything// you can only cancel blocks that haven't yet started executing!computerOtherThings.cancel()// this second block *would* in fact get cancelled,// since it hasn't started executing yet}
More info about Async can be found at git.io/async
Generics were new to many iOS developers when they debuted in Swift 1.0 last year. They are a way of specifying parts of your code can work with different kinds of objects of a given type. but you don't want to hardcode one single type. What if you wanted to say something like “this function accepts any object of some type and returns an object of that same type, but I don't care what type that is.” It ends up looking like this:
funcaGenericFunction<SomeType>(thing:SomeType)->SomeType{returnthing}aGenericFunction("Hello")// returns the String "Hello"
Whoa! Where did those < > brackets come from? That's how you tell the Swift compiler that you are writing a generic function. The SomeType can be any name you want, and you can use it in your code in place of a concrete type. What if you wanted your new generic function to only accept types that conformed to the a protocol? Simple:
Functions are great, but what about types themselves? The concepts behind Generics work exactly the same there. Let's look at this by making a generic DataSource struct that will power a table view controller:
The Photos Framework is the modern equivalent to the old Assets Library Framework. It's how you access and interact with the user's photo library on iOS and OS X. Here are a few examples of things you can do with it:
Changes must happen asynchronously when working with the Photos Framework so we use a PHAssetChangeRequest to mark a photo as a favorite.
Retrieve an Image at Specific Size
letmanager=PHCachingImageManager()manager.requestImageForAsset(asset,targetSize:CGSizeMake(50.0,50.0),contentMode:PHImageContentMode.AspectFill,options:nil,resultHandler:{(image:UIImage?,userInfo:[NSObject:AnyObject]?)in// yay image!})
We didn't specify any options there, but you can use the PHImageRequestOptions to customize if you'd like to allow loading images over the network, as well as get a progress callback as they are downloaded.
CoreImage has long been a staple on OS X, and was added to iOS a few years ago. It's an incredibly feature-packed image processing API that can apply just about any type of filter or image manipulation you can dream of. Let's take a look at applying a simple color tint to an image:
CoreImage works on CIImages not UIImages, so we convert our image to a CIImage, and use guard since the CIImage property on UIImage is optional. CoreImage also doesn't use UIColor, so we create a CIColor instead.
Then the fun part, we create our filter by name. CoreImage has literally 100's of different filters available, and instead of subclasses, you instantiate them by name.
CIFilters also don't have explicit class properties, you instead supply values for "Input Parameters" on your filter.
Lastly, we apply the filter by simply asking for it's outputImage.
Diving deeper into CloudKit today we'll look at some more advanced ways of interacting with your app's data using operations. CloudKit's NSOperation-based API allows for things like batch saves/changes, ‘paging' through data and more.
CKQueryOperation‘squeryCompletionBlock provides an optional CKQueryCursor. You can store this and supply it to your next query to load the next logical 'page' of records.
Progress
One of the great things about CKModifyRecordsOperation and CKQueryOperation is their ability to report progress at a per-record level.
Testing is incredibly important in ensuring our app arrives to our users in as good a state as possible.
Xcode has had great unit testing support for a few releases now, but testing our UI has always been a challenge.
Instruments added support for it a while back, but it’s never felt like a first class citizen... Until now!
Xcode 7 brings us an awesome, fully baked set of UI testing tools. We can even make Xcode write much of your test code for us by simply interacting with our app.
Here's an example test created using the default Master-Detail template in Xcode. It presses the add button 3 times, then verifies that 3 cells now exist in the table view:
Recording UI Tests couldn't be easier. We just write an new empty test function, put our cursor in the middle of it, and press record.
UI Testing uses Accessibility under the hood to identify and retrieve UI elements at runtime. Just one more reason to make our apps as accessible as possible!