Swift: How to create iOS Today extension and share data with containing app

Do you happen had a chance to see the 3d touch icon widget or iOS Today Extension, let’s take a look at how to create an Extension in swift, how to open containing app and how to share data with containing app.

To add Today Extension to the project click “file” -> “new” -> “target”: chose “Today Extension”.
Let’s go to “Capabilities” tab and enable “App Groups” for both AppWithExtension and Extension targets, for both app and extension you need to click “+” icon under the “App Groups” and add Groups to you app id, e.g. like here


Continue reading “Swift: How to create iOS Today extension and share data with containing app”

Social Share Toolbar

Swift: how to use 3D Touch – Introduction

3D Touch or force touch – is a new feature that have been introduced in the iPhone 6s. It’s interesting to know that you can get touch force, it is a number from 0 up to 6.6667, actually it a number from 0 up to maximumPossibleForce.

Let’s take a look at how to check if 3d touch is supported on current device:

func is3dTouchAvailable(traitCollection: UITraitCollection) -> Bool {
    return traitCollection.forceTouchCapability == UIForceTouchCapability.available
}
 
// Let's call it somewhere from the GameScene for instance
override func didMove(to view: SKView) {
 
    hardwareService.initDevice()
    if(is3dTouchAvailable(traitCollection: self.view!.traitCollection)) {
       //...
    }
 
}

Feel free to take a look at the example project – I’ll provide the link in the end of this post.
So this is a good practice to check whether 3d touch is supported or not.

Here’s an example of how to get the force of the 3d touch

func touchMoved(touch: UITouch, toPoint pos: CGPoint) {
    let location = touch.location(in: self)
    let node = self.atPoint(location)
 
    //...
    if is3dTouchEnabled {
        bubble.setPressure(pressurePercent: touch.force / touch.maximumPossibleForce)
    } else {
        // It is important to use touchMoved for iPhones w/o 3dTouch,
        // So we can "emulate" it.
        bubble.setPressure(pressurePercent: 1)
    }
}

The current value of the Touch’s force is touch.force and the touch.maximumPossibleForce is a good one to convert force value to the “percentage”.
This code is from the small example of how to “emulate” the bubble reacting on the force of the touch.
https://github.com/DjComandos/3dTouch
To try this on your iPhone 6s/7 you can install this bubble wrap app real quick.

Here is my previous post with more details about Haptic feedback.

Social Share Toolbar

WatchOS 2 tutorial: Activity Ring – WKInterfaceActivityRing

Just a quick example how to use WKInterfaceActivityRing and HKActivitySummary to show Activity Ring.

Simulator Screen Shot Jun 7, 2016, 8.58.27 PM

Go ahead and drag “Activity Ring” on your Interface Controller, use Assistant Editor to add Outlet to your Controller, e.g.

@IBOutlet var activityRing: WKInterfaceActivityRing!

All you need to set up Activity Ring values is to create and initialize HKActivitySummary, here is the sample code (for simplicity we are going to use the same values for all the rings)

let summary = HKActivitySummary()
let value: Double = 2
let goal: Double = 10
 
summary.activeEnergyBurned = HKQuantity(unit: HKUnit.kilocalorieUnit(), doubleValue: value)
summary.activeEnergyBurnedGoal = HKQuantity(unit: HKUnit.kilocalorieUnit(), doubleValue: goal)
 
summary.appleExerciseTime = HKQuantity(unit: HKUnit.minuteUnit(), doubleValue: value)
summary.appleExerciseTimeGoal = HKQuantity(unit: HKUnit.minuteUnit(), doubleValue: goal)
 
summary.appleStandHours = HKQuantity(unit: HKUnit.countUnit(), doubleValue: value)
summary.appleStandHoursGoal = HKQuantity(unit: HKUnit.countUnit(), doubleValue: goal)
 
activityRing.setActivitySummary(summary, animated: true)

That’s pretty much it.

Social Share Toolbar

Watch OS 2: Refreshing Apple Watch complication

To make WCSession work fine with Complications we have to use transferCurrentComplicationUserInfo on the iPhone side.
So here’s how to send updated dictionary from iPhone

// update complication
if session.watchAppInstalled {
    session.transferCurrentComplicationUserInfo(dataDictionary)
}

Let’s get back to Apple Watch Extension, here’s what should be in the InterfaceController which uses WCSession to subscribe on updates:

func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
    if let dateString = userInfo["dataKey"] as? String {
         // save new value to user defaults
         let defaults = NSUserDefaults.standardUserDefaults()
         defaults.setObject(dateString, forKey: "dataKey")
 
         // reload complication data
         reloadComplications()
     }
 }
 
func reloadComplications() {
    let server = CLKComplicationServer.sharedInstance()
    guard let complications = server.activeComplications where complications.count > 0 else {
        return
    }
 
    for complication in complications  {
        server.reloadTimelineForComplication(complication)
    }
}

