├── readme ├── browse.gif ├── login.gif ├── config1.png ├── config2.png ├── array-bad.gif └── array-good.gif ├── Browse ├── Podfile ├── Browse.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj └── Browse │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ObjectViewController.swift │ ├── FireTiny.swift │ ├── TableViewController.swift │ └── AppDelegate.swift ├── Login ├── Podfile ├── Login.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj └── Login │ ├── MasterNavigationController.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── AppDelegate.swift │ ├── LoginViewController.swift │ ├── ProfileViewController.swift │ └── Fire.swift ├── .gitignore ├── readme.md └── Fire.swift /readme/browse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/Firebase/HEAD/readme/browse.gif -------------------------------------------------------------------------------- /readme/login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/Firebase/HEAD/readme/login.gif -------------------------------------------------------------------------------- /readme/config1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/Firebase/HEAD/readme/config1.png -------------------------------------------------------------------------------- /readme/config2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/Firebase/HEAD/readme/config2.png -------------------------------------------------------------------------------- /readme/array-bad.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/Firebase/HEAD/readme/array-bad.gif -------------------------------------------------------------------------------- /readme/array-good.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayakraft/Firebase/HEAD/readme/array-good.gif -------------------------------------------------------------------------------- /Browse/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | use_frameworks! 3 | 4 | target 'Browse' do 5 | pod 'Firebase', '~> 4.0' 6 | pod 'FirebaseDatabase', '~> 4.0' 7 | end -------------------------------------------------------------------------------- /Login/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | use_frameworks! 3 | 4 | target 'Login' do 5 | pod 'Firebase', '~> 4.0' 6 | pod 'FirebaseDatabase', '~> 4.0' 7 | pod 'FirebaseAuth', '~> 4.0' 8 | pod 'FirebaseStorage', '~> 2.0' 9 | end -------------------------------------------------------------------------------- /Browse/Browse.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Login/Login.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | GoogleService-Info.plist 3 | *.xcworkspace 4 | 5 | # Created by https://www.gitignore.io/api/xcode 6 | ### Xcode ### 7 | # Xcode 8 | # 9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 10 | 11 | ## Build generated 12 | build/ 13 | DerivedData/ 14 | 15 | ## Various settings 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata/ 25 | 26 | ## Other 27 | *.moved-aside 28 | *.xccheckout 29 | *.xcscmblueprint 30 | 31 | # Pods - for those of you who use CocoaPods 32 | Pods -------------------------------------------------------------------------------- /Login/Login/MasterNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterNavigationController.swift 3 | // Login 4 | // 5 | // Created by Robby on 8/8/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | class MasterNavigationController: UINavigationController { 13 | 14 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 15 | // calling init() calls this function 16 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 17 | initCustom() 18 | } 19 | override init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) { 20 | super.init(navigationBarClass: navigationBarClass, toolbarClass: toolbarClass) 21 | initCustom() 22 | } 23 | override init(rootViewController: UIViewController) { 24 | super.init(rootViewController: rootViewController) 25 | initCustom() 26 | } 27 | required init?(coder aDecoder: NSCoder) { 28 | super.init(coder: aDecoder) 29 | initCustom() 30 | } 31 | 32 | func initCustom(){ 33 | self.viewControllers = [ProfileViewController()] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Login/Login/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Browse/Browse/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Browse/Browse/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Login/Login/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | NSPhotoLibraryUsageDescription 36 | Access to photo library for use for profile picture 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Login/Login/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Browse/Browse/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Browse/Browse/ObjectViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringViewController.swift 3 | // Browse 4 | // 5 | // Created by Robby on 8/10/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ObjectViewController: UIViewController { 12 | 13 | func urlIsImage(url:URL) -> Bool{ 14 | let ext:String? = url.pathExtension 15 | if (ext != nil && ["png", "jpg", "jpeg"].contains(ext!)) { 16 | return true 17 | } 18 | return false 19 | } 20 | 21 | var data:Any?{ 22 | didSet{ 23 | if let d = data{ 24 | let type = FireTiny.shared.typeOf(FirebaseData: d) 25 | self.title = stringForType( type ) 26 | // if type is image, display image 27 | self.textView.text = String(describing: d) 28 | if type == .isURL{ 29 | // force cast is okay, we know it's a string 30 | if let url = URL(string:d as! String){ 31 | if urlIsImage(url: url){ 32 | DispatchQueue.global(qos: .default).async { 33 | do{ 34 | let data = try Data(contentsOf: url) 35 | DispatchQueue.main.async { 36 | self.textView.text = "" 37 | self.imageView.image = UIImage(data: data) 38 | } 39 | } catch{ } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | let textView = UITextView() 49 | let imageView = UIImageView() 50 | 51 | override func viewWillAppear(_ animated: Bool) { 52 | super.viewWillAppear(animated) 53 | 54 | self.view.backgroundColor = UIColor.white 55 | 56 | textView.frame = view.frame 57 | textView.font = UIFont.systemFont(ofSize: 18) 58 | textView.backgroundColor = UIColor.clear 59 | self.view.addSubview(textView) 60 | 61 | imageView.frame = view.frame 62 | imageView.contentMode = .scaleAspectFit 63 | imageView.backgroundColor = UIColor.clear 64 | self.view.addSubview(imageView) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Browse/Browse/FireTiny.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FireTiny.swift 3 | // Browse 4 | // 5 | // Created by Robby on 8/11/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import Firebase 10 | 11 | enum JSONDataType { 12 | case isBool, isInt, isFloat, isString, isArray, isDictionary, isURL, isNULL 13 | // isURL is a special kind of string, kind of weird design i know, but it ends up being helpful 14 | } 15 | func stringForType(_ dataType:JSONDataType) -> String{ 16 | switch dataType { 17 | case .isNULL: return "NULL" 18 | case .isBool: return "Bool" 19 | case .isInt: return "Int" 20 | case .isFloat: return "Float" 21 | case .isURL: return "URL" 22 | case .isString: return "String" 23 | case .isDictionary: return "Dictionary" 24 | case .isArray: return "Array" 25 | } 26 | } 27 | 28 | class FireTiny { 29 | static let shared = FireTiny() 30 | let database: DatabaseReference = Database.database().reference() 31 | 32 | fileprivate init() { } 33 | 34 | // childURL = nil returns the root of the database 35 | // childURL can contain multiple subdirectories separated with a slash: "one/two/three" 36 | func getData(_ childURL:String?, completionHandler: @escaping (Any?) -> ()) { 37 | var reference = self.database 38 | if let url = childURL{ 39 | reference = self.database.child(url) 40 | } 41 | reference.observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 42 | completionHandler(snapshot.value) 43 | } 44 | } 45 | 46 | 47 | func doesDataExist(at path:String, completionHandler: @escaping (_ doesExist:Bool, _ dataType:JSONDataType, _ data:Any?) -> ()) { 48 | database.child(path).observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 49 | if let data = snapshot.value{ 50 | completionHandler(true, self.typeOf(FirebaseData: data), data) 51 | } else{ 52 | completionHandler(false, .isNULL, nil) 53 | } 54 | } 55 | } 56 | 57 | func typeOf(FirebaseData object:Any) -> JSONDataType { 58 | if object is NSNumber{ 59 | let nsnum = object as! NSNumber 60 | let boolID = CFBooleanGetTypeID() // the type ID of CFBoolean 61 | let numID = CFGetTypeID(nsnum) // the type ID of num 62 | if numID == boolID{ 63 | return .isBool 64 | } 65 | if nsnum.floatValue == Float(nsnum.intValue){ 66 | return .isInt 67 | } 68 | return .isFloat 69 | } else if object is String { 70 | if let url: URL = URL(string: object as! String) { 71 | if UIApplication.shared.canOpenURL(url){ 72 | return .isURL 73 | } else{ 74 | return .isString 75 | } 76 | } else{ 77 | return .isString 78 | } 79 | } else if object is NSArray || object is NSMutableArray{ 80 | return .isArray 81 | } else if object is NSDictionary || object is NSMutableDictionary{ 82 | return .isDictionary 83 | } 84 | return .isNULL 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Login/Login/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Login 4 | // 5 | // Created by Robby on 8/5/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func launchApp(_ requireLogin:Bool){ 18 | self.window = UIWindow() 19 | self.window?.frame = UIScreen.main.bounds 20 | let loginVC : LoginViewController = LoginViewController() 21 | if let user = Auth.auth().currentUser{ 22 | loginVC.emailField.text = user.email 23 | } 24 | self.window?.rootViewController = loginVC 25 | self.window?.makeKeyAndVisible() 26 | 27 | if(!requireLogin){ 28 | loginVC.present(MasterNavigationController(), animated: false, completion: nil) 29 | } 30 | } 31 | 32 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 33 | // Override point for customization after application launch. 34 | 35 | FirebaseApp.configure() 36 | 37 | _ = Fire.shared 38 | 39 | if (Auth.auth().currentUser) != nil { 40 | // User is signed in. 41 | launchApp(false) 42 | } else { 43 | // No user is signed in. 44 | launchApp(true) 45 | } 46 | return true 47 | } 48 | 49 | func applicationWillResignActive(_ application: UIApplication) { 50 | // 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. 51 | // 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. 52 | } 53 | 54 | func applicationDidEnterBackground(_ application: UIApplication) { 55 | // 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. 56 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 57 | } 58 | 59 | func applicationWillEnterForeground(_ application: UIApplication) { 60 | // 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. 61 | } 62 | 63 | func applicationDidBecomeActive(_ application: UIApplication) { 64 | // 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. 65 | } 66 | 67 | func applicationWillTerminate(_ application: UIApplication) { 68 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Fire.swift 2 | 3 | > a singleton class to assist interfacing with the database, Firebase storage bucket, caching files, managing your Auth info and interacting with other Auth users 4 | 5 | # Example Project: Login Portal 6 | 7 | create a personal account / login, logs you into your profile page where you can edit your profile details, upload picture. Everyone has a profile, that's it. Foundation for social media-type apps. 8 | 9 | ![animation](https://raw.github.com/robbykraft/Firebase/master/readme/login.gif) 10 | 11 | # Example Project: Browse 12 | 13 | Browse the entire database in a simple UITableView. Firebase sometimes delivers JSON objects as an `Array` or `Dictionary`, this handles each case, and includes object type detection. 14 | 15 | ![animation](https://raw.github.com/robbykraft/Firebase/master/readme/browse.gif) 16 | 17 | ## Example projects require Cocoa Pod and Firebase Install 18 | 19 | 1. Install Cocoa Pods: 20 | * in command line navigate to the directory with the .xcodeproj file, type `pod install` 21 | * from now on, open the .xcworkspace file 22 | 2. Copy in your `GoogleService-Info.plist` with your Firebase account info. Get it here: 23 | 24 | ![animation](https://raw.github.com/robbykraft/Firebase/master/readme/config1.png) 25 | 26 | ![animation](https://raw.github.com/robbykraft/Firebase/master/readme/config2.png) 27 | 28 | see [Firebase Getting Started Documentation](https://firebase.google.com/docs/ios/setup) sections __Add Firebase to your app__ and __Add the SDK__ 29 | 30 | # Documentation 31 | 32 | ## Fire.swift covers 3 subjects: 33 | 34 | * Database 35 | * User 36 | * Storage 37 | 38 | ## Database 39 | 40 | * `getData()` get data from database 41 | * `setData()` overwrite data at a certain location in the database 42 | * `addData()` generate a new key and add data as a child 43 | * `doesDataExist()` check if data exists at a certain location 44 | 45 | ### Why this is important 46 | 47 | Firebase sometimes stores data as Arrays, but an auto-generated firebase key won't check to match the array type: 48 | 49 | ![animation](https://raw.github.com/robbykraft/Firebase/master/readme/array-bad.gif) 50 | 51 | using this function checks against the database before adding: 52 | 53 | ```swift 54 | func addData(_ object:Any, asChildAt path:String, completionHandler: ...){} 55 | ``` 56 | 57 | Corrected: 58 | 59 | ![animation](https://raw.github.com/robbykraft/Firebase/master/readme/array-good.gif) 60 | 61 | ## User 62 | 63 | Firebase comes with FireAuth with a "user" class, but you can't edit it. Solution: create a "users" entry in database with copies of User data but with more info. 64 | 65 | * each user is stored under their user.uid 66 | * can add as many fields as you want (nickname, photo, etc..) 67 | 68 | all the references to "user" are to our database's user entries, not the proper FIRAuth entry 69 | 70 | * `getCurrentUser()` get all your profile information 71 | * `getUser()` get a different user's info 72 | * `updateCurrentUserWith()` update your profile with new information 73 | * `newUser()` create a new entry for a user (usually for yourself after 1st login) 74 | * `userExists()` check if a user exists 75 | 76 | ## Storage 77 | 78 | since Firebase Storage doesn't keep track of the files you upload, 79 | this maintains a record of the uploaded files in your database 80 | 81 | * `fileCache:[String:Data]` the cache for all incoming files (images/pdfs) from the storage bucket 82 | * `imageFromStorageBucket()` get an image from Firebase storage (or the cache if it's already there) 83 | * `uploadFileAndMakeRecord()` upload a file to storage, make a record in our database, a -------------------------------------------------------------------------------- /Browse/Browse/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Browse 4 | // 5 | // Created by Robby on 8/10/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | class TableViewController: UITableViewController { 13 | 14 | // if tableview's datasource is a DICTIONARY: 15 | // it populates its rows with its KEYS 16 | // if datasource is ARRAY 17 | // it populates with array (indexPath row numbers) 18 | 19 | var keyArray : [String]? // only used if dataSource is a DICTIONARY 20 | 21 | // the DATA SOURCE 22 | var data: Any? { 23 | didSet{ 24 | if let d = self.data as? [String:Any]{ 25 | self.keyArray = Array(d.keys) 26 | } 27 | if let d = self.data{ 28 | self.title = stringForType(FireTiny.shared.typeOf(FirebaseData: d)) 29 | } 30 | } 31 | } 32 | 33 | var address: URL? // database location, shows up as titleForHeader 34 | 35 | func dataIsArray() -> Bool { 36 | return self.data is [Any] 37 | } 38 | func dataIsDictionary() -> Bool { 39 | return self.data is [String:Any] 40 | } 41 | 42 | override func numberOfSections(in tableView: UITableView) -> Int { 43 | if self.data != nil{ 44 | return 1 45 | } 46 | return 0 47 | } 48 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 49 | if let isArray = self.data as? [Any]{ 50 | return isArray.count 51 | } 52 | if dataIsDictionary(){ 53 | if let keys = self.keyArray{ 54 | return keys.count 55 | } 56 | } 57 | return 0 58 | } 59 | 60 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 61 | if let url = self.address{ 62 | return url.absoluteString 63 | } 64 | return nil 65 | } 66 | 67 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 68 | let cell = UITableViewCell.init(style: .value1, reuseIdentifier: "tableCell") 69 | var text: String = "" 70 | var detailText: String = "" 71 | var rowObject: Any? 72 | 73 | if(dataIsArray()){ 74 | text = String(indexPath.row) 75 | let dataArray = self.data! as! [AnyObject] 76 | rowObject = dataArray[indexPath.row] 77 | } 78 | if let isDictionary = self.data as? [String:Any]{ 79 | if let keys = keyArray{ 80 | text = String(keys[indexPath.row]) 81 | rowObject = isDictionary[ keys[indexPath.row] ] 82 | } 83 | } 84 | 85 | if let object = rowObject{ 86 | let type = FireTiny.shared.typeOf(FirebaseData: object) 87 | switch type{ 88 | case .isArray, .isDictionary: 89 | detailText = stringForType(type) 90 | default: 91 | detailText = String(describing: object) 92 | } 93 | } 94 | cell.textLabel?.text = text 95 | cell.detailTextLabel?.text = detailText 96 | return cell 97 | } 98 | 99 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 100 | var object:Any? 101 | var objectAddress:String = "" 102 | 103 | // depending on DICTIONARY or ARRAY, let's grab the next object to show 104 | if let isArray = self.data as? [Any]{ 105 | object = isArray[indexPath.row] 106 | objectAddress = String(indexPath.row) 107 | } 108 | if let isDictionary = self.data as? [String:Any]{ 109 | if let keys = keyArray{ 110 | let key: String = keys[indexPath.row] 111 | object = isDictionary[ key ] 112 | objectAddress = key 113 | } 114 | } 115 | 116 | if let obj = object{ 117 | let nextType = FireTiny.shared.typeOf(FirebaseData: obj) 118 | switch nextType{ 119 | case .isArray, .isDictionary: 120 | let vc: TableViewController = TableViewController() 121 | vc.data = obj 122 | vc.address = self.address?.appendingPathComponent(objectAddress) 123 | self.navigationController?.pushViewController(vc, animated: true) 124 | default: 125 | // leaf (last level down) 126 | let vc: ObjectViewController = ObjectViewController() 127 | vc.data = obj 128 | self.navigationController?.pushViewController(vc, animated: true) 129 | } 130 | } 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /Browse/Browse/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Browse 4 | // 5 | // Created by Robby on 8/10/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func launchWithData(data:Any){ 18 | self.window = UIWindow() 19 | self.window?.frame = UIScreen.main.bounds 20 | let navigationController = UINavigationController() 21 | 22 | switch FireTiny.shared.typeOf(FirebaseData: data) { 23 | case .isArray, .isDictionary: 24 | let vc : TableViewController = TableViewController() 25 | vc.data = data 26 | vc.address = NSURL.init(string: "/") as URL? 27 | navigationController.setViewControllers([vc], animated:false) 28 | default: 29 | // DATA is a leaf node 30 | let vc : ObjectViewController = ObjectViewController() 31 | vc.data = data 32 | navigationController.setViewControllers([vc], animated:false) 33 | } 34 | self.window?.rootViewController = navigationController 35 | self.window?.makeKeyAndVisible() 36 | } 37 | 38 | func launchWithError(errorString:String?){ 39 | self.window = UIWindow() 40 | self.window?.frame = UIScreen.main.bounds 41 | let vc : UIViewController = UIViewController() 42 | self.window?.rootViewController = vc 43 | self.window?.makeKeyAndVisible() 44 | let alert = UIAlertController.init(title: errorString, message: nil, preferredStyle: .alert) 45 | let okay = UIAlertAction.init(title: "Quit", style: .cancel) { (action) in 46 | exit(0) 47 | } 48 | alert.addAction(okay) 49 | vc.present(alert, animated: true, completion: nil) 50 | } 51 | 52 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 53 | // Override point for customization after application launch. 54 | FirebaseApp.configure() 55 | 56 | _ = FireTiny.shared 57 | 58 | FireTiny.shared.getData(nil) { (data) in 59 | if let d = data{ 60 | self.launchWithData(data: d) 61 | } else{ 62 | self.launchWithError(errorString: "problem connecting to the database") 63 | } 64 | } 65 | 66 | FireTiny.shared.database.observe(.childChanged, with: { (_) in 67 | FireTiny.shared.getData(nil) { (data) in 68 | if let d = data{ 69 | self.launchWithData(data: d) 70 | } else{ 71 | self.launchWithError(errorString: "problem connecting to the database") 72 | } 73 | } 74 | }) 75 | return true 76 | } 77 | 78 | func applicationWillResignActive(_ application: UIApplication) { 79 | // 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. 80 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 81 | } 82 | 83 | func applicationDidEnterBackground(_ application: UIApplication) { 84 | // 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. 85 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 86 | } 87 | 88 | func applicationWillEnterForeground(_ application: UIApplication) { 89 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 90 | } 91 | 92 | func applicationDidBecomeActive(_ application: UIApplication) { 93 | // 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. 94 | } 95 | 96 | func applicationWillTerminate(_ application: UIApplication) { 97 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 98 | } 99 | 100 | 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Login/Login/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Login 4 | // 5 | // Created by Robby on 8/5/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | class LoginViewController: UIViewController, UITextFieldDelegate { 13 | 14 | var emailField:UITextField = UITextField() 15 | var passwordField:UITextField = UITextField() 16 | var loginButton:UIButton = UIButton() 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | let lightBlue = UIColor(red:0.33, green:0.65, blue:0.95, alpha:1.00) 22 | let whiteSmoke = UIColor(red:0.96, green:0.96, blue:0.96, alpha:1.00) 23 | 24 | self.view.backgroundColor = whiteSmoke 25 | emailField.backgroundColor = UIColor.white 26 | passwordField.backgroundColor = UIColor.white 27 | passwordField.isSecureTextEntry = true 28 | 29 | emailField.delegate = self 30 | passwordField.delegate = self 31 | 32 | emailField.placeholder = "Email Address" 33 | passwordField.placeholder = "Password" 34 | 35 | let paddingEmail = UIView.init(frame: CGRect(x: 0, y: 0, width: 10, height: 20)) 36 | let paddingPassword = UIView.init(frame: CGRect(x: 0, y: 0, width: 10, height: 20)) 37 | emailField.leftView = paddingEmail 38 | passwordField.leftView = paddingPassword 39 | emailField.leftViewMode = .always 40 | passwordField.leftViewMode = .always 41 | 42 | self.view.addSubview(emailField) 43 | self.view.addSubview(passwordField) 44 | 45 | loginButton.setTitle("Login / Create Account", for: UIControlState()) 46 | loginButton.addTarget(self, action: #selector(buttonHandler), for: UIControlEvents.touchUpInside) 47 | loginButton.backgroundColor = lightBlue 48 | 49 | self.view.addSubview(loginButton) 50 | } 51 | 52 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 53 | self.view.endEditing(true) 54 | return false 55 | } 56 | 57 | override func viewWillAppear(_ animated: Bool) { 58 | super.viewWillAppear(animated) 59 | 60 | emailField.frame = CGRect(x: 0, y: self.view.bounds.size.height * 0.5 - 52 - 20, width: self.view.bounds.size.width, height: 52) 61 | passwordField.frame = CGRect(x: 0, y: self.view.bounds.size.height * 0.5, width: self.view.bounds.size.width, height: 52) 62 | loginButton.frame = CGRect(x: 0, y: self.view.bounds.size.height * 0.5 + 52 + 20, width: self.view.bounds.size.width, height: 44) 63 | } 64 | 65 | func buttonHandler(){ 66 | loginWithCredentials(emailField.text!, pass: passwordField.text!) 67 | } 68 | 69 | func loginWithCredentials(_ username: String, pass:String){ 70 | Auth.auth().signIn(withEmail: username, password: pass, completion: { (user, error) in 71 | if(error == nil){ 72 | // Success, logging in with email 73 | self.present(MasterNavigationController(), animated: true, completion: nil); 74 | } else{ 75 | // 2 POSSIBILITIES: (1) Account doesn't exist (2) Account exists, password was incorrect 76 | Auth.auth().createUser(withEmail: username, password: pass, completion: { (user, error) in 77 | if(error == nil){ 78 | // Success, created account, logging in now 79 | Fire.shared.newUser(user!, completionHandler: { (success) in 80 | self.present(MasterNavigationController(), animated: true, completion: nil) 81 | }) 82 | } else{ 83 | let errorMessage = "Account exists but password is incorrect" 84 | let alert = UIAlertController(title: username, message: errorMessage, preferredStyle: .alert) 85 | let action1 = UIAlertAction.init(title: "Try Again", style: .default, handler: nil) 86 | let action2 = UIAlertAction.init(title: "Send a password-reset email", style: .destructive, handler: { (action) in 87 | Auth.auth().sendPasswordReset(withEmail: username) { error in 88 | if error == nil{ 89 | // Password reset email sent. 90 | let alert = UIAlertController(title: "Email Sent", message: nil, preferredStyle: .alert) 91 | let action1 = UIAlertAction.init(title: "Okay", style: .default, handler: nil) 92 | alert.addAction(action1) 93 | self.present(alert, animated: true, completion: nil) 94 | } else{ 95 | let alert = UIAlertController(title: "Error sending email", message: error!.localizedDescription, preferredStyle: .alert) 96 | let action1 = UIAlertAction.init(title: "Okay", style: .default, handler: nil) 97 | alert.addAction(action1) 98 | self.present(alert, animated: true, completion: nil) 99 | } 100 | } 101 | }) 102 | alert.addAction(action1) 103 | alert.addAction(action2) 104 | self.present(alert, animated: true, completion: nil) 105 | } 106 | }) 107 | } 108 | }) 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /Login/Login/ProfileViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileViewController.swift 3 | // Login 4 | // 5 | // Created by Robby on 8/5/16. 6 | // Copyright © 2016 Robby. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | func statusBarHeight() -> CGFloat { 13 | let statusBarSize = UIApplication.shared.statusBarFrame.size 14 | return Swift.min(statusBarSize.width, statusBarSize.height) 15 | } 16 | 17 | func dateStringForUnixTime(_ unixTime:Double) -> String{ 18 | let date:Date = Date(timeIntervalSince1970: unixTime) 19 | let dateFormatter:DateFormatter = DateFormatter.init() 20 | dateFormatter.dateStyle = .long 21 | return dateFormatter.string(from: date) 22 | } 23 | 24 | func timeStringForUnixTime(_ unixTime:Double) -> String { 25 | let date:Date = Date(timeIntervalSince1970: unixTime) 26 | let dateFormatter:DateFormatter = DateFormatter.init() 27 | dateFormatter.timeStyle = .medium 28 | return dateFormatter.string(from: date) 29 | } 30 | 31 | 32 | class ProfileViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate{ 33 | 34 | let profileImageView:UIImageView = UIImageView() 35 | let profileImageButton:UIButton = UIButton() 36 | let nameField: UITextField = UITextField() 37 | let emailField: UITextField = UITextField() 38 | let creationDateField: UITextField = UITextField() 39 | let detailField: UITextField = UITextField() 40 | let signoutButton: UIButton = UIButton() 41 | 42 | var updateTimer:Timer? // live updates to profile entries, prevents updating too frequently 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | 47 | let lightBlue = UIColor(red:0.33, green:0.65, blue:0.95, alpha:1.00) 48 | let gray = UIColor(red:0.45, green:0.45, blue:0.45, alpha:1.00) 49 | let whiteSmoke = UIColor(red:0.96, green:0.96, blue:0.96, alpha:1.00) 50 | 51 | self.view.backgroundColor = whiteSmoke 52 | 53 | self.title = "MY PROFILE" 54 | 55 | // buttons 56 | signoutButton.setTitle("Sign Out", for: UIControlState()) 57 | profileImageButton.addTarget(self, action: #selector(profilePictureButtonHandler), for: .touchUpInside) 58 | signoutButton.addTarget(self, action: #selector(logOut), for: UIControlEvents.touchUpInside) 59 | 60 | // ui custom 61 | nameField.delegate = self 62 | emailField.delegate = self 63 | creationDateField.delegate = self 64 | detailField.delegate = self 65 | profileImageView.contentMode = .scaleAspectFill 66 | profileImageView.backgroundColor = UIColor.white 67 | profileImageView.clipsToBounds = true 68 | nameField.backgroundColor = UIColor.white 69 | emailField.backgroundColor = UIColor.white 70 | creationDateField.backgroundColor = UIColor.white 71 | detailField.backgroundColor = UIColor.white 72 | signoutButton.backgroundColor = lightBlue 73 | nameField.placeholder = "Name" 74 | emailField.placeholder = "Email Address" 75 | creationDateField.placeholder = "Creation Date" 76 | detailField.placeholder = "Detail Text" 77 | 78 | emailField.isEnabled = false 79 | creationDateField.isEnabled = false 80 | emailField.textColor = gray 81 | creationDateField.textColor = gray 82 | 83 | // text field padding 84 | let paddingName = UIView.init(frame: CGRect(x: 0, y: 0, width: 10, height: 40)) 85 | let paddingEmail = UIView.init(frame: CGRect(x: 0, y: 0, width: 10, height: 40)) 86 | let paddingCreationDate = UIView.init(frame: CGRect(x: 0, y: 0, width: 10, height: 40)) 87 | let paddingDetail = UIView.init(frame: CGRect(x: 0, y: 0, width: 10, height: 40)) 88 | nameField.leftView = paddingName 89 | emailField.leftView = paddingEmail 90 | creationDateField.leftView = paddingCreationDate 91 | detailField.leftView = paddingDetail 92 | nameField.leftViewMode = .always 93 | emailField.leftViewMode = .always 94 | creationDateField.leftViewMode = .always 95 | detailField.leftViewMode = .always 96 | 97 | self.view.addSubview(profileImageView) 98 | self.view.addSubview(profileImageButton) 99 | self.view.addSubview(nameField) 100 | self.view.addSubview(emailField) 101 | self.view.addSubview(creationDateField) 102 | self.view.addSubview(detailField) 103 | self.view.addSubview(signoutButton) 104 | } 105 | 106 | override func viewWillAppear(_ animated: Bool) { 107 | super.viewWillAppear(animated) 108 | 109 | let navBarHeight:CGFloat = self.navigationController!.navigationBar.frame.height 110 | let statusHeight:CGFloat = statusBarHeight() 111 | 112 | let header = navBarHeight + statusHeight 113 | 114 | // frames 115 | let imgSize:CGFloat = self.view.bounds.size.width * 0.4 116 | let imgArea:CGFloat = self.view.bounds.size.width * 0.5 117 | profileImageView.frame = CGRect(x: 0, y: 0, width: imgSize, height: imgSize) 118 | profileImageView.center = CGPoint(x: self.view.center.x, y: header + imgArea*0.5) 119 | profileImageView.layer.cornerRadius = imgSize*0.5 120 | profileImageButton.frame = profileImageView.frame 121 | nameField.frame = CGRect(x: 0, y: header + imgArea + 10, width: self.view.bounds.size.width, height: 44) 122 | emailField.frame = CGRect(x: 0, y: header + imgArea + 10*2 + 44*1, width: self.view.bounds.size.width, height: 44) 123 | creationDateField.frame = CGRect(x: 0, y: header + imgArea + 10*3 + 44*2, width: self.view.bounds.size.width, height: 44) 124 | detailField.frame = CGRect(x: 0, y: header + imgArea + 10*4 + 44*3, width: self.view.bounds.size.width, height: 44) 125 | signoutButton.frame = CGRect(x: 0, y: header + imgArea + 10*5 + 44*4, width: self.view.bounds.size.width, height: 44) 126 | 127 | // populate screen 128 | Fire.shared.getCurrentUser { (uid, userData) in 129 | self.populateUserData(uid, userData: userData) 130 | } 131 | 132 | NotificationCenter.default.addObserver(self, selector: #selector(textFieldDidChange), name: NSNotification.Name(rawValue: "UITextFieldTextDidChangeNotification"), object: nil) 133 | } 134 | 135 | deinit{ 136 | if(updateTimer != nil){ 137 | updateWithDelay() 138 | } 139 | } 140 | 141 | func populateUserData(_ uid:String, userData:[String:Any]){ 142 | if let profileImage = userData["image"] as? String{ 143 | Fire.shared.imageFromStorageBucket(profileImage, completionHandler: { (image, didRequireDownload) in 144 | self.profileImageView.image = image 145 | }) 146 | } else{ 147 | // blank profile image 148 | profileImageView.image = nil 149 | } 150 | 151 | let dateString = dateStringForUnixTime(userData["createdAt"] as! Double) 152 | let timeString = timeStringForUnixTime(userData["createdAt"] as! Double) 153 | 154 | emailField.text = userData["email"] as? String 155 | nameField.text = userData["displayName"] as? String 156 | creationDateField.text = dateString + " " + timeString 157 | detailField.text = userData["detail"] as? String 158 | } 159 | 160 | func logOut(){ 161 | do{ 162 | try Auth.auth().signOut() 163 | self.navigationController?.dismiss(animated: true, completion: nil) 164 | }catch{ 165 | 166 | } 167 | } 168 | 169 | func profilePictureButtonHandler(_ sender:UIButton){ 170 | let alert = UIAlertController.init(title: "Change Profile Image", message: nil, preferredStyle: .actionSheet) 171 | let action1 = UIAlertAction.init(title: "Camera", style: .default) { (action) in 172 | self.openImagePicker(.camera) 173 | } 174 | let action2 = UIAlertAction.init(title: "Photos", style: .default) { (action) in 175 | self.openImagePicker(.photoLibrary) 176 | } 177 | let action3 = UIAlertAction.init(title: "Cancel", style: .cancel) { (action) in } 178 | alert.addAction(action1) 179 | alert.addAction(action2) 180 | alert.addAction(action3) 181 | 182 | if let popoverController = alert.popoverPresentationController { 183 | popoverController.sourceView = sender 184 | popoverController.sourceRect = sender.bounds 185 | } 186 | self.present(alert, animated: true, completion: nil) 187 | } 188 | 189 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 190 | self.view.endEditing(true) 191 | return false 192 | } 193 | func textFieldDidChange(_ notif: Notification) { 194 | if(updateTimer != nil){ 195 | updateTimer?.invalidate() 196 | updateTimer = nil 197 | } 198 | updateTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateWithDelay), userInfo: nil, repeats: false) 199 | } 200 | 201 | func updateWithDelay() { 202 | // TODO: list all UITextFields here 203 | if let nameText = nameField.text{ 204 | Fire.shared.updateCurrentUserWith(key:"displayName", object: nameText, completionHandler: nil) 205 | } 206 | if let detailText = detailField.text{ 207 | Fire.shared.updateCurrentUserWith(key: "detail", object: detailText, completionHandler: nil) 208 | } 209 | if(updateTimer != nil){ 210 | updateTimer?.invalidate() 211 | updateTimer = nil 212 | } 213 | } 214 | 215 | 216 | func textFieldDidEndEditing(_ textField: UITextField) { 217 | // TODO: list all UITextFields here 218 | if(textField.isEqual(nameField)){ 219 | Fire.shared.updateCurrentUserWith(key: "displayName", object: textField.text!, completionHandler: nil) 220 | } 221 | if(textField.isEqual(detailField)){ 222 | Fire.shared.updateCurrentUserWith(key: "detail", object: textField.text!, completionHandler: nil) 223 | } 224 | } 225 | 226 | func openImagePicker(_ sourceType:UIImagePickerControllerSourceType) { 227 | let imagePicker = UIImagePickerController() 228 | imagePicker.delegate = self 229 | imagePicker.allowsEditing = false 230 | imagePicker.sourceType = sourceType 231 | self.navigationController?.present(imagePicker, animated: true, completion: nil) 232 | } 233 | 234 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 235 | let image = info[UIImagePickerControllerOriginalImage] as! UIImage 236 | let imageData = UIImageJPEGRepresentation(image, 0.5) 237 | if let data = imageData{ 238 | Fire.shared.uploadFileAndMakeRecord(data, fileType: .JPG, description: nil, completionHandler: { (metadata) in 239 | Fire.shared.updateCurrentUserWith(key: "image", object: metadata.filename, completionHandler: { (success) in 240 | if success{ 241 | self.profileImageView.image = image 242 | } 243 | }) 244 | }) 245 | } 246 | self.navigationController?.dismiss(animated: true, completion: nil) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /Browse/Browse.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4B0DAD2DF4AD349BDDF93048 /* Pods_Browse.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31524A62167C6642166754B8 /* Pods_Browse.framework */; }; 11 | D523C2E91DA5759000490E71 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C2E81DA5759000490E71 /* AppDelegate.swift */; }; 12 | D523C2F01DA5759000490E71 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D523C2EF1DA5759000490E71 /* Assets.xcassets */; }; 13 | D523C2F31DA5759000490E71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D523C2F11DA5759000490E71 /* LaunchScreen.storyboard */; }; 14 | D523C2FB1DA575CB00490E71 /* FireTiny.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C2FA1DA575CB00490E71 /* FireTiny.swift */; }; 15 | D523C2FD1DA575D400490E71 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C2FC1DA575D400490E71 /* TableViewController.swift */; }; 16 | D523C2FF1DA575DD00490E71 /* ObjectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C2FE1DA575DD00490E71 /* ObjectViewController.swift */; }; 17 | D523C3011DA5763300490E71 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D523C3001DA5763300490E71 /* GoogleService-Info.plist */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 0EABB5F6B30CC8FBD3641FD1 /* Pods-Browse.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Browse.release.xcconfig"; path = "Pods/Target Support Files/Pods-Browse/Pods-Browse.release.xcconfig"; sourceTree = ""; }; 22 | 31524A62167C6642166754B8 /* Pods_Browse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Browse.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | D523C2E51DA5759000490E71 /* Browse.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Browse.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | D523C2E81DA5759000490E71 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | D523C2EF1DA5759000490E71 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | D523C2F21DA5759000490E71 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | D523C2F41DA5759000490E71 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | D523C2FA1DA575CB00490E71 /* FireTiny.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireTiny.swift; sourceTree = ""; }; 29 | D523C2FC1DA575D400490E71 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 30 | D523C2FE1DA575DD00490E71 /* ObjectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectViewController.swift; sourceTree = ""; }; 31 | D523C3001DA5763300490E71 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 32 | F77D4790D0BCED6E2EABE744 /* Pods-Browse.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Browse.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Browse/Pods-Browse.debug.xcconfig"; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | D523C2E21DA5759000490E71 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | 4B0DAD2DF4AD349BDDF93048 /* Pods_Browse.framework in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 02B589BC5E251C928E130765 /* Frameworks */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | 31524A62167C6642166754B8 /* Pods_Browse.framework */, 51 | ); 52 | name = Frameworks; 53 | sourceTree = ""; 54 | }; 55 | 1711026CD6942D24FD3B46A4 /* Pods */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | F77D4790D0BCED6E2EABE744 /* Pods-Browse.debug.xcconfig */, 59 | 0EABB5F6B30CC8FBD3641FD1 /* Pods-Browse.release.xcconfig */, 60 | ); 61 | name = Pods; 62 | sourceTree = ""; 63 | }; 64 | D523C2DC1DA5759000490E71 = { 65 | isa = PBXGroup; 66 | children = ( 67 | D523C2E71DA5759000490E71 /* Browse */, 68 | D523C2E61DA5759000490E71 /* Products */, 69 | 1711026CD6942D24FD3B46A4 /* Pods */, 70 | 02B589BC5E251C928E130765 /* Frameworks */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | D523C2E61DA5759000490E71 /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | D523C2E51DA5759000490E71 /* Browse.app */, 78 | ); 79 | name = Products; 80 | sourceTree = ""; 81 | }; 82 | D523C2E71DA5759000490E71 /* Browse */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | D523C2FA1DA575CB00490E71 /* FireTiny.swift */, 86 | D523C2E81DA5759000490E71 /* AppDelegate.swift */, 87 | D523C2FE1DA575DD00490E71 /* ObjectViewController.swift */, 88 | D523C2FC1DA575D400490E71 /* TableViewController.swift */, 89 | D523C2EF1DA5759000490E71 /* Assets.xcassets */, 90 | D523C2F11DA5759000490E71 /* LaunchScreen.storyboard */, 91 | D523C3001DA5763300490E71 /* GoogleService-Info.plist */, 92 | D523C2F41DA5759000490E71 /* Info.plist */, 93 | ); 94 | path = Browse; 95 | sourceTree = ""; 96 | }; 97 | /* End PBXGroup section */ 98 | 99 | /* Begin PBXNativeTarget section */ 100 | D523C2E41DA5759000490E71 /* Browse */ = { 101 | isa = PBXNativeTarget; 102 | buildConfigurationList = D523C2F71DA5759000490E71 /* Build configuration list for PBXNativeTarget "Browse" */; 103 | buildPhases = ( 104 | 21D51B27FEEE05BA276CB926 /* [CP] Check Pods Manifest.lock */, 105 | D523C2E11DA5759000490E71 /* Sources */, 106 | D523C2E21DA5759000490E71 /* Frameworks */, 107 | D523C2E31DA5759000490E71 /* Resources */, 108 | 9CC8EF1A4230E8483A5474AC /* [CP] Embed Pods Frameworks */, 109 | 317D795BE949EF960AFCCCED /* [CP] Copy Pods Resources */, 110 | ); 111 | buildRules = ( 112 | ); 113 | dependencies = ( 114 | ); 115 | name = Browse; 116 | productName = Browse; 117 | productReference = D523C2E51DA5759000490E71 /* Browse.app */; 118 | productType = "com.apple.product-type.application"; 119 | }; 120 | /* End PBXNativeTarget section */ 121 | 122 | /* Begin PBXProject section */ 123 | D523C2DD1DA5759000490E71 /* Project object */ = { 124 | isa = PBXProject; 125 | attributes = { 126 | LastSwiftUpdateCheck = 0800; 127 | LastUpgradeCheck = 0800; 128 | ORGANIZATIONNAME = "Robby Kraft"; 129 | TargetAttributes = { 130 | D523C2E41DA5759000490E71 = { 131 | CreatedOnToolsVersion = 8.0; 132 | ProvisioningStyle = Automatic; 133 | }; 134 | }; 135 | }; 136 | buildConfigurationList = D523C2E01DA5759000490E71 /* Build configuration list for PBXProject "Browse" */; 137 | compatibilityVersion = "Xcode 3.2"; 138 | developmentRegion = English; 139 | hasScannedForEncodings = 0; 140 | knownRegions = ( 141 | en, 142 | Base, 143 | ); 144 | mainGroup = D523C2DC1DA5759000490E71; 145 | productRefGroup = D523C2E61DA5759000490E71 /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | D523C2E41DA5759000490E71 /* Browse */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXResourcesBuildPhase section */ 155 | D523C2E31DA5759000490E71 /* Resources */ = { 156 | isa = PBXResourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | D523C2F31DA5759000490E71 /* LaunchScreen.storyboard in Resources */, 160 | D523C3011DA5763300490E71 /* GoogleService-Info.plist in Resources */, 161 | D523C2F01DA5759000490E71 /* Assets.xcassets in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXShellScriptBuildPhase section */ 168 | 21D51B27FEEE05BA276CB926 /* [CP] Check Pods Manifest.lock */ = { 169 | isa = PBXShellScriptBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | ); 173 | inputPaths = ( 174 | ); 175 | name = "[CP] Check Pods Manifest.lock"; 176 | outputPaths = ( 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | shellPath = /bin/sh; 180 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 181 | showEnvVarsInLog = 0; 182 | }; 183 | 317D795BE949EF960AFCCCED /* [CP] Copy Pods Resources */ = { 184 | isa = PBXShellScriptBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | ); 188 | inputPaths = ( 189 | ); 190 | name = "[CP] Copy Pods Resources"; 191 | outputPaths = ( 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | shellPath = /bin/sh; 195 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Browse/Pods-Browse-resources.sh\"\n"; 196 | showEnvVarsInLog = 0; 197 | }; 198 | 9CC8EF1A4230E8483A5474AC /* [CP] Embed Pods Frameworks */ = { 199 | isa = PBXShellScriptBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | ); 203 | inputPaths = ( 204 | ); 205 | name = "[CP] Embed Pods Frameworks"; 206 | outputPaths = ( 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | shellPath = /bin/sh; 210 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Browse/Pods-Browse-frameworks.sh\"\n"; 211 | showEnvVarsInLog = 0; 212 | }; 213 | /* End PBXShellScriptBuildPhase section */ 214 | 215 | /* Begin PBXSourcesBuildPhase section */ 216 | D523C2E11DA5759000490E71 /* Sources */ = { 217 | isa = PBXSourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | D523C2FF1DA575DD00490E71 /* ObjectViewController.swift in Sources */, 221 | D523C2FB1DA575CB00490E71 /* FireTiny.swift in Sources */, 222 | D523C2E91DA5759000490E71 /* AppDelegate.swift in Sources */, 223 | D523C2FD1DA575D400490E71 /* TableViewController.swift in Sources */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXSourcesBuildPhase section */ 228 | 229 | /* Begin PBXVariantGroup section */ 230 | D523C2F11DA5759000490E71 /* LaunchScreen.storyboard */ = { 231 | isa = PBXVariantGroup; 232 | children = ( 233 | D523C2F21DA5759000490E71 /* Base */, 234 | ); 235 | name = LaunchScreen.storyboard; 236 | sourceTree = ""; 237 | }; 238 | /* End PBXVariantGroup section */ 239 | 240 | /* Begin XCBuildConfiguration section */ 241 | D523C2F51DA5759000490E71 /* Debug */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | ALWAYS_SEARCH_USER_PATHS = NO; 245 | CLANG_ANALYZER_NONNULL = YES; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_WARN_BOOL_CONVERSION = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 253 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 254 | CLANG_WARN_EMPTY_BODY = YES; 255 | CLANG_WARN_ENUM_CONVERSION = YES; 256 | CLANG_WARN_INFINITE_RECURSION = YES; 257 | CLANG_WARN_INT_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = dwarf; 265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 266 | ENABLE_TESTABILITY = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_DYNAMIC_NO_PIC = NO; 269 | GCC_NO_COMMON_BLOCKS = YES; 270 | GCC_OPTIMIZATION_LEVEL = 0; 271 | GCC_PREPROCESSOR_DEFINITIONS = ( 272 | "DEBUG=1", 273 | "$(inherited)", 274 | ); 275 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 276 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 277 | GCC_WARN_UNDECLARED_SELECTOR = YES; 278 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 279 | GCC_WARN_UNUSED_FUNCTION = YES; 280 | GCC_WARN_UNUSED_VARIABLE = YES; 281 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 282 | MTL_ENABLE_DEBUG_INFO = YES; 283 | ONLY_ACTIVE_ARCH = YES; 284 | SDKROOT = iphoneos; 285 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 286 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 287 | TARGETED_DEVICE_FAMILY = "1,2"; 288 | }; 289 | name = Debug; 290 | }; 291 | D523C2F61DA5759000490E71 /* Release */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | ALWAYS_SEARCH_USER_PATHS = NO; 295 | CLANG_ANALYZER_NONNULL = YES; 296 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 297 | CLANG_CXX_LIBRARY = "libc++"; 298 | CLANG_ENABLE_MODULES = YES; 299 | CLANG_ENABLE_OBJC_ARC = YES; 300 | CLANG_WARN_BOOL_CONVERSION = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 303 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INFINITE_RECURSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 309 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 310 | CLANG_WARN_UNREACHABLE_CODE = YES; 311 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 312 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 313 | COPY_PHASE_STRIP = NO; 314 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 315 | ENABLE_NS_ASSERTIONS = NO; 316 | ENABLE_STRICT_OBJC_MSGSEND = YES; 317 | GCC_C_LANGUAGE_STANDARD = gnu99; 318 | GCC_NO_COMMON_BLOCKS = YES; 319 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 320 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 321 | GCC_WARN_UNDECLARED_SELECTOR = YES; 322 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 323 | GCC_WARN_UNUSED_FUNCTION = YES; 324 | GCC_WARN_UNUSED_VARIABLE = YES; 325 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 326 | MTL_ENABLE_DEBUG_INFO = NO; 327 | SDKROOT = iphoneos; 328 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | VALIDATE_PRODUCT = YES; 331 | }; 332 | name = Release; 333 | }; 334 | D523C2F81DA5759000490E71 /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | baseConfigurationReference = F77D4790D0BCED6E2EABE744 /* Pods-Browse.debug.xcconfig */; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | INFOPLIST_FILE = Browse/Info.plist; 340 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 341 | PRODUCT_BUNDLE_IDENTIFIER = com.robbykraft.Browse; 342 | PRODUCT_NAME = "$(TARGET_NAME)"; 343 | SWIFT_VERSION = 3.0; 344 | }; 345 | name = Debug; 346 | }; 347 | D523C2F91DA5759000490E71 /* Release */ = { 348 | isa = XCBuildConfiguration; 349 | baseConfigurationReference = 0EABB5F6B30CC8FBD3641FD1 /* Pods-Browse.release.xcconfig */; 350 | buildSettings = { 351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 352 | INFOPLIST_FILE = Browse/Info.plist; 353 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 354 | PRODUCT_BUNDLE_IDENTIFIER = com.robbykraft.Browse; 355 | PRODUCT_NAME = "$(TARGET_NAME)"; 356 | SWIFT_VERSION = 3.0; 357 | }; 358 | name = Release; 359 | }; 360 | /* End XCBuildConfiguration section */ 361 | 362 | /* Begin XCConfigurationList section */ 363 | D523C2E01DA5759000490E71 /* Build configuration list for PBXProject "Browse" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | D523C2F51DA5759000490E71 /* Debug */, 367 | D523C2F61DA5759000490E71 /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | D523C2F71DA5759000490E71 /* Build configuration list for PBXNativeTarget "Browse" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | D523C2F81DA5759000490E71 /* Debug */, 376 | D523C2F91DA5759000490E71 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | /* End XCConfigurationList section */ 382 | }; 383 | rootObject = D523C2DD1DA5759000490E71 /* Project object */; 384 | } 385 | -------------------------------------------------------------------------------- /Fire.swift: -------------------------------------------------------------------------------- 1 | // Fire.swift 2 | // Created by Robby on 8/6/16. 3 | // Copyright © 2016 Robby. All rights reserved. 4 | 5 | ///////////////////////////////////////////////////////////////////////// 6 | // THREE PARTS: DATABASE, USER, STORAGE 7 | 8 | // DATABASE: 9 | // guards for setting and retrieving data 10 | // handling all types of JSON data: nil, bool, int, float, string, array, dictionary 11 | // Firebase uses Arrays which takes some extra safeguarding to manage 12 | // 13 | // getData() get data from database 14 | // setData() overwrite data at a certain location in the database 15 | // addData() generate a new key and add data as a child 16 | // doesDataExist() check if data exists at a certain location 17 | 18 | 19 | // USER: 20 | // Firebase comes with FireAuth with a "user" class, but you can't edit it. 21 | // solution: "users" entry in database with copies of User entries but with more info 22 | // - each user is stored under their user.uid 23 | // - can add as many fields as you want (nickname, photo, etc..) 24 | // all the references to "user" are to our database's user entries, not the proper FIRAuth entry 25 | // 26 | // 27 | // getCurrentUser() get all your profile information 28 | // updateCurrentUserWith() update your profile with new information 29 | // newUser() create a new entry for a user (usually for yourself after 1st login) 30 | // userExists() check if a user exists 31 | 32 | // STORAGE: 33 | // firebase storage doesn't let you ask for the contents of its folder 34 | // 1) everytime you save an image, it creates an entry for it in your database 35 | // now you are manually maintaining a list of the contents of your firebase storage 36 | // (unless you upload by some other means) 37 | // 38 | // 2) this class also maintains a cache of already-loaded images 39 | 40 | 41 | import Firebase 42 | 43 | enum JSONDataType { 44 | case isBool, isInt, isFloat, isString, isArray, isDictionary, isURL, isNULL 45 | // isURL is a special kind of string, kind of weird design i know, but it ends up being helpful 46 | } 47 | 48 | let STORAGE_IMAGE_DIR:String = "images/" 49 | let STORAGE_DOCUMENT_DIR:String = "documents/" 50 | enum StorageFileType : String{ 51 | case JPG, PNG, PDF 52 | } 53 | 54 | struct StorageFileMetadata { 55 | var filename:String 56 | var fullpath:String 57 | var directory:String 58 | var contentType:String 59 | var type:StorageFileType 60 | var size:Int 61 | var url:URL? 62 | var description:String? 63 | } 64 | 65 | // getting an image requires restriction on anticipated image file size 66 | let IMG_SIZE_MAX:Int64 = 15 // megabytes 67 | 68 | 69 | class Fire { 70 | static let shared = Fire() 71 | 72 | let database: DatabaseReference = Database.database().reference() 73 | let storage = Storage.storage().reference() 74 | 75 | // can monitor, pause, resume the current upload task 76 | var currentUpload:StorageUploadTask? 77 | 78 | var myUID:String? // if you are logged in, if not, == nil 79 | 80 | fileprivate init() { 81 | // setup USER listener 82 | Auth.auth().addStateDidChangeListener { auth, listenerUser in 83 | if let user = listenerUser { 84 | print("SIGN IN: \(user.email ?? user.uid)") 85 | self.myUID = user.uid 86 | self.userExists(user, completionHandler: { (exists) in 87 | if(!exists){ 88 | self.newUser(user, completionHandler: nil) 89 | } 90 | }) 91 | } else { 92 | self.myUID = nil 93 | print("SIGN OUT: no user") 94 | } 95 | } 96 | 97 | let connectedRef = Database.database().reference(withPath: ".info/connected") 98 | connectedRef.observe(.value, with: { snapshot in 99 | if let connected = snapshot.value as? Bool , connected { 100 | // internet connected 101 | // banner alert 102 | } else { 103 | // internet disconnected 104 | // banner alert 105 | } 106 | }) 107 | } 108 | 109 | //////////////////////////////////////////////////////////////////////////////////////////// 110 | //////////////////////////////////////////////////////////////////////////////////////////// 111 | // 112 | // DATABASE 113 | 114 | // childURL = nil returns the root of the database 115 | // childURL can contain multiple subdirectories separated with a slash: "one/two/three" 116 | func getData(_ childURL:String?, completionHandler: @escaping (Any?) -> ()) { 117 | var reference = self.database 118 | if let url = childURL{ 119 | reference = self.database.child(url) 120 | } 121 | reference.observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 122 | completionHandler(snapshot.value) 123 | } 124 | } 125 | 126 | // add an object to the database at a childURL, function returns the auto-generated key to that object 127 | func setData(_ object:Any, at path:String, completionHandler: ((Bool, DatabaseReference) -> ())?) { 128 | self.database.child(path).setValue(object) { (error, ref) in 129 | if let e = error{ 130 | print(e.localizedDescription) 131 | if let completion = completionHandler{ 132 | completion(false, ref) 133 | } 134 | } else{ 135 | if let completion = completionHandler{ 136 | completion(true, ref) 137 | } 138 | } 139 | } 140 | } 141 | 142 | // add an object AS A CHILD to the path, returns the key to that object 143 | // ONLY if the object at path is a dictionary or array 144 | // if it is a leaf (String, Number, Bool) it doesn't do anything (prevents overwriting) 145 | func addData(_ object:Any, asChildAt path:String, completionHandler: ((_ success:Bool, _ newKey:String?, DatabaseReference?) -> ())?) { 146 | self.doesDataExist(at: path) { (exists, dataType, data) in 147 | switch dataType{ 148 | // 1) if array, it MAINTAINS the array structure (number key, not dictionary string key) 149 | case .isArray: 150 | let dbArray = data as! NSMutableArray 151 | dbArray.add(object) 152 | self.database.child(path).setValue(dbArray) { (error, ref) in 153 | if let e = error{ 154 | print(e.localizedDescription) 155 | if let completion = completionHandler{ 156 | completion(false, nil, nil) 157 | } 158 | } else{ 159 | if let completion = completionHandler{ 160 | completion(true, String(describing:dbArray.count-1), ref) 161 | } 162 | } 163 | } 164 | // 2) if dictionary, or doesn't exist, makes a new string key like usual 165 | case .isDictionary, .isNULL: 166 | self.database.child(path).childByAutoId().setValue(object) { (error, ref) in 167 | if let e = error{ 168 | print(e.localizedDescription) 169 | if let completion = completionHandler{ 170 | completion(false, nil, nil) 171 | } 172 | } else{ 173 | if let completion = completionHandler{ 174 | completion(true, ref.key, ref) 175 | } 176 | } 177 | } 178 | // 3) if object at path is a String or Int etc..(leaf node), return without doing anything 179 | default: 180 | if let completion = completionHandler{ 181 | completion(false, nil, nil); 182 | } 183 | } 184 | } 185 | } 186 | 187 | 188 | func doesDataExist(at path:String, completionHandler: @escaping (_ doesExist:Bool, _ dataType:JSONDataType, _ data:Any?) -> ()) { 189 | database.child(path).observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 190 | if let data = snapshot.value{ 191 | completionHandler(true, self.typeOf(FirebaseData: data), data) 192 | } else{ 193 | completionHandler(false, .isNULL, nil) 194 | } 195 | } 196 | } 197 | 198 | 199 | func typeOf(FirebaseData object:Any) -> JSONDataType { 200 | if object is NSNumber{ 201 | let nsnum = object as! NSNumber 202 | let boolID = CFBooleanGetTypeID() // the type ID of CFBoolean 203 | let numID = CFGetTypeID(nsnum) // the type ID of num 204 | if numID == boolID{ 205 | return .isBool 206 | } 207 | if nsnum.floatValue == Float(nsnum.intValue){ 208 | return .isInt 209 | } 210 | return .isFloat 211 | } else if object is String { 212 | if let url: URL = URL(string: object as! String) { 213 | if UIApplication.shared.canOpenURL(url){ 214 | return .isURL 215 | } else{ 216 | return .isString 217 | } 218 | } else{ 219 | return .isString 220 | } 221 | } else if object is NSArray || object is NSMutableArray{ 222 | return .isArray 223 | } else if object is NSDictionary || object is NSMutableDictionary{ 224 | return .isDictionary 225 | } 226 | return .isNULL 227 | } 228 | 229 | 230 | //////////////////////////////////////////////////////////////////////////////////////////// 231 | //////////////////////////////////////////////////////////////////////////////////////////// 232 | // 233 | // USER 234 | 235 | func getCurrentUser(_ completionHandler: @escaping (String, [String:Any]) -> ()) { 236 | guard let user = Auth.auth().currentUser else{ 237 | return 238 | } 239 | database.child("users").child(user.uid).observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 240 | if let userData = snapshot.value as? [String:Any]{ 241 | completionHandler(user.uid, userData) 242 | } else{ 243 | print("user has no data") 244 | } 245 | } 246 | } 247 | 248 | func getUser(UID:String, _ completionHandler: @escaping ([String:Any]) -> ()){ 249 | database.child("users").child(UID).observeSingleEvent(of: .value, with: { (snapshot) in 250 | if let userData = snapshot.value as? [String:Any]{ 251 | completionHandler(userData) 252 | } 253 | }) 254 | } 255 | 256 | func updateCurrentUserWith(key:String, object value:Any, completionHandler: ((_ success:Bool) -> ())? ) { 257 | guard let user = Auth.auth().currentUser else{ 258 | if let completion = completionHandler{ 259 | completion(false) 260 | } 261 | return 262 | } 263 | database.child("users").child(user.uid).updateChildValues([key:value]) { (error, ref) in 264 | if let e = error{ 265 | print(e.localizedDescription) 266 | if let completion = completionHandler{ 267 | completion(false) 268 | } 269 | } else{ 270 | // print("saving \(value) into \(key)") 271 | if let completion = completionHandler{ 272 | completion(true) 273 | } 274 | } 275 | } 276 | } 277 | 278 | func newUser(_ user:User, completionHandler: ((_ success:Bool) -> ())? ) { 279 | var newUser:[String:Any] = [ 280 | "createdAt": Date.init().timeIntervalSince1970 281 | ] 282 | // copy user data over from AUTH 283 | if let nameString = user.displayName { newUser["name"] = nameString } 284 | if let imageURL = user.photoURL { newUser["image"] = imageURL } 285 | if let emailString = user.email { newUser["email"] = emailString } 286 | 287 | database.child("users").child(user.uid).updateChildValues(newUser) { (error, ref) in 288 | if let e = error{ 289 | print(e.localizedDescription) 290 | if let completion = completionHandler{ 291 | completion(false) 292 | } 293 | } else{ 294 | if let completion = completionHandler{ 295 | completion(true) 296 | } 297 | } 298 | } 299 | } 300 | 301 | func userExists(_ user: User, completionHandler: @escaping (Bool) -> ()) { 302 | database.child("users").child(user.uid).observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 303 | if snapshot.value != nil{ 304 | completionHandler(true) 305 | } else{ 306 | completionHandler(false) 307 | } 308 | } 309 | } 310 | 311 | //////////////////////////////////////////////////////////////////////////////////////////// 312 | //////////////////////////////////////////////////////////////////////////////////////////// 313 | // 314 | // STORAGE 315 | 316 | // Key is filename in the images/ folder in the Firebase storage bucket 317 | // example: "0C5BABB0D5CA.jpg" 318 | var fileCache:[String:Data] = [:] 319 | 320 | func imageFromStorageBucket(_ filename: String, completionHandler: @escaping (_ image:UIImage, _ didRequireDownload:Bool) -> ()) { 321 | if let imageData = fileCache[filename]{ 322 | if let image = UIImage(data: imageData){ 323 | //TODO: check timestamp against database, force a data refresh 324 | completionHandler(image, false) 325 | return 326 | } 327 | } 328 | 329 | let storage = Storage.storage().reference() 330 | let imageRef = storage.child(STORAGE_IMAGE_DIR + filename) 331 | 332 | imageRef.getData(maxSize: IMG_SIZE_MAX * 1024 * 1024) { (data, error) in 333 | if let e = error{ 334 | print(e.localizedDescription) 335 | } else{ 336 | if let imageData = data { 337 | if let image = UIImage(data: imageData){ 338 | self.fileCache[filename] = imageData 339 | completionHandler(image, true) 340 | } else{ 341 | print("problem making image out of received data") 342 | } 343 | } 344 | } 345 | } 346 | } 347 | 348 | // specify a UUIDFilename, or it will generate one for you 349 | func uploadFileAndMakeRecord(_ data:Data, fileType:StorageFileType, description:String?, completionHandler: @escaping (_ metadata:StorageFileMetadata) -> ()) { 350 | 351 | // prep file info 352 | var filename:String = UUID.init().uuidString 353 | var storageDir:String 354 | let uploadMetadata = StorageMetadata() 355 | switch fileType { 356 | case .JPG: 357 | filename = filename + ".jpg" 358 | storageDir = STORAGE_IMAGE_DIR 359 | uploadMetadata.contentType = "image/jpeg" 360 | case .PNG: 361 | filename = filename + ".png" 362 | storageDir = STORAGE_IMAGE_DIR 363 | uploadMetadata.contentType = "image/png" 364 | case .PDF: 365 | filename = filename + ".pdf" 366 | storageDir = STORAGE_DOCUMENT_DIR 367 | uploadMetadata.contentType = "application/pdf" 368 | } 369 | let filenameAndPath:String = storageDir + filename 370 | 371 | // STEP 1 - upload file to storage 372 | // TODO: make currentUpload an array, if upload in progress add this to array 373 | currentUpload = storage.child(filenameAndPath).putData(data, metadata: uploadMetadata, completion: { (metadata, error) in 374 | if let e = error { 375 | print(e.localizedDescription) 376 | } else { 377 | // upload success, add file to cache 378 | self.fileCache[filename] = data 379 | if let meta = metadata{ 380 | // STEP 2 - record new file in database 381 | var entry:[String:Any] = ["filename":filename, 382 | "fullpath":filenameAndPath, 383 | "directory":storageDir, 384 | "content-type":uploadMetadata.contentType ?? "", 385 | "type":fileType.rawValue, 386 | "size":data.count] 387 | 388 | if let downloadURL = meta.downloadURL(){ 389 | entry["url"] = downloadURL.absoluteString 390 | } 391 | if let descriptionString = description{ 392 | entry["description"] = descriptionString 393 | } 394 | let key = self.database.child("files/" + storageDir).childByAutoId().key 395 | self.database.child("files/" + storageDir).updateChildValues([key:entry]) { (error, ref) in 396 | let info:StorageFileMetadata = StorageFileMetadata(filename: filename, fullpath: filenameAndPath, directory: storageDir, contentType: uploadMetadata.contentType ?? "", type: fileType, size: data.count, url: meta.downloadURL(), description: description) 397 | completionHandler(info) 398 | } 399 | } 400 | } 401 | }) 402 | } 403 | } 404 | 405 | 406 | 407 | extension UIImageView { 408 | 409 | public func imageFromStorage(_ filename: String){ 410 | // filename:String is the filename in the Firebase Storage bucket, no directories 411 | // example: "0C5BABB0D5CA.jpg" 412 | if let imageData = Fire.shared.fileCache[filename]{ 413 | if let image = UIImage(data: imageData){ 414 | self.image = image 415 | return 416 | } 417 | } 418 | let storage = Storage.storage().reference() 419 | let imageRef = storage.child("images/" + filename) 420 | imageRef.getData(maxSize: IMG_SIZE_MAX * 1024 * 1024) { (data, error) in 421 | if let e = error{ 422 | print(e.localizedDescription) 423 | } else{ 424 | if let imageData = data { 425 | if let image = UIImage(data: imageData){ 426 | Fire.shared.fileCache[filename] = imageData 427 | self.image = image 428 | } 429 | } 430 | } 431 | } 432 | } 433 | 434 | public func profileImageForUser(uid: String){ 435 | Fire.shared.getUser(UID: uid) { (userData) in 436 | if let imageFilename = userData["image"] as? String{ 437 | if let imageData = Fire.shared.fileCache[imageFilename]{ 438 | if let image = UIImage(data: imageData){ 439 | self.image = image 440 | return 441 | } 442 | } 443 | let storage = Storage.storage().reference() 444 | let imageRef = storage.child("images/" + imageFilename) 445 | imageRef.getData(maxSize: IMG_SIZE_MAX * 1024 * 1024) { (data, error) in 446 | if let e = error{ 447 | print(e.localizedDescription) 448 | } else{ 449 | if let imageData = data { 450 | if let image = UIImage(data: imageData){ 451 | Fire.shared.fileCache[imageFilename] = imageData 452 | self.image = image 453 | } 454 | } 455 | } 456 | } 457 | } 458 | } 459 | } 460 | 461 | public func imageFromUrl(_ urlString: String) { 462 | if let url = URL(string: urlString) { 463 | let request:URLRequest = URLRequest(url: url) 464 | let session:URLSession = URLSession.shared 465 | let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in 466 | DispatchQueue.main.async { 467 | if let imageData = data as Data? { 468 | self.image = UIImage(data: imageData) 469 | } 470 | } 471 | }) 472 | task.resume() 473 | } 474 | } 475 | 476 | } 477 | -------------------------------------------------------------------------------- /Login/Login/Fire.swift: -------------------------------------------------------------------------------- 1 | // Fire.swift 2 | // Created by Robby on 8/6/16. 3 | // Copyright © 2016 Robby. All rights reserved. 4 | 5 | ///////////////////////////////////////////////////////////////////////// 6 | // THREE PARTS: DATABASE, USER, STORAGE 7 | 8 | // DATABASE: 9 | // guards for setting and retrieving data 10 | // handling all types of JSON data: nil, bool, int, float, string, array, dictionary 11 | // Firebase uses Arrays which takes some extra safeguarding to manage 12 | // 13 | // getData() get data from database 14 | // setData() overwrite data at a certain location in the database 15 | // addData() generate a new key and add data as a child 16 | // doesDataExist() check if data exists at a certain location 17 | 18 | 19 | // USER: 20 | // Firebase comes with FireAuth with a "user" class, but you can't edit it. 21 | // solution: "users" entry in database with copies of User entries but with more info 22 | // - each user is stored under their user.uid 23 | // - can add as many fields as you want (nickname, photo, etc..) 24 | // all the references to "user" are to our database's user entries, not the proper FIRAuth entry 25 | // 26 | // 27 | // getCurrentUser() get all your profile information 28 | // updateCurrentUserWith() update your profile with new information 29 | // newUser() create a new entry for a user (usually for yourself after 1st login) 30 | // userExists() check if a user exists 31 | 32 | // STORAGE: 33 | // firebase storage doesn't let you ask for the contents of its folder 34 | // 1) everytime you save an image, it creates an entry for it in your database 35 | // now you are manually maintaining a list of the contents of your firebase storage 36 | // (unless you upload by some other means) 37 | // 38 | // 2) this class also maintains a cache of already-loaded images 39 | 40 | 41 | import Firebase 42 | 43 | enum JSONDataType { 44 | case isBool, isInt, isFloat, isString, isArray, isDictionary, isURL, isNULL 45 | // isURL is a special kind of string, kind of weird design i know, but it ends up being helpful 46 | } 47 | 48 | let STORAGE_IMAGE_DIR:String = "images/" 49 | let STORAGE_DOCUMENT_DIR:String = "documents/" 50 | enum StorageFileType : String{ 51 | case JPG, PNG, PDF 52 | } 53 | 54 | struct StorageFileMetadata { 55 | var filename:String 56 | var fullpath:String 57 | var directory:String 58 | var contentType:String 59 | var type:StorageFileType 60 | var size:Int 61 | var url:URL? 62 | var description:String? 63 | } 64 | 65 | // getting an image requires restriction on anticipated image file size 66 | let IMG_SIZE_MAX:Int64 = 15 // megabytes 67 | 68 | 69 | class Fire { 70 | static let shared = Fire() 71 | 72 | let database: DatabaseReference = Database.database().reference() 73 | let storage = Storage.storage().reference() 74 | 75 | // can monitor, pause, resume the current upload task 76 | var currentUpload:StorageUploadTask? 77 | 78 | var myUID:String? // if you are logged in, if not, == nil 79 | 80 | fileprivate init() { 81 | // setup USER listener 82 | Auth.auth().addStateDidChangeListener { auth, listenerUser in 83 | if let user = listenerUser { 84 | print("SIGN IN: \(user.email ?? user.uid)") 85 | self.myUID = user.uid 86 | self.userExists(user, completionHandler: { (exists) in 87 | if(!exists){ 88 | self.newUser(user, completionHandler: nil) 89 | } 90 | }) 91 | } else { 92 | self.myUID = nil 93 | print("SIGN OUT: no user") 94 | } 95 | } 96 | 97 | let connectedRef = Database.database().reference(withPath: ".info/connected") 98 | connectedRef.observe(.value, with: { snapshot in 99 | if let connected = snapshot.value as? Bool , connected { 100 | // internet connected 101 | // banner alert 102 | } else { 103 | // internet disconnected 104 | // banner alert 105 | } 106 | }) 107 | } 108 | 109 | //////////////////////////////////////////////////////////////////////////////////////////// 110 | //////////////////////////////////////////////////////////////////////////////////////////// 111 | // 112 | // DATABASE 113 | 114 | // childURL = nil returns the root of the database 115 | // childURL can contain multiple subdirectories separated with a slash: "one/two/three" 116 | func getData(_ childURL:String?, completionHandler: @escaping (Any?) -> ()) { 117 | var reference = self.database 118 | if let url = childURL{ 119 | reference = self.database.child(url) 120 | } 121 | reference.observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 122 | completionHandler(snapshot.value) 123 | } 124 | } 125 | 126 | // add an object to the database at a childURL, function returns the auto-generated key to that object 127 | func setData(_ object:Any, at path:String, completionHandler: ((Bool, DatabaseReference) -> ())?) { 128 | self.database.child(path).setValue(object) { (error, ref) in 129 | if let e = error{ 130 | print(e.localizedDescription) 131 | if let completion = completionHandler{ 132 | completion(false, ref) 133 | } 134 | } else{ 135 | if let completion = completionHandler{ 136 | completion(true, ref) 137 | } 138 | } 139 | } 140 | } 141 | 142 | // add an object AS A CHILD to the path, returns the key to that object 143 | // ONLY if the object at path is a dictionary or array 144 | // if it is a leaf (String, Number, Bool) it doesn't do anything (prevents overwriting) 145 | func addData(_ object:Any, asChildAt path:String, completionHandler: ((_ success:Bool, _ newKey:String?, DatabaseReference?) -> ())?) { 146 | self.doesDataExist(at: path) { (exists, dataType, data) in 147 | switch dataType{ 148 | // 1) if array, it MAINTAINS the array structure (number key, not dictionary string key) 149 | case .isArray: 150 | let dbArray = data as! NSMutableArray 151 | dbArray.add(object) 152 | self.database.child(path).setValue(dbArray) { (error, ref) in 153 | if let e = error{ 154 | print(e.localizedDescription) 155 | if let completion = completionHandler{ 156 | completion(false, nil, nil) 157 | } 158 | } else{ 159 | if let completion = completionHandler{ 160 | completion(true, String(describing:dbArray.count-1), ref) 161 | } 162 | } 163 | } 164 | // 2) if dictionary, or doesn't exist, makes a new string key like usual 165 | case .isDictionary, .isNULL: 166 | self.database.child(path).childByAutoId().setValue(object) { (error, ref) in 167 | if let e = error{ 168 | print(e.localizedDescription) 169 | if let completion = completionHandler{ 170 | completion(false, nil, nil) 171 | } 172 | } else{ 173 | if let completion = completionHandler{ 174 | completion(true, ref.key, ref) 175 | } 176 | } 177 | } 178 | // 3) if object at path is a String or Int etc..(leaf node), return without doing anything 179 | default: 180 | if let completion = completionHandler{ 181 | completion(false, nil, nil); 182 | } 183 | } 184 | } 185 | } 186 | 187 | 188 | func doesDataExist(at path:String, completionHandler: @escaping (_ doesExist:Bool, _ dataType:JSONDataType, _ data:Any?) -> ()) { 189 | database.child(path).observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 190 | if let data = snapshot.value{ 191 | completionHandler(true, self.typeOf(FirebaseData: data), data) 192 | } else{ 193 | completionHandler(false, .isNULL, nil) 194 | } 195 | } 196 | } 197 | 198 | 199 | func typeOf(FirebaseData object:Any) -> JSONDataType { 200 | if object is NSNumber{ 201 | let nsnum = object as! NSNumber 202 | let boolID = CFBooleanGetTypeID() // the type ID of CFBoolean 203 | let numID = CFGetTypeID(nsnum) // the type ID of num 204 | if numID == boolID{ 205 | return .isBool 206 | } 207 | if nsnum.floatValue == Float(nsnum.intValue){ 208 | return .isInt 209 | } 210 | return .isFloat 211 | } else if object is String { 212 | if let url: URL = URL(string: object as! String) { 213 | if UIApplication.shared.canOpenURL(url){ 214 | return .isURL 215 | } else{ 216 | return .isString 217 | } 218 | } else{ 219 | return .isString 220 | } 221 | } else if object is NSArray || object is NSMutableArray{ 222 | return .isArray 223 | } else if object is NSDictionary || object is NSMutableDictionary{ 224 | return .isDictionary 225 | } 226 | return .isNULL 227 | } 228 | 229 | 230 | //////////////////////////////////////////////////////////////////////////////////////////// 231 | //////////////////////////////////////////////////////////////////////////////////////////// 232 | // 233 | // USER 234 | 235 | func getCurrentUser(_ completionHandler: @escaping (String, [String:Any]) -> ()) { 236 | guard let user = Auth.auth().currentUser else{ 237 | return 238 | } 239 | database.child("users").child(user.uid).observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 240 | if let userData = snapshot.value as? [String:Any]{ 241 | completionHandler(user.uid, userData) 242 | } else{ 243 | print("user has no data") 244 | } 245 | } 246 | } 247 | 248 | func getUser(UID:String, _ completionHandler: @escaping ([String:Any]) -> ()){ 249 | database.child("users").child(UID).observeSingleEvent(of: .value, with: { (snapshot) in 250 | if let userData = snapshot.value as? [String:Any]{ 251 | completionHandler(userData) 252 | } 253 | }) 254 | } 255 | 256 | func updateCurrentUserWith(key:String, object value:Any, completionHandler: ((_ success:Bool) -> ())? ) { 257 | guard let user = Auth.auth().currentUser else{ 258 | if let completion = completionHandler{ 259 | completion(false) 260 | } 261 | return 262 | } 263 | database.child("users").child(user.uid).updateChildValues([key:value]) { (error, ref) in 264 | if let e = error{ 265 | print(e.localizedDescription) 266 | if let completion = completionHandler{ 267 | completion(false) 268 | } 269 | } else{ 270 | // print("saving \(value) into \(key)") 271 | if let completion = completionHandler{ 272 | completion(true) 273 | } 274 | } 275 | } 276 | } 277 | 278 | func newUser(_ user:User, completionHandler: ((_ success:Bool) -> ())? ) { 279 | var newUser:[String:Any] = [ 280 | "createdAt": Date.init().timeIntervalSince1970 281 | ] 282 | // copy user data over from AUTH 283 | if let nameString = user.displayName { newUser["name"] = nameString } 284 | if let imageURL = user.photoURL { newUser["image"] = imageURL } 285 | if let emailString = user.email { newUser["email"] = emailString } 286 | 287 | database.child("users").child(user.uid).updateChildValues(newUser) { (error, ref) in 288 | if let e = error{ 289 | print(e.localizedDescription) 290 | if let completion = completionHandler{ 291 | completion(false) 292 | } 293 | } else{ 294 | if let completion = completionHandler{ 295 | completion(true) 296 | } 297 | } 298 | } 299 | } 300 | 301 | func userExists(_ user: User, completionHandler: @escaping (Bool) -> ()) { 302 | database.child("users").child(user.uid).observeSingleEvent(of: .value) { (snapshot: DataSnapshot) in 303 | if snapshot.value != nil{ 304 | completionHandler(true) 305 | } else{ 306 | completionHandler(false) 307 | } 308 | } 309 | } 310 | 311 | //////////////////////////////////////////////////////////////////////////////////////////// 312 | //////////////////////////////////////////////////////////////////////////////////////////// 313 | // 314 | // STORAGE 315 | 316 | // Key is filename in the images/ folder in the Firebase storage bucket 317 | // example: "0C5BABB0D5CA.jpg" 318 | var fileCache:[String:Data] = [:] 319 | 320 | func imageFromStorageBucket(_ filename: String, completionHandler: @escaping (_ image:UIImage, _ didRequireDownload:Bool) -> ()) { 321 | if let imageData = fileCache[filename]{ 322 | if let image = UIImage(data: imageData){ 323 | //TODO: check timestamp against database, force a data refresh 324 | completionHandler(image, false) 325 | return 326 | } 327 | } 328 | 329 | let storage = Storage.storage().reference() 330 | let imageRef = storage.child(STORAGE_IMAGE_DIR + filename) 331 | 332 | imageRef.getData(maxSize: IMG_SIZE_MAX * 1024 * 1024) { (data, error) in 333 | if let e = error{ 334 | print(e.localizedDescription) 335 | } else{ 336 | if let imageData = data { 337 | if let image = UIImage(data: imageData){ 338 | self.fileCache[filename] = imageData 339 | completionHandler(image, true) 340 | } else{ 341 | print("problem making image out of received data") 342 | } 343 | } 344 | } 345 | } 346 | } 347 | 348 | // specify a UUIDFilename, or it will generate one for you 349 | func uploadFileAndMakeRecord(_ data:Data, fileType:StorageFileType, description:String?, completionHandler: @escaping (_ metadata:StorageFileMetadata) -> ()) { 350 | 351 | // prep file info 352 | var filename:String = UUID.init().uuidString 353 | var storageDir:String 354 | let uploadMetadata = StorageMetadata() 355 | switch fileType { 356 | case .JPG: 357 | filename = filename + ".jpg" 358 | storageDir = STORAGE_IMAGE_DIR 359 | uploadMetadata.contentType = "image/jpeg" 360 | case .PNG: 361 | filename = filename + ".png" 362 | storageDir = STORAGE_IMAGE_DIR 363 | uploadMetadata.contentType = "image/png" 364 | case .PDF: 365 | filename = filename + ".pdf" 366 | storageDir = STORAGE_DOCUMENT_DIR 367 | uploadMetadata.contentType = "application/pdf" 368 | } 369 | let filenameAndPath:String = storageDir + filename 370 | 371 | // STEP 1 - upload file to storage 372 | // TODO: make currentUpload an array, if upload in progress add this to array 373 | currentUpload = storage.child(filenameAndPath).putData(data, metadata: uploadMetadata, completion: { (metadata, error) in 374 | if let e = error { 375 | print(e.localizedDescription) 376 | } else { 377 | // upload success, add file to cache 378 | self.fileCache[filename] = data 379 | if let meta = metadata{ 380 | // STEP 2 - record new file in database 381 | var entry:[String:Any] = ["filename":filename, 382 | "fullpath":filenameAndPath, 383 | "directory":storageDir, 384 | "content-type":uploadMetadata.contentType ?? "", 385 | "type":fileType.rawValue, 386 | "size":data.count] 387 | 388 | if let downloadURL = meta.downloadURL(){ 389 | entry["url"] = downloadURL.absoluteString 390 | } 391 | if let descriptionString = description{ 392 | entry["description"] = descriptionString 393 | } 394 | let key = self.database.child("files/" + storageDir).childByAutoId().key 395 | self.database.child("files/" + storageDir).updateChildValues([key:entry]) { (error, ref) in 396 | let info:StorageFileMetadata = StorageFileMetadata(filename: filename, fullpath: filenameAndPath, directory: storageDir, contentType: uploadMetadata.contentType ?? "", type: fileType, size: data.count, url: meta.downloadURL(), description: description) 397 | completionHandler(info) 398 | } 399 | } 400 | } 401 | }) 402 | } 403 | } 404 | 405 | 406 | 407 | extension UIImageView { 408 | 409 | public func imageFromStorage(_ filename: String){ 410 | // filename:String is the filename in the Firebase Storage bucket, no directories 411 | // example: "0C5BABB0D5CA.jpg" 412 | if let imageData = Fire.shared.fileCache[filename]{ 413 | if let image = UIImage(data: imageData){ 414 | self.image = image 415 | return 416 | } 417 | } 418 | let storage = Storage.storage().reference() 419 | let imageRef = storage.child("images/" + filename) 420 | imageRef.getData(maxSize: IMG_SIZE_MAX * 1024 * 1024) { (data, error) in 421 | if let e = error{ 422 | print(e.localizedDescription) 423 | } else{ 424 | if let imageData = data { 425 | if let image = UIImage(data: imageData){ 426 | Fire.shared.fileCache[filename] = imageData 427 | self.image = image 428 | } 429 | } 430 | } 431 | } 432 | } 433 | 434 | public func profileImageForUser(uid: String){ 435 | Fire.shared.getUser(UID: uid) { (userData) in 436 | if let imageFilename = userData["image"] as? String{ 437 | if let imageData = Fire.shared.fileCache[imageFilename]{ 438 | if let image = UIImage(data: imageData){ 439 | self.image = image 440 | return 441 | } 442 | } 443 | let storage = Storage.storage().reference() 444 | let imageRef = storage.child("images/" + imageFilename) 445 | imageRef.getData(maxSize: IMG_SIZE_MAX * 1024 * 1024) { (data, error) in 446 | if let e = error{ 447 | print(e.localizedDescription) 448 | } else{ 449 | if let imageData = data { 450 | if let image = UIImage(data: imageData){ 451 | Fire.shared.fileCache[imageFilename] = imageData 452 | self.image = image 453 | } 454 | } 455 | } 456 | } 457 | } 458 | } 459 | } 460 | 461 | public func imageFromUrl(_ urlString: String) { 462 | if let url = URL(string: urlString) { 463 | let request:URLRequest = URLRequest(url: url) 464 | let session:URLSession = URLSession.shared 465 | let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in 466 | DispatchQueue.main.async { 467 | if let imageData = data as Data? { 468 | self.image = UIImage(data: imageData) 469 | } 470 | } 471 | }) 472 | task.resume() 473 | } 474 | } 475 | 476 | } 477 | -------------------------------------------------------------------------------- /Login/Login.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6CAB172B2428740D07A3DB7C /* Pods_Login.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE3AE4BCC15AA67C8518263F /* Pods_Login.framework */; }; 11 | D523C30F1DA5807300490E71 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C30E1DA5807300490E71 /* AppDelegate.swift */; }; 12 | D523C3161DA5807300490E71 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D523C3151DA5807300490E71 /* Assets.xcassets */; }; 13 | D523C3191DA5807300490E71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D523C3171DA5807300490E71 /* LaunchScreen.storyboard */; }; 14 | D523C3211DA580E600490E71 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D523C3201DA580E600490E71 /* GoogleService-Info.plist */; }; 15 | D523C3281DA5814C00490E71 /* Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C3231DA5814C00490E71 /* Fire.swift */; }; 16 | D523C3291DA5814C00490E71 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C3241DA5814C00490E71 /* LoginViewController.swift */; }; 17 | D523C32A1DA5814C00490E71 /* MasterNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C3251DA5814C00490E71 /* MasterNavigationController.swift */; }; 18 | D523C32B1DA5814C00490E71 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D523C3261DA5814C00490E71 /* ProfileViewController.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 17AA2B5ECF045427A2673B06 /* Pods-Login.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Login.release.xcconfig"; path = "Pods/Target Support Files/Pods-Login/Pods-Login.release.xcconfig"; sourceTree = ""; }; 23 | D523C30B1DA5807300490E71 /* Login.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Login.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | D523C30E1DA5807300490E71 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | D523C3151DA5807300490E71 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | D523C3181DA5807300490E71 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | D523C31A1DA5807300490E71 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | D523C3201DA580E600490E71 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 29 | D523C3231DA5814C00490E71 /* Fire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fire.swift; sourceTree = ""; }; 30 | D523C3241DA5814C00490E71 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 31 | D523C3251DA5814C00490E71 /* MasterNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterNavigationController.swift; sourceTree = ""; }; 32 | D523C3261DA5814C00490E71 /* ProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; 33 | DE3AE4BCC15AA67C8518263F /* Pods_Login.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Login.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | EEAAF9767C02C5D6FD18CF73 /* Pods-Login.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Login.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Login/Pods-Login.debug.xcconfig"; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | D523C3081DA5807300490E71 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | 6CAB172B2428740D07A3DB7C /* Pods_Login.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | B2C185232ABD589F5A978A0A /* Pods */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | EEAAF9767C02C5D6FD18CF73 /* Pods-Login.debug.xcconfig */, 53 | 17AA2B5ECF045427A2673B06 /* Pods-Login.release.xcconfig */, 54 | ); 55 | name = Pods; 56 | sourceTree = ""; 57 | }; 58 | D523C3021DA5807300490E71 = { 59 | isa = PBXGroup; 60 | children = ( 61 | D523C30D1DA5807300490E71 /* Login */, 62 | D523C30C1DA5807300490E71 /* Products */, 63 | B2C185232ABD589F5A978A0A /* Pods */, 64 | FDC91438E70CC481142F7DDB /* Frameworks */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | D523C30C1DA5807300490E71 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | D523C30B1DA5807300490E71 /* Login.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | D523C30D1DA5807300490E71 /* Login */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | D523C30E1DA5807300490E71 /* AppDelegate.swift */, 80 | D523C3231DA5814C00490E71 /* Fire.swift */, 81 | D523C3241DA5814C00490E71 /* LoginViewController.swift */, 82 | D523C3251DA5814C00490E71 /* MasterNavigationController.swift */, 83 | D523C3261DA5814C00490E71 /* ProfileViewController.swift */, 84 | D523C3151DA5807300490E71 /* Assets.xcassets */, 85 | D523C3171DA5807300490E71 /* LaunchScreen.storyboard */, 86 | D523C3201DA580E600490E71 /* GoogleService-Info.plist */, 87 | D523C31A1DA5807300490E71 /* Info.plist */, 88 | ); 89 | path = Login; 90 | sourceTree = ""; 91 | }; 92 | FDC91438E70CC481142F7DDB /* Frameworks */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | DE3AE4BCC15AA67C8518263F /* Pods_Login.framework */, 96 | ); 97 | name = Frameworks; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXNativeTarget section */ 103 | D523C30A1DA5807300490E71 /* Login */ = { 104 | isa = PBXNativeTarget; 105 | buildConfigurationList = D523C31D1DA5807300490E71 /* Build configuration list for PBXNativeTarget "Login" */; 106 | buildPhases = ( 107 | F9B7291D694139980AC75FDE /* [CP] Check Pods Manifest.lock */, 108 | D523C3071DA5807300490E71 /* Sources */, 109 | D523C3081DA5807300490E71 /* Frameworks */, 110 | D523C3091DA5807300490E71 /* Resources */, 111 | 41773AB43F1BAD61B5DBC3DC /* [CP] Embed Pods Frameworks */, 112 | 5CA941367A70697A759D8936 /* [CP] Copy Pods Resources */, 113 | ); 114 | buildRules = ( 115 | ); 116 | dependencies = ( 117 | ); 118 | name = Login; 119 | productName = Login; 120 | productReference = D523C30B1DA5807300490E71 /* Login.app */; 121 | productType = "com.apple.product-type.application"; 122 | }; 123 | /* End PBXNativeTarget section */ 124 | 125 | /* Begin PBXProject section */ 126 | D523C3031DA5807300490E71 /* Project object */ = { 127 | isa = PBXProject; 128 | attributes = { 129 | LastSwiftUpdateCheck = 0800; 130 | LastUpgradeCheck = 0830; 131 | ORGANIZATIONNAME = "Robby Kraft"; 132 | TargetAttributes = { 133 | D523C30A1DA5807300490E71 = { 134 | CreatedOnToolsVersion = 8.0; 135 | ProvisioningStyle = Automatic; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = D523C3061DA5807300490E71 /* Build configuration list for PBXProject "Login" */; 140 | compatibilityVersion = "Xcode 3.2"; 141 | developmentRegion = English; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = D523C3021DA5807300490E71; 148 | productRefGroup = D523C30C1DA5807300490E71 /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | D523C30A1DA5807300490E71 /* Login */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | D523C3091DA5807300490E71 /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | D523C3191DA5807300490E71 /* LaunchScreen.storyboard in Resources */, 163 | D523C3211DA580E600490E71 /* GoogleService-Info.plist in Resources */, 164 | D523C3161DA5807300490E71 /* Assets.xcassets in Resources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXResourcesBuildPhase section */ 169 | 170 | /* Begin PBXShellScriptBuildPhase section */ 171 | 41773AB43F1BAD61B5DBC3DC /* [CP] Embed Pods Frameworks */ = { 172 | isa = PBXShellScriptBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | ); 176 | inputPaths = ( 177 | ); 178 | name = "[CP] Embed Pods Frameworks"; 179 | outputPaths = ( 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | shellPath = /bin/sh; 183 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Login/Pods-Login-frameworks.sh\"\n"; 184 | showEnvVarsInLog = 0; 185 | }; 186 | 5CA941367A70697A759D8936 /* [CP] Copy Pods Resources */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "[CP] Copy Pods Resources"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Login/Pods-Login-resources.sh\"\n"; 199 | showEnvVarsInLog = 0; 200 | }; 201 | F9B7291D694139980AC75FDE /* [CP] Check Pods Manifest.lock */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputPaths = ( 207 | ); 208 | name = "[CP] Check Pods Manifest.lock"; 209 | outputPaths = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | shellPath = /bin/sh; 213 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 214 | showEnvVarsInLog = 0; 215 | }; 216 | /* End PBXShellScriptBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | D523C3071DA5807300490E71 /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | D523C32A1DA5814C00490E71 /* MasterNavigationController.swift in Sources */, 224 | D523C30F1DA5807300490E71 /* AppDelegate.swift in Sources */, 225 | D523C32B1DA5814C00490E71 /* ProfileViewController.swift in Sources */, 226 | D523C3291DA5814C00490E71 /* LoginViewController.swift in Sources */, 227 | D523C3281DA5814C00490E71 /* Fire.swift in Sources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXSourcesBuildPhase section */ 232 | 233 | /* Begin PBXVariantGroup section */ 234 | D523C3171DA5807300490E71 /* LaunchScreen.storyboard */ = { 235 | isa = PBXVariantGroup; 236 | children = ( 237 | D523C3181DA5807300490E71 /* Base */, 238 | ); 239 | name = LaunchScreen.storyboard; 240 | sourceTree = ""; 241 | }; 242 | /* End PBXVariantGroup section */ 243 | 244 | /* Begin XCBuildConfiguration section */ 245 | D523C31B1DA5807300490E71 /* Debug */ = { 246 | isa = XCBuildConfiguration; 247 | buildSettings = { 248 | ALWAYS_SEARCH_USER_PATHS = NO; 249 | CLANG_ANALYZER_NONNULL = YES; 250 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 251 | CLANG_CXX_LIBRARY = "libc++"; 252 | CLANG_ENABLE_MODULES = YES; 253 | CLANG_ENABLE_OBJC_ARC = YES; 254 | CLANG_WARN_BOOL_CONVERSION = YES; 255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 264 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 268 | COPY_PHASE_STRIP = NO; 269 | DEBUG_INFORMATION_FORMAT = dwarf; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | ENABLE_TESTABILITY = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 282 | GCC_WARN_UNDECLARED_SELECTOR = YES; 283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 284 | GCC_WARN_UNUSED_FUNCTION = YES; 285 | GCC_WARN_UNUSED_VARIABLE = YES; 286 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 287 | MTL_ENABLE_DEBUG_INFO = YES; 288 | ONLY_ACTIVE_ARCH = YES; 289 | SDKROOT = iphoneos; 290 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 291 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 292 | TARGETED_DEVICE_FAMILY = "1,2"; 293 | }; 294 | name = Debug; 295 | }; 296 | D523C31C1DA5807300490E71 /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_ANALYZER_NONNULL = YES; 301 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 302 | CLANG_CXX_LIBRARY = "libc++"; 303 | CLANG_ENABLE_MODULES = YES; 304 | CLANG_ENABLE_OBJC_ARC = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_CONSTANT_CONVERSION = YES; 307 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 308 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INFINITE_RECURSION = YES; 312 | CLANG_WARN_INT_CONVERSION = YES; 313 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 314 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 315 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 316 | CLANG_WARN_UNREACHABLE_CODE = YES; 317 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 318 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 319 | COPY_PHASE_STRIP = NO; 320 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 321 | ENABLE_NS_ASSERTIONS = NO; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | GCC_C_LANGUAGE_STANDARD = gnu99; 324 | GCC_NO_COMMON_BLOCKS = YES; 325 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 326 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 327 | GCC_WARN_UNDECLARED_SELECTOR = YES; 328 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 329 | GCC_WARN_UNUSED_FUNCTION = YES; 330 | GCC_WARN_UNUSED_VARIABLE = YES; 331 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 332 | MTL_ENABLE_DEBUG_INFO = NO; 333 | SDKROOT = iphoneos; 334 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 335 | TARGETED_DEVICE_FAMILY = "1,2"; 336 | VALIDATE_PRODUCT = YES; 337 | }; 338 | name = Release; 339 | }; 340 | D523C31E1DA5807300490E71 /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | baseConfigurationReference = EEAAF9767C02C5D6FD18CF73 /* Pods-Login.debug.xcconfig */; 343 | buildSettings = { 344 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 345 | INFOPLIST_FILE = Login/Info.plist; 346 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 347 | PRODUCT_BUNDLE_IDENTIFIER = com.robbykraft.Login; 348 | PRODUCT_NAME = "$(TARGET_NAME)"; 349 | SWIFT_VERSION = 3.0; 350 | }; 351 | name = Debug; 352 | }; 353 | D523C31F1DA5807300490E71 /* Release */ = { 354 | isa = XCBuildConfiguration; 355 | baseConfigurationReference = 17AA2B5ECF045427A2673B06 /* Pods-Login.release.xcconfig */; 356 | buildSettings = { 357 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 358 | INFOPLIST_FILE = Login/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 360 | PRODUCT_BUNDLE_IDENTIFIER = com.robbykraft.Login; 361 | PRODUCT_NAME = "$(TARGET_NAME)"; 362 | SWIFT_VERSION = 3.0; 363 | }; 364 | name = Release; 365 | }; 366 | /* End XCBuildConfiguration section */ 367 | 368 | /* Begin XCConfigurationList section */ 369 | D523C3061DA5807300490E71 /* Build configuration list for PBXProject "Login" */ = { 370 | isa = XCConfigurationList; 371 | buildConfigurations = ( 372 | D523C31B1DA5807300490E71 /* Debug */, 373 | D523C31C1DA5807300490E71 /* Release */, 374 | ); 375 | defaultConfigurationIsVisible = 0; 376 | defaultConfigurationName = Release; 377 | }; 378 | D523C31D1DA5807300490E71 /* Build configuration list for PBXNativeTarget "Login" */ = { 379 | isa = XCConfigurationList; 380 | buildConfigurations = ( 381 | D523C31E1DA5807300490E71 /* Debug */, 382 | D523C31F1DA5807300490E71 /* Release */, 383 | ); 384 | defaultConfigurationIsVisible = 0; 385 | defaultConfigurationName = Release; 386 | }; 387 | /* End XCConfigurationList section */ 388 | }; 389 | rootObject = D523C3031DA5807300490E71 /* Project object */; 390 | } 391 | --------------------------------------------------------------------------------