Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3

January 09, 2015
Shrikar Archak

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/yikyak1.png" height={350} width={1000} placeholder="blur" quality={100} />

This is the part 3 of Building the Yik Yak Clone tutorial series. In this part of the tutorial we will be implementing commenting system on post/yak messages using UITableview and Footerview. If you haven’t gone through the Building Yik Yak Clone Part 1 and Building Yik Yak Clone Part 2 I will suggest you to go through them first before continuing.

Xcode 6 and Interface builder setup

  • Drag a UIViewController on to the main storyboard
  • Embed the UIViewController in Navigation Controller.
  • Drag a two UILabel on the UIViewController.
  • Drag a mapview on the UIViewController.
  • Drag a table view below the map and a UITableViewCell in the table view.
  • Drag from UITableViewCell of the previous yak listing controller to this <Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv10.png" height={350} width={1000} placeholder="blur" quality={100} />

Create custom DetailViewController and CommentTableViewCell

Create new view controller DetailViewController of type UIViewController and a custom tableview cell CommentTableView of type UITableViewCell.

Setup the IBoutlet for yakText, timeLabel , mapView and tableView in DetailViewController. And IBOutlet commentText in CommentTableViewCell.

Setup the constraints for all the elements as below.

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv1.png" height={350} width={1000} placeholder="blur" quality={100} />

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv2.png" height={350} width={1000} placeholder="blur" quality={100} />

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv3.png" height={350} width={1000} placeholder="blur" quality={100} />

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv4.png" height={350} width={1000} placeholder="blur" quality={100} />

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv5.png" height={350} width={1000} placeholder="blur" quality={100} />

In the detail view controller there are a static elements for displaying for the yak message , date and a map view. We will be using the table view for displaying the comments for the post.

UITableView provide mechanism to add custom footer and header view. In our case we will be using the footer view to provide text view for commenting. We need to take care of an issue where the keyboard could hide the text view and prevent us from commenting. To get around this problem we will be using the Keyboard notifications to prevent keyboard from covering the text view Receiving Keyboard Notifications.

NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyBoardWillShow:", name: UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyBoardWillHide:", name: UIKeyboardWillHideNotification, object: nil)

We will be using NSNotificationCenter which broadcasts observers about any event that happen and the app is interested in. The events acts as a customization hook to modify the behaviour of how the app should work on these events. In our case we will tell the NotificationCenter to call KeyBoardWillShow and keyBoardWillHide methods on the UIKeyboardWillShowNotification and UIKeyboardWillHideNotification events accordingly.

In KeyBoardWillShow method we will find the keyboard height and UIEdgeInsets accordingly to scroll the content above the keyboard.

In KeyBoardWillHide method we will reset the contentInsets undoing what we did in the keyBoardWillShow method.

I am also substracting the extra 40px without which the content wouldn’t be offset correctly. I dont need to substract the extra 40px when I drag in the UITableViewController directly. This seems to be a issue when you implement the UITableViewDataSource and UITableViewDelegate yourself. I found that we people were offsetting it by 40 to 44px on stackoverflow. This might not be the correct way to implement but if you have a better way to fix please let me know.

More on UIScrollView

//
//  DetailViewController.swift
//  YikYak
//
//  Created by Shrikar Archak on 1/6/15.
//  Copyright (c) 2015 Shrikar Archak. All rights reserved.
//

import UIKit
import MapKit
class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate {

    var yak: PFObject?
    var commentView: UITextView?
    var footerView: UIView?
    var contentHeight: CGFloat = 0

    var comments: [String]?
    let FOOTERHEIGHT : CGFloat = 50;

    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var timeLabel: UILabel!
    @IBOutlet weak var yakLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()

        /* Setup the datasource delegate */
        tableView.delegate = self
        tableView.dataSource = self

        /* Setup the keyboard notifications */
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyBoardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyBoardWillHide:", name: UIKeyboardWillHideNotification, object: nil)

        /* Setup the contentInsets */
        self.tableView.contentInset = UIEdgeInsetsZero
        self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero

        self.edgesForExtendedLayout = UIRectEdge.None
        /* Make sure the content doesn't go below tabbar/navbar */
        self.extendedLayoutIncludesOpaqueBars = true

        self.automaticallyAdjustsScrollViewInsets = false

        /* Setup Map */
        let geo = yak?.objectForKey("location") as PFGeoPoint
        let coordinate = CLLocationCoordinate2D(latitude: geo.latitude, longitude: geo.longitude)
        let reg = MKCoordinateRegionMakeWithDistance(coordinate, 1500, 1500)
        self.mapView.setRegion(reg, animated: true)
        self.mapView.showsUserLocation = true