That’s basically it, now we can get these values from the Cache in the Complication itself.

Social Share Toolbar

watchOS 2: Writing a WatchKit Complication

How to create custom WatchKit Complication which could be easily added to Apple Watch face.
This is one of the extremely exciting new additions to the WatchKit Framework in watchOS 2 – ability to add custom complications to the clock faces provided by Apple.

First let’s add Complication Data Source

import ClockKit
 
class Complication: NSObject, CLKComplicationDataSource {
 
    func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
        handler(nil)      
    }
 
    func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
        handler(nil)
    }
 
    func getPrivacyBehaviorForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationPrivacyBehavior) -> Void) {
        handler(CLKComplicationPrivacyBehavior.ShowOnLockScreen)
    }
 
    func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimelineEntry?) -> Void) {
        handler(nil)
    }
 
    func getTimelineEntriesForComplication(complication: CLKComplication, beforeDate date: NSDate, limit: Int, withHandler handler: ([CLKComplicationTimelineEntry]?) -> Void) {
        handler(nil)
    }
 
    func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: ([CLKComplicationTimelineEntry]?) -> Void) {
        handler([])
    }
 
    func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) {
        handler([CLKComplicationTimeTravelDirections.None])        
    }
 
    func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(NSDate())
    }
 
    func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(NSDate())
    }
}

Let’s register it in the info.plist, make sure, that the following properties are set correctly
CLKComplicationPrincipalClass set to e.g. “$(PRODUCT_MODULE_NAME).Complication”
CLKComplicationSupportedFamilies has at least one value – CLKComplicationFamilyCircularSmall
RemoteInterfacePrincipalClass set to e.g. $(PRODUCT_MODULE_NAME).InterfaceController

Let’s update our data source template to provide some percentage like data, let say we have goal of 10 something and right now our progress is 8/10 – let’s render it (let’s make data static not to overcomplicate the example):

import ClockKit
 
class Complication: NSObject, CLKComplicationDataSource {
 
    func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
        // Update hourly
        handler(NSDate(timeIntervalSinceNow: 60*60))
    }
 
    func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
        var template: CLKComplicationTemplate? = nil
        switch complication.family {
        case .ModularSmall:
            template = nil
        case .ModularLarge:
            template = nil
        case .UtilitarianSmall:
            template = nil
        case .UtilitarianLarge:
            template = nil
        case .CircularSmall:
            let modularTemplate = CLKComplicationTemplateCircularSmallRingText()
            modularTemplate.textProvider = CLKSimpleTextProvider(text: "--")
            modularTemplate.fillFraction = 0.7
            modularTemplate.ringStyle = CLKComplicationRingStyle.Closed
            template = modularTemplate
        }
        handler(template)
    }
 
    func getPrivacyBehaviorForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationPrivacyBehavior) -> Void) {
        handler(CLKComplicationPrivacyBehavior.ShowOnLockScreen)
    }
 
    func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimelineEntry?) -> Void) {
        if complication.family == .CircularSmall {
            // these values should be fetched from somewhere...
            let progress = 8
            let goal = 10
 
            let template = CLKComplicationTemplateCircularSmallRingText()
            template.textProvider = CLKSimpleTextProvider(text: "\(progress)")
            template.fillFraction = Float(progress) / Float(goal)
            template.ringStyle = CLKComplicationRingStyle.Closed
 
            let timelineEntry = CLKComplicationTimelineEntry(date: NSDate(), complicationTemplate: template)
            handler(timelineEntry)
        } else {
            handler(nil)
        }
    }
 
    func getTimelineEntriesForComplication(complication: CLKComplication, beforeDate date: NSDate, limit: Int, withHandler handler: ([CLKComplicationTimelineEntry]?) -> Void) {
        handler(nil)
    }
 
    func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: ([CLKComplicationTimelineEntry]?) -> Void) {
        handler([])
    }
 
    func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) {
        handler([CLKComplicationTimeTravelDirections.None])
    }
 
    func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(NSDate())
    }
 
    func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(NSDate())
    }
}

Feel free to use transferCurrentComplicationUserInfo for getting actual data, or simply get the data from NSCache().

Social Share Toolbar

swift: How to render a UIView to a UIImage

You can render any UIView into UIImage in just several lines of code. Never mind subviews – that will be handled automatically. Here’s the code:

 
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, true, 0)
    view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

For sure you can extend UIImage for convenience

import UIKit
 
 
extension UIImage {    
    convenience init(view: UIView) {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, true, 0)
        view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(CGImage: image.CGImage!)
    }
}

After that you’ll need just:

 
    UIImage(view: someChartView)

That’s it!

P.S. Available from iOS 7.0

Social Share Toolbar

AWS Mobile hub, swift and dynamoDB

