├── 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 | 
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 | 
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 | 
25 |
26 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------