Large diffs are not rendered by default.

@@ -0,0 +1,45 @@
//
// InfiniteScrollActivityView.swift
// Twitter
//
// Created by Brian Lee on 2/11/16.
// Copyright © 2016 brianlee. All rights reserved.
//
import UIKit

class InfiniteScrollActivityView: UIView {
var activityIndicatorView: UIActivityIndicatorView = UIActivityIndicatorView()
static let defaultHeight:CGFloat = 60.0

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupActivityIndicator()
}

override init(frame aRect: CGRect) {
super.init(frame: aRect)
setupActivityIndicator()
}

override func layoutSubviews() {
super.layoutSubviews()
activityIndicatorView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2)
}

func setupActivityIndicator() {
activityIndicatorView.activityIndicatorViewStyle = .Gray
activityIndicatorView.hidesWhenStopped = true
self.addSubview(activityIndicatorView)
}

func stopAnimating() {
self.activityIndicatorView.stopAnimating()
self.hidden = true
}

func startAnimating() {
self.hidden = false
self.activityIndicatorView.startAnimating()
}
}
@@ -0,0 +1,82 @@
//
// Tweet.swift
// Twitter
//
// Created by Brian Lee on 2/9/16.
// Copyright © 2016 brianlee. All rights reserved.
//
import UIKit

class Tweet: NSObject {
let user: User?
let text: String?
let createdAtString: String?
let createdAt: NSDate?
let favorited: Int?
var retweeted: Int?
let id: Int?

init(dictionary: NSDictionary){
user = User(dictionary: (dictionary["user"] as! NSDictionary))
text = dictionary["text"] as? String
createdAtString = dictionary["created_at"] as? String

let formatter = NSDateFormatter()
formatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
createdAt = formatter.dateFromString(createdAtString!)

favorited = dictionary["favorite_count"] as? Int
retweeted = dictionary["retweet_count"] as? Int

let idString = dictionary["id_str"] as? String
id = Int(idString!)
}

class func tweetsWithArray(array: [NSDictionary]) -> [Tweet]{
var tweets = [Tweet]()

for dictionary in array{
tweets.append(Tweet(dictionary: dictionary))
}

return tweets
}

}

extension NSDate {
func weeksFrom(date:NSDate) -> Int{
return NSCalendar.currentCalendar().components(.WeekOfYear, fromDate: date, toDate: self, options: []).weekOfYear
}
func daysFrom(date:NSDate) -> Int{
return NSCalendar.currentCalendar().components(.Day, fromDate: date, toDate: self, options: []).day
}
func hoursFrom(date:NSDate) -> Int{
return NSCalendar.currentCalendar().components(.Hour, fromDate: date, toDate: self, options: []).hour
}
func minutesFrom(date:NSDate) -> Int{
return NSCalendar.currentCalendar().components(.Minute, fromDate: date, toDate: self, options: []).minute
}
func secondsFrom(date:NSDate) -> Int{
return NSCalendar.currentCalendar().components(.Second, fromDate: date, toDate: self, options: []).second
}
func offsetFrom(date:NSDate) -> String {
if weeksFrom(date) > 0 {
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day , .Month , .Year], fromDate: date)

let year = components.year
let month = components.month
let day = components.day

return "\(month)/\(day)/\(year)"
}
if daysFrom(date) > 0 { return "\(daysFrom(date))d" }
if hoursFrom(date) > 0 { return "\(hoursFrom(date))h" }
if minutesFrom(date) > 0 { return "\(minutesFrom(date))m" }
if secondsFrom(date) > 0 { return "\(secondsFrom(date))s" }
return ""
}
}

@@ -0,0 +1,55 @@
//
// TweetDetailCell.swift
// Twitter
//
// Created by Brian Lee on 2/10/16.
// Copyright © 2016 brianlee. All rights reserved.
//
import UIKit

class TweetDetailCell: UITableViewCell {

@IBOutlet weak var tweetLabel: UILabel!
@IBOutlet weak var screenNameLabel: UILabel!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var retweetLabel: UILabel!
@IBOutlet weak var favoriteLabel: UILabel!
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var retweetButton: UIButton!


var tweet: Tweet! {
didSet{
tweetLabel.text = tweet.text
screenNameLabel.text = "@" + (tweet.user?.screenname)!
nameLabel.text = tweet.user?.name

let url = NSURL(string: (tweet.user?.profileImageURL)!)
avatarImageView.setImageWithURL(url!)

retweetLabel.text = "\(tweet.retweeted!)"
favoriteLabel.text = "\(tweet.favorited!)"

let dateOffset = NSDate().offsetFrom(tweet.createdAt!)
timeLabel.text = dateOffset

}
}

override func awakeFromNib() {
super.awakeFromNib()

avatarImageView.layer.cornerRadius = 3
avatarImageView.clipsToBounds = true
// Initialization code
}

override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)

// Configure the view for the selected state
}

}
@@ -0,0 +1,122 @@
//
// TweetsViewController.swift
// Twitter
//
// Created by Brian Lee on 2/10/16.
// Copyright © 2016 brianlee. All rights reserved.
//
import UIKit

class TweetsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate {

@IBOutlet weak var tableView: UITableView!

var tweets:[Tweet]?
var isMoreDataLoading = false
var loadingMoreView: InfiniteScrollActivityView?

override func viewDidLoad() {
super.viewDidLoad()

tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 120

let frame = CGRectMake(0, tableView.contentSize.height, tableView.bounds.size.width, InfiniteScrollActivityView.defaultHeight)
loadingMoreView = InfiniteScrollActivityView(frame: frame)
loadingMoreView!.hidden = true
tableView.addSubview(loadingMoreView!)

var insets = tableView.contentInset;
insets.bottom += InfiniteScrollActivityView.defaultHeight;
tableView.contentInset = insets

// Do any additional setup after loading the view.
TwitterClient.sharedInstance.homeTimelineWithParams(nil, completion: { (tweets, error) -> () in
self.tweets = tweets
self.tableView.reloadData()
})
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

@IBAction func onLogout(sender: AnyObject) {
User.currentUser?.logout()
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tweets != nil {
return tweets!.count
}else{
return 0
}
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TweetDetailCell", forIndexPath: indexPath) as! TweetDetailCell

cell.tweet = tweets![indexPath.row]
cell.retweetButton.addTarget(self, action: "retweetButtonPressed:", forControlEvents: .TouchUpInside)
cell.retweetButton.tag = indexPath.row

return cell
}

func retweetButtonPressed(sender: UIButton){
let tweet = tweets![sender.tag]
let id = tweet.id!
let param = ["id": id]
TwitterClient.sharedInstance.retweetTweet(param)
tweet.retweeted!++
tableView.reloadData()
}

func loadMoreData() {
let lastTweet = tweets![(tweets!.count)-1]
let lastID = lastTweet.id!
let param = ["max_id": lastID]
TwitterClient.sharedInstance.homeTimelineWithParams(param, completion: { (var tweets, error) -> () in
tweets?.removeFirst()
self.tweets?.appendContentsOf(tweets!)
self.tableView.reloadData()
self.isMoreDataLoading = false
self.loadingMoreView!.stopAnimating()
})
}

func scrollViewDidScroll(scrollView: UIScrollView) {
if (!isMoreDataLoading) {
// Calculate the position of one screen length before the bottom of the results
let scrollViewContentHeight = tableView.contentSize.height
let scrollOffsetThreshold = scrollViewContentHeight - tableView.bounds.size.height

// When the user has scrolled past the threshold, start requesting
if(scrollView.contentOffset.y > scrollOffsetThreshold && tableView.dragging) {

isMoreDataLoading = true

let frame = CGRectMake(0, tableView.contentSize.height, tableView.bounds.size.width, InfiniteScrollActivityView.defaultHeight)
loadingMoreView?.frame = frame
loadingMoreView!.startAnimating()

loadMoreData()
}
}
}

/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/

}
@@ -15,12 +15,69 @@ let twitterBaseURL = NSURL(string: "https://api.twitter.com")


class TwitterClient: BDBOAuth1SessionManager {

var loginCompletion: ((user: User?, error: NSError?) -> ())?

class var sharedInstance: TwitterClient {
struct Static {
static let instance = TwitterClient(baseURL: twitterBaseURL, consumerKey: twitterConsumerKey, consumerSecret: twitterConsumerSecret)

}
return Static.instance
}


func retweetTweet(params: NSDictionary?){
let id = params!["id"] as! Int
POST("1.1/statuses/retweet/\(id).json", parameters: params, progress: nil, success: { (operation, response) -> Void in

print("retweet successful")

}, failure: { (operation, error) -> Void in
print("retweet unsuccessful")
})
}

func homeTimelineWithParams(params: NSDictionary?, completion: (tweets: [Tweet]?, error: NSError?) -> ()){
GET("1.1/statuses/home_timeline.json", parameters: params, progress: nil, success: { (operation, response) -> Void in

let tweets = Tweet.tweetsWithArray(response as! [NSDictionary])
completion(tweets: tweets, error: nil)

}, failure: { (operation, error) -> Void in
completion(tweets: nil, error: error)
})
}

func loginWithCompletion(completion: (user: User?, error: NSError?) -> ()){
loginCompletion = completion

//Fetch request token and redirect to authorization page
requestSerializer.removeAccessToken()
fetchRequestTokenWithPath("oauth/request_token", method: "GET", callbackURL: NSURL(string: "cptwitterdemo://oauth"), scope: nil, success: { (requestToken: BDBOAuth1Credential!) -> Void in
print("Got the request token")
let authURL = NSURL(string: "https://api.twitter.com/oauth/authorize?oauth_token=\(requestToken.token)")
UIApplication.sharedApplication().openURL(authURL!)
}) { (error: NSError!) -> Void in
self.loginCompletion?(user:nil, error: error)
}
}

func openURL(url: NSURL){
fetchAccessTokenWithPath("oauth/access_token", method: "POST", requestToken: BDBOAuth1Credential(queryString: url.query), success: { (accessToken: BDBOAuth1Credential!) -> Void in
print("got the access token")
self.requestSerializer.saveAccessToken(accessToken)
self.GET("1.1/account/verify_credentials.json", parameters: nil, progress: nil, success: { (operation, response) -> Void in
let user = User(dictionary: response as! NSDictionary)
User.currentUser = user
self.loginCompletion?(user: user, error: nil)
}, failure: { (operation, error) -> Void in
print("error getting user")
self.loginCompletion?(user:nil, error: error)
})
}) { (error: NSError!) -> Void in
self.loginCompletion?(user:nil, error: error)
}
}
}


@@ -0,0 +1,68 @@
//
// User.swift
// Twitter
//
// Created by Brian Lee on 2/9/16.
// Copyright © 2016 brianlee. All rights reserved.
//
import UIKit

var _currentUser: User?
let currentUserKey = "kCurrentUserKey"
let userDidLoginNotification = "userDidLoginNotification"
let userDidLogoutNotification = "userDidLogoutNotification"

class User: NSObject {
let name: String?
let screenname: String?
let profileImageURL: String?
let tagline: String?
let dictionary: NSDictionary

init(dictionary: NSDictionary){
self.dictionary = dictionary
name = dictionary["name"] as? String
screenname = dictionary["screen_name"] as? String
profileImageURL = dictionary["profile_image_url"] as? String
tagline = dictionary["description"] as? String
}

func logout(){
User.currentUser = nil
TwitterClient.sharedInstance.requestSerializer.removeAccessToken()

NSNotificationCenter.defaultCenter().postNotificationName(userDidLogoutNotification, object: nil)
}

class var currentUser: User? {
get{
if _currentUser == nil{
let data = NSUserDefaults.standardUserDefaults().objectForKey(currentUserKey) as? NSData
if data != nil{
do{
let dictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(rawValue:0)) as! NSDictionary
_currentUser = User(dictionary: dictionary)
} catch{
print("error reading json")
}
}
}
return _currentUser
}
set(user){
_currentUser = user
if _currentUser != nil{
do{
let data = try NSJSONSerialization.dataWithJSONObject(user!.dictionary, options: NSJSONWritingOptions(rawValue:0))
NSUserDefaults.standardUserDefaults().setObject(data, forKey: currentUserKey)
} catch{
print("error writing json")
}
} else{
NSUserDefaults.standardUserDefaults().setObject(nil, forKey: currentUserKey)
}
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}
@@ -23,14 +23,15 @@ class ViewController: UIViewController {
}

@IBAction func onLogin(sender: AnyObject) {
TwitterClient.sharedInstance.requestSerializer.removeAccessToken()
TwitterClient.sharedInstance.fetchRequestTokenWithPath("oauth/request_token", method: "GET", callbackURL: NSURL(string: "cptwitterdemo://oauth"), scope: nil, success: { (requestToken: BDBOAuth1Credential!) -> Void in
print("Got the request token")
let authURL = NSURL(string: "https://api.twitter.com/oauth/authorize?oauth_token=\(requestToken.token)")
UIApplication.sharedApplication().openURL(authURL!)
}) { (error: NSError!) -> Void in
print("Failed to get request token")
TwitterClient.sharedInstance.loginWithCompletion(){
(user: User?, error: NSError?) in
if user != nil{
self.performSegueWithIdentifier("loginSegue", sender: self)
} else{
print("error logging in")
}
}

}
}