Using UITableView and FooterView to implement commenting system: Building Yik Yak Clone Part 3
<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.