From d5f16190f74ec0de8bd201913e3d47594140539b Mon Sep 17 00:00:00 2001 From: Francisco Bernal Yescas Date: Thu, 14 Sep 2017 15:19:24 -0700 Subject: [PATCH] Swift 3 --- .gitignore | 4 +- .../project.pbxproj | 14 +- .../AncestorDetails.swift | 248 ++++----- FamilySearchIosSampleApp/AppDelegate.swift | 63 ++- .../Base.lproj/Main.storyboard | 110 ++-- FamilySearchIosSampleApp/Extensions.swift | 22 +- FamilySearchIosSampleApp/LoginVC.swift | 471 ++++++++---------- FamilySearchIosSampleApp/MemoriesVC.swift | 240 ++++----- FamilySearchIosSampleApp/TreeTVC.swift | 456 ++++++++--------- FamilySearchIosSampleApp/Utilities.swift | 199 ++++---- 10 files changed, 825 insertions(+), 1002 deletions(-) diff --git a/.gitignore b/.gitignore index 50eccc9..9086150 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .git .DS_Store -FamilySearchIosSampleApp/AppKeys.swift \ No newline at end of file +FamilySearchIosSampleApp/AppKeys.swift +FamilySearchIosSampleApp.xcodeproj/project.xcworkspace/xcuserdata +FamilySearchIosSampleApp.xcodeproj/xcuserdata diff --git a/FamilySearchIosSampleApp.xcodeproj/project.pbxproj b/FamilySearchIosSampleApp.xcodeproj/project.pbxproj index 62bae72..94e7d61 100644 --- a/FamilySearchIosSampleApp.xcodeproj/project.pbxproj +++ b/FamilySearchIosSampleApp.xcodeproj/project.pbxproj @@ -7,9 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 0EE073E01F69D521006CD2D6 /* AppKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE073DF1F69D521006CD2D6 /* AppKeys.swift */; }; 5E7D1C301D4BFD5E00874503 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7D1C2E1D4BFB7D00874503 /* Extensions.swift */; }; A00150AF1D08FF76008E53E8 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00150AE1D08FF76008E53E8 /* User.swift */; }; - A00BB4AB1D06628D009ADBDB /* AppKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00BB4AA1D06628D009ADBDB /* AppKeys.swift */; }; A00BB4B01D06741E009ADBDB /* TreeTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00BB4AF1D06741E009ADBDB /* TreeTVC.swift */; }; A00FDD3D1D02755700C7384B /* Links.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00FDD3C1D02755700C7384B /* Links.swift */; }; A0151B321D10FBA6008DADD8 /* MemoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0151B311D10FBA6008DADD8 /* MemoryCell.swift */; }; @@ -29,9 +29,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 0EE073DF1F69D521006CD2D6 /* AppKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppKeys.swift; sourceTree = ""; }; 5E7D1C2E1D4BFB7D00874503 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; A00150AE1D08FF76008E53E8 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; - A00BB4AA1D06628D009ADBDB /* AppKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppKeys.swift; sourceTree = ""; }; A00BB4AF1D06741E009ADBDB /* TreeTVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeTVC.swift; sourceTree = ""; }; A00FDD3C1D02755700C7384B /* Links.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Links.swift; sourceTree = ""; }; A0151B311D10FBA6008DADD8 /* MemoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryCell.swift; sourceTree = ""; }; @@ -103,7 +103,6 @@ A01D9FD51D023344005473BD /* Main.storyboard */, A01D9FDA1D023344005473BD /* LaunchScreen.storyboard */, A01D9FDD1D023344005473BD /* Info.plist */, - A00BB4AA1D06628D009ADBDB /* AppKeys.swift */, A05168E31D09EDDA005E7EE9 /* Localizable.strings */, ); path = FamilySearchIosSampleApp; @@ -134,6 +133,7 @@ children = ( A0D6047E1D03D99A00F2A83D /* Utilities.swift */, A03BAE501D106A91002572BA /* WaitingView.swift */, + 0EE073DF1F69D521006CD2D6 /* AppKeys.swift */, ); name = Utils; sourceTree = ""; @@ -170,6 +170,8 @@ TargetAttributes = { A01D9FCD1D023344005473BD = { CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = E75E2R8ZH4; + LastSwiftMigration = 0830; }; }; }; @@ -213,10 +215,10 @@ A01D9FD41D023344005473BD /* LoginVC.swift in Sources */, A01B4FBC1D0CFB3600E90D6D /* MemoriesVC.swift in Sources */, A0151B321D10FBA6008DADD8 /* MemoryCell.swift in Sources */, + 0EE073E01F69D521006CD2D6 /* AppKeys.swift in Sources */, A00FDD3D1D02755700C7384B /* Links.swift in Sources */, A0D6047F1D03D99A00F2A83D /* Utilities.swift in Sources */, A0296F111D0A688A00FAE4E0 /* PersonCell.swift in Sources */, - A00BB4AB1D06628D009ADBDB /* AppKeys.swift in Sources */, A00BB4B01D06741E009ADBDB /* TreeTVC.swift in Sources */, A01D9FD21D023344005473BD /* AppDelegate.swift in Sources */, A03BAE511D106A91002572BA /* WaitingView.swift in Sources */, @@ -339,11 +341,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = E75E2R8ZH4; INFOPLIST_FILE = FamilySearchIosSampleApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.FamilySearch.IosSampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -351,11 +355,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = E75E2R8ZH4; INFOPLIST_FILE = FamilySearchIosSampleApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.FamilySearch.IosSampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/FamilySearchIosSampleApp/AncestorDetails.swift b/FamilySearchIosSampleApp/AncestorDetails.swift index 9c6918e..f560f5e 100644 --- a/FamilySearchIosSampleApp/AncestorDetails.swift +++ b/FamilySearchIosSampleApp/AncestorDetails.swift @@ -11,156 +11,112 @@ import UIKit class AncestorDetails : UIViewController { - @IBOutlet weak var ancestorImageView: UIImageView! - @IBOutlet weak var ancestorNameLabel: UILabel! - @IBOutlet weak var ancestorBirthLabelTitle: UILabel! - @IBOutlet weak var ancestorBirthLabelValue: UILabel! - @IBOutlet weak var ancestorDeathLabelTitle: UILabel! - @IBOutlet weak var ancestorDeathLabelValue: UILabel! - @IBOutlet weak var ancestorImageIndicator: UIActivityIndicatorView! - @IBOutlet weak var ancestorDataIndicator: UIActivityIndicatorView! + @IBOutlet weak var ancestorImageView: UIImageView! + @IBOutlet weak var ancestorNameLabel: UILabel! + @IBOutlet weak var ancestorBirthLabelTitle: UILabel! + @IBOutlet weak var ancestorBirthLabelValue: UILabel! + @IBOutlet weak var ancestorDeathLabelTitle: UILabel! + @IBOutlet weak var ancestorDeathLabelValue: UILabel! + @IBOutlet weak var ancestorImageIndicator: UIActivityIndicatorView! + @IBOutlet weak var ancestorDataIndicator: UIActivityIndicatorView! + + var person : Person? + + override func viewDidLoad() { + self.navigationItem.title = NSLocalizedString("ancestorDetailsTitle", comment: "Word Ancestor") - var person : Person? + // hide data labels until the data gets downloaded + self.ancestorBirthLabelTitle.isHidden = true + self.ancestorBirthLabelValue.isHidden = true + self.ancestorDeathLabelTitle.isHidden = true + self.ancestorDeathLabelValue.isHidden = true - override func viewDidLoad() { - self.navigationItem.title = NSLocalizedString("ancestorDetailsTitle", comment: "Word Ancestor") - - // hide data labels until the data gets downloaded - self.ancestorBirthLabelTitle.hidden = true - self.ancestorBirthLabelValue.hidden = true - self.ancestorDeathLabelTitle.hidden = true - self.ancestorDeathLabelValue.hidden = true - - // get the access token from NSUserDefaults - let preferences = NSUserDefaults.standardUserDefaults() - let accessToken = preferences.stringForKey(Utilities.KEY_ACCESS_TOKEN) - - Utilities.getImageFromUrl(person!.personLinkHref!, accessToken: accessToken!) { (data, response, error) in - dispatch_async(dispatch_get_main_queue()) { [weak self] () -> Void in - self?.ancestorImageView.image = UIImage(data: data!) - self?.ancestorImageIndicator.hidesWhenStopped = true - self?.ancestorImageIndicator.stopAnimating() - } - } - - // set labels - self.ancestorNameLabel.text = person?.displayName - - // download data from Person.personLinkHref - getAncestorDetailsData(person?.personLinkHref, - accessToken: accessToken!, - completionAncestorDetails: { [weak self] (personDetails, errorResponse) -> Void in - if (errorResponse == nil) - { - dispatch_async(dispatch_get_main_queue(),{ - self?.ancestorImageIndicator.hidesWhenStopped = true - self?.ancestorDataIndicator.stopAnimating() - self?.ancestorDataIndicator.hidden = true - - // birth data - self?.ancestorBirthLabelTitle.text = NSLocalizedString("ancestorDetailsBirth", comment: "Birth:") - self?.ancestorBirthLabelTitle.hidden = false - self?.ancestorBirthLabelValue.text = personDetails?.personBirthDate - self?.ancestorBirthLabelValue.hidden = false - - // death data - if (personDetails?.personDeathDate != nil) - { - self?.ancestorDeathLabelTitle.text = NSLocalizedString("ancestorDetailsDeath", comment: "Death:") - self?.ancestorDeathLabelTitle.hidden = false - self?.ancestorDeathLabelValue.text = personDetails?.personDeathDate - self?.ancestorDeathLabelValue.hidden = false - } - }) - } - }) + // get the access token from UserDefaults + let preferences = UserDefaults.standard + let accessToken = preferences.string(forKey: Utilities.KEY_ACCESS_TOKEN) + + Utilities.getImageFromUrl(person!.personLinkHref!, accessToken: accessToken!) { (data, response, error) in + DispatchQueue.main.async { [weak self] () -> Void in + self?.ancestorImageView.image = UIImage(data: data!) + self?.ancestorImageIndicator.hidesWhenStopped = true + self?.ancestorImageIndicator.stopAnimating() + } } - func getAncestorDetailsData(personUrlString:String?, - accessToken:String, - completionAncestorDetails:(responseDetails: PersonDetails?, reponseError:NSError?) -> ()) - { - - guard let personUrlString = personUrlString, ancestorDetailsUrl = NSURL(string: personUrlString) else { - return + // set labels + self.ancestorNameLabel.text = person?.displayName + + // download data from Person.personLinkHref + getAncestorDetailsData(person?.personLinkHref, + accessToken: accessToken!, + completionAncestorDetails: { [weak self] (personDetails, errorResponse) -> Void in + if (errorResponse == nil) + { + DispatchQueue.main.async(execute: { + self?.ancestorImageIndicator.hidesWhenStopped = true + self?.ancestorDataIndicator.stopAnimating() + self?.ancestorDataIndicator.isHidden = true + + // birth data + self?.ancestorBirthLabelTitle.text = NSLocalizedString("ancestorDetailsBirth", comment: "Birth:") + self?.ancestorBirthLabelTitle.isHidden = false + self?.ancestorBirthLabelValue.text = personDetails?.personBirthDate + self?.ancestorBirthLabelValue.isHidden = false + + // death data + if (personDetails?.personDeathDate != nil) + { + self?.ancestorDeathLabelTitle.text = NSLocalizedString("ancestorDetailsDeath", comment: "Death:") + self?.ancestorDeathLabelTitle.isHidden = false + self?.ancestorDeathLabelValue.text = personDetails?.personDeathDate + self?.ancestorDeathLabelValue.isHidden = false + } + }) + } + }) + } + + func getAncestorDetailsData(_ personUrlString:String?, + accessToken:String, + completionAncestorDetails:@escaping (_ responseDetails: PersonDetails?, _ reponseError: Error?) -> ()) + { + + guard let personUrlString = personUrlString, let ancestorDetailsUrl = URL(string: personUrlString) else { + return + } + + let configuration = URLSessionConfiguration.default + let headers: [AnyHashable: Any] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken] + configuration.httpAdditionalHeaders = headers + let session = URLSession(configuration: configuration) + let ancestorDetailDataTask = session.dataTask(with: ancestorDetailsUrl, completionHandler: { (ancestorData, ancestorResponse, ancestorError) in + if (ancestorError == nil) + { + do + { + guard let ancestryDataJson = try JSONSerialization.jsonObject(with: ancestorData!, options: .allowFragments) as? [String : Any], + let persons = ancestryDataJson["persons"] as? [[String:AnyObject]], + let person = persons.first, + let display = person["display"] as? [String: Any], + let birthDate = display["birthDate"] as? String, + let deathDate = display["deathDate"] as? String else { + return + } + print("ancestryDataJson = \(ancestryDataJson)") + + let personDetails = PersonDetails() + personDetails.personBirthDate = birthDate + personDetails.personDeathDate = deathDate + + completionAncestorDetails(personDetails, nil) } - - let configuration = NSURLSessionConfiguration.defaultSessionConfiguration(); - let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken]; - configuration.HTTPAdditionalHeaders = headers; - let session = NSURLSession(configuration: configuration) - let ancestorDetailDataTask = session.dataTaskWithURL(ancestorDetailsUrl) { (ancestorData, ancestorResponse, ancestorError) in - if (ancestorError == nil) - { - do - { - let ancestryDataJson = try NSJSONSerialization.JSONObjectWithData(ancestorData!, options: .AllowFragments); - // print("ancestryDataJson = \(ancestryDataJson)") - - let persons = ancestryDataJson["persons"] as? [[String:AnyObject]] - let person = persons!.first - - let display = person!["display"] as? NSDictionary - - let birthDate = display!["birthDate"] as? String - let deathDate = display!["deathDate"] as? String - - let personDetails = PersonDetails() - personDetails.personBirthDate = birthDate - personDetails.personDeathDate = deathDate - - completionAncestorDetails(responseDetails: personDetails, reponseError: nil) - } - catch - { - completionAncestorDetails(responseDetails: nil, reponseError: ancestorError) - } - } + catch + { + completionAncestorDetails(nil, ancestorError) } - - ancestorDetailDataTask.resume() - } + } + }) + + ancestorDetailDataTask.resume() + } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FamilySearchIosSampleApp/AppDelegate.swift b/FamilySearchIosSampleApp/AppDelegate.swift index 08b3640..2517eea 100644 --- a/FamilySearchIosSampleApp/AppDelegate.swift +++ b/FamilySearchIosSampleApp/AppDelegate.swift @@ -10,37 +10,34 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - - - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func applicationWillResignActive(application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + } - diff --git a/FamilySearchIosSampleApp/Base.lproj/Main.storyboard b/FamilySearchIosSampleApp/Base.lproj/Main.storyboard index 0be3a87..9919e24 100644 --- a/FamilySearchIosSampleApp/Base.lproj/Main.storyboard +++ b/FamilySearchIosSampleApp/Base.lproj/Main.storyboard @@ -1,8 +1,12 @@ - - + + + + + - + + @@ -14,37 +18,37 @@ - + - + - + - + - + - + @@ -66,17 +70,17 @@ - - + + - + @@ -102,22 +106,22 @@ - + - + - + - + - + @@ -127,15 +131,15 @@ @@ -170,16 +174,16 @@ - + - + - + @@ -188,7 +192,7 @@ - + @@ -196,9 +200,9 @@ + - @@ -214,7 +218,7 @@ - + @@ -225,71 +229,71 @@ - + - + - - + + - - + + - + @@ -322,7 +326,7 @@ - + @@ -331,7 +335,7 @@ - + @@ -340,7 +344,7 @@ - + @@ -358,13 +362,13 @@ - + - - + + diff --git a/FamilySearchIosSampleApp/Extensions.swift b/FamilySearchIosSampleApp/Extensions.swift index a9d278b..4aa1bb3 100644 --- a/FamilySearchIosSampleApp/Extensions.swift +++ b/FamilySearchIosSampleApp/Extensions.swift @@ -8,14 +8,16 @@ import UIKit -extension UIViewController { - func showAlert(title: String, description: String) { - let alertController = UIAlertController(title: title, - message: description, - preferredStyle: .Alert) - - let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil) - alertController.addAction(defaultAction) - self.presentViewController(alertController, animated: true, completion: nil) - } +extension UIViewController { + + func showAlert(_ title: String, description: String) { + let alertController = UIAlertController(title: title, + message: description, + preferredStyle: .alert) + + let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil) + alertController.addAction(defaultAction) + self.present(alertController, animated: true, completion: nil) + } + } diff --git a/FamilySearchIosSampleApp/LoginVC.swift b/FamilySearchIosSampleApp/LoginVC.swift index 2ac38be..b8e8475 100644 --- a/FamilySearchIosSampleApp/LoginVC.swift +++ b/FamilySearchIosSampleApp/LoginVC.swift @@ -9,284 +9,227 @@ import UIKit class LoginVC: UIViewController { + + @IBOutlet weak var usernameTextField: UITextField! + @IBOutlet weak var passwordTextField: UITextField! + @IBOutlet weak var loginButtonOutlet: UIButton! + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! + @IBOutlet weak var dataUsageLabel: UILabel! + + override func viewDidLoad() { + super.viewDidLoad() - @IBOutlet weak var usernameTextField: UITextField! - @IBOutlet weak var passwordTextField: UITextField! - @IBOutlet weak var loginButtonOutlet: UIButton! - @IBOutlet weak var activityIndicator: UIActivityIndicatorView! - @IBOutlet weak var dataUsageLabel: UILabel! + unlockScreen() - override func viewDidLoad() { - super.viewDidLoad() - + usernameTextField.placeholder = NSLocalizedString("usernamePlaceholderText", comment: "username, in email form") + passwordTextField.placeholder = NSLocalizedString("passwordPlaceholderText", comment: "password") + loginButtonOutlet.setTitle(NSLocalizedString("loginText", comment: "text for login button"), for: UIControlState.normal) + dataUsageLabel.text = NSLocalizedString("loginDataUsage", comment: "description of data usage") + } + + @IBAction func loginAction(_ sender: AnyObject) + { + lockScreen() + + guard let + username = usernameTextField.text, + let password = passwordTextField.text, !username.isEmpty && !password.isEmpty + else { + self.showAlert("Error", description: "User name or password missing") unlockScreen() - - usernameTextField.placeholder = NSLocalizedString("usernamePlaceholderText", comment: "username, in email form") - passwordTextField.placeholder = NSLocalizedString("passwordPlaceholderText", comment: "password") - loginButtonOutlet.setTitle(NSLocalizedString("loginText", comment: "text for login button"), forState: UIControlState.Normal) - dataUsageLabel.text = NSLocalizedString("loginDataUsage", comment: "description of data usage") - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. + return } - @IBAction func loginAction(sender: AnyObject) - { - lockScreen() - - guard let - username = usernameTextField.text, - password = passwordTextField.text - where !username.isEmpty && !password.isEmpty - else { - self.showAlert("Error", description: "User name or password missing") - unlockScreen() - return - } - - // get initial GET call to collections - Utilities.getUrlsFromCollections({ [weak self] (collectionsResponse, error) -> Void in - - guard error == nil else { - print("Error getting collections data from server. Error = \(error?.description)") - self?.activityIndicator.stopAnimating() - return - } - - // get the login token - self?.getToken(collectionsResponse.tokenUrlString!, - username: username, - password: password, - client_id: AppKeys.API_KEY, - completionToken: {(responseToken, errorToken) -> Void in - guard errorToken == nil else { - dispatch_async(dispatch_get_main_queue(),{ - self!.showAlert("Error", description: errorToken!.localizedDescription) - self!.unlockScreen() + // get initial GET call to collections + Utilities.getUrlsFromCollections({ [weak self] (collectionsResponse, error) -> Void in + + guard error == nil else { + print("Error getting collections data from server. Error = \(error.debugDescription)") + self?.activityIndicator.stopAnimating() + return + } + + // get the login token + self?.getToken(collectionsResponse.tokenUrlString!, + username: username, + password: password, + client_id: AppKeys.API_KEY, + completionToken: {(responseToken, errorToken) -> Void in + guard errorToken == nil else { + DispatchQueue.main.async(execute: { + self!.showAlert("Error", description: errorToken!.localizedDescription) + self!.unlockScreen() }) - + return - } - - // get user data, with the newly acquired token - self?.getCurrentUserData(collectionsResponse.currentUserString!, - accessToken: responseToken!, - completionCurrentUser:{(responseUser, errorUser) -> Void in - guard errorToken == nil else { - dispatch_async(dispatch_get_main_queue(),{ - self!.showAlert("Error", description: errorToken!.localizedDescription) - self!.unlockScreen() - }) - return - } - // all login data needed has been downloaded - // push to the next view controller, in the main thread - dispatch_async(dispatch_get_main_queue(),{ - [weak self] in - self?.performSegueWithIdentifier("segueToTabBar", sender: responseUser) - }) - - }) - - } - ) - }) - } + } + + // get user data, with the newly acquired token + self?.getCurrentUserData(collectionsResponse.currentUserString!, + accessToken: responseToken!, + completionCurrentUser:{(responseUser, errorUser) -> Void in + guard errorToken == nil else { + DispatchQueue.main.async(execute: { + self!.showAlert("Error", description: errorToken!.localizedDescription) + self!.unlockScreen() + }) + return + } + // all login data needed has been downloaded + // push to the next view controller, in the main thread + DispatchQueue.main.async(execute: { + [weak self] in + self?.performSegue(withIdentifier: "segueToTabBar", sender: responseUser) + }) + }) + } + ) + }) + } + + func getToken(_ tokenUrlAsString : String, username : String, password : String, client_id : String, completionToken:@escaping (_ responseToken:String?, _ errorToken:Error?) -> ()) { + let grant_type = "password" - func getToken(tokenUrlAsString : String, username : String, password : String, client_id : String, completionToken:(responseToken:String?, errorToken:NSError?) -> ()) { - let grant_type = "password"; - - let params = "?username=" + username + - "&password=" + password + - "&grant_type=" + grant_type + - "&client_id=" + AppKeys.API_KEY; - - let urlAsString = tokenUrlAsString + params - - // create the post request - let request = NSMutableURLRequest(URL: NSURL(string: urlAsString)!) - request.HTTPMethod = "POST" - - let task = NSURLSession.sharedSession().dataTaskWithRequest(request){ data, response, error in - guard error == nil else { - print("Error downloading token. Error: \(error)") - completionToken(responseToken: nil, errorToken: error) - return - } - do - { - let jsonToken = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments); - - if let error = jsonToken["error"] as? String - { - let description = jsonToken["error_description"] as? String - print("\(error) \(description)") - - let userInfo = [NSLocalizedDescriptionKey : description!] - completionToken(responseToken: nil, errorToken: NSError(domain: "FamilySearch", code: 1, userInfo: userInfo)) - } - - if let token = jsonToken["access_token"] as? String - { - // parse the json to get the access_token, and save this token in NSUserDefaults - let preferences = NSUserDefaults.standardUserDefaults() - preferences.setValue(token, forKey: Utilities.KEY_ACCESS_TOKEN) - preferences.synchronize() - - completionToken(responseToken: token, errorToken: nil) - } - } - catch - { - print("Error parsing token JSON. Error: \(error)"); - } - + let params = "?username=" + username + + "&password=" + password + + "&grant_type=" + grant_type + + "&client_id=" + AppKeys.API_KEY + + let urlAsString = tokenUrlAsString + params + + // create the post request + var request = URLRequest(url: URL(string: urlAsString)!) + request.httpMethod = "POST" + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + guard error == nil else { + print("Error downloading token. Error: \(error.debugDescription)") + completionToken(nil, error) + return + } + do + { + guard let jsonToken = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] else { + return } - - task.resume() + if let error = jsonToken["error"] as? String, + let description = jsonToken["error_description"] as? String { + print("\(error) \(description)") + + let userInfo = [NSLocalizedDescriptionKey : description] + completionToken(nil, NSError(domain: "FamilySearch", code: 1, userInfo: userInfo)) + } + else if let token = jsonToken["access_token"] as? String + { + // parse the json to get the access_token, and save this token in UserDefaults + let preferences = UserDefaults.standard + preferences.setValue(token, forKey: Utilities.KEY_ACCESS_TOKEN) + preferences.synchronize() + + completionToken(token, nil) + } + } + catch + { + print("Error parsing token JSON. Error: \(error)") + } + } - // get the user data - func getCurrentUserData(currentUserUrlString : String, accessToken : String, completionCurrentUser:(responseUser:User?, errorUser:NSError?) -> ()) - { - let currentUserUrl = NSURL(string: currentUserUrlString); + task.resume() + } + + // get the user data + func getCurrentUserData(_ currentUserUrlString : String, accessToken : String, completionCurrentUser:@escaping (_ responseUser:User?, _ errorUser:Error?) -> ()) + { + let currentUserUrl = URL(string: currentUserUrlString) + + let configuration = URLSessionConfiguration.default + let headers: [AnyHashable: Any] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken] + configuration.httpAdditionalHeaders = headers + let session = URLSession(configuration: configuration) + + let currentUserTask = session.dataTask(with: currentUserUrl!, completionHandler: { (data, currentUserResponse, errorUserData) in + // parse the currentUser data + do + { + guard let data = data, + let currentUserJson = try JSONSerialization.jsonObject(with: data, + options: .allowFragments) as? [String : Any], + let usersJsonObject = currentUserJson["users"] as? [[String : AnyObject]], + let userJsonObject = usersJsonObject.first else { + print("The user JSON does not contain any data") + completionCurrentUser(nil, nil) + return + } - let configuration = NSURLSessionConfiguration.defaultSessionConfiguration(); - let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken]; - configuration.HTTPAdditionalHeaders = headers; - let session = NSURLSession(configuration: configuration) + let user = User() + user.id = userJsonObject["id"] as? String + user.contactName = userJsonObject["contactName"] as? String + user.helperAccessPin = userJsonObject["helperAccessPin"] as? String + user.givenName = userJsonObject["givenName"] as? String + user.familyName = userJsonObject["familyName"] as? String + user.email = userJsonObject["email"] as? String + user.country = userJsonObject["country"] as? String + user.gender = userJsonObject["gender"] as? String + user.birthDate = userJsonObject["birthDate"] as? String + user.phoneNumber = userJsonObject["phoneNumber"] as? String + user.mailingAddress = userJsonObject["mailingAddress"] as? String + user.preferredLanguage = userJsonObject["preferredLanguage"] as? String + user.displayName = userJsonObject["displayName"] as? String + user.personId = userJsonObject["personId"] as? String + user.treeUserId = userJsonObject["treeUserId"] as? String - let currentUserTask = session.dataTaskWithURL(currentUserUrl!) { (data, currentUserResponse, errorUserData) in - // parse the currentUser data - do - { - let currentUserJson = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments); - - if let usersJsonObject = currentUserJson["users"] as? [[String : AnyObject]] - { - let user = User() - - if let userJsonObject = usersJsonObject.first - { - user.id = userJsonObject["id"] as? String - user.contactName = userJsonObject["contactName"] as? String - user.helperAccessPin = userJsonObject["helperAccessPin"] as? String - user.givenName = userJsonObject["givenName"] as? String - user.familyName = userJsonObject["familyName"] as? String - user.email = userJsonObject["email"] as? String - user.country = userJsonObject["country"] as? String - user.gender = userJsonObject["gender"] as? String - user.birthDate = userJsonObject["birthDate"] as? String - user.phoneNumber = userJsonObject["phoneNumber"] as? String - user.mailingAddress = userJsonObject["mailingAddress"] as? String - user.preferredLanguage = userJsonObject["preferredLanguage"] as? String - user.displayName = userJsonObject["displayName"] as? String - user.personId = userJsonObject["personId"] as? String - user.treeUserId = userJsonObject["treeUserId"] as? String - - // The Memories activity will need the URL that comes from user.links.artifact.href - // in order to get the memories data - let links = userJsonObject["links"] as? NSDictionary - let artifacts = links!["artifacts"] as? NSDictionary - user.artifactsHref = artifacts!["href"] as? String - - completionCurrentUser(responseUser:user, errorUser:nil) - - return - } - else - { - print("The user JSON does not contain any data") - } - - completionCurrentUser(responseUser:nil, errorUser:nil) - } - - } - catch - { - completionCurrentUser(responseUser:nil, errorUser:errorUserData) - } - } - currentUserTask.resume() + // The Memories activity will need the URL that comes from user.links.artifact.href + // in order to get the memories data + let links = userJsonObject["links"] as? [String: Any] + let artifacts = links!["artifacts"] as? [String: Any] + user.artifactsHref = artifacts!["href"] as? String - } - -// MARK: - Segue methods - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) + completionCurrentUser(user, nil) + } + catch + { + completionCurrentUser(nil, errorUserData) + } + }) + currentUserTask.resume() + } + + // MARK: - Segue methods + override func prepare(for segue: UIStoryboardSegue, sender: Any?) + { + if (segue.identifier == "segueToTabBar") { - if (segue.identifier == "segueToTabBar") - { - let tabBarController : UITabBarController = (segue.destinationViewController as? UITabBarController)! - tabBarController.tabBar.items![0].title = NSLocalizedString("tabAncestorsName", comment: "name for list tab") - tabBarController.tabBar.items![1].title = NSLocalizedString("tabMemoriesName", comment: "name for memories tab") - - guard let treeTVC = tabBarController.viewControllers?[0] as? TreeTVC else { - fatalError("The first viewController in the tabBarController should be an instance of TreeTVC") - } - // need to pass a User object to the tree table view controller - treeTVC.user = sender as? User - - guard let memoriesVC = tabBarController.viewControllers?[1] as? MemoriesVC else { - fatalError("The second viewController in the tabBarController should be an instance of MemoriesVC") - } - memoriesVC.user = sender as? User - - self.activityIndicator.stopAnimating() - } + let tabBarController : UITabBarController = (segue.destination as? UITabBarController)! + tabBarController.tabBar.items![0].title = NSLocalizedString("tabAncestorsName", comment: "name for list tab") + tabBarController.tabBar.items![1].title = NSLocalizedString("tabMemoriesName", comment: "name for memories tab") + + guard let treeTVC = tabBarController.viewControllers?[0] as? TreeTVC else { + fatalError("The first viewController in the tabBarController should be an instance of TreeTVC") + } + // need to pass a User object to the tree table view controller + treeTVC.user = sender as? User + + guard let memoriesVC = tabBarController.viewControllers?[1] as? MemoriesVC else { + fatalError("The second viewController in the tabBarController should be an instance of MemoriesVC") + } + memoriesVC.user = sender as? User + + self.activityIndicator.stopAnimating() } - -// MARK: - Private methods - private func lockScreen() { - usernameTextField.enabled = false - passwordTextField.enabled = false - activityIndicator.startAnimating() - } - - private func unlockScreen() { - usernameTextField.enabled = true - passwordTextField.enabled = true - activityIndicator.stopAnimating() - } + } + + // MARK: - Private methods + fileprivate func lockScreen() { + usernameTextField.isEnabled = false + passwordTextField.isEnabled = false + activityIndicator.startAnimating() + } + + fileprivate func unlockScreen() { + usernameTextField.isEnabled = true + passwordTextField.isEnabled = true + activityIndicator.stopAnimating() + } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FamilySearchIosSampleApp/MemoriesVC.swift b/FamilySearchIosSampleApp/MemoriesVC.swift index 9aafb3c..8f160ab 100644 --- a/FamilySearchIosSampleApp/MemoriesVC.swift +++ b/FamilySearchIosSampleApp/MemoriesVC.swift @@ -11,158 +11,116 @@ import UIKit class MemoriesVC : UICollectionViewController { - var user : User? + var user : User? + + var arrayOfImageThumbnailHrefs = [String]() + + var accessToken : String? + + override func viewDidLoad() { - let arrayOfImageThumbnailHrefs = NSMutableArray() + // display waiting activity indicator + Utilities.displayWaitingView(self.view) - var accessToken : String? + // get the access token from UserDefaults + let preferences = UserDefaults.standard + accessToken = preferences.string(forKey: Utilities.KEY_ACCESS_TOKEN) - override func viewDidLoad() { - - // display waiting activity indicator - Utilities.displayWaitingView(self.view) - - // get the access token from NSUserDefaults - let preferences = NSUserDefaults.standardUserDefaults() - accessToken = preferences.stringForKey(Utilities.KEY_ACCESS_TOKEN) - - // get an array of the links of images - getMemoriesLinksForUser(accessToken!, - completionLinks: {(completionLinks, errorLinks) -> Void in - if (errorLinks == nil) - { - // update collection view to display images - dispatch_async(dispatch_get_main_queue(), - { - // remove waiting activity indicator - Utilities.removeWaitingView(self.view) - - // update collectionView - self.collectionView?.reloadData() - }) - } - }) + // get an array of the links of images + getMemoriesLinksForUser(accessToken!, + completionLinks: {(completionLinks, errorLinks) -> Void in + if (errorLinks == nil) + { + // update collection view to display images + DispatchQueue.main.async(execute: { + // remove waiting activity indicator + Utilities.removeWaitingView(self.view) + + // update collectionView + self.collectionView?.reloadData() + }) + } + }) + } + + func getMemoriesLinksForUser(_ accessToken:String, + completionLinks:@escaping (_ responseLinks:[String]?, _ errorLinks:Error?) -> ()) + { + let configuration = URLSessionConfiguration.default + let headers: [AnyHashable: Any] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken] + configuration.httpAdditionalHeaders = headers + let session = URLSession(configuration: configuration) + + guard let memoriesHref = URL(string: user!.artifactsHref!) else { + return } - func getMemoriesLinksForUser(accessToken:String, - completionLinks:(responseLinks:NSMutableArray?, errorLinks:NSError?) -> ()) - { - let configuration = NSURLSessionConfiguration.defaultSessionConfiguration(); - let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken]; - configuration.HTTPAdditionalHeaders = headers; - let session = NSURLSession(configuration: configuration) - - guard let memoriesHref = NSURL(string: user!.artifactsHref!) else { + let memoriesTask = session.dataTask(with: memoriesHref, completionHandler: { [weak self] (memoriesData, response, memoriesError) in + do + { + guard let memoriesDataJson = try JSONSerialization.jsonObject(with: memoriesData!, options: .allowFragments) as? [String : Any], + let sourceDescriptions = memoriesDataJson["sourceDescriptions"] as? [[String: Any]] else { return } - - let memoriesTask = session.dataTaskWithURL(memoriesHref) { [weak self] (memoriesData, response, memoriesError) in - do - { - let memoriesDataJson = try NSJSONSerialization.JSONObjectWithData(memoriesData!, options: .AllowFragments); - - let sourceDescriptions = memoriesDataJson["sourceDescriptions"] as? [NSDictionary] - for sourceDescription in sourceDescriptions! - { - // for this demo we're only downloading images, so we need to check if the sourceDescription contains an image - let mediaType = sourceDescription["mediaType"] as? String - if (mediaType == "image/jpeg") - { - let links = sourceDescription["links"] as? NSDictionary - let linkImageThumbnail = links?["image-thumbnail"] as? NSDictionary - let linkImageThumbnailHref = linkImageThumbnail!["href"] as? String - self?.arrayOfImageThumbnailHrefs.addObject(linkImageThumbnailHref!) - } - else - { - continue - } - } - completionLinks(responseLinks: self?.arrayOfImageThumbnailHrefs, errorLinks: nil) - } - catch - { - completionLinks(responseLinks: nil, errorLinks: memoriesError) - } + for sourceDescription in sourceDescriptions + { + // for this demo we're only downloading images, so we need to check if the sourceDescription contains an image + if let mediaType = sourceDescription["mediaType"] as? String, + mediaType == "image/jpeg", + let links = sourceDescription["links"] as? [String: Any], + let linkImageThumbnail = links["image-thumbnail"] as? [String: Any], + let linkImageThumbnailHref = linkImageThumbnail["href"] as? String + { + self?.arrayOfImageThumbnailHrefs.append(linkImageThumbnailHref) + } + else + { + continue + } } - memoriesTask.resume() - } + completionLinks(self?.arrayOfImageThumbnailHrefs, nil) + } + catch + { + completionLinks(nil, memoriesError) + } + }) + memoriesTask.resume() + } + + // MARK: - UI Collection View Controller methods + override func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.arrayOfImageThumbnailHrefs.count + } + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - // MARK: - UI Collection View Controller methods - override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { - return 1 - } + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MemoryCell", for: indexPath) as! MemoryCell - override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return self.arrayOfImageThumbnailHrefs.count - } + let link = arrayOfImageThumbnailHrefs[indexPath.row] - override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { - - let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MemoryCell", forIndexPath: indexPath) as! MemoryCell - - let linkHref = arrayOfImageThumbnailHrefs.objectAtIndex(indexPath.row) as? String - - // make sure the link and token variables are not nil - if let link = linkHref, token = accessToken - { - Utilities.getImageFromUrl(link, accessToken: token) { (data, response, error) in - dispatch_async(dispatch_get_main_queue()) { () -> Void in - guard let imageData = data else { - // no image data - return - } - cell.memoryImageView.image = UIImage(data: imageData) - } - } - } - else - { - // TODO: handle case for when linkHref or accessToken are nil + // make sure the link and token variables are not nil + if let token = accessToken + { + Utilities.getImageFromUrl(link, accessToken: token) { (data, response, error) in + DispatchQueue.main.async { () -> Void in + guard let imageData = data else { + // no image data + return + } + cell.memoryImageView.image = UIImage(data: imageData) } - - return cell + } } + else + { + // TODO: handle case for when linkHref or accessToken are nil + } + + return cell + } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FamilySearchIosSampleApp/TreeTVC.swift b/FamilySearchIosSampleApp/TreeTVC.swift index e6adde7..3cb4925 100644 --- a/FamilySearchIosSampleApp/TreeTVC.swift +++ b/FamilySearchIosSampleApp/TreeTVC.swift @@ -9,274 +9,228 @@ import UIKit class TreeTVC: UITableViewController { + + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! + @IBOutlet weak var navItemTitle: UINavigationItem! + + var user : User? + + var personArray = [Person]() + + var accessToken : String? + + let cache = NSCache() + + override func viewDidLoad() { + super.viewDidLoad() - @IBOutlet weak var activityIndicator: UIActivityIndicatorView! - @IBOutlet weak var navItemTitle: UINavigationItem! + Utilities.displayWaitingView(self.view) - var user : User? + // set cache limits to 20 images or 10mb + cache.countLimit = 50 + cache.totalCostLimit = 30*1024*1024 - var personArray = NSArray() + // get the access token from UserDefaults + let preferences = UserDefaults.standard + accessToken = preferences.string(forKey: Utilities.KEY_ACCESS_TOKEN) - var accessToken : String? - - let cache = NSCache() - - override func viewDidLoad() { - super.viewDidLoad() - - Utilities.displayWaitingView(self.view) - - // set cache limits to 20 images or 10mb - cache.countLimit = 50 - cache.totalCostLimit = 30*1024*1024 - - // get the access token from NSUserDefaults - let preferences = NSUserDefaults.standardUserDefaults() - accessToken = preferences.stringForKey(Utilities.KEY_ACCESS_TOKEN) - - // get url for family tree from Collections - Utilities.getUrlsFromCollections({ [weak self] (collectionsResponse, error) -> Void in - if (error == nil) - { - // download the Ancestry query url - self?.getAncestryQueryUrlAsString(collectionsResponse.familyTreeUrlString!, - completionQuery: {(responseTemplate, errorQuery) -> Void in - if (errorQuery == nil) - { - // getAncestryTree - self?.getAncestryTree(responseTemplate!, - userPersonId: (self?.user!.personId!)!, - accessToken: (self?.accessToken!)!, - completionTree:{(responsePersons, errorTree) -> Void in - if (errorTree == nil) - { - // set the received array, update table - self?.personArray = (responsePersons! as NSArray as? [Person])! - dispatch_async(dispatch_get_main_queue(),{ - - // remove loading spinner view from tvc - Utilities.removeWaitingView((self?.view)!) - - // update table view - self?.tableView.reloadData() - }) - } - }) - } - }) - } + // get url for family tree from Collections + Utilities.getUrlsFromCollections({ [weak self] (collectionsResponse, error) -> Void in + if (error == nil) + { + // download the Ancestry query url + self?.getAncestryQueryUrlAsString(collectionsResponse.familyTreeUrlString!, + completionQuery: {(responseTemplate, errorQuery) -> Void in + if (errorQuery == nil) + { + // getAncestryTree + self?.getAncestryTree(responseTemplate!, + userPersonId: (self?.user!.personId!)!, + accessToken: (self?.accessToken!)!, + completionTree:{(responsePersons, errorTree) -> Void in + if (errorTree == nil) + { + // set the received array, update table + guard let responsePersons = responsePersons else { + return + } + self?.personArray = responsePersons + DispatchQueue.main.async(execute: { + + // remove loading spinner view from tvc + Utilities.removeWaitingView((self?.view)!) + + // update table view + self?.tableView.reloadData() + }) + } + }) + } }) - } + } + }) + } + + func getAncestryQueryUrlAsString(_ familyTreeUrlAsString : String, completionQuery:@escaping (_ responseTemplate:String?, _ errorQuery:Error?) -> ()) + { + let configuration = URLSessionConfiguration.default + let headers: [AnyHashable: Any] = ["Accept":"application/json"] + configuration.httpAdditionalHeaders = headers + let session = URLSession(configuration: configuration) - func getAncestryQueryUrlAsString(familyTreeUrlAsString : String, completionQuery:(responseTemplate:String?, errorQuery:NSError?) -> ()) - { - let configuration = NSURLSessionConfiguration.defaultSessionConfiguration(); - let headers: [NSObject : AnyObject] = ["Accept":"application/json"]; - configuration.HTTPAdditionalHeaders = headers; - let session = NSURLSession(configuration: configuration) - - let familyTreeTask = session.dataTaskWithURL(NSURL(string:familyTreeUrlAsString)! ) { (familyTreeData, response, familyTreeError) in - do - { - let familyTreeJson = try NSJSONSerialization.JSONObjectWithData(familyTreeData!, options: .AllowFragments); - //print("familyTreeJson = \(familyTreeJson)") - - // from here, we only care about the value of collections.links.ancestry-query.template, where collections is a json array - if let collectionsJsonObject = familyTreeJson["collections"] as? [[String : AnyObject]] - { - let collection = collectionsJsonObject.first! - let links = collection["links"] as? NSDictionary - let ancestryQuery = links!["ancestry-query"] as? NSDictionary - let entireTemplate = ancestryQuery!["template"] as! String - - // need to split the template URL, and get the left side of the { symbol - let templateSplit = entireTemplate.componentsSeparatedByString("{") - let template = templateSplit[0] - completionQuery(responseTemplate:template, errorQuery:nil) - } - - } - catch - { - print("Error parsing the ancestry-query") - completionQuery(responseTemplate:nil, errorQuery:familyTreeError) - } - } - familyTreeTask.resume() - } - - // getAncestryTree - func getAncestryTree(ancestryRootUrlString:String, - userPersonId:String, accessToken:String, - completionTree:(responsePersons:NSMutableArray?, errorTree:NSError?) ->()) - { - var ancestryUrlString = ancestryRootUrlString + "?" + "person=" + userPersonId - ancestryUrlString = ancestryUrlString + "&" + "generations=" + "4" - - let ancestryUrl = NSURL(string: ancestryUrlString); - - let configuration = NSURLSessionConfiguration.defaultSessionConfiguration(); - let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken]; - configuration.HTTPAdditionalHeaders = headers; - let session = NSURLSession(configuration: configuration) - - let ancestryTreeTask = session.dataTaskWithURL(ancestryUrl!) { (ancestryData, ancestryResponse, ancestryError) in - if (ancestryError == nil) - { - do - { - let ancestryDataJson = try NSJSONSerialization.JSONObjectWithData(ancestryData!, options: .AllowFragments); - //print("ancestryDataJson = \(ancestryDataJson)") - - let persons = ancestryDataJson["persons"] as? [[String : AnyObject]] - let arrayOfPersons = NSMutableArray() - - for eachPerson in persons! - { - let person = Person() - //print("eachPerson = \(eachPerson)") - - // get the display.name string - let display = eachPerson["display"] as! NSDictionary - let displayName = display["name"] as! String - let lifespan = display["lifespan"] as! String - - // get the links.person.href string - let links = eachPerson["links"] as! NSDictionary - let personLink = links["person"] as! NSDictionary - let personLinkHref = personLink["href"] as! String - - person.displayName = displayName - person.lifespan = lifespan - person.personLinkHref = personLinkHref - arrayOfPersons.addObject(person) - } - - completionTree(responsePersons: arrayOfPersons, errorTree: nil) - } - catch - { - print("Error getting ancestry tree data. Error = \(ancestryError)") - completionTree(responsePersons: nil, errorTree: ancestryError) - } - } - else - { - completionTree(responsePersons: nil, errorTree: ancestryError) - } + let familyTreeTask = session.dataTask(with: URL(string:familyTreeUrlAsString)!, completionHandler: { (familyTreeData, response, familyTreeError) in + do + { + guard let familyTreeJson = try JSONSerialization.jsonObject(with: familyTreeData!, options: .allowFragments) as? [String : Any], + let collectionsJsonObject = familyTreeJson["collections"] as? [[String : AnyObject]] else { + return } + // from here, we only care about the value of collections.links.ancestry-query.template, where collections is a json array + let collection = collectionsJsonObject.first! + let links = collection["links"] as? [String: Any] + let ancestryQuery = links!["ancestry-query"] as? [String: Any] + let entireTemplate = ancestryQuery!["template"] as! String - ancestryTreeTask.resume() - } + // need to split the template URL, and get the left side of the { symbol + let templateSplit = entireTemplate.components(separatedBy: "{") + let template = templateSplit[0] + completionQuery(template, nil) + } + catch + { + print("Error parsing the ancestry-query") + completionQuery(nil, familyTreeError) + } + } ) + familyTreeTask.resume() + } + + // getAncestryTree + func getAncestryTree(_ ancestryRootUrlString:String, + userPersonId:String, accessToken:String, + completionTree:@escaping (_ responsePersons:[Person]?, _ errorTree:Error?) ->()) + { + var ancestryUrlString = ancestryRootUrlString + "?" + "person=" + userPersonId + ancestryUrlString = ancestryUrlString + "&" + "generations=" + "4" - // MARK: - Table View Controller methods - override func numberOfSectionsInTableView(tableView: UITableView) -> Int { - return 1; - } + let ancestryUrl = URL(string: ancestryUrlString) - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.personArray.count - } + let configuration = URLSessionConfiguration.default + let headers: [AnyHashable: Any] = ["Accept":"application/json", "Authorization":"Bearer " + accessToken] + configuration.httpAdditionalHeaders = headers + let session = URLSession(configuration: configuration) - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell : PersonCell = self.tableView.dequeueReusableCellWithIdentifier("PersonCell")! as! PersonCell - - let person = personArray.objectAtIndex(indexPath.row) as! Person - cell.ancestorName.text = person.displayName - cell.ancestorLifespan.text = person.lifespan - - // set default ancestorImage to display while scrolling - cell.ancestorPicture.image = UIImage(named: "genderUnknownCircle2XL") - - if let imageLink = person.personLinkHref + let ancestryTreeTask = session.dataTask(with: ancestryUrl!, completionHandler: { (ancestryData, ancestryResponse, ancestryError) in + if (ancestryError == nil) + { + do { - // the code below is to create an image cache - var ancestorImage = UIImage() - if let cachedImage = cache.objectForKey(imageLink) as? UIImage - { - // image exists in cache, so use the cached image - ancestorImage = cachedImage - cell.ancestorPicture.image = ancestorImage - } - else - { - // no image found in cache, so need to create cached image from download service - Utilities.getImageFromUrl(imageLink, accessToken: accessToken!) { (data, response, error) in - dispatch_async(dispatch_get_main_queue()) { () -> Void in - ancestorImage = UIImage(data: data!)! - self.cache.setObject(ancestorImage, forKey: imageLink) - cell.ancestorPicture.image = ancestorImage - } - } - - } + guard let ancestryDataJson = try JSONSerialization.jsonObject(with: ancestryData!, options: .allowFragments) as? [String : Any] else { + return + } + //print("ancestryDataJson = \(ancestryDataJson)") + + let persons = ancestryDataJson["persons"] as? [[String : AnyObject]] + var people = [Person]() + + for eachPerson in persons! + { + let person = Person() + //print("eachPerson = \(eachPerson)") + + // get the display.name string + let display = eachPerson["display"] as! [String: Any] + let displayName = display["name"] as! String + let lifespan = display["lifespan"] as! String + + // get the links.person.href string + let links = eachPerson["links"] as! [String: Any] + let personLink = links["person"] as! [String: Any] + let personLinkHref = personLink["href"] as! String + + person.displayName = displayName + person.lifespan = lifespan + person.personLinkHref = personLinkHref + people.append(person) + } + + completionTree(people, nil) } - else + catch { - // TODO: handle case for when the image link is nil + print("Error getting ancestry tree data. Error = \(ancestryError.debugDescription)") + completionTree(nil, ancestryError) } - - return cell - } + } + else + { + completionTree(nil, ancestryError) + } + }) - override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { - let person = personArray.objectAtIndex(indexPath.row) as! Person - - self.performSegueWithIdentifier("segueToAncestorDetails", sender: person) - } + ancestryTreeTask.resume() + } + + // MARK: - Table View Controller methods + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.personArray.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell : PersonCell = self.tableView.dequeueReusableCell(withIdentifier: "PersonCell")! as! PersonCell - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { - if (segue.identifier == "segueToAncestorDetails") - { - let detailsVC = (segue.destinationViewController as? AncestorDetails)! - detailsVC.person = sender as? Person + let person = personArray[indexPath.row] + cell.ancestorName.text = person.displayName + cell.ancestorLifespan.text = person.lifespan + + // set default ancestorImage to display while scrolling + cell.ancestorPicture.image = UIImage(named: "genderUnknownCircle2XL") + + if let imageLink = person.personLinkHref + { + // the code below is to create an image cache + var ancestorImage = UIImage() + if let cachedImage = cache.object(forKey: imageLink as AnyObject) as? UIImage + { + // image exists in cache, so use the cached image + ancestorImage = cachedImage + cell.ancestorPicture.image = ancestorImage + } + else + { + // no image found in cache, so need to create cached image from download service + Utilities.getImageFromUrl(imageLink, accessToken: accessToken!) { (data, response, error) in + DispatchQueue.main.async { () -> Void in + ancestorImage = UIImage(data: data!)! + self.cache.setObject(ancestorImage, forKey: imageLink as AnyObject) + cell.ancestorPicture.image = ancestorImage + } } + } + } + else + { + // TODO: handle case for when the image link is nil } + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let person = personArray[indexPath.row] + + self.performSegue(withIdentifier: "segueToAncestorDetails", sender: person) + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if (segue.identifier == "segueToAncestorDetails") + { + let detailsVC = (segue.destination as? AncestorDetails)! + detailsVC.person = sender as? Person + } + } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FamilySearchIosSampleApp/Utilities.swift b/FamilySearchIosSampleApp/Utilities.swift index 9b4193a..2670002 100644 --- a/FamilySearchIosSampleApp/Utilities.swift +++ b/FamilySearchIosSampleApp/Utilities.swift @@ -10,117 +10,118 @@ import Foundation import UIKit class Utilities: NSObject { + + static let KEY_ACCESS_TOKEN = "access_token"; + + static func getUrlsFromCollections(_ completionHandler:@escaping (_ response:Links, _ error:Error?) -> ()) + { + let collectionUrlString = "https://familysearch.org/platform/collection" - static let KEY_ACCESS_TOKEN = "access_token"; + let linksObject = Links() - static func getUrlsFromCollections(completionHandler:(response:Links, error:NSError?) -> ()) - { - let collectionUrlString = "https://familysearch.org/platform/collection" - - let linksObject = Links() - - let collectionUrl = NSURL(string: collectionUrlString); - - let configuration = NSURLSessionConfiguration.defaultSessionConfiguration(); - let headers: [NSObject : AnyObject] = ["Accept":"application/json"]; - configuration.HTTPAdditionalHeaders = headers; - let session = NSURLSession(configuration: configuration) - + let collectionUrl = URL(string: collectionUrlString); + + let configuration = URLSessionConfiguration.default; + let headers: [AnyHashable: Any] = ["Accept":"application/json"]; + configuration.httpAdditionalHeaders = headers; + let session = URLSession(configuration: configuration) + + let configurationUrlTask = session.dataTask(with: collectionUrl!, completionHandler: {(data, response, error) in + + // parse the list of possible configuration urls, go just get the + do + { + guard let data = data, + let jsonCollections = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String : AnyObject], + let collectionsJsonObject = jsonCollections["collections"] as? [[String : AnyObject]] else { + return + } - let configurationUrlTask = session.dataTaskWithURL(collectionUrl!) {(data, response, error) in + for collection in collectionsJsonObject + { + if let links = collection["links"] as? [String : AnyObject] + { + // uncomment the line below to see the list of links available + //print("links = \(links)"); - // parse the list of possible configuration urls, go just get the - do + // get the url to get the token + if let tokenUrlObject = links["http://oauth.net/core/2.0/endpoint/token"] as? [String : AnyObject] { - let jsonCollections = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments); - if let collectionsJsonObject = jsonCollections["collections"] as? [[String : AnyObject]] - { - for collection in collectionsJsonObject - { - if let links = collection["links"] as? [String : AnyObject] - { - // uncomment the line below to see the list of links available - //print("links = \(links)"); - - // get the url to get the token - if let tokenUrlObject = links["http://oauth.net/core/2.0/endpoint/token"] as? [String : AnyObject] - { - if let tokenUrlString = tokenUrlObject["href"] as? String - { - linksObject.tokenUrlString = tokenUrlString - } - } - - // get the url to get the data for the current user - if let currentUserUrlObject = links["current-user"] as? [String : AnyObject] - { - if let currentUserUrlString = currentUserUrlObject["href"] as? String - { - linksObject.currentUserString = currentUserUrlString - } - } - - // get the url to get the data for the family tree - if let familyTreeUrlObject = links["family-tree"] as? [String : AnyObject] - { - if let familyTreeUrlString = familyTreeUrlObject["href"] as? String - { - linksObject.familyTreeUrlString = familyTreeUrlString - } - } - } - } - } + if let tokenUrlString = tokenUrlObject["href"] as? String + { + linksObject.tokenUrlString = tokenUrlString + } } - catch + + // get the url to get the data for the current user + if let currentUserUrlObject = links["current-user"] as? [String : AnyObject] { - print("Error parsing collections JSON. Error: \(error)"); + if let currentUserUrlString = currentUserUrlObject["href"] as? String + { + linksObject.currentUserString = currentUserUrlString + } } - completionHandler(response: linksObject, error: error) + // get the url to get the data for the family tree + if let familyTreeUrlObject = links["family-tree"] as? [String : AnyObject] + { + if let familyTreeUrlString = familyTreeUrlObject["href"] as? String + { + linksObject.familyTreeUrlString = familyTreeUrlString + } + } + } } - - configurationUrlTask.resume() - } - - // helper function to download images - static func getImageFromUrl(urlAsString:String, accessToken:String, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) - { - // this is the url of the default image - // notice that this url is HTTP, which means that the app has to allow arbitraty loads for non-HTTPS calls. - // This can be found under Target > Info > App Transport Security Settings - let defaultImageUrl = "http://fsicons.org/wp-content/uploads/2014/10/gender-unknown-circle-2XL.png" - - var imageUrlString = urlAsString + "/portrait" - imageUrlString = imageUrlString + "?access_token=" + accessToken; - imageUrlString = imageUrlString + "&default=" + defaultImageUrl; - NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: imageUrlString)!) { (data, response, error) in - completion(data: data, response: response, error: error) - }.resume() - } + } + catch + { + print("Error parsing collections JSON. Error: \(error)"); + } + + completionHandler(linksObject, error) + }) - // helper function to display an activity indicator - static func displayWaitingView(view:UIView) - { - // creating a loading spinner on top of the table view controller, while data downloads - let waitingView = WaitingView(frame: CGRectMake(0, 0, view.frame.width, view.frame.height)) - let spinnerWhileWaiting = UIActivityIndicatorView(frame: CGRectMake(view.frame.width / 2, view.frame.height / 2, 0, 0)) - spinnerWhileWaiting.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge - spinnerWhileWaiting.color = UIColor.lightGrayColor() - spinnerWhileWaiting.startAnimating() - waitingView.addSubview(spinnerWhileWaiting) - view.addSubview(waitingView) - } + configurationUrlTask.resume() + } + + // helper function to download images + static func getImageFromUrl(_ urlAsString:String, accessToken:String, completion: @escaping ((_ data: Data?, _ response: URLResponse?, _ error: Error? ) -> Void)) + { + // this is the url of the default image + // notice that this url is HTTP, which means that the app has to allow arbitraty loads for non-HTTPS calls. + // This can be found under Target > Info > App Transport Security Settings + let defaultImageUrl = "http://fsicons.org/wp-content/uploads/2014/10/gender-unknown-circle-2XL.png" - // helper function to remove the activity indicator created by displayWaitingView - static func removeWaitingView(view:UIView) + var imageUrlString = urlAsString + "/portrait" + imageUrlString = imageUrlString + "?access_token=" + accessToken; + imageUrlString = imageUrlString + "&default=" + defaultImageUrl; + URLSession.shared.dataTask(with: URL(string: imageUrlString)!, completionHandler: { (data, response, error) in + completion(data, response, error) + }) .resume() + } + + // helper function to display an activity indicator + static func displayWaitingView(_ view:UIView) + { + // creating a loading spinner on top of the table view controller, while data downloads + let waitingView = WaitingView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)) + let spinnerWhileWaiting = UIActivityIndicatorView(frame: CGRect(x: view.frame.width / 2, y: view.frame.height / 2, width: 0, height: 0)) + spinnerWhileWaiting.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge + spinnerWhileWaiting.color = UIColor.lightGray + spinnerWhileWaiting.startAnimating() + waitingView.addSubview(spinnerWhileWaiting) + view.addSubview(waitingView) + } + + // helper function to remove the activity indicator created by displayWaitingView + static func removeWaitingView(_ view:UIView) + { + for eachView in view.subviews { - for eachView in view.subviews - { - if eachView.isKindOfClass(WaitingView) - { - eachView.removeFromSuperview() - } - } + if eachView.isKind(of: WaitingView.self) + { + eachView.removeFromSuperview() + } } + } }