If you will google “backend for ios application” or “backend for swift application” you can notice that Amazone Mobile Hub will be in the top results, articles whatever, the easiest way to start your backend right now is for sure https://aws.amazon.com/mobile especially if you already familiar with aws. The wizard is quite well and detailed, it will guide you through the process and will even generate sample project YourApp-aws-my-sample-app-ios-swift which will use the backend which you have just configured in the web.
I believe you can play with wizard by yourself and reach even the point when you will copy-paste all AWS stuff to you project – wizard greatly describe even this part. I just want to highlight several steps/moments which were not very obviously for me, and show the example how to use mobile hub even easier w/o copying all the code from AWS – I mean tables, helpers, and all that swift stuff, you will need only One file – aws data model, and that’s it, you are all set to make backend requests.

Please start with completing this wizard https://aws.amazon.com/mobile
Several things they forgot to mention in the step “Use MySampleApp as an Example”:
1. While copying settings from Sample’s Info.plist to yours one, additionally copy “App Transport Security Settings”.
001
It is easy to forget and it’s really required to allow your app to make http requests to aws instances
2. On the step “Add a Run Script phase to your project” you need to add “bash “${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/AWSCore.framework/strip-frameworks.sh” line to the “Build Phases”.
It’s vital to have “Run script” section under the “Embed frameworks” and basically all other, just drag it to the bottom, so you can avoid “…Frameworks/AWSCore.framework/strip-frameworks.sh: No such file or directory” error during the Build of your app.
So drag it down like that
002
and try to clean and build your project once again – should be fine in case if you had such an error.

Read further if you have successfully finished all wizards steps and if you have your app up and running and connected with aws backend.

Lets say we want to create a new table for collecting Feedback, we just need to post the “Feedback” and that’s it, let go ahead and create a new table in mobilehub, so I clicked to “Configure” section, than to NoSql Databases and choose “Add new table” option on the bottom, here is my scheme:
003
Ensure that in the “Queries this table can perform.” you have “Find all items with deviceId = ABC” option, otherwise you will have only one Feedback from one device, it will be the latest one.

You can try to download the latest version of Sample App, but eventually the only thing you nee is the table ID which looks like this “AppName-mobilehub-xxxxxxxxxx-Feedback”. You don’t really need those humongous Tables and Helper classes, the only one thing you might want to copy-paste from the latest Sample is the Feedback.swift which is data model for AWS DynamoDB, it looks like this (Feedback.swift):

//...
 
import Foundation
import UIKit
import AWSDynamoDB
class Feedback: AWSDynamoDBObjectModel, AWSDynamoDBModeling {
 
    var _deviceId: String?
    var _appName: String?
    var _text: String?
    var _userContact: String?
    var _date: String?
 
    class func dynamoDBTableName() -> String {
        return "AppName-mobilehub-xxxxxxxxxxx-Feedback"
    }
 
    class func hashKeyAttribute() -> String {
        return "_deviceId"
    }
 
    class func rangeKeyAttribute() -> String {
        return "_date"
    }
 
    override class func JSONKeyPathsByPropertyKey() -> [NSObject : AnyObject] {
        return [
            "_deviceId" : "deviceId",
            "_appName" : "appName",
            "_text" : "text",
            "_userContact" : "userContact",
            "_date" : "date",
        ]
    }
}

Ok, we are ready to post the feedback, here is all we need:

    func postFeedback(contact: String, text: String, completionHandler: (error: NSError?) -> Void) {
        let feedback = Feedback()
        feedback._deviceId = Identity.getUserUniqueId() // AWS Identity
        feedback._date = String(NSDate())
        feedback._appName = "AppName" // Just in case if you'll want to use this table from several apps.
        feedback._userContact = contact
        feedback._text = text
 
        insertFeedbackWithCompletionHandler(feedback, completionHandler: completionHandler)
    }
 
    // MARK: AWS Methods
    internal func insertFeedbackWithCompletionHandler(item: Feedback, completionHandler: (error: NSError?) -> Void) {
        let objectMapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
 
        objectMapper.save(item, completionHandler: {(error: NSError?) -> Void in
            dispatch_async(dispatch_get_main_queue(), {
                completionHandler(error: error)
            })
        })
    }

So this is basically it, go ahead and run you app and it should work (obviously you had to create a UI and call this method on your own). You might noticed this line “Identity.getUserUniqueId()” just in case here is what’s under the hood:

    func getUserUniqueId() -> String {
        let identityManager = AWSIdentityManager.defaultIdentityManager()
        return identityManager.identityId ?? TEST_ID
    }

This is it, AWS mobilehub is very easy and way to create a backend for you iso app, I still had no chance to push it’s limits and I’m sure that there is a price for such a high-level abstraction but it’s very easy for simple use cases. May be I’ll post some results on my researches on it’s performance or dealing with edge cases. It was kind of overview of this new technology.

P.S. To check out the content of your DynamoDB go here https://console.aws.amazon.com/dynamodb/home?region=us-east-1#tables: choose the right region, click to “…-Feedback” table and click “Items” tab, here you can see all the items from your database and even scan or query them.

Social Share Toolbar