Today we will learn how to make a Dribbble Client in Swift using XCode 6.3 :

Snubbble

Getting started

Open Xcode and create a new project by choosing the Single View Application template. Choose iPhone under “Devices”. Choose Swift as main language.

Templates

Next, you are presented with the page to set many of the important aspects of your project, particularly your “Product Name” and “Language”, which will be Swift, of course.

After you click the “Next” button and then “Create” button, the files for your project are generated and you are probably looking at your AppDelegate.swift file.

Getting the Access Token

To access all the contents from Dribbble we need to create an access token for our application. Without this dribbble will not allow us to access their JSON data.

First, visit Dribbble Developer Website & click register an application.

Developer Dribbble

Login when prompted, a form will appear asking for the credentials of the application. Enter the name, description, website, etc. for your app and click Register Application. After successful registration you will be presented with Client ID, Secret & Access Token as shown below:

Access Token

Keep this Access Token copied in some safe place as we will need it later in our app. (Note: You should avoid spreading your Access Token to other people.)

Setting up the Storyboard

Now we’ll set up the storyboard.  This app will present the user with a UITableView.  Each cell will present an Image, Image Title, Date Published, User Profile Image loaded using Dribbble API. That’s pretty much everything we will cover in our 1st Part.

Open Main.storyboard and drag in a UITableView to our ViewController. Go to Editor\Embed in\Navigation Controller to create a navigation controller.

  1. Drag a UITableViewCell into your UITableView.
  2. Set the TableView Cell height to 281 manually from Size Inspector.
  3. Add a UIImageView to the Cell that covers the entire area of the cell (Add necessary constraints). This will be our Dribbble shot image view.
  4. Add a UILabel to the top-right corner with appropriate constraints. This will show the date when dribbble shot was published.
  5. Add another UIImageView to the top-left corner with size 50×50 px. This will be our profile image view.
  6. Add a UIView touching the bottom of the cell with width equal to the screen width & height of 40px.
  7. Set the background color of UIView black and alpha of the view to 0.45 in the Attributes Inspector.
  8. Add a UILabel on top of the UIView just created and make it wide enough to cover the screen size.

You should now have a layout like this in the storyboard:

Storyboard

Add the delegate & dataSource for the TableView to the ViewController itself. Right Click the UITableView and drag the delegate then dataSource to the yellow circle at the top ,i.e, your ViewController.

Screen Shot 2015-08-08 at 2.46.34 PM

Now, select the single cell in the collection view and set the Reuse Identifier to shotCell using the attributes inspector. This should also be familiar for table views – the data source will use this identifier to dequeue or create new cells.

TableViewCell

Create a new class for the CollectionViewCell named MainTableViewCell.Open the Assistant Editor and select MainTableViewCell , then control-drag from the big UIImageView present on the tableview to the MainTableViewCell class and choose the outlet:

mainImageView

Similarly, create an outlet for the ProfileImageView, UILabel on top-right corner, UIView at the bottom & the UILabel residing inside it.

The naming structure for the outlets in my project will be as follows:

Outlets

Well, that’s it for the storyboard on this part, let’s move on to some real coding!

Setting up the ViewController

First link the UITableView to the ViewController using the AssistantEditor. Drag the UITableView to the ViewController and name the outlet as tableView.

Now you need to download some classes for the Dribbble API that contains code for parsing the JSON that we fetch from their servers. Download Link

This code is directly provided to you just for your convenience. It contains SDWebImage Class for Async Image Loading, Utils & DribbleAPI folders for User, Comment & Shots JSON Parsing. In this part we will only use Shots.swift for loading the Dribbble Shots on your main screen. User, comments will be implemented in the next tutorial.

Just Drag these 3 folders into your XCode project to get them ready for use. Let’s see the Shots.swift file and try to understand what’s going on :

class Shot {

    var id : Int!
    var title : String!
    var date : String!
    var description : String!
    var commentCount : Int!
    var likesCount :  Int!
    var viewsCount : Int!
    var commentUrl : String!
    var imageUrl : String!

    var imageData : NSData?

    var user: User!

    init(data : NSDictionary){

        self.id = data["id"] as! Int
        self.commentCount = data["comments_count"] as! Int
        self.likesCount = data["likes_count"] as! Int
        self.viewsCount = data["views_count"] as! Int

        self.commentUrl = Utils.getStringFromJSON(data, key: "comments_url")
        self.title = Utils.getStringFromJSON(data, key: "title")

        let dateInfo = Utils.getStringFromJSON(data, key: "created_at")
        self.date = Utils.formatDate(dateInfo)

        let desc = Utils.getStringFromJSON(data, key: "description")
        self.description = Utils.stripHTML(desc)

        let images = data["images"] as! NSDictionary
        self.imageUrl = Utils.getStringFromJSON(images, key: "normal")

        if let userData = data["user"] as? NSDictionary {
            self.user = User(data: userData)
        }
    }
}

The class Shot above declares many variables to store data and then init function is called by passing data which is an NSDictionary. We use DribbbleAPI.swift to pass data to this Shots init function. The init function just parses the data dictionary by saving data for various variables such as userID,  likes,  comment_count, imagesURL, userData, etc. This shots data can be used into our ViewController as follows:

var shots : [Shot]!

Now, open DribbbleAPI.swift file and paste your Access token that we obtained earlier in this post.

let accessToken = "dxxxxxx3xxxe02fxxxxxxbf51xxx90c1dxxxxxxxxxxxxxxxxxxxxxxxxxxx"

The DribbbleAPI.swift file connects to the Dribbble servers and fetches the JSON data for you by using your access token and various other parameters such as page=1&per_page=100, this parameter tells the server to send the data for shots ranking from 1-100 at a time.

We now edit the MainTableViewCell for some UI Modifications in the tableview cell. Paste the following code in the awakeFromNib function.

override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code

        self.gradientLayer.clipsToBounds = true

        let myGradientLayer = CAGradientLayer()
        myGradientLayer.frame = CGRectMake(0, 0, 1000, 90)
        myGradientLayer.colors = [UIColor(white: 0.0, alpha: 0.0).CGColor, UIColor(white: 0.0, alpha: 0.5).CGColor]

        self.gradientLayer.layer.addSublayer(myGradientLayer)

        //label modification
        shotNameLabel.textColor = UIColor.whiteColor()
        shotNameLabel.font = UIFont(name: FontPack.fontName, size: 18)
        minsAgoLabel.font = UIFont(name: FontPack.fontName, size: 13)

        profileImageView.layer.masksToBounds = true
        profileImageView.layer.borderColor = UIColor(white: 1, alpha: 1.0).CGColor
        profileImageView.layer.cornerRadius = 25
        profileImageView.layer.borderWidth = 1
    }

The lines of code above add a CAGradient to the UIView in our tableCell along with adding radius, border to profileImageView. We also change the font of the shotLabel using fonts class named FontPack . Let’s create the FontPack class as shown below:

import UIKit

class FontPack {

    class var fontName : String {
        return "Avenir-Book"
    }

    class var boldFontName : String {
        return "Avenir-Black"
    }

    class var semiBoldFontName : String {
        return "Avenir-Heavy"
    }

    class var lighterFontName : String {
        return "Avenir-Light"
    }

    class var darkColor : UIColor {
        return UIColor.blackColor()
    }

    class var lightColor : UIColor {
        return UIColor(white: 0.6, alpha: 1.0)
    }
}

The FontPack just stores various good looking Font names as String. They are used throughout the app so creating a separate class makes our project clean while working.

Jump back to ViewController.swift file and add the following lines:

var shots : [Shot]!
    var boxView = UIView()
    let screenSize: CGRect = UIScreen.mainScreen().bounds
    var screenWidth = CGFloat()

    @IBOutlet var topGradientView: UIView!

    func isConnectedToNetwork() -> Bool {

        var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
        zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
        zeroAddress.sin_family = sa_family_t(AF_INET)

        let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
            SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)).takeRetainedValue()
        }

        var flags: SCNetworkReachabilityFlags = 0
        if SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) == 0 {
            return false
        }

        let isReachable = (flags & UInt32(kSCNetworkFlagsReachable)) != 0
        let needsConnection = (flags & UInt32(kSCNetworkFlagsConnectionRequired)) != 0

        return (isReachable && !needsConnection) ? true : false
    }

The code above declares a Shot Class object, a UIView to present the loading indicator, screen size variables to get the width of the device being used. Next we have a standard Apple function named isConnectedToNetwork which when called returns a Bool stating whether the device is connected to Internet or not. For this function to work properly we also need to import Foundation & SystemConfiguration at the top in our ViewController.swift file. Further, we edit the viewDidLoad function of the ViewController to fire the Dribbble JSON fetching from the servers.

override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view
        let attributes = [NSFontAttributeName : UIFont(name: FontPack.fontName, size: 19)!, NSForegroundColorAttributeName : UIColor.blackColor()]
        self.navigationController?.navigationBar.titleTextAttributes = attributes
        shots = [Shot]()
        let api = DribbbleAPI()
        api.loadShots("https://api.dribbble.com/v1/shots", completion: didLoadShots)
        addSavingPhotoView()
        var timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: Selector("removeLoad"), userInfo: nil, repeats: true)
        self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
        screenWidth = screenSize.width
    }

Let’s check what is happening when the view loads. The starting lines are used to apply custom attributes such as font size, font name to the NavigationBar Title. Then we create an object for the DribbbleAPI class and call the function loadShots which on completion will call another function named didLoadShots . We call a function named addSavingPhotoView which adds a loading indicator to the screen until the Dribbble images are loaded. A timer is set which calls the removeLoad function that will remove the loading indicator from the screen. We also keep the UITableViewCellSeparatorStyle to None which hides the tableview cell separators.

Now, add the functions that are called in the viewDidLoad section:

func didLoadShots(shots: [Shot]){
        self.shots = shots
        tableView.reloadData()
    }

    func addSavingPhotoView() {
        // You only need to adjust this frame to move it anywhere you want
        boxView = UIView(frame: CGRect(x: view.frame.midX - 90, y: view.frame.midY - 25, width: 180, height: 50))
        boxView.backgroundColor = UIColor.blackColor()
        boxView.alpha = 0.8
        boxView.layer.cornerRadius = 10

        //Here the spinnier is initialized
        var activityView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.White)
        activityView.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
        activityView.startAnimating()

        var textLabel = UILabel(frame: CGRect(x: 60, y: 0, width: 200, height: 50))
        textLabel.textColor = UIColor.whiteColor()
        textLabel.text = "Loading Shots"

        boxView.addSubview(activityView)
        boxView.addSubview(textLabel)

        view.addSubview(boxView)
    }

    func removeLoad() {
        //This removes the boxView which is the loading indicator
        boxView.removeFromSuperview()
    }

The didLoadShots function adds the Dribbble shots obtained from the server into itself for further use. The addSavingPhotoView function initializes the loading indicator by adding the boxView that we declared at the top. We can also use UIActivity Indicator for this, but we used a UIView for learning new Indicator styles. The last function removeLoad removes the boxView from the superView to hide the indicator.

iOS Simulator Screen Shot Aug 8, 2015, 1.37.47 PM

Next, we move on to adding the tableView dataSource functions as show below:

// MARK: UITableViewDelegate + UITableViewDataSource Implementations

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 1
    }
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return 281
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 100
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell:MainTableViewCell = tableView.dequeueReusableCellWithIdentifier("shotCell", forIndexPath: indexPath) as! MainTableViewCell
        if shots.isEmpty {
            cell.shotNameLabel.text = ""
            cell.minsAgoLabel.text = ""
            cell.gradientLayer.hidden = true
        } else {
            let shot = shots[indexPath.row]
            cell.gradientLayer.hidden = false
            cell.shotNameLabel.text = shot.title+" by "+shot.user.name

            let imgURL = NSURL(string: shot.imageUrl)

            cell.mainImageView.image = nil

            SDWebImageDownloader.sharedDownloader().downloadImageWithURL(imgURL, options: nil, progress: nil, completed: {[weak self] (image, data, error, finished) in
                if let wSelf = self {
                    // do what you want with the image/self

                    var newImage = self!.imageResize(image, sizeChange: CGSizeMake(self!.screenWidth, ((self!.screenWidth * 300)/400)))
                    cell.mainImageView.image = newImage
                }
                })

            let request: NSURLRequest = NSURLRequest(URL: imgURL!)
            let mainQueue = NSOperationQueue.mainQueue()
            NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
                if error == nil {
                    // Convert the downloaded data in to a UIImage object
                    let image:UIImage = UIImage(data: data)!
                    // Update the cell
                    dispatch_async(dispatch_get_main_queue(), {
                        var contentColor:MDContentColor = image.md_imageContentColor()

                        if( contentColor == MDContentColor.Light ) {
                            // background is light, so use dark text
                            cell.minsAgoLabel.textColor = UIColor.blackColor()
                            cell.minsAgoLabel.text = shot.date
                            cell.minsAgoLabel.shadowColor = UIColor.whiteColor()
                            cell.minsAgoLabel.shadowOffset = CGSizeMake(0, 0)
                            cell.minsAgoLabel.layer.shadowRadius = 3.0
                            cell.minsAgoLabel.layer.shadowOpacity = 0.5
                            cell.minsAgoLabel.layer.masksToBounds = false
                            cell.minsAgoLabel.layer.shouldRasterize = true
                        }
                        else {
                            // background is dark, so use light text
                            cell.minsAgoLabel.textColor = UIColor.whiteColor()
                            cell.minsAgoLabel.text = shot.date
                            cell.minsAgoLabel.shadowColor = UIColor.blackColor()
                            cell.minsAgoLabel.shadowOffset = CGSizeMake(0, 0)
                            cell.minsAgoLabel.layer.shadowRadius = 3.0
                            cell.minsAgoLabel.layer.shadowOpacity = 0.5
                            cell.minsAgoLabel.layer.masksToBounds = false
                            cell.minsAgoLabel.layer.shouldRasterize = true
                        }
                    })
                }
                else {
                    println("Error: \(error.localizedDescription)")
                }
            })

            let profURL = NSURL(string: shot.user.avatarUrl)
            cell.profileImageView.sd_setImageWithURL(profURL, completed: nil)

        }
        return cell
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

        let cell:MainTableViewCell = tableView.dequeueReusableCellWithIdentifier("shotCell") as! MainTableViewCell
        self.tableView!.deselectRowAtIndexPath(indexPath, animated: true)
    }

Here is the main implementation of the data we fetched from the servers and populate it to the UITableView. The numberOfSectionsInTableView is used to define number of sections, in our case it’s 1. The heightForRowAtIndexPath is used to state the height of the tableView cells manually, in our case it returns 281. The numberOfRowsInSection is used to specify how many rows will be present in the tableView, which will be 100 in our case since we fetched only 100 shots from the server earlier. The didSelectRowAtIndexPath is used to specify what will happen when the user taps on a single cell, for this part we will not have anything to occur on cell selection. The last and most important function is cellForRowAtIndexPath which specifies what is to be displayed for every cell in the tableview.

Let’s break down cellForRowAtIndexPath to understand better on what is happening:

First we create a cell object which is quite common as well as mandatory in this function that uses reusableCell to decrease load on the device. Then we have an if loop that checks whether the shots object is empty or filled with data. If it is empty then empty all the labels and hide all the views. If we have some data then we display it using shots[indexPath.row] that fetches all the data from the dictionary for that particular row. We then set the image title as:

cell.shotNameLabel.text = shot.title+" by "+shot.user.name
This prints the title of the image and then the username of the publisher. We then store the URL of the image in a variable imgURL which is used by the SDWebImage class to download the images asynchronously. Once the images are loaded they are tried to fit in the UIImageView without caring about the aspect ratio of the image and making it look ugly. To avoid this we resize the image based on the screen width and change the ratio of the image accordingly as shown below:
var newImage = self!.imageResize(image, sizeChange: CGSizeMake(self!.screenWidth, ((self!.screenWidth * 300)/400)))
The newImage is then displayed to the tableview using cell.mainImageView.image = newImage . The profileImageView is also loaded using the SDWebImage asynchronously from the profURL.

The NSURLConnection.sendAsynchronousRequest is used for changing the color of the dateLabel in the background thread. This has to be done since many images on dribbble can have white background which overlap with the white color of the UILabel and it seems as if no label is present there. Same is the case if we have black label with black background. This function uses MDContentColor of the SDWebImage which calculates the brightness of every pixels in the image and accordingly gives the results. Is it returns MDContentColor.Light then the image is most probably light and a Black color UILabel would be visible on such backgrounds & vice versa. We also add some shadow to the dateLabel for better view. Here is a demo of how it will look:

Snubbble Shot

If you have noticed we used imageResize() function in our cellForRowAtIndexPath, well that is defined at the bottom and is as shown below:

func imageResize(imageObj:UIImage, sizeChange:CGSize)-> UIImage {

        let hasAlpha = false
        let scale: CGFloat = 0.0 // Automatically use scale factor of main screen

        UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)
        imageObj.drawInRect(CGRect(origin: CGPointZero, size: sizeChange))

        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext() // !!!
        return scaledImage
    }

This function helps in resizing the UIImage for better visual. And that is all for this part of tutorial, congratulations you have just learned how to make a Dribbble client for displaying the images. In our next tutorial we will learn to implement Dribbble Shot screen with comments & user profile screens.

Running the App

So now you run the app with the big “Play” button in the top left of Xcode.  You then need to set what device to run the app on.  You can select a physical device (if you have a paid developer license).  Otherwise, or even just for simpler testing, you can run it in the simulator.  Choose the simulated device to run it on from the same menu.

Then wait a bit while the simulator loads.  Mine took about 30 seconds to load.

Snubbble iOS Dribble Client

Here is the source code on Github for the tutorial above !

I hope you found this article helpful.  If you did, please don’t hesitate to share this post on Twitter or your social media of choice.  The blog is still pretty new, and every share helps.  Of course, if you have any questions, don’t hesitate to contact me on Twitter @swifty_os, and I’ll see what I can do.  As I said the second part of this tutorial will add Dribbble shots detail view with user comments & user profile screens. Thanks!

Advertisements