Image caching and custom alerts

This week started on a more challenging note, the Berlin weather jumping 20 degrees at a time influencing my level of energy. After a monday off the week was good though, by tuesday I was back on track and attended a nice workshop on regular expressions, and spent the rest of the week working, among other things, on image caching and custom alerts and notifications.

Image caching in swift

As I am doing a social network app there is a lot of images I need to load all the time: avatar, cover picture, photo albums etc, so it was crucial to load them asynchronously, and to save them in the cache so the app doesn’t need to download them all the time.

In order to do that, I created a new swift file called `Utils`, and wrote this code:

class Utils {
    
    static var imageCache: Dictionary<String,UIImage> = Dictionary()

     class func getImageAsync(url:String, completion: (UIImage?) -> ()){
        //we escape the url to avoid sending non-conform characters
        var escapedUrl = url.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
        
        var imageUrl = NSURL(string: escapedUrl!)
        if(imageUrl == nil)
        {
            println("Invalid Url!")
            completion(nil)
        }
        else
        {
            //we check if the image exists already in the cache. If so, we just return it
            if let imageFromCache:UIImage = imageCache[escapedUrl!]
            {
                dispatch_async(dispatch_get_main_queue()) {
                    completion(imageFromCache);
                }
            }
            else
            {
               //if the image doesn't exist, we download it, save it in our array for later, and then return it
                var stdUrl = imageUrl!.standardizedURL!
                let req = NSURLRequest(URL: stdUrl)
                NSURLConnection.sendAsynchronousRequest(req, queue: NSOperationQueue.mainQueue()) { (resp, data, error) -> Void in
                    var image: UIImage?
                    if data != nil {
                        image = UIImage(data: data!)!
                        self.imageCache[escapedUrl!] = image
                    }
                    
                    dispatch_async(dispatch_get_main_queue()) {
                        completion(image)
                    }

                }
            }
        }
        
    }

}

Then I can simply call this function in any file like this:

 Utils.getImageAsync(image_url, completion: { (image) -> () in
    self.imageView.image = image
}

But what if I want image compression, or automatic cache expiration handling?

The code above is very useful for downloading a few images, but if your app will be doing that a lot, I would recommend using a library which is maintained and debugged for you. I started using SDWebImage, because it provides:

  • An UIImageView category adding web image and cache management to the Cocoa Touch framework
  • An asynchronous image downloader
  • An asynchronous memory + disk image caching with automatic cache expiration handling
  • Animated GIF support
  • WebP format support
  • A background image decompression
  • A guarantee that the same URL won’t be downloaded several times
  • A guarantee that bogus URLs won’t be retried again and again
  • A guarantee that main thread will never be blocked
  • Performances!
  • Use GCD and ARC
  • Arm64 support

As usual, you can just install it through cocoapods, by adding pod ‘SDWebImage’ to your podfile, and then running pod install. Then add #import <SDWebImage/UIImageView+WebCache.h>  to your bridging header. Now you can just download an image with the simple extension provided to UIImageView:

self.imageView.sd_setImageWithURL(NSURL(string:image_url))

Et voilà, now you can download image asynchronously and cache them in swift with just one line!

 

Custom Alerts in swift

The other challenge I had this week was to display custom alert at the top of the screen when a new message arrived. I chose to use TSMessage as it is the most complete and easy to use library I’ve found. After integration, it looks like this:

To install it, just put this line into your Podfile pod ‘TSMessages’, :git => ‘https://github.com/KrauseFx/TSMessages.git’ , I used this specific branch as I ran into some issue with the latest release, but feel free to just put pod ‘TSMessages’ if the bug is fixed. Once done, run pod install in the terminal to install the library, and finally add `#import <TSMessages/TSMessage.h>` to your bridging header.

For my purpose I needed to trigger it every time I received a push notification, so I created an event in `didReceiveRemoteNotification` in `AppDelegate` to which every controller wanting to display a notification will listen to:

//Send notification of a new message to controllers who listen to it,
//passing in userInfo the conversation we want to open
self.nc.postNotificationName("newMessage", object:nil, userInfo:[0:currentConversation])             

In the viewController, register to the notification in `viewWillAppear`, and deregister to it (important) in `viewWillDisappear`:

let nc = NSNotificationCenter.defaultCenter()

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        //We add a listener for new messages
        nc.addObserver(self, selector: "goToMessage:", name: "newMessage", object: nil)
    }
    
override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        
        nc.removeObserver(self)
    }

Then create the function `goToMessage` which is triggered every time there is a new message:

func goToMessage(notification:NSNotification){
        
   let userInfo:Dictionary<Int,ModelConversation!> = notification.userInfo as! Dictionary<Int,ModelConversation!>
        conversation = userInfo[0]
        
   Utils.displayAlert(notification, view: self, segueId: "FavoriteToMessages", conversation: conversation!)
        
}

So in this function we assign to the variable `conversation` the conversation object sent through `userInfo`, and I pass it to `DisplayAlert`. `DisplayAlert` is a generic function I created in my Utils class so I don’t have to rewrite the whole code everytime I want to trigger a custom alert. This function contains the TSMessage call, and looks like this:

class Utils {

class func displayAlert(notification:NSNotification, view:UIViewController, segueId:String, conversation:ModelConversation ){
        
        //Assign a different json file to customize the look and feel of the alert
        TSMessage.addCustomDesignFromFileWithName("TSMessagesCustomDesign.json")
        
        //Display the alert with a link to the message
        TSMessage.showNotificationInViewController(
            view,
            title: "New message from \(conversation.displayName!)",
            subtitle: conversation.previewText!,
            image:UIImage(named: "timeline-chat"),
            type: TSMessageNotificationType.Message,
            duration: 5, //in seconds
            callback: {
                view.performSegueWithIdentifier(segueId, sender: nil) //action to perform on click
            },
            buttonTitle: "",
            buttonCallback: { () -> Void in },
            atPosition: TSMessageNotificationPosition.Top,
            canBeDismissedByUser: true
        )
        
    }

}

That’s it, a simple way to display custom alerts anywhere in your code 🙂