Shrikar Archak

It does not matter how slow you go so long as you do not stop. ~Confucius

iOS Sharing With UIActivityViewController in Swift

iOS Sharing with UIActivityViewController in Swift

Do you want to make your app viral? If so one of the key ingredient is adding ios sharing functionality in your app. You can share to Social Media, or traditional means of message and mail.

iOS 7 and iOS 8 provide UIActivityViewController for making the social sharing a lot easier. With UIActivityViewController we can add ios sharing capabilities with a few lines of code.

uiactivityviewcontroller
1
2
3
UIActivityViewController
- init(activityItems activityItems: [AnyObject],
applicationActivities applicationActivities: [AnyObject]?)

activityItems are the data object on which to perform the activity. Activity Items can be simple type like String, image , url or custom object which conform to UIActivityItemSource protocol.

If you look at the documentation for UIActivityItemSource protocol there are two function which are required.

uiactivityviewcontroller
1
2
3
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> AnyObject
func activityViewController(_ activityViewController: UIActivityViewController,
   itemForActivityType activityType: String) -> AnyObject?

Lets take a look at a example . We have a custom News reading application where the news appear as a tinder style flash cards and we want to share that card.

images

uiactivityviewcontroller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//
//  FlashCard.swift
//  FlashCard
//
//  Created by Shrikar Archak on 12/1/14.
//  Copyright (c) 2014 Shrikar Archak. All rights reserved.
//

import UIKit

class FlashCard: NSObject, UIActivityItemSource {
    var news: String = ""
    var url : String = ""

    func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject {
        NSLog("Place holder")
        return news;
    }

    func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? {
        NSLog("Place holder itemForActivity")
        if(activityType == UIActivityTypeMail){
            return news
        } else if(activityType == UIActivityTypePostToTwitter){
            return news + " via @iOSgetstarted " + url
        } else {
            return news + " url"
        }
    }

    func activityViewController(activityViewController: UIActivityViewController, subjectForActivityType activityType: String?) -> String {
        NSLog("Place holder subjectForActivity")
        if(activityType == UIActivityTypeMail){
            return "Hey check this out!!"
        } else if(activityType == UIActivityTypePostToTwitter){
            return news + " via @iOSgetstarted " + url
        } else {
            return news + " via @iOSgetstarted " + url
        }
    }
}

And this is how you would present the UIActivityViewController

uiactivityviewcontroller
1
2
3
4
5
6
7
func share(card : FlashCard) {
    let vc = UIActivityViewController(activityItems: [card, NSURL(string: card.url)!], applicationActivities: nil)
    /* If you want to exclude certain types from sharing
    options you could add them to the excludedActivityTypes */
//        vc.excludedActivityTypes = [UIActivityTypeMail]
    self.presentViewController(vc, animated: true, completion: nil)
}

images

Let me know if you have any question / feedback.

How to Make an App Talk to Database Using RestKit

Most of the successful apps that are created today will most certainly talk to a remote database of some sort. There are different ways by which the data present in these databases are exposed to outside world.

  • ) REST API
  • ) Client Libraries ( Which makes it really easy to talk to a remote database by hiding most of the low level networking code).

When it comes to iOS backend many people go with Parse which is a pretty popular backend as a service. They provide libraries for iOS, Android and javascript api’s.

In this post we will use MongoLab which provide REST api to mongodb database and RestKit which is a object mapper to retrieve data from mongodb and convert to swift objects.

This is the app for which we are building the backend.

Create Mongolab account

  • ) Go to Mongolab and create an account
  • ) Create a new database called iosdb
  • ) Create a new collection iosnews and document like below
{
    "news":"iOS 8 CloudKit tutorial",
    "url" :"http://shrikar.com/blog/2014/10/12/ios8-cloudkit-tutorial-part-1/"
}

In the documentation we can find out how to get the list of all documents in the collection https://api.mongolab.com/api/1/databases/iosdb/collections/iosnews?apiKey=myAPIKey

RestKit Mapping.

Lets create a FlashCard class which maps the mongodb document into objects.

getObjects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//
//  FlashCard.swift
//  FlashCard
//
//  Created by Shrikar Archak on 12/1/14.
//  Copyright (c) 2014 Shrikar Archak. All rights reserved.
//

import UIKit

class FlashCard: NSObject {
    var news: String = ""
    var url : String = ""
}

RKObjectMapping

An RKObjectMapping object describes a transformation between object representations using key-value coding and run-time type introspection. The mapping is defined in terms of a source object class and a collection of RKPropertyMapping objects describing how key paths in the source representation should be transformed into attributes and relationships on the target object.

