iOS app: From scratch to app store Part 3

Custom TableViewCell
Here is the link to the project we worked on in part two. We will build on this. The project was initially written in Swift 2.2 but I have updated it to swift 3. In part 2 we agreed will be building a custom cell to display each movie/tv shows. Here is a screen shot of what we want to achieve. Alt Text

At the top we have a label for the name of the movie/tv show. Right below the name is an image. Next is a label for the release date and finally a summary/synopsis. Also the cells is designed as cards with the edges curved a bit.

Lets get right into it.

Right click on tvzone folder, select New File > Cocoa Touch Class. Click on Next. For “Subclass of:” select UITableViewCell. Check Also create XIB file. Name the class MoviesTableViewCell. Click next, click Create.

Click on MoviesTableViewCell.xib. This is where we will design the custom cell and support it with little code.
Select the cell, click on size inspector. For Row Height check custom, and give it a height of 350. Below RowHeight you will find Width and Height properties of the cell. Set width to 320 and height to 340. We will let the content of the cell determine the size of the cell but for now 350 gives us a size to work with in the interface. The editor area should like this now

Alt Text

Drag a view from the Object library onto the cell’s Content View. With the view selected, click on Pin and pin the view to all corners of the content view. top:5, bottom:5, leading:8, trailing:8.
Alt Text

Let’s call this view, “Card View”. To do this, click on identity inspector, just right below “Document” you will “Label”, enter “Card View” for the field.

Create an IBOutlet for the view. You do this by opening the assistant inspector and click > dragging (while holding control button) from card view to it’s class. Call the outlet cardView.

Alt Text

Notice the “Spring Wood” color of the background of the card in the screenshot. Lets apply the same color to our card view. We will do this in a UIView method called layoutSubviews. According to apple:

Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.

Though layoutSubviews sounds likes where we can only alter the layout/frame of our subviews, we can also take advantage of it to alter some other components of the subviews that doesn’t relate to the views frame.

override func layoutSubviews() {  
//1
    self.cardView.backgroundColor = UIColor(red:    251/255.0, green: 250/255.0, blue: 248/255.0, alpha: 1)
//2
   self.cardView.layer.borderWidth = 1
   self.cardView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.3).cgColor
//3
    self.cardView.layer.cornerRadius = self.frame.size.width * 0.015
    self.cardView.layer.shadowRadius = 3
    self.cardView.layer.shadowOpacity = 0                self.cardView.layer.shadowOffset = CGSize(width: 1, height: 1)
    }
  1. The first line of code sets the background color of the card. To set color in iOS, we use the UIColor object which has an init method
    init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)
    this takes opacity and RGB component values to create and return a color object
  2. There is a tiny faint-colored border line around the card. We give the card a borderWith of 1 and set the color of the border to lightGray. We get the color using the UIColor object, adjust the opacity to 0.3.
  3. The edges of the card is not sharp, it’s a little curved. To achieve this, we set the cornerRadius property of the layer. We set this to a fraction of the card width. We add some little effect by setting the shadowRadius, shadowOpacity and shadowOffset.

Now let get back to cell interface. Grab a label and place inside card view. Pin it 8 to the left and right and 4 to the top. With the label selected, go over the Utility Area, select Attributes inspector. Let’s change the font. Click on the bold “T” to the extreme end of font field.

Alt Text

For font, click on the drop down and select Custom, for family select Avenir, for style, select Medium. Give it a size of 20.

Right underneath Font is Alignment. Select the second option to center the text in the label.

Go to the Object Library, get an ImageView and drop it vertically right next to the label. Pin the image view 8 to both left and right. 4 to the top

Alt Text

Next we add two more label. One for year of release and one for summary.
Year of release label: Pin 4 to the image view, 8 both left and right of card view. Change font to Avenir, style: Book, size: 15.
Summary label: Apply same font for year of release. Pin 4 to Year of release label, 8 left and right. Pin to the bottom too.
With the summary label selected, go over to Size Inspector. scroll down to the constraints. Lets edit the bottom constraint. Click on edit on the "Bottom Space to:SuperView”.

Alt Text
Change Constant to >=, and the value to 8.
This will allow card view to make space enough for the summary label content.

Alt Text

With the label still selected, go back to attribute inspector. Right below Alignment is Lines. UILabel has a property called “numberOfLines” which specifies the maximum number of lines to use for rendering text. We will set this to 0 since we don’t know the actual number of lines we will need for the summary text.
At this point, our cell should look like this

Alt Text

Lets go ahead and wire the elements to the class.
Click on Assistant editor. Make sure the MoviesTableViewCell file is the one displayed in the assistant editor. While holding down control on keyboard, click and drag from the first label to MoviesTableViewCell class. Call the outlet titleLabel. Do the same for the imageView (movieImageView), year of release label (releaseDateLabel) and summary label (summaryLabel).

Finally we can concentrate on code now. Go over to ListViewController. Our table will be displaying a list of movies so we can go ahead and delete the “names” array. We create an array called movies. This will be an array of dictionaries. Our table will display a list of two movies. Since we already have a picture of Messi and Ronaldo, let create a movie for the two stars.

let movies = [  
        ["title":"CR7", "image":"ronaldo", "year":"2012", "summary":"Cristiano Ronaldo dos Santos Aveiro, ComM, GOIH (Portuguese pronunciation: [kɾiʃ'tjɐnu ʁuˈnaɫdu], born 5 February 1985) is a Portuguese professional footballer who plays for Spanish club Real Madrid and the Portugal national team. He is a forward and serves as captain for Portugal. In 2008, he won his first Ballon d'Or and FIFA World Player of the Year awards. He then won the FIFA Ballon d'Or in 2013 and 2014. In 2015, Ronaldo scored his 500th senior career goal for club and country."],
    ["title":"The Leo", "image":"messi", "year”:"1999", "summary":"Lionel Andrés \"Leo\" Messi (Spanish pronunciation: [ljoˈnel anˈdɾes ˈmesi] ( listen); born 24 June 1987) is an Argentine professional footballer who plays as a forward for Spanish club FC Barcelona and the Argentina national team. Often considered the best player in the world and rated by many in the sport as the greatest of all time, Messi is the only football player in history to win five FIFA Ballons d'Or, four of which he won consecutively, and the first player to win three European Golden Shoes.[note 2] With Barcelona he has won eight La Liga titles and four UEFA Champions League titles, as well as four Copas del Rey. Both a prolific goalscorer and a creative playmaker, Messi holds the records for most goals scored in La Liga, a La Liga season (50), a football season (82), and a calendar year (91), as well as those for most assists made in La Liga and the Copa América. He has scored over 500 senior career goals for club and country."]
    ]

For summary I just grabbed something from the Wikipedia.

The table view has no knowledge of the existence of our custom cell, we have to register the cell. We will do this in viewDidLoad. But first, go to main storyboard and create an outlet for tableView. Call it tableView.

override func viewDidLoad() {  
 tableView.register(UINib(nibName:"MoviesTableViewCell",bundle:nil) , forCellReuseIdentifier: "MoviesTableViewCell")
}

So the tableView can be a able to dequeue the custom cell, we register a nib object containing the custom cell with the specified identifier.

Go to MoviesTableViewCell.xib and give the cell a reuse identifier MoviesTableViewCell

You must noticed an error from the moment we deleted the array names. Let fix that. For numberOfRows return movies.count
We change the implementation of cellForRowAt indexPath

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {  
        let cell = tableView.dequeueReusableCell(withIdentifier: "MoviesTableViewCell", for: indexPath) as! MoviesTableViewCell

        let movie = movies[indexPath.row]

        cell.titleLabel.text = movie["title"]

        cell.releaseDateLabel.text = movie["year"]

        cell.summaryLabel.text = movie["summary"]

        cell.movieImageView.image = UIImage(named: movie["image"]!)

        return cell

    }

We create the cell and forcefully cast it to type MoviesTableViewCell. This is fine since we are sure it is of that type. The movies array is an array of dictionaries. We can grab each dictionary/movie using indexPath.row each time cellForRowAt indexPath is called. This we did with the line of code

let movie = movies[indexPath.row]

We then set the cell contents by accessing the values of the dictionary using the keys. Finally we returned the cell.
Run the project now.

Alt Text

That is not what we want to achieve.
We forgot to set the size for our cell. To do this, we will implement two method from UITableViewDelegate.

extension ListViewController:UITableViewDelegate {

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {

        return UITableViewAutomaticDimension

    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

        return UITableViewAutomaticDimension

    }

}

The first method asks the delegate for the estimated height of a row in a specified location. While the second asks the delegate for the height to use for a row in a specified location.
If we have a fixed size for our cell, we could return that size. Say we want our cell to be height 250, then we return 250. In this case we don't have a fixed size. We want the cell to take the size of it's content. UITableViewAutomaticDimension requests that UITableView use the default value for a given dimension.
Before we run the project, go main storyboard, and set the delegate for the table view. Same way we set the datasource in part one.

Run the project.

Alt Text

SWEET!!

The complete project can be found here.
In part 4 we will be adding traktv api and see how to pull content from a web service.