| @@ -0,0 +1,45 @@ | ||
| // | ||
| // InfiniteScrollActivityView.swift | ||
| // | ||
| // 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 | ||
| // | ||
| // 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 | ||
| // | ||
| // 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 | ||
| // | ||
| // 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. | ||
| } | ||
| */ | ||
|
|
||
| } |
| @@ -0,0 +1,68 @@ | ||
| // | ||
| // User.swift | ||
| // | ||
| // 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() | ||
| } | ||
| } | ||
| } |