        if(yak?.objectForKey("comments") != nil) {
            comments = yak?.objectForKey("comments") as [String]
        }
        println(yak)
        println(yak?.objectForKey("text"))
        self.yakLabel.text = yak?.objectForKey("text") as String
    }

    func keyBoardWillShow(notification: NSNotification) {
        var info:NSDictionary = notification.userInfo!
        var keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as NSValue).CGRectValue()

        var keyboardHeight:CGFloat =  keyboardSize.height - 40

        var animationDuration:CGFloat = info[UIKeyboardAnimationDurationUserInfoKey] as CGFloat

        var contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardHeight, 0.0);
        self.tableView.contentInset = contentInsets
        self.tableView.scrollIndicatorInsets = contentInsets

    }

    func keyBoardWillHide(notification: NSNotification) {

        self.tableView.contentInset = UIEdgeInsetsZero
        self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()

    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let count = comments?.count {
            return count
        }
        return 0
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("commentCell", forIndexPath: indexPath) as CommentTableViewCell
        cell.commentText?.text = comments![indexPath.row]
        return cell
    }

    func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        if self.footerView != nil {
            return self.footerView!.bounds.height
        }
        return FOOTERHEIGHT
    }

    func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: FOOTERHEIGHT))
        footerView?.backgroundColor = UIColor(red: 243.0/255, green: 243.0/255, blue: 243.0/255, alpha: 1)
        commentView = UITextView(frame: CGRect(x: 10, y: 5, width: tableView.bounds.width - 80 , height: 40))
        commentView?.backgroundColor = UIColor.whiteColor()
        commentView?.textContainerInset = UIEdgeInsetsMake(5, 5, 5, 5)
        commentView?.layer.cornerRadius = 2
        commentView?.scrollsToTop = true

        footerView?.addSubview(commentView!)
        let button = UIButton(frame: CGRect(x: tableView.bounds.width - 65, y: 10, width: 60 , height: 30))
        button.setTitle("Reply", forState: UIControlState.Normal)
        button.backgroundColor = UIColor(red: 155.0/255, green: 189.0/255, blue: 113.0/255, alpha: 1)
        button.layer.cornerRadius = 5
        button.addTarget(self, action: "reply", forControlEvents: UIControlEvents.TouchUpInside)
        footerView?.addSubview(button)
        commentView?.delegate = self
        return footerView
    }

    func textViewDidChange(textView: UITextView) {

        if (contentHeight == 0) {
            contentHeight = commentView!.contentSize.height
        }

        if(commentView!.contentSize.height != contentHeight && commentView!.contentSize.height > footerView!.bounds.height) {
            UIView.animateWithDuration(0.2, animations: { () -> Void in
                let myview = self.footerView
                println(self.commentView!.contentSize.height)
                println(self.commentView?.font.lineHeight)
                let newHeight : CGFloat = self.commentView!.font.lineHeight
                let myFrame = CGRect(x: myview!.frame.minX, y: myview!.frame.minY - newHeight , width: myview!.bounds.width, height: newHeight + myview!.bounds.height)
                myview?.frame = myFrame

                let mycommview = self.commentView
                let newCommHeight : CGFloat = self.commentView!.contentSize.height
                let myCommFrame = CGRect(x: mycommview!.frame.minX, y: mycommview!.frame.minY, width: mycommview!.bounds.width, height: newCommHeight)
                mycommview?.frame = myCommFrame

                self.commentView = mycommview
                self.footerView  = myview

                for item in self.footerView!.subviews {
                    if(item.isKindOfClass(UIButton.self)){
                        let button = item as UIButton
                        let newY = self.footerView!.bounds.height / 2 - button.bounds.height / 2
                        let buttonFrame = CGRect(x: button.frame.minX, y: newY , width: button.bounds.width, height : button.bounds.height)
                        button.frame = buttonFrame

                    }
                }
            })

            println(self.footerView?.frame)
            println(self.commentView?.frame)
            contentHeight = commentView!.contentSize.height
        }

    }

    func reply() {
        yak?.addObject(commentView?.text, forKey: "comments")
        yak?.saveInBackground()
        if let tmpText = commentView?.text {
            comments?.append(tmpText)
        }
        commentView?.text = ""
        println(comments?.count)
        self.commentView?.resignFirstResponder()
        self.tableView.reloadData()
    }
}

Final App

So we have build the Listing of yaks, Posting a Yak, Implementing commenting. There is one more feature which is creating the setting tab which you can find out how to implement here Xcode 6 Grouped TableViews

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv6.png" height={350} width={1000} placeholder="blur" quality={100} />

<Image alt="Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3" objectFit="contain" src="/static/images/tv7.png" height={350} width={1000} placeholder="blur" quality={100} />

Please let me know if you have any question / feedback. If you have followed along the tutorial series let me know how it went in the comments sections.

Subscribe to the newsletter

Get notified when new content or topic is released.

You won't receive any spam! ✌️