From 911b76efc5f3c0559ac0bf31dc1e27d960d899f4 Mon Sep 17 00:00:00 2001 From: Sarah Smith Date: Sun, 3 Sep 2017 22:39:17 +1000 Subject: [PATCH 1/5] Update to Swift 3.1/Xcode 8.3 --- .gitignore | 34 +++++++--- SwiftTweetGettr.xcodeproj/project.pbxproj | 26 ++++++-- SwiftTweetGettr/AppDelegate.swift | 4 +- SwiftTweetGettr/Base.lproj/Main.storyboard | 44 +++++++------ SwiftTweetGettr/Extensions.swift | 63 ++++++++++--------- SwiftTweetGettr/Info.plist | 6 +- SwiftTweetGettr/Tweet.swift | 12 ++-- SwiftTweetGettr/TweetCell.swift | 4 +- SwiftTweetGettr/TweetsTableViewDelegate.swift | 8 +-- SwiftTweetGettr/TwitterAuthorization.swift | 14 ++--- SwiftTweetGettr/TwitterClient.swift | 58 ++++++++++------- SwiftTweetGettr/ViewController.swift | 39 ++++++------ 12 files changed, 188 insertions(+), 124 deletions(-) diff --git a/.gitignore b/.gitignore index d522f94..3c2c9b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,28 @@ -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? -# -# Pods/ +# Xcode +.DS_Store +build/ +*.dll +*.a +*.approj +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +*.xcworkspace +!default.xcworkspace +*/xcuserdata +profile +*.moved-aside +DerivedData +.idea/ +.sconsign.dblite +Scripts/cache +Scripts/json +Scripts/venv +Carthage/Checkouts +Carthage/Build diff --git a/SwiftTweetGettr.xcodeproj/project.pbxproj b/SwiftTweetGettr.xcodeproj/project.pbxproj index 069d66b..9c4b946 100644 --- a/SwiftTweetGettr.xcodeproj/project.pbxproj +++ b/SwiftTweetGettr.xcodeproj/project.pbxproj @@ -113,11 +113,13 @@ 63FEA3C6193E888700400515 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0600; + LastUpgradeCheck = 0830; ORGANIZATIONNAME = "Jeff Menter"; TargetAttributes = { 63FEA3CD193E888700400515 = { CreatedOnToolsVersion = 6.0; + DevelopmentTeam = PQHPAVF5T9; + LastSwiftMigration = 0830; }; }; }; @@ -194,15 +196,19 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -215,7 +221,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; METAL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -236,8 +242,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -245,15 +253,17 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; METAL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; }; name = Release; @@ -263,10 +273,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + DEVELOPMENT_TEAM = PQHPAVF5T9; INFOPLIST_FILE = SwiftTweetGettr/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = au.com.smithsoft.SwiftTweetGettr; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -275,10 +288,13 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + DEVELOPMENT_TEAM = PQHPAVF5T9; INFOPLIST_FILE = SwiftTweetGettr/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = au.com.smithsoft.SwiftTweetGettr; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/SwiftTweetGettr/AppDelegate.swift b/SwiftTweetGettr/AppDelegate.swift index fa6b2cd..2019381 100644 --- a/SwiftTweetGettr/AppDelegate.swift +++ b/SwiftTweetGettr/AppDelegate.swift @@ -6,11 +6,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { let greenHue:CGFloat = 123.0/360.0 self.window?.tintColor = UIColor(hue: greenHue, saturation: 0.5, brightness: 0.5, alpha: 1.0) UINavigationBar.appearance().barTintColor = UIColor(hue: greenHue, saturation: 0.05, brightness: 0.95, alpha: 1.0) return true } -} \ No newline at end of file +} diff --git a/SwiftTweetGettr/Base.lproj/Main.storyboard b/SwiftTweetGettr/Base.lproj/Main.storyboard index 0d03a6c..f7aacb2 100644 --- a/SwiftTweetGettr/Base.lproj/Main.storyboard +++ b/SwiftTweetGettr/Base.lproj/Main.storyboard @@ -1,8 +1,12 @@ - - + + + + + - - + + + @@ -10,11 +14,11 @@ - + - + @@ -24,13 +28,15 @@ - + - + + + @@ -38,27 +44,27 @@ @@ -78,17 +84,17 @@ line 3 - + @@ -109,9 +115,9 @@ line 3 - + - + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. @@ -144,6 +150,6 @@ line 3 - + diff --git a/SwiftTweetGettr/Extensions.swift b/SwiftTweetGettr/Extensions.swift index 1451d35..a306be1 100644 --- a/SwiftTweetGettr/Extensions.swift +++ b/SwiftTweetGettr/Extensions.swift @@ -1,94 +1,99 @@ import UIKit -extension NSURLResponse { +extension URLResponse { func isHTTPResponseValid() -> Bool { - if let response = self as? NSHTTPURLResponse { + if let response = self as? HTTPURLResponse { return (response.statusCode >= 200 && response.statusCode <= 299) } return false } } -extension NSData { +extension Data { func json() -> AnyObject { - return NSJSONSerialization.JSONObjectWithData(self, options: nil, error: nil)! + let result = try? JSONSerialization.jsonObject(with: self, options: []) + return result! as AnyObject } } extension UITableView { - func scrollToTop(#animated: Bool) + func scrollToTop(animated: Bool) { - scrollRectToVisible(CGRectMake(0, 0, 1, 0), animated: true) + scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 0), animated: true) } } extension UIViewController { - func showAlertViewWithMessage(message : String) + func showAlertViewWithMessage(_ message : String) { - var alertController = UIAlertController(title: "Oops!", message: message, preferredStyle: UIAlertControllerStyle.Alert) - alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil)) - presentViewController(alertController, animated: true, completion: nil) + let alertController = UIAlertController(title: "Oops!", message: message, preferredStyle: UIAlertControllerStyle.alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alertController, animated: true, completion: nil) } } extension String { - func data() -> NSData + func data() -> Data { - return dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + return self.data(using: String.Encoding.utf8, allowLossyConversion: false)! } func base64Encoded() -> String { - return data().base64EncodedStringWithOptions(nil) + let dataFromString = self.data() + return dataFromString.base64EncodedString(options: []) } - func createURL() -> NSURL + func createURL() -> URL { - return NSURL(string: self)! + return URL(string: self)! } func stringByRemovingWhitespace() -> String { - let trimmed = self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - return trimmed.stringByReplacingOccurrencesOfString(" ", withString: "", options: nil, range: nil) + let trimmed = self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + return trimmed.replacingOccurrences(of: " ", with:"", options: [], range: nil) } } extension NSMutableURLRequest { - class func getRequestWithURL(url:NSURL) -> NSMutableURLRequest + class func getRequestWithURL(_ url:URL) -> NSMutableURLRequest { - var request = NSMutableURLRequest(URL: url) - request.HTTPMethod = "GET" + let request = NSMutableURLRequest(url: url) + request.httpMethod = "GET" return request } - class func postRequestWithURL(url:NSURL, body:String) -> NSMutableURLRequest + class func postRequestWithURL(_ url:URL, body:String) -> NSMutableURLRequest { - var request = NSMutableURLRequest(URL: url) - request.HTTPMethod = "POST" - request.HTTPBody = body.data() + let request = NSMutableURLRequest(url: url) + request.httpMethod = "POST" + request.httpBody = body.data() return request } } extension UIImageView { - func loadURL(url:String) { - NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: NSURL(string: url)!), queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in - if response.isHTTPResponseValid() { - if let image = UIImage(data: data) { + func loadURL(_ url:String) { + + let request = URLRequest(url: URL(string: url)!) + URLSession.shared.dataTask(with: request) { (data, response, error) -> Void in + let validResponse = response?.isHTTPResponseValid() ?? false + if validResponse { + if let image = UIImage(data: data!) { self.image = image } } } } -} \ No newline at end of file +} diff --git a/SwiftTweetGettr/Info.plist b/SwiftTweetGettr/Info.plist index ef7c0da..744d1cd 100644 --- a/SwiftTweetGettr/Info.plist +++ b/SwiftTweetGettr/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.yournamehere.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -30,5 +30,9 @@ armv7 + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + diff --git a/SwiftTweetGettr/Tweet.swift b/SwiftTweetGettr/Tweet.swift index 5bc201e..4b891c0 100644 --- a/SwiftTweetGettr/Tweet.swift +++ b/SwiftTweetGettr/Tweet.swift @@ -3,12 +3,12 @@ import UIKit class Tweet { - class func tweetsFromArray(from: Array>) -> Array + class func tweetsFromArray(_ from: Array>) -> Array { return from.map( { Tweet(tweetDictionary: $0) } ) } - private let tweetDictionary:Dictionary + fileprivate let tweetDictionary:Dictionary var text:String { return tweetDictionary["text"] as! String } var createdAt:String { return tweetDictionary["created_at"] as! String } @@ -19,13 +19,13 @@ class Tweet { var profileImageURL:String? { return user()["profile_image_url"] as? String } var biggerProfileImageURL:String? { if let url = user()["profile_image_url"] as? String { - return url.stringByReplacingOccurrencesOfString("_normal", withString: "_bigger") + return url.replacingOccurrences(of: "_normal", with: "_bigger") } return nil } var originalProfileImageURL:String? { if let url = user()["profile_image_url"] as? String { - return url.stringByReplacingOccurrencesOfString("_normal", withString: "") + return url.replacingOccurrences(of: "_normal", with: "") } return nil } @@ -38,9 +38,9 @@ class Tweet { }) } - private func user() -> Dictionary + fileprivate func user() -> Dictionary { return tweetDictionary["user"] as! Dictionary } -} \ No newline at end of file +} diff --git a/SwiftTweetGettr/TweetCell.swift b/SwiftTweetGettr/TweetCell.swift index 1e5ec1a..bd85f66 100644 --- a/SwiftTweetGettr/TweetCell.swift +++ b/SwiftTweetGettr/TweetCell.swift @@ -8,7 +8,7 @@ class TweetCell : UITableViewCell { @IBOutlet weak var handleTextField: UILabel! @IBOutlet weak var statusTextField: UILabel! - func applyTweet(tweet:Tweet) -> Void + func applyTweet(_ tweet:Tweet) -> Void { nameTextField.text = tweet.name handleTextField.text = tweet.screenName @@ -16,4 +16,4 @@ class TweetCell : UITableViewCell { userImageView.image = tweet.userImage userImageView.loadURL(tweet.biggerProfileImageURL!) } -} \ No newline at end of file +} diff --git a/SwiftTweetGettr/TweetsTableViewDelegate.swift b/SwiftTweetGettr/TweetsTableViewDelegate.swift index f0b3085..9bbd93e 100644 --- a/SwiftTweetGettr/TweetsTableViewDelegate.swift +++ b/SwiftTweetGettr/TweetsTableViewDelegate.swift @@ -5,14 +5,14 @@ class TweetsTableViewDelegate : NSObject, UITableViewDataSource, UITableViewDele var tweets = Array() - func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tweets.count } - func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithIdentifier("tweetCell", forIndexPath: indexPath) as! TweetCell + let cell = tableView.dequeueReusableCell(withIdentifier: "tweetCell", for: indexPath) as! TweetCell let tweet = tweets[indexPath.row] cell.applyTweet(tweet) @@ -20,4 +20,4 @@ class TweetsTableViewDelegate : NSObject, UITableViewDataSource, UITableViewDele return cell } -} \ No newline at end of file +} diff --git a/SwiftTweetGettr/TwitterAuthorization.swift b/SwiftTweetGettr/TwitterAuthorization.swift index 78ed9c2..2f50b83 100644 --- a/SwiftTweetGettr/TwitterAuthorization.swift +++ b/SwiftTweetGettr/TwitterAuthorization.swift @@ -7,22 +7,22 @@ class TwitterAuthorization { class func token() -> String? { - return NSUserDefaults.standardUserDefaults().stringForKey(kAuthorizationTokenStorageKey) + return UserDefaults.standard.string(forKey: kAuthorizationTokenStorageKey) } - class func setToken(token:String?) -> Void + class func setToken(_ token:String?) -> Void { - if let actuallyToken = token { - NSUserDefaults.standardUserDefaults().setObject(token, forKey: kAuthorizationTokenStorageKey) + if token != nil { + UserDefaults.standard.set(token, forKey: kAuthorizationTokenStorageKey) } else { - NSUserDefaults.standardUserDefaults().removeObjectForKey(kAuthorizationTokenStorageKey) + UserDefaults.standard.removeObject(forKey: kAuthorizationTokenStorageKey) } - NSUserDefaults.standardUserDefaults().synchronize() + UserDefaults.standard.synchronize() } class func hasToken() -> Bool { - if let actuallyToken = token() { + if token() != nil { return true } return false diff --git a/SwiftTweetGettr/TwitterClient.swift b/SwiftTweetGettr/TwitterClient.swift index 5d30e08..1c5fd51 100644 --- a/SwiftTweetGettr/TwitterClient.swift +++ b/SwiftTweetGettr/TwitterClient.swift @@ -14,55 +14,67 @@ private let kAuthorizationContentType = "application/x-www-form-urlencoded;chars class TwitterClient { - class func fetchAuthorizationToken(#success:() -> Void, failure:(String) -> Void) + class func fetchAuthorizationToken(success:@escaping () -> Void, _ failure:@escaping (String) -> Void) { - var tokenRequest = NSMutableURLRequest.postRequestWithURL(kOAuthRootURL.createURL(), body: kAuthorizationBody) + var tokenRequest = URLRequest(url: kOAuthRootURL.createURL()) + tokenRequest.httpBody = kAuthorizationBody.data() tokenRequest.addValue(kAuthorizationContentType, forHTTPHeaderField: kContentTypeHeader) tokenRequest.addValue(headerForAuthorization(), forHTTPHeaderField: kAuthorizationHeaderKey) - - NSURLConnection.sendAsynchronousRequest(tokenRequest, queue: NSOperationQueue.mainQueue(), completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in - if response.isHTTPResponseValid() { - TwitterAuthorization.setToken(data.json()["access_token"] as? String) + tokenRequest.httpMethod = "POST" + + print("fetchAuthorizationToken") + let task = URLSession.shared.dataTask(with: tokenRequest) { (data: Data?, response: URLResponse?, error: Error?) -> Void in + let validResponse = response?.isHTTPResponseValid() ?? false + print(" valid responnse: \(validResponse)") + if validResponse { + TwitterAuthorization.setToken(data?.json()["access_token"] as? String) if TwitterAuthorization.hasToken() { success() } else { failure("response has no access_token") } } else { + print(error?.localizedDescription ?? "No info on error") self.handleFailure(failure, error: error, response: response) } - }) + } + task.resume() } - class func fetchTweetsForUser(userName:String, success:(Array) -> Void, failure:(String) -> Void) + class func fetchTweetsForUser(_ userName:String, success:@escaping (Array) -> Void, failure:@escaping (String) -> Void) { - var tweetRequest = NSMutableURLRequest.getRequestWithURL((kTimelineRootURL + userName.stringByRemovingWhitespace()).createURL()) - tweetRequest.addValue(headerWithAuthorization(), forHTTPHeaderField: kAuthorizationHeaderKey) - - NSURLConnection.sendAsynchronousRequest(tweetRequest, queue: NSOperationQueue.mainQueue(), completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in - if response.isHTTPResponseValid() { - if let results:Array> = data.json() as? Array { + let userURL = kTimelineRootURL + userName.stringByRemovingWhitespace() + let tweetRequest = URLRequest(url: userURL.createURL()) + + let task = URLSession.shared.dataTask(with: tweetRequest) { (data: Data?, response: URLResponse?, error: Error?) -> Void in + let validResponse = response?.isHTTPResponseValid() ?? false + if validResponse { + if let results:Array> = data?.json() as? Array { success(Tweet.tweetsFromArray(results)) } } else { self.handleFailure(failure, error: error, response: response) } - }) + } + task.resume() } - class func fetchImageAtURL(url:String, success:(UIImage?) -> Void) -> Void + class func fetchImageAtURL(_ url:String, success:@escaping (UIImage?) -> Void) -> Void { - NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: NSURL(string: url)!), queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in - if response.isHTTPResponseValid() { - success(UIImage(data: data)) + let imageURLRequest = URLRequest(url: URL(string: url)!) + let task = URLSession.shared.dataTask(with: imageURLRequest) { (data, response, error) -> Void in + let validResponse = response?.isHTTPResponseValid() ?? false + if validResponse { + success(UIImage(data: data!)) } } + task.resume() } - private class func handleFailure(failure:(String) -> Void, error:NSError!, response: NSURLResponse!) -> Void + fileprivate class func handleFailure(_ failure:(String) -> Void, error:Error!, response: URLResponse!) -> Void { if let actuallyError = error { - failure(actuallyError.description) + failure(actuallyError.localizedDescription) } else if let actuallyResponse = response { failure(actuallyResponse.description) } else { @@ -70,12 +82,12 @@ class TwitterClient { } } - private class func headerForAuthorization() -> String + fileprivate class func headerForAuthorization() -> String { return "Basic " + (kAPIKey + ":" + kAPISecret).base64Encoded() } - private class func headerWithAuthorization() -> String + fileprivate class func headerWithAuthorization() -> String { return "Bearer " + TwitterAuthorization.token()! } diff --git a/SwiftTweetGettr/ViewController.swift b/SwiftTweetGettr/ViewController.swift index c4fda4c..c8af34d 100644 --- a/SwiftTweetGettr/ViewController.swift +++ b/SwiftTweetGettr/ViewController.swift @@ -3,7 +3,7 @@ import UIKit class ViewController : UIViewController, UITextFieldDelegate { - var spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray) + var spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray) var tweetsTableViewDelegate = TweetsTableViewDelegate() @IBOutlet var textField : UITextField! @@ -15,50 +15,50 @@ class ViewController : UIViewController, UITextFieldDelegate { super.viewDidLoad() textField.rightView = spinner - textField.rightViewMode = .Always + textField.rightViewMode = .always button.layer.cornerRadius = 5 button.layer.borderWidth = 1 - button.layer.borderColor = button.titleLabel?.textColor.CGColor + button.layer.borderColor = button.titleLabel?.textColor.cgColor tableView.dataSource = tweetsTableViewDelegate tableView.delegate = tweetsTableViewDelegate } - override func viewDidAppear(animated: Bool) + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - if let selected = tableView.indexPathForSelectedRow() { - tableView.deselectRowAtIndexPath(tableView.indexPathForSelectedRow()!, animated: animated) + if tableView.indexPathForSelectedRow != nil { + tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: animated) } - if textField.text.isEmpty { + if (textField.text?.isEmpty)! { textField.becomeFirstResponder() } } - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - let index = tableView.indexPathForSelectedRow()?.row + let index = tableView.indexPathForSelectedRow?.row let tweet = tweetsTableViewDelegate.tweets[index!] - (segue.destinationViewController.view as! UITextView).text = tweet.description + (segue.destination.view as! UITextView).text = tweet.description } - func textFieldShouldReturn(textField: UITextField) -> Bool + func textFieldShouldReturn(_ textField: UITextField) -> Bool { - if !textField.text.isEmpty { + if !(textField.text?.isEmpty)! { buttonWasTapped(nil) } return textField.resignFirstResponder() } - @IBAction func textFieldDidChange(sender : AnyObject) + @IBAction func textFieldDidChange(_ sender : AnyObject) { - button.enabled = !textField.text.isEmpty - button.layer.borderColor = button.titleLabel?.textColor.CGColor + button.isEnabled = !(textField.text?.isEmpty)! + button.layer.borderColor = button.titleLabel?.textColor.cgColor } - @IBAction func buttonWasTapped(sender : AnyObject?) + @IBAction func buttonWasTapped(_ sender : AnyObject?) { textField.resignFirstResponder() spinner.startAnimating() @@ -68,7 +68,7 @@ class ViewController : UIViewController, UITextFieldDelegate { } else { TwitterClient.fetchAuthorizationToken(success: { () -> Void in self.fetchTweets() - }, failure: { (message) -> Void in + }, { (message) -> Void in self.showAlertViewWithMessage("Something went wrong getting token. \(message)") self.spinner.stopAnimating() }) @@ -76,9 +76,10 @@ class ViewController : UIViewController, UITextFieldDelegate { } func fetchTweets() { - TwitterClient.fetchTweetsForUser(textField.text.stringByRemovingWhitespace(), success: { (tweets) -> Void in + guard let userFieldText = textField.text else { return } + TwitterClient.fetchTweetsForUser(userFieldText.stringByRemovingWhitespace(), success: { (tweets) -> Void in self.tweetsTableViewDelegate.tweets = tweets - self.tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Fade) + self.tableView.reloadSections(IndexSet([0]), with: UITableViewRowAnimation.fade) self.tableView.scrollToTop(animated: true) self.spinner.stopAnimating() }, failure: { (message) -> Void in From e583756ff2e3ed6805fdd7c32479849b8cd0077b Mon Sep 17 00:00:00 2001 From: Sarah Smith Date: Sat, 16 Sep 2017 15:37:42 +1000 Subject: [PATCH 2/5] Read credentials from a file --- .gitignore | 2 ++ SwiftTweetGettr.xcodeproj/project.pbxproj | 10 +++++++--- SwiftTweetGettr/TwitterClient.swift | 13 +++++++++++-- SwiftTweetGettr/TwitterCredentials.plist | 10 ++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 SwiftTweetGettr/TwitterCredentials.plist diff --git a/.gitignore b/.gitignore index 3c2c9b5..5cd9fa9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ Scripts/venv Carthage/Checkouts Carthage/Build + +SwiftTweetGettr/TwitterCredentials.plist diff --git a/SwiftTweetGettr.xcodeproj/project.pbxproj b/SwiftTweetGettr.xcodeproj/project.pbxproj index 9c4b946..a17398a 100644 --- a/SwiftTweetGettr.xcodeproj/project.pbxproj +++ b/SwiftTweetGettr.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 63FEA3D6193E888700400515 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63FEA3D5193E888700400515 /* ViewController.swift */; }; 63FEA3D9193E888700400515 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63FEA3D7193E888700400515 /* Main.storyboard */; }; 63FEA3DB193E888700400515 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63FEA3DA193E888700400515 /* Images.xcassets */; }; + F6E571DE1F6CE557008DF6C3 /* TwitterCredentials.plist in Resources */ = {isa = PBXBuildFile; fileRef = F6E571DD1F6CE557008DF6C3 /* TwitterCredentials.plist */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -32,6 +33,7 @@ 63FEA3D5193E888700400515 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 63FEA3D8193E888700400515 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63FEA3DA193E888700400515 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + F6E571DD1F6CE557008DF6C3 /* TwitterCredentials.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = TwitterCredentials.plist; path = SwiftTweetGettr/TwitterCredentials.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -48,6 +50,7 @@ 63FEA3C5193E888700400515 = { isa = PBXGroup; children = ( + F6E571DD1F6CE557008DF6C3 /* TwitterCredentials.plist */, 63FEA3D0193E888700400515 /* SwiftTweetGettr */, 63FEA3CF193E888700400515 /* Products */, ); @@ -118,7 +121,7 @@ TargetAttributes = { 63FEA3CD193E888700400515 = { CreatedOnToolsVersion = 6.0; - DevelopmentTeam = PQHPAVF5T9; + DevelopmentTeam = 6KYAUVEW7R; LastSwiftMigration = 0830; }; }; @@ -148,6 +151,7 @@ files = ( 63FEA3D9193E888700400515 /* Main.storyboard in Resources */, 63FEA3DB193E888700400515 /* Images.xcassets in Resources */, + F6E571DE1F6CE557008DF6C3 /* TwitterCredentials.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -273,7 +277,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - DEVELOPMENT_TEAM = PQHPAVF5T9; + DEVELOPMENT_TEAM = 6KYAUVEW7R; INFOPLIST_FILE = SwiftTweetGettr/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -288,7 +292,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - DEVELOPMENT_TEAM = PQHPAVF5T9; + DEVELOPMENT_TEAM = 6KYAUVEW7R; INFOPLIST_FILE = SwiftTweetGettr/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/SwiftTweetGettr/TwitterClient.swift b/SwiftTweetGettr/TwitterClient.swift index 1c5fd51..d4fe7ff 100644 --- a/SwiftTweetGettr/TwitterClient.swift +++ b/SwiftTweetGettr/TwitterClient.swift @@ -16,11 +16,16 @@ class TwitterClient { class func fetchAuthorizationToken(success:@escaping () -> Void, _ failure:@escaping (String) -> Void) { + if headerForAuthorization().isEmpty + { + failure("API Key & Secret not configured!") + return + } var tokenRequest = URLRequest(url: kOAuthRootURL.createURL()) tokenRequest.httpBody = kAuthorizationBody.data() tokenRequest.addValue(kAuthorizationContentType, forHTTPHeaderField: kContentTypeHeader) tokenRequest.addValue(headerForAuthorization(), forHTTPHeaderField: kAuthorizationHeaderKey) - tokenRequest.httpMethod = "POST" + tokenRequest.httpMethod = "POST" print("fetchAuthorizationToken") let task = URLSession.shared.dataTask(with: tokenRequest) { (data: Data?, response: URLResponse?, error: Error?) -> Void in @@ -84,7 +89,11 @@ class TwitterClient { fileprivate class func headerForAuthorization() -> String { - return "Basic " + (kAPIKey + ":" + kAPISecret).base64Encoded() + let twitterCredentials = NSDictionary(contentsOfFile: "TwitterCredentials.plist") + let apiKey = twitterCredentials?["APIKey"] as? String ?? kAPIKey + let apiSecret = twitterCredentials?["APISecret"] as? String ?? kAPISecret + if apiKey.isEmpty || apiSecret.isEmpty { return "" } + return "Basic " + (apiKey + ":" + apiSecret).base64Encoded() } fileprivate class func headerWithAuthorization() -> String diff --git a/SwiftTweetGettr/TwitterCredentials.plist b/SwiftTweetGettr/TwitterCredentials.plist new file mode 100644 index 0000000..334c045 --- /dev/null +++ b/SwiftTweetGettr/TwitterCredentials.plist @@ -0,0 +1,10 @@ + + + + + APISecret + + APIKey + + + From 4dbec3f628d1c3a064e36b27b02a133a32e3cbc0 Mon Sep 17 00:00:00 2001 From: Sarah Smith Date: Sat, 16 Sep 2017 18:31:32 +1000 Subject: [PATCH 3/5] Added back in bearer token --- README.md | 8 ++++++++ SwiftTweetGettr.xcodeproj/project.pbxproj | 8 ++++---- SwiftTweetGettr/TwitterClient.swift | 12 ++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 43b6aa1..3f3baf3 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,11 @@ SwiftTweetGettr New to iOS or Swift? This tiny twitter client project will hopefully get you started building your own apps or seeing common iOS patterns in Swift. You'll need to replace the API Key and API Secret in the Client class with those of your own. You can get them by getting a Twitter developer account and creating an app. Just go here: https://apps.twitter.com/app/new + +# Updated + +Updated for Swift 3.1 / Xcode 8.3.3+ by @sarah_j_smith + +You can put the credentials into the PLIST file `SwiftTweetGettr/TwitterCredentials.plist` and they'll be read from there (so you don't need to embed them in the code if you don't want to). Handy if you're going to be displaying the code in a group setting. :-) + +Handy git tip: `git update-index --assume-unchanged SwiftTweetGetter/TwitterCredentials.plist` will ignore your changes so you don't push secrets to your repo, if you wind up forking this project. diff --git a/SwiftTweetGettr.xcodeproj/project.pbxproj b/SwiftTweetGettr.xcodeproj/project.pbxproj index a17398a..a023dea 100644 --- a/SwiftTweetGettr.xcodeproj/project.pbxproj +++ b/SwiftTweetGettr.xcodeproj/project.pbxproj @@ -17,7 +17,7 @@ 63FEA3D6193E888700400515 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63FEA3D5193E888700400515 /* ViewController.swift */; }; 63FEA3D9193E888700400515 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63FEA3D7193E888700400515 /* Main.storyboard */; }; 63FEA3DB193E888700400515 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63FEA3DA193E888700400515 /* Images.xcassets */; }; - F6E571DE1F6CE557008DF6C3 /* TwitterCredentials.plist in Resources */ = {isa = PBXBuildFile; fileRef = F6E571DD1F6CE557008DF6C3 /* TwitterCredentials.plist */; }; + F6E571E01F6CF253008DF6C3 /* TwitterCredentials.plist in Resources */ = {isa = PBXBuildFile; fileRef = F6E571DF1F6CF253008DF6C3 /* TwitterCredentials.plist */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -33,7 +33,7 @@ 63FEA3D5193E888700400515 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 63FEA3D8193E888700400515 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63FEA3DA193E888700400515 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - F6E571DD1F6CE557008DF6C3 /* TwitterCredentials.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = TwitterCredentials.plist; path = SwiftTweetGettr/TwitterCredentials.plist; sourceTree = ""; }; + F6E571DF1F6CF253008DF6C3 /* TwitterCredentials.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TwitterCredentials.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -50,7 +50,6 @@ 63FEA3C5193E888700400515 = { isa = PBXGroup; children = ( - F6E571DD1F6CE557008DF6C3 /* TwitterCredentials.plist */, 63FEA3D0193E888700400515 /* SwiftTweetGettr */, 63FEA3CF193E888700400515 /* Products */, ); @@ -85,6 +84,7 @@ 63FEA3D1193E888700400515 /* Supporting Files */ = { isa = PBXGroup; children = ( + F6E571DF1F6CF253008DF6C3 /* TwitterCredentials.plist */, 63FEA3D2193E888700400515 /* Info.plist */, ); name = "Supporting Files"; @@ -151,7 +151,7 @@ files = ( 63FEA3D9193E888700400515 /* Main.storyboard in Resources */, 63FEA3DB193E888700400515 /* Images.xcassets in Resources */, - F6E571DE1F6CE557008DF6C3 /* TwitterCredentials.plist in Resources */, + F6E571E01F6CF253008DF6C3 /* TwitterCredentials.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftTweetGettr/TwitterClient.swift b/SwiftTweetGettr/TwitterClient.swift index d4fe7ff..b028634 100644 --- a/SwiftTweetGettr/TwitterClient.swift +++ b/SwiftTweetGettr/TwitterClient.swift @@ -27,10 +27,8 @@ class TwitterClient { tokenRequest.addValue(headerForAuthorization(), forHTTPHeaderField: kAuthorizationHeaderKey) tokenRequest.httpMethod = "POST" - print("fetchAuthorizationToken") let task = URLSession.shared.dataTask(with: tokenRequest) { (data: Data?, response: URLResponse?, error: Error?) -> Void in let validResponse = response?.isHTTPResponseValid() ?? false - print(" valid responnse: \(validResponse)") if validResponse { TwitterAuthorization.setToken(data?.json()["access_token"] as? String) if TwitterAuthorization.hasToken() { @@ -39,7 +37,6 @@ class TwitterClient { failure("response has no access_token") } } else { - print(error?.localizedDescription ?? "No info on error") self.handleFailure(failure, error: error, response: response) } } @@ -49,7 +46,8 @@ class TwitterClient { class func fetchTweetsForUser(_ userName:String, success:@escaping (Array) -> Void, failure:@escaping (String) -> Void) { let userURL = kTimelineRootURL + userName.stringByRemovingWhitespace() - let tweetRequest = URLRequest(url: userURL.createURL()) + var tweetRequest = URLRequest(url: userURL.createURL()) + tweetRequest.addValue(headerWithAuthorization(), forHTTPHeaderField: kAuthorizationHeaderKey) let task = URLSession.shared.dataTask(with: tweetRequest) { (data: Data?, response: URLResponse?, error: Error?) -> Void in let validResponse = response?.isHTTPResponseValid() ?? false @@ -66,7 +64,8 @@ class TwitterClient { class func fetchImageAtURL(_ url:String, success:@escaping (UIImage?) -> Void) -> Void { - let imageURLRequest = URLRequest(url: URL(string: url)!) + let secureURL = url.replacingOccurrences(of: "http://", with: "https://") + let imageURLRequest = URLRequest(url: URL(string: secureURL)!) let task = URLSession.shared.dataTask(with: imageURLRequest) { (data, response, error) -> Void in let validResponse = response?.isHTTPResponseValid() ?? false if validResponse { @@ -89,7 +88,8 @@ class TwitterClient { fileprivate class func headerForAuthorization() -> String { - let twitterCredentials = NSDictionary(contentsOfFile: "TwitterCredentials.plist") + let credentialsPath = Bundle.main.path(forResource: "TwitterCredentials", ofType: "plist") + let twitterCredentials = NSDictionary(contentsOfFile: credentialsPath!) let apiKey = twitterCredentials?["APIKey"] as? String ?? kAPIKey let apiSecret = twitterCredentials?["APISecret"] as? String ?? kAPISecret if apiKey.isEmpty || apiSecret.isEmpty { return "" } From 27e37b63f4fdf1df959d9f8c493137fa1d3aa48a Mon Sep 17 00:00:00 2001 From: Sarah Smith Date: Sat, 16 Sep 2017 18:33:41 +1000 Subject: [PATCH 4/5] Flagrant self promo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f3baf3..2ebacb5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ You'll need to replace the API Key and API Secret in the Client class with those # Updated -Updated for Swift 3.1 / Xcode 8.3.3+ by @sarah_j_smith +Updated for Swift 3.1 / Xcode 8.3.3+ by [@sarah_j_smith](https://github.com/sarah-j-smith) You can put the credentials into the PLIST file `SwiftTweetGettr/TwitterCredentials.plist` and they'll be read from there (so you don't need to embed them in the code if you don't want to). Handy if you're going to be displaying the code in a group setting. :-) From 3335bdb770b93f0993404188f3198918cc846d2f Mon Sep 17 00:00:00 2001 From: Sarah Smith Date: Sun, 17 Sep 2017 17:19:19 +1000 Subject: [PATCH 5/5] Fix auto-layout, images bug, other issues. The table wasn't using auto-layout so you could not scroll to the lower rows of the table - it was off the screen. Now fixed, should also mean it works better on different sized screens. Fix bug introduced with Swift 3.1 upgrade where the URLDataSessionTask in the UIImage extension was not resumed after creation and thus did nothing. Fix code that altered the UI from the background thread - this will crash the app and make it generally unstable - but does not work from the autolayout engine. --- SwiftTweetGettr/Base.lproj/Main.storyboard | 65 ++++++++++++------- SwiftTweetGettr/Extensions.swift | 32 +++------ .../AppIcon.appiconset/Contents.json | 20 ++++++ SwiftTweetGettr/TwitterClient.swift | 4 +- SwiftTweetGettr/ViewController.swift | 24 ++++--- 5 files changed, 86 insertions(+), 59 deletions(-) diff --git a/SwiftTweetGettr/Base.lproj/Main.storyboard b/SwiftTweetGettr/Base.lproj/Main.storyboard index f7aacb2..f8fec72 100644 --- a/SwiftTweetGettr/Base.lproj/Main.storyboard +++ b/SwiftTweetGettr/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -13,13 +13,16 @@ + + + + - - - + + @@ -27,38 +30,49 @@ - - - + + + - + - + - - + + + + + + + + + + + diff --git a/SwiftTweetGettr/Extensions.swift b/SwiftTweetGettr/Extensions.swift index a306be1..ec8f312 100644 --- a/SwiftTweetGettr/Extensions.swift +++ b/SwiftTweetGettr/Extensions.swift @@ -54,7 +54,8 @@ extension String { func createURL() -> URL { - return URL(string: self)! + let secureURL = self.replacingOccurrences(of: "http://", with: "https://") + return URL(string: secureURL)! } func stringByRemovingWhitespace() -> String @@ -64,36 +65,21 @@ extension String { } } -extension NSMutableURLRequest { - - class func getRequestWithURL(_ url:URL) -> NSMutableURLRequest - { - let request = NSMutableURLRequest(url: url) - request.httpMethod = "GET" - return request - } - - class func postRequestWithURL(_ url:URL, body:String) -> NSMutableURLRequest - { - let request = NSMutableURLRequest(url: url) - request.httpMethod = "POST" - request.httpBody = body.data() - return request - } -} - extension UIImageView { func loadURL(_ url:String) { - let request = URLRequest(url: URL(string: url)!) - URLSession.shared.dataTask(with: request) { (data, response, error) -> Void in + let request = URLRequest(url: url.createURL()) + let task = URLSession.shared.dataTask(with: request) { (data, response, error) -> Void in let validResponse = response?.isHTTPResponseValid() ?? false if validResponse { - if let image = UIImage(data: data!) { - self.image = image + DispatchQueue.main.async { [unowned self] in + if let image = UIImage(data: data!) { + self.image = image + } } } } + task.resume() } } diff --git a/SwiftTweetGettr/Images.xcassets/AppIcon.appiconset/Contents.json b/SwiftTweetGettr/Images.xcassets/AppIcon.appiconset/Contents.json index 33ec0bc..b8236c6 100644 --- a/SwiftTweetGettr/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/SwiftTweetGettr/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,15 +1,35 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "60x60", diff --git a/SwiftTweetGettr/TwitterClient.swift b/SwiftTweetGettr/TwitterClient.swift index b028634..eafe0ed 100644 --- a/SwiftTweetGettr/TwitterClient.swift +++ b/SwiftTweetGettr/TwitterClient.swift @@ -47,6 +47,7 @@ class TwitterClient { { let userURL = kTimelineRootURL + userName.stringByRemovingWhitespace() var tweetRequest = URLRequest(url: userURL.createURL()) + tweetRequest.httpMethod = "GET" tweetRequest.addValue(headerWithAuthorization(), forHTTPHeaderField: kAuthorizationHeaderKey) let task = URLSession.shared.dataTask(with: tweetRequest) { (data: Data?, response: URLResponse?, error: Error?) -> Void in @@ -64,8 +65,7 @@ class TwitterClient { class func fetchImageAtURL(_ url:String, success:@escaping (UIImage?) -> Void) -> Void { - let secureURL = url.replacingOccurrences(of: "http://", with: "https://") - let imageURLRequest = URLRequest(url: URL(string: secureURL)!) + let imageURLRequest = URLRequest(url: url.createURL()) let task = URLSession.shared.dataTask(with: imageURLRequest) { (data, response, error) -> Void in let validResponse = response?.isHTTPResponseValid() ?? false if validResponse { diff --git a/SwiftTweetGettr/ViewController.swift b/SwiftTweetGettr/ViewController.swift index c8af34d..1ead4c3 100644 --- a/SwiftTweetGettr/ViewController.swift +++ b/SwiftTweetGettr/ViewController.swift @@ -68,25 +68,31 @@ class ViewController : UIViewController, UITextFieldDelegate { } else { TwitterClient.fetchAuthorizationToken(success: { () -> Void in self.fetchTweets() - }, { (message) -> Void in + }, { [unowned self](message) -> Void in + DispatchQueue.main.async { self.showAlertViewWithMessage("Something went wrong getting token. \(message)") self.spinner.stopAnimating() + } }) } } func fetchTweets() { guard let userFieldText = textField.text else { return } - TwitterClient.fetchTweetsForUser(userFieldText.stringByRemovingWhitespace(), success: { (tweets) -> Void in - self.tweetsTableViewDelegate.tweets = tweets - self.tableView.reloadSections(IndexSet([0]), with: UITableViewRowAnimation.fade) - self.tableView.scrollToTop(animated: true) - self.spinner.stopAnimating() - }, failure: { (message) -> Void in - self.showAlertViewWithMessage("Something went wrong getting tweets. \(message)") + TwitterClient.fetchTweetsForUser(userFieldText.stringByRemovingWhitespace(), success: { [unowned self](tweets) -> Void in + DispatchQueue.main.async { + self.tweetsTableViewDelegate.tweets = tweets + self.tableView.reloadSections(IndexSet([0]), with: UITableViewRowAnimation.fade) + self.tableView.scrollToTop(animated: true) self.spinner.stopAnimating() + } + }, failure: { [unowned self](message) -> Void in + DispatchQueue.main.async { + self.showAlertViewWithMessage("Something went wrong getting tweets. \(message)") + self.spinner.stopAnimating() + } }) } - + }