Find more documentation here RestKit

In our case it will be mapping url and news key paths.

getObjects
1
2
3
let mapDict = ["news":"news","url":"url"]
let flashCardMapping = RKObjectMapping(forClass: FlashCard.self)
flashCardMapping.addAttributeMappingsFromDictionary(mapDict)

RKResponseDescriptor

Describes an object mappable response that may be returned from a remote web application in terms of an object mapping, a key path, a SOCKit pattern for matching the URL, and a set of status codes that define the circumstances in which the mapping is appropriate for a given response.

getObjects
1
2
3
4
5
6
let responseDescriptor = RKResponseDescriptor(mapping: flashCardMapping,
method: RKRequestMethod.GET,
pathPattern: "/api/1/databases/iosdb/collections/iosnews/",
keyPath: nil,
statusCodes: NSIndexSet(index: 200))
objectManager?.addResponseDescriptor(responseDescriptor)

getObjectsAtPath

The next step once we have the objectManager configured is to get the objects like below

getObjects
1
2
3
4
5
6
7
objectManager?.getObjectsAtPath("/api/1/databases/iosdb/collections/iosnews/", parameters: ["apiKey" : "yourkey","s":"{\"_id\":-1}"], success: { (operation, result) -> Void in
       let data : [FlashCard] = result!.array() as [FlashCard]
      /* Here the data is the rest response mapped as an array of FlashCard objects */
       self.flashCards = data
       }, failure: { (operation, error) -> Void in
           NSLog("\(error)")
   })

iOS App as a Money Making Machine Using iAd App Monetization Technique

iAd is an ad platform from Apple.

In this post we will discuss how to monetize your apps using iAd platform. Lets get started.

Four types of ads

  • ) Banner ads
  • ) Interstitial ads
  • ) Medium Rect
  • ) Pre roll video

Banner

  • ) Full device width view
  • ) Placed at the bottom of the content
  • ) The ads keeps rolling no configuration required
  • ) Supports iphone and ipad

    Integration

    • Link iAd framework
    • Import iAd framework in your view controller and configure view controller
     import iAd
     viewController.canDisplayBannerAds = true

images

images

#### Handling Banner ad clicks
When the banner ad is clicked a full screen pop will show more about the ad. Before this happens we need to pause any activity in the viewWillDisappear method and resume the activity in viewWillAppear.

Interstitial

  • ) Full screen add

    Prepare for Interstitial

    In applicationDidFinishLaunchingWithOptions prepare for interstitial ads display by calling UIViewController.prepareInterstitialAds()

#### Integration In automatic mode you dont can’t control when the ad shows up

viewController.interstitialPresentationPolicy = ADInterstitialPresentationPolicy.Automatic

In manual mode you control when to show up the ad

viewController.interstitialPresentationPolicy = ADInterstitialPresentationPolicy.Manual

images

Medium Rect

  • Positined inline with content
  • Automatic recycling
  • Fullscreen ad on tap

This is a bit more complicated than the banner and interstitial ads. We need to create the banner explicity and handle delegate to display medium rect ad.

MediumRectViewController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//
//  MediumRectViewController.swift
//  iAds
//
//  Created by Shrikar Archak on 12/6/14.
//  Copyright (c) 2014 Shrikar Archak. All rights reserved.
//

import UIKit
import iAd
class MediumRectViewController: UIViewController, ADBannerViewDelegate {
    var adView: ADBannerView?
    override func viewDidLoad() {
        super.viewDidLoad()

        adView = ADBannerView(adType: ADAdType.MediumRectangle)
        adView!.delegate = self

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func bannerViewDidLoadAd(banner: ADBannerView!) {
        self.view.addSubview(adView!)
        self.view.layoutIfNeeded()
    }

    func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
        adView!.removeFromSuperview()
        self.view.layoutIfNeeded()

    }

}

images

Pre Roll Ad

  • Short video before content
  • Full screen ads on tap
PreRollVideoViewController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//
//  PreRollVideoViewController.swift
//  iAds
//
//  Created by Shrikar Archak on 12/6/14.
//  Copyright (c) 2014 Shrikar Archak. All rights reserved.
//

import UIKit
import MediaPlayer
import iAd

class PreRollVideoViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let url = NSURL(string: "https://www.youtube.com/watch?v=m3ZyU98N3Fk")
        let moviePlayer = MPMoviePlayerController(contentURL: url)
        moviePlayer.prepareToPlay()
        moviePlayer.view.frame = self.view.bounds

        NSLog("Self : frame : \(NSStringFromCGRect(self.view.frame))")
        NSLog("Self : bounds : \(NSStringFromCGRect(self.view.bounds))")
        NSLog("Movie : frame : \(NSStringFromCGRect(moviePlayer.view.frame))")
        NSLog("Movie : bounds : \(NSStringFromCGRect(moviePlayer.view.bounds))")

        self.view.addSubview(moviePlayer.view)
        moviePlayer.view.layoutIfNeeded()
        self.view.layoutIfNeeded()

        moviePlayer.playPrerollAdWithCompletionHandler { (error) -> Void in
            NSLog("\(error)")
            moviePlayer.play()
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

}

Download the code from iAds Github

Getting Started With Mobile App Development

I got started with iOS app development a couple of months back and got my app approved in the appstore today. The journey to getting an app in the appstore was really exciting for me and I am sure many app developers have the same dream.

In the course of learning iOS App Development I have been found many resources which can help others to learn iOS Development. Here are the list of blogs, books, courses and other resources. I have included the resources which I found useful and might not include all other good resources which I might have missed. Please comment below to include any other resources and I will include them.

Here is the link to the app which got approved today Journey Diary App

Courses

Books

Blogs

Useful Cocoapods for iOS App Development

Mobile Patterns

Tools/Assets/Icons

Here is the link to the app which got approved today Journey Diary App

iOS 8 Notifications in Swift

In this iOS Development tutorial series we will look at notifications

User Notification

These are the interactions which user can interact with or see that are delivered by your app when the app is in the background mode. * Badge Notification * Sound Notification * Alerts

  * Modal Alert stay until user interact
  * Alert in the notification center
  * Alert displayed on the lock screen.

User notification can be delivered locally using UILocalNotification or remotely using Push Notification

Registering for User Notification
registration
1
2
3
4
5
6
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
   let notificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
   let settings = UIUserNotificationSettings(forTypes: notificationType, categories: nil)
   application.registerUserNotificationSettings(settings)
   return true
}

After the alert for getting the permission is shown the didRegisterUserNotificationSettings is called with the necessary notification settings the user has allowed.

Notification Actions

In this section we will describe the workflow for dealing with Notification Actions.

  • Register for Notification Action
  • Push/Schedule Notification
  • Handle Notification Action
Register for Notification Actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let notificationType = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound
let acceptAction = UIMutableUserNotificationAction()
acceptAction.identifier = "Accept"
acceptAction.title = "Accept"
acceptAction.activationMode = UIUserNotificationActivationMode.Background
acceptAction.destructive = false
acceptAction.authenticationRequired = false

let declineAction = UIMutableUserNotificationAction()
declineAction.identifier = "Decline"
declineAction.title = "Decline"
declineAction.activationMode = UIUserNotificationActivationMode.Background
declineAction.destructive = false
declineAction.authenticationRequired = false


let category = UIMutableUserNotificationCategory()
category.identifier = "invite"
category.setActions([acceptAction, declineAction], forContext: UIUserNotificationActionContext.Default)
let categories = NSSet(array: [category])
let settings = UIUserNotificationSettings(forTypes: notificationType, categories: categories)
application.registerUserNotificationSettings(settings)

Schedule Local Notification

The next step once we have configured the settings is to schedule local notification. Make sure the category is what we have configured in our case invite identifier.

1
2
3
4
5
6
7
var localNotification:UILocalNotification = UILocalNotification()
localNotification.alertAction = "Testing notifications on iOS8"
localNotification.alertBody = "Local notifications are working"
localNotification.fireDate = NSDate(timeIntervalSinceNow: 5)
localNotification.soundName = UILocalNotificationDefaultSoundName
localNotification.category = "invite"
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
Handle Local Notification

After we schedule the notification we need to perform certain action depending on what option the user selected. We need to implement the function handleActionWithIdentifier which gets called when the user selects any of the option.

1
2
3
4
5
func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
   NSLog("Handle identifier : \(identifier)")
   // Must be called when finished
   completionHandler()
}

Remote Notification

This by itself needs a complete blog post take a look at Remote Notification or Tuts+ Remote Notification

Location Notification

Location notification helps app in triggering a notification when the user enter a particular region. Location notification can be configured to trigger once or multiple times.

User should register for Core Location Authorization before the Location notification can be used.

Prerequisite

Registering core location.

1
2
3
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
Scheduling Local Notification

One thing to note is that when scheduling a location notification make sure you dont have a firedate set for the local notification as it would crash if we set both the fire data as well as the region together.

1
2
3
4
var localNotification:UILocalNotification = UILocalNotification()
localNotification.regionTriggersOnce = true
localNotification.region = CLCircularRegion(circularRegionWithCenter: CLLocationCoordinate2D(latitude: 37.33233141, longitude: -122.03121860), radius: 50.0, identifier: "Location1")
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)

Let me know if you have any questions / suggestion in the comments below.

iOS Development Tutorial: Visual Blur With UIVisualEffectView

iOS Development Tutorial

In this tutorial we will talk about how to use UIVisualEffectView in Swift. UIVisualEffectView can be a really good assets in building a beautiful app.

There are lot of app in the wild which used the blurring effect. Previously providing these effect was cumbersome. With UIVisualEffectView we can implement the same thing much more efficiently.

Example

images

More example


Create a Single View Application

images


Drag a image view on to the view and set the constraint by clicking on the pin icon and setting constraints on all side to 0

images


Drag the blur image view onto the image view and set the constraints by setting the bottom, left and right constraints to 0 and set the height to 200px

images


Drag a UIlabel and add constraints to set the align it in the horizontal and vertical center

images


You could change the blur type to look perfect for your app.

images

Do suggest for any iOS topic you would like to see in the comments section.

iOS App Development : Upload Images to AWS S3

What we will cover

  • ) Create Amazon Cognito account
  • ) Uploading image to Amazon S3

Amazon Cognito

If we need the functionality to upload images or any other assets to Amazon S3 we add the AWS id and AWS secret to the app. As we know this is not the best approach for security purposes. Amazon recently added Amazon Cognito which tries to solve the problem.

Create a pool

images

Set permission for Authentication Roles as below.

images

AWS setup in the Iphone App.

The next thing we need to do is setup the AWS credentials in the iphone app. We will setup the credentials in the AppDelegate.swift.

AppDelegate.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//
//  AppDelegate.swift
//  picbucket
//
//  Created by Shrikar Archak on 11/1/14.
//  Copyright (c) 2014 Shrikar Archak. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let cognitoAccountId = "xxxxxxxxxxxxxxxxx"
    let cognitoIdentityPoolId = "xxxxxxxxxxxxxxxxx"
    let cognitoUnauthRoleArn = "xxxxxxxxxxxxxxxxx"
    let cognitoAuthRoleArn = "xxxxxxxxxxxxxxxxx"

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        let credentialsProvider = AWSCognitoCredentialsProvider.credentialsWithRegionType(
            AWSRegionType.USEast1,
            accountId: cognitoAccountId,
            identityPoolId: cognitoIdentityPoolId,
            unauthRoleArn: cognitoUnauthRoleArn,
            authRoleArn: cognitoAuthRoleArn)
        let defaultServiceConfiguration = AWSServiceConfiguration(
            region: AWSRegionType.USEast1,
            credentialsProvider: credentialsProvider)
        AWSServiceManager.defaultServiceManager().setDefaultServiceConfiguration(defaultServiceConfiguration)

        return true
    }

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}

If you want to upload an image with name image to Amazon s3 we do it as mentioned below.

Upload Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   let transferManager = AWSS3TransferManager.defaultS3TransferManager()
    let testFileURL1 = NSURL(fileURLWithPath: NSTemporaryDirectory().stringByAppendingPathComponent("temp"))
    let uploadRequest1 : AWSS3TransferManagerUploadRequest = AWSS3TransferManagerUploadRequest()

    let data = UIImageJPEGRepresentation(image, 0.5)
    data.writeToURL(testFileURL1!, atomically: true)
    uploadRequest1.bucket = "shrikar-picbucket"
    uploadRequest1.key =  "bingo"
    uploadRequest1.body = testFileURL1

    let task = transferManager.upload(uploadRequest1)
    task.continueWithBlock { (task) -> AnyObject! in
        if task.error != nil {
            println("Error: \(task.error)")
        } else {
            println("Upload successful")
        }
        return nil
    }

Similarly if you want to read an image from Amazon S3 with name bingo use the code below

Download Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   let downloadingFilePath1 = NSTemporaryDirectory().stringByAppendingPathComponent("temp-download")
    let downloadingFileURL1 = NSURL(fileURLWithPath: downloadingFilePath1 )
    let transferManager = AWSS3TransferManager.defaultS3TransferManager()


    let readRequest1 : AWSS3TransferManagerDownloadRequest = AWSS3TransferManagerDownloadRequest()
    readRequest1.bucket = "shrikar-picbucket"
    readRequest1.key =  "bingo"
    readRequest1.downloadingFileURL = downloadingFileURL1

    let task = transferManager.download(readRequest1)
    task.continueWithBlock { (task) -> AnyObject! in
        println(task.error)
        if task.error != nil {
        } else {
            dispatch_async(dispatch_get_main_queue()
                , { () -> Void in
                    self.selectedImage.image = UIImage(contentsOfFile: downloadingFilePath1)
                    self.selectedImage.setNeedsDisplay()
                    self.selectedImage.reloadInputViews()

            })
            println("Fetched image")
        }
        return nil
    }

If you keep getting permission error make sure you have setup the permission properly in the IAM Roles created.

Github didn’t allow me to add framework . So you might want to explicitly add by following the link Amazon IOS Dependencies and the completed github code is available here Picbucket.

Learn how to build a complete Trivia App

iOS Development Tutorial : UIActionSheet and UIAlertController in Swift

What we will cover

  • Creating App
  • UIAlertView using UIAlertController
  • UIActionSheet using UIAlertController
  • Using UIAlertView/UIActionSheet without breaking the code for iOS 7

UIAlertView and UIActionSheet are deprecated in iOS 8 and are replaced by UIAlertController.

We use UIAlertView/UIActionSheet/UIAlertController to allow user to perform interactions in your app.

UIAlertView is used to alert user about something that happened within your app, the operations you can do when displaying an alertview is very limited. Normally you tend to have an acknowledge button like OK and more more button to CANCEL or dismiss the current interaction.

UIActionSheet is another way you can provide interactions to the user in you app. Example being selecting a photo to upload. You can allow interactions like “Select Picture from the photo library” or provide the user to take a picture.

Create App

Let go ahead and create a new project picbucket. images images

UIAlertView using UIAlertController

A UIAlertController object displays an alert message to the user. This class replaces the UIActionSheet and UIAlertView classes for displaying alerts. After configuring the alert controller with the actions and style you want, present it using the presentViewController:animated:completion: method.

title: The title of the alert to get user’s attention message: Detailed message for the alert style : can be UIAlertControllerStyle.Alert

We use completion handler to perform actions depending on what the user selected.

UIAlertView
1
2
3
4
5
6
7
8
9
10
11
 let alert = UIAlertController(title: "This is an alert!", message: "Using UIAlertController", preferredStyle: UIAlertControllerStyle.Alert)
        let okButton = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (okSelected) -> Void in
            println("Ok Selected")
        }
        let cancelButton = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { (cancelSelected) -> Void in
            println("Cancel Selected")
        }
        alert.addAction(okButton)
        alert.addAction(cancelButton)

        self.presentViewController(alert, animated: true, completion: nil)

images

UIActionSheet using UIAlertController

Similary we can create an UIActionSheet using the code below title: The title of the alert to get user’s attention message: Detailed message for the alert style : UIAlertControllerStyle.ActionSheet

UIActionSheet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let alert = UIAlertController(title: "Lets get a picture", message: "Simple Message", preferredStyle: UIAlertControllerStyle.ActionSheet)
        let libButton = UIAlertAction(title: "Select photo from library", style: UIAlertActionStyle.Default) { (alert) -> Void in
            imageController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
            self.presentViewController(imageController, animated: true, completion: nil)
        }
        if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
            let cameraButton = UIAlertAction(title: "Take a picture", style: UIAlertActionStyle.Default) { (alert) -> Void in
                println("Take Photo")
                imageController.sourceType = UIImagePickerControllerSourceType.Camera
                self.presentViewController(imageController, animated: true, completion: nil)

            }
            alert.addAction(cameraButton)
        } else {
            println("Camera not available")

        }
        let cancelButton = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { (alert) -> Void in
            println("Cancel Pressed")
        }

        alert.addAction(libButton)
        alert.addAction(cancelButton)
        self.presentViewController(alert, animated: true, completion: nil)

In the above example we have certain conditional which will help us to add new button conditionally. In our example we will add the “Take a picture button” only if camera is available. This code works properly and add “Take a picture” button on the device and you guys just need to trust me on that.

images

How to provide backward compatibility in iOS 7

Since UIAlertController is not available in iOS7 and the above code will crash if you run it on iOS7. Let see how to use UIAlertView and UIActionSheet across iOS7 and iOS8 effectively. controllerAvailable is a helper function which will help us branch our code depending on the available feature for that iOS.

controllerAvailable
1
2
3
4
5
6
7
8
9
func controllerAvailable() -> Bool {
    if let gotModernAlert: AnyClass = NSClassFromString("UIAlertController") {
        return true;
    }
    else {
        return false;
    }

}

For iOS 7 we need to implement the UIActionSheetDelegate and the func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int).

ViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//
//  ViewController.swift
//  picbucket
//
//  Created by Shrikar Archak on 11/1/14.
//  Copyright (c) 2014 Shrikar Archak. All rights reserved.
//
import UIKit

class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIActionSheetDelegate {

@IBOutlet weak var selectedImage: UIImageView!

@IBAction func selectImage(sender: AnyObject) {

    /* Supports UIAlert Controller */
    if( controllerAvailable()){
        handleIOS8()
    } else {
        var actionSheet:UIActionSheet
        if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
            actionSheet = UIActionSheet(title: "Hello this is IOS7", delegate: self, cancelButtonTitle: "Cancel", destructiveButtonTitle: nil,otherButtonTitles:"Select photo from library", "Take a picture")
        } else {
            actionSheet = UIActionSheet(title: "Hello this is IOS7", delegate: self, cancelButtonTitle: "Cancel", destructiveButtonTitle: nil,otherButtonTitles:"Select photo from library")
        }
        actionSheet.delegate = self
        actionSheet.showInView(self.view)
        /* Implement the delegate for actionSheet */
    }

}

func handleIOS8(){
    let imageController = UIImagePickerController()
    imageController.editing = false
    imageController.delegate = self;

    let alert = UIAlertController(title: "Lets get a picture", message: "Simple Message", preferredStyle: UIAlertControllerStyle.ActionSheet)
    let libButton = UIAlertAction(title: "Select photo from library", style: UIAlertActionStyle.Default) { (alert) -> Void in
        imageController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
        self.presentViewController(imageController, animated: true, completion: nil)
    }
    if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
        let cameraButton = UIAlertAction(title: "Take a picture", style: UIAlertActionStyle.Default) { (alert) -> Void in
            println("Take Photo")
            imageController.sourceType = UIImagePickerControllerSourceType.Camera
            self.presentViewController(imageController, animated: true, completion: nil)

        }
        alert.addAction(cameraButton)
    } else {
        println("Camera not available")

    }
    let cancelButton = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { (alert) -> Void in
        println("Cancel Pressed")
    }

    alert.addAction(libButton)
    alert.addAction(cancelButton)

    /* Code for UIAlert View Controller
        let alert = UIAlertController(title: "This is an alert!", message: "Using UIAlertController", preferredStyle: UIAlertControllerStyle.Alert)
        let okButton = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (okSelected) -> Void in
            println("Ok Selected")
        }
        let cancelButton = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { (cancelSelected) -> Void in
            println("Cancel Selected")
        }
        alert.addAction(okButton)
        alert.addAction(cancelButton)
    */
    self.presentViewController(alert, animated: true, completion: nil)

}

func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
    self.dismissViewControllerAnimated(true, nil)
    self.selectedImage.image = image;

}

func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) {
    println("Title : \(actionSheet.buttonTitleAtIndex(buttonIndex))")
    println("Button Index : \(buttonIndex)")
    let imageController = UIImagePickerController()
    imageController.editing = false
    imageController.delegate = self;
    if( buttonIndex == 1){
        imageController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
    } else if(buttonIndex == 2){
        imageController.sourceType = UIImagePickerControllerSourceType.Camera
    } else {

    }
    self.presentViewController(imageController, animated: true, completion: nil)
}

func controllerAvailable() -> Bool {
    if let gotModernAlert: AnyClass = NSClassFromString("UIAlertController") {
        return true;
    }
    else {
        return false;
    }

}
override func viewDidLoad() {
    super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}

For Complete code take a look here Github Repo Also do comment if I am missing something or for any feedback.

Learn how to build a complete Trivia App

IOS8 Cloudkit Tutorial - Part 2

This is the part 2 of the iOS 8 CloudKit tutorial series. If you haven’t read the part 1 I suggest you to take a look at Part1 before you move on to the next section

What we will cover

  • UI for ListViewController
  • NSPredicate
  • NSSortDescriptors
  • CKQuery
  • Protocol and Delegate/Delegation iOS (Our example CloudKitDelegate)
  • CKModifyRecordsOperation
  • Final Working Fetching Code

UI for ListViewController

This is how our story board looked at the end of part 1. For more information on layouts checkout these videos iOS Adaptive Layouts and iOS Autolayouts images

Lets go to object library and drag a navigation controller from the library onto the main storyboard. images

Select the view controller which was already existing. Goto Editor embed in navigation controller. images

CTRL-DRAG from Root View Controller to the just embedded navigation view controller and select present modally.

images

Add bar buttons for Cancel and Done . Finally setup the IBActions and IBOutlets.

NSPredicate

NSPredicate is basically the matching criteria like id should be "123" or id should be "123" and count > 5

predicate can’t be nil if you want to fetch all records use the below mentioned predicate
let predicate = NSPredicate(value: true)

NSSortDescriptors

NSSortDescriptors defined the order in which the records are retrieved from the iCloud using cloudKit. Its take the key on which the sorting is supposed to be done along with the ordering like ascending:true or ascending:false. If there are more than one sort descriptors they can be passed as an array.

CKQuery

CKQuery is analogous to select query in RDBMS world. The query consists of 2 or more parts.

  • RecordType : What type of object to search for. In our example it is Todos but for other applications it can be a Post, Message etc.
  • Predicate : Predicates are the condition on which the records should be matched against
  • SortDescriptors : The order in which the keys should be returned. We provide the key and the order like ascending or descending.

Example of the above scenario

Query
1
2
3
4
5
6
let predicate = NSPredicate(value: true)
let sort = NSSortDescriptor(key: "creationDate", ascending: false)

let query = CKQuery(recordType: "Todos",
    predicate:  predicate)
query.sortDescriptors = [sort]

Protocol and Delegate/Delegation iOS

Protocols : are similar to interfaces in OOP world.

Delegate : “A delegate is an object that acts on behalf of, or in coordination with, another object when that object encounters an event in a program.”

In CloudKit most of the operations are async, hence are very good candidates for the Delegation pattern. Delegating object will send a message or call a callback when certain events are completed. Its the responsibility of the delegate object to implement those protocols and handle callbacks generated by the delegating object.

CloudKitDelegate
1
2
3
4
protocol CloudKitDelegate {
    func errorUpdating(error: NSError)
    func modelUpdated()
}

In our case our viewcontroller will handle this protocol and take appropriate actions when CloudKit events are triggered

CKModifyOperation

I faced a wierd issue when I tried to display the todo entries after adding to iCloud. I was not able to fetch the last entry added. Documentation mentioned that all the operations are async and are run on the low priority threads and if I need to save something immediately I need to use CKModifyOperation. I tried but unfortunately didn’t work for me. If someone finds a solution to this please let me know in the comments.

This method saves the record with a low priority, which may cause the task to execute after higher-priority tasks. To save records more urgently, create a CKModifyRecordsOperation object with the desired priority. You can also use that operation object to save multiple records simultaneously.

Problem

CloudKit not returning the most recent data

fetch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let todoRecord = CKRecord(recordType: "Todos")
todoRecord.setValue(todo, forKey: "todotext")
publicDB.saveRecord(todoRecord, completionHandler: { (record, error) -> Void in
        NSLog("Saved in cloudkit")
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "Todos",
            predicate:  predicate)

        self.publicDB.performQuery(query, inZoneWithID: nil) {
            results, error in
            if error != nil {
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.errorUpdating(error)
                    return
                }
            } else {
                NSLog("###### fetch after save : \(results.count)")
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.modelUpdated()
                    return
                }
            }
        }

Result

Before saving in cloud kit : 3
Saved in cloudkit
###### Count after save : 3

WorkAround

Add it to the todos array on the client side.

What I tried

CKModifyRecordOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
let ops = CKModifyRecordsOperation(recordsToSave: [todoRecord], recordIDsToDelete: nil)
   ops.savePolicy = CKRecordSavePolicy.AllKeys
  
   ops.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
       NSLog("Completed Save to cloud")
  
       let predicate = NSPredicate(value: true)
       let query = CKQuery(recordType: "Todos",
           predicate:  predicate)
  
       self.publicDB.performQuery(query, inZoneWithID: nil) {
           results, error in
           if error != nil {
               dispatch_async(dispatch_get_main_queue()) {
                   self.delegate?.errorUpdating(error)
                   return
               }
           } else {
               self.todos.removeAll()
               for record in results{
  
                   let todo = Todos(record: record as CKRecord, database: self.publicDB)
                   self.todos.append(todo)
  
               }
               NSLog("fetch after save : \(self.todos.count)")
               dispatch_async(dispatch_get_main_queue()) {
                   self.delegate?.modelUpdated()
                   return
               }
           }
       }
   }
   publicDB.addOperation(ops)

Final Working Fetching Code

CloudKitHelper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import Foundation
import CloudKit

protocol CloudKitDelegate {
    func errorUpdating(error: NSError)
    func modelUpdated()
}


class CloudKitHelper {
    var container : CKContainer
    var publicDB : CKDatabase
    let privateDB : CKDatabase
    var delegate : CloudKitDelegate?
    var todos = [Todos]()

    class func sharedInstance() -> CloudKitHelper {
        return cloudKitHelper
    }

    init() {
        container = CKContainer.defaultContainer()
        publicDB = container.publicCloudDatabase
        privateDB = container.privateCloudDatabase
    }

    func saveRecord(todo : NSString) {
        let todoRecord = CKRecord(recordType: "Todos")
        todoRecord.setValue(todo, forKey: "todotext")
        publicDB.saveRecord(todoRecord, completionHandler: { (record, error) -> Void in
            NSLog("Before saving in cloud kit : \(self.todos.count)")
            NSLog("Saved in cloudkit")
            self.fetchTodos(record)
        })

    }

    func fetchTodos(insertedRecord: CKRecord?) {
        let predicate = NSPredicate(value: true)
        let sort = NSSortDescriptor(key: "creationDate", ascending: false)

        let query = CKQuery(recordType: "Todos",
            predicate:  predicate)
        query.sortDescriptors = [sort]
        publicDB.performQuery(query, inZoneWithID: nil) {
            results, error in
            if error != nil {
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.errorUpdating(error)
                    return
                }
            } else {
                self.todos.removeAll()
                for record in results{
                    let todo = Todos(record: record as CKRecord, database: self.publicDB)
                    self.todos.append(todo)
                }
                if let tmp = insertedRecord {
                    let todo = Todos(record: insertedRecord! as CKRecord, database: self.publicDB)
                    /* Work around at the latest entry at index 0 */
                    self.todos.insert(todo, atIndex: 0)
                }
                NSLog("fetch after save : \(self.todos.count)")
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.modelUpdated()
                    return
                }
            }
        }
    }
}
let cloudKitHelper = CloudKitHelper()

Learn how to build a complete Trivia App

If you have any questions/comments do comment on the post.

Github Repo : CloudKit

Learn iOS8 with Swift

IOS8 Cloudkit Tutorial - Part 1

In this tutorial we will create an iOS app that will store a simple text in icloud using CloudKit technology released for iOS8.

What we will cover

  • Creating a new project.
  • CloudKit Configuration
  • CloudKit Terminologies
  • Schema design in Cloudkit Dashboard
  • CloudKit Workflow
  • Save records in iCloud Storage.

CloudKit helps your to move structured data between your app and iCloud.

Creating a new project

  • Open Xcode
  • File > New Project > Single Page Application
  • ProductName : CloudKit
  • Language : Swift
  • Device : Iphone
  • Next and save the project

CloudKit Configuration

Click on the target and make sure we have configured our app for iCloud.

images
images

CloudKit Terminologies

  • CKContainer represents a namespace for your app. If your account has 2 different apps then there will be 2 container one for each app.
  • Container is divided into 2 databases : public and private database(CKDatabase). Data stored in the private database is only accessible to the current user and in their user’s icloud account. Public Database is accessible as readonly to all people who are not logged in using iCloud account.
  • Record Type is a schema for the objects that are stored in icloud.( Think class in OOP terms)
  • CKRecord is an instance of the record type (Think objects in OOP terms) and contains key value pairs which represent the object.

Schema Design

  • login to cloudkit dashboard
  • Create a new Record Type called Todos. Our example we will just have one field todotext which will be of type text. images
    images

CloudKit Workflow

  • Get current container
  • Get the CKDatabase object that corresponds to the database (public or private) that contains the records.
  • After storing the data you can find them in cloudkit dashboard -> Public Data -> default zone
CloudKitHelper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
import CloudKit

class CloudKitHelper {
    var container : CKContainer
    var publicDB : CKDatabase
    let privateDB : CKDatabase

    init() {
        container = CKContainer.defaultContainer()
        publicDB = container.publicCloudDatabase
        privateDB = container.privateCloudDatabase
    }

    func saveRecord(todo : NSString) {
        let todoRecord = CKRecord(recordType: "Todos")
        todoRecord.setValue(todo, forKey: "todotext")
        publicDB.saveRecord(todoRecord, completionHandler: { (record, error) -> Void in
            NSLog("Saved to cloud kit")

        })

    }
}

Run the project in Simulator

If you get an error like this or the data is not stored in iCloud.

Some error you might face : Not Authenticated” (9/1002); “This request requires an authenticated account

Make sure you are logged into icloud in the icloud simulator.

Settings -> icloud -> login

Enter some text in the textfield and save to CloudKit. Go to icloud dashboard to see if the data is stored properly.

images

In the next part we will see how we can fetch the data from icloud using CloudKit. Let me know if you have any comments in the section below. Find more information about Part 2 here

Github Repo : CloudKit

Learn how to build a complete Trivia App

Learn iOS8 with Swift