This is the Part 2 of the Building Yik Yak Clone. If you haven’t looked at Part1 I suggest you to look at Building Yik Yak Clone Part 1 before continuing further.

In the part one we left at the point where the data in the UITableView was static and hardcoded in the app. In the real world application we normally tend to fetch the data from a REST based server or Mobile backend as a service like Parse. In this tutorial series we will use Parse for storing the data. Find more on how to Integrate Parse in your app

Lets get started.

Add a bar button item on to the top right of the table view and change the identifier to Compose

Drag a view controller next to the existing table view controller.

CTRL+Drag from the compose button on to the view controller you just added and select present modally

Now embed the view controller you just selected in a navigation controller using Editor -> Embed in -> navigation controller

Drag a text view on to the new view controller you just added and have the settings/attributes as mentioned below.



In this particular example I am not adding autolayout constraints and getting scrollviews to work with Autolayouts is a bit tricky.. I will have a seperate post just for that. For now set these values(or appropriate values) in the size inspector.



Now lets drag two bar button items on the navigation bar and call them Cancel and Post . CTRL+Drag from these button onto PostViewController.swift by opening them side by side using Assistant Editor and call them cancelPressed and postPressed accordingly.

Now we have the skeleton for the app setup.

Parse provides an implementation of UITableViewController called PFQueryTableViewController which provides many features like pulltorefresh , pagination and loading data from Parse. So to use this feature we need to make sure that our implementation of tableview controller TableViewController.swift should extend PFQueryTableViewController instead of UITableViewController similarly our TableViewCell.swift should extend PFTableViewCell .

Once you are done with changing those files. Let go ahead and create the upvote and downvote functionality

We need to have the IBAction for the topButton and buttonButton. Create two new IBAction from these button to TableViewController file and name them topButton and bottomButton accordingly.

To identify which yak was upvoted or downvoted we need to find which cell these upvote and downvote belong to. There is a simple way to do that, first identify the point in the tableview the touch happened and convert it to indexPath using indexPathForRowAtPoint function.

let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView) let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)

At this point the updated code would look like this

// // TableViewController.swift // YikYak // // Created by Shrikar Archak on 12/31/14. // Copyright (c) 2014 Shrikar Archak. All rights reserved. // import UIKit import CoreLocation class TableViewController: PFQueryTableViewController, CLLocationManagerDelegate { var yaks = ["Getting Started with building a Yik Yak Clone in Swift","Xcode 6 Tutorial using Autolayouts", "In this tutorial you will also learn how to talk to Parse Backend", "Learning Swift by building real world applications", "Test"] let locationManager = CLLocationManager() var currLocation : CLLocationCoordinate2D? override init!(style: UITableViewStyle, className: String!) { super.init(style: style, className: className) } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.parseClassName = "Yak" self.textKey = "text" self.pullToRefreshEnabled = true self.objectsPerPage = 200 } private func alert(message : String) { let alert = UIAlertController(title: "Oops something went wrong.", message: message, preferredStyle: UIAlertControllerStyle.Alert) let action = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil) let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil) let settings = UIAlertAction(title: "Settings", style: UIAlertActionStyle.Default) { (action) -> Void in UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!) return } alert.addAction(settings) alert.addAction(action) self.presentViewController(alert, animated: true, completion: nil) } override func viewDidLoad() { super.viewDidLoad() self.tableView.estimatedRowHeight = 60 self.tableView.rowHeight = UITableViewAutomaticDimension locationManager.desiredAccuracy = 1000 locationManager.delegate = self locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) { alert("Cannot fetch your location") } override func queryForTable() -> PFQuery! { let query = PFQuery(className: "Yak") if let queryLoc = currLocation { query.whereKey("location", nearGeoPoint: PFGeoPoint(latitude: queryLoc.latitude, longitude: queryLoc.longitude), withinMiles: 10) query.limit = 200; query.orderByDescending("createdAt") } else { /* Decide on how the application should react if there is no location available */ query.whereKey("location", nearGeoPoint: PFGeoPoint(latitude: 37.411822, longitude: -121.941125), withinMiles: 10) query.limit = 200; query.orderByDescending("createdAt") } return query } func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) { locationManager.stopUpdatingLocation() if(locations.count > 0){ let location = locations[0] as CLLocation println(location.coordinate) currLocation = location.coordinate } else { alert("Cannot fetch your location") } } override func objectAtIndexPath(indexPath: NSIndexPath!) -> PFObject! { var obj : PFObject? = nil if(indexPath.row < self.objects.count){ obj = self.objects[indexPath.row] as PFObject } return obj } override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject!) -> PFTableViewCell! { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell cell.yakText.text = object.valueForKey("text") as String cell.yakText.numberOfLines = 0 let score = object.valueForKey("count") as Int cell.count.text = "\(score)" cell.time.text = "\((indexPath.row + 1) * 3)m ago" cell.replies.text = "\((indexPath.row + 1) * 1) replies" return cell } @IBAction func topButton(sender: AnyObject) { let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView) let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint) let object = objectAtIndexPath(hitIndex) object.incrementKey("count") object.saveInBackground() self.tableView.reloadData() NSLog("Top Index Path \(hitIndex?.row)") } @IBAction func bottomButton(sender: AnyObject) { let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView) let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint) let object = objectAtIndexPath(hitIndex) object.incrementKey("count", byAmount: -1) object.saveInBackground() self.tableView.reloadData() NSLog("Bottom Index Path \(hitIndex?.row)") } }

And this is the part where we save the object in Parse

// // PostViewController.swift // YikYak // // Created by Shrikar Archak on 12/31/14. // Copyright (c) 2014 Shrikar Archak. All rights reserved. // import UIKit class PostViewController: UIViewController, UITextViewDelegate, CLLocationManagerDelegate { @IBOutlet weak var postView: UITextView! var currLocation: CLLocationCoordinate2D? var reset:Bool = false let locationManager = CLLocationManager() private func alert() { let alert = UIAlertController(title: "Cannot fetch your location", message: "Please enable location in the settings menu", preferredStyle: UIAlertControllerStyle.Alert) let action = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil) let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil) let settings = UIAlertAction(title: "Settings", style: UIAlertActionStyle.Default) { (action) -> Void in UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!) return } alert.addAction(settings) alert.addAction(action) self.presentViewController(alert, animated: true, completion: nil) } override func viewDidLoad() { super.viewDidLoad() self.postView.selectedRange = NSMakeRange(0, 0); self.postView.delegate = self self.postView.becomeFirstResponder() locationManager.desiredAccuracy = kCLLocationAccuracyBest locationManager.delegate = self locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() } func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) { locationManager.stopUpdatingLocation() if(locations.count > 0){ let location = locations[0] as CLLocation currLocation = location.coordinate } else { alert() } } func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) { println(error) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } @IBAction func cancelPressed(sender: AnyObject) { self.dismissViewControllerAnimated(true , completion: nil) } @IBAction func postPressed(sender: AnyObject) { if(currLocation != nil){ let testObject = PFObject(className: "Yak") testObject["text"] = self.postView.text testObject["count"] = 0 testObject["replies"] = 0 testObject["location"] = PFGeoPoint(latitude: currLocation!.latitude , longitude: currLocation!.longitude) testObject.saveInBackground() self.dismissViewControllerAnimated(true , completion: nil) } else { alert() } } func textViewDidChange(textView: UITextView) { if(reset == false){ self.postView.text = String(Array(self.postView.text)[0]) reset = true } } }

Note once you save the object you can pull down the tableview to see the latest post you created.

Here is the link to the github YikYak

Please let me know if you have any questions/feedback.