In-App purchase restore button restores non purchased In-Apps - arrays

I am very lost when it comes to in app purchases. I found a very solid post on stack overflow that has been helpful. [Stack Post][1] I am still having issues after this though. If I have an in app purchase thats never been bought before and I tap the restore button it will unlock it anyway. Any help would be amazing thanks.
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
#IBOutlet weak var nonConsumableLabel: UILabel!
let id_sold = "id_sold"
var productID = ""
var productsRequest = SKProductsRequest()
var iapProducts = [SKProduct]()
var nonConsumablePurchaseMade = UserDefaults.standard.bool(forKey: "nonConsumablePurchaseMade")
override func viewDidLoad() {
super.viewDidLoad()
// Check your In-App Purchases
print("NON CONSUMABLE PURCHASE MADE: \(nonConsumablePurchaseMade)")
if nonConsumablePurchaseMade { nonConsumableLabel.text = "Premium version PURCHASED!"
} else { nonConsumableLabel.text = "Premium version LOCKED!"}
// Fetch IAP Products available
fetchAvailableProducts()
}
#IBAction func nonConsumableBtn(_ sender: Any) {
purchaseMyProduct(product: iapProducts[0])
}
#IBAction func printer(_ sender: Any) {
print(iapProducts)
}
// MARK: - RESTORE NON-CONSUMABLE PURCHASE BUTTON
#IBAction func restorePurchaseButt(_ sender: Any) {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
let alert = UIAlertController(title: "IAP Tutorial", message: "You've successfully restored your purchase!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.present(alert, animated: true)
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "id_sold":
// implement the given in-app purchase as if it were bought
print("Second IAP restored")
default:
print("iap not found")
}
}
}
}
// MARK: - FETCH AVAILABLE IAP PRODUCTS
func fetchAvailableProducts() {
// Put here your IAP Products ID's
let productIdentifiers = NSSet(objects:
id_sold
)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
// MARK: - REQUEST IAP PRODUCTS
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
if (response.products.count > 0) {
iapProducts = response.products
// Get its price from iTunes Connect
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = .behavior10_4
numberFormatter.numberStyle = .currency
// 2nd IAP Product (Non-Consumable) ------------------------------
let secondProd = response.products[0] as SKProduct
// Get its price from iTunes Connect
numberFormatter.locale = secondProd.priceLocale
}
}
// MARK: - MAKE PURCHASE OF A PRODUCT
func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() }
func purchaseMyProduct(product: SKProduct) {
if self.canMakePurchases() {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
print("PRODUCT TO PURCHASE: \(product.productIdentifier)")
productID = product.productIdentifier
// IAP Purchases dsabled on the Device
} else {
let alert = UIAlertController(title: "IAP Tutorial", message: "You've successfully restored your purchase!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
// MARK:- IAP PAYMENT QUEUE
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
if productID == id_sold {
// Save your purchase locally (needed only for Non-Consumable IAP)
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
nonConsumableLabel.text = "Premium version PURCHASED!"
let alert = UIAlertController(title: "IAP Tutorial", message: "You've successfully unlocked the Premium version!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
default: break
}
}
}
}

You are saying
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
}
But that method is called even if nothing was restored. All it means is that the restoration interaction is over, not that it succeeded. But you are going ahead and acting as if it succeeded, and awarding your user the magic user defaults key.

Related

Get document for each user from Firestore Swift

Im trying to get document for each user login to his account , i have two collections in Firestore one called ( firstName ) and the second one called ( lastName ) and i want from user 1 to get ( firstName ) collection only and user number 2 to get ( lastName ) collection only.
i tried with if statement but it doesn't work with me .
import UIKit
import FirebaseFirestore
import Firebase
import FirebaseAuth
class namesTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet var names: UITableView!
var db: Firestore!
var firstName = [String]()
var lastName = [String]()
override func viewDidLoad() {
super.viewDidLoad()
names.register(UINib(nibName: "Order1TableViewCell", bundle: nil) , forCellReuseIdentifier: "order")
names.dataSource = self
names.delegate = self
db = Firestore.firestore()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let viewController1 = storyboard.instantiateViewController(withIdentifier: "login") as? logInViewController else{return}
viewController1.userNameField?.delegate = self
if viewController1.userNameField?.text == “1#1.com" {
loadData1()
}
if viewController1.userNameField?.text == “2#2.com” {
loadData2()
}
func loadData1() {
db.collection("firstName").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
self.firstName.append(document.get("firstname") as? String ?? "")
self.lastName.append(document.get("lastname") as? String ?? "")
}
}
self.names.reloadData()
}
}
func loadData2() {
db.collection("lastName").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
self.firstName.append(document.get("firstname") as? String ?? "")
self.lastName.append(document.get("lastname") as? String ?? "")
}
}
self.names.reloadData()
}
}
}
this is logInViewController code
import UIKit
import Firebase
import FirebaseAuth
class logInViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var userNameField: UITextField!
#IBOutlet var passwordField: UITextField!
#IBOutlet var logInButton: UIButton!
override func viewDidLoad() {
logInButton.layer.cornerRadius = 4.0
super.viewDidLoad()
userNameField.delegate = self
passwordField.delegate = self
// Do any additional setup after loading the view.
}
#IBAction func logInButtonClicked(_ sender: Any) {
Auth.auth().signIn(withEmail: (userNameField.text ?? ""), password: (passwordField.text ?? "")) { (result, error) in
if let _eror = error{
let alert = UIAlertController(title: "Error", message: error!.localizedDescription, preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(okAction)
self.present(alert,animated: true)
print(_eror.localizedDescription)
}else{
if let _res = result{
print(_res)
}
let VC1 = self.storyboard!.instantiateViewController(withIdentifier: "order1") as! namesTableViewController
self.navigationController!.pushViewController(VC1, animated: true)
}
}
}
}
this is fire store structure
image
Be careful by doing let view1 = firstViewController() you are not grabbing the correct instance of firstViewController but one you just created. User.text therefore won't be set. You need to grab the right instance of it, if you are using storyboard you can do:
let storyboard = UIStoryboard(name: "nameofstoryboard", bundle: nil)
guard let viewController1 = storyboard.instantiateViewController(withIdentifier: "myIdentifier") as? firstViewController else{return} //set VC Identifier in Storyboard identity inspector
Then you can check the User property with if-statement

My firebase data is geting fetched but is not appending into my array? [duplicate]

Currently I am attempting to push values into an array of note objects from firebase
The only issue is due to Firebases asynchronous nature, I am having trouble getting the main thread to wait until the fetch function is completed. I have viewed many answers on this site and I have read up on the Semaphores and Dispatch queue documentation however I cannot get this fetch to work. It appears that most of the people here are attempting to use a table view which I am not.
Here is the fetch code
func fetchUser(){
FIRDatabase.database().reference().child("notes").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = noteClass(dictionary: dictionary)
self.coordinotes.append(user)
}
}, withCancel: nil)
}
I have removed all of my semaphore and dispatch main attempts due to none of them working. This function is called in my view did load. When i check the values of my array that i push them into 'coordinotes' the value is not yet placed in and i get an out of bounds error.
Rest of code
import UIKit
import MapKit
import CoreLocation
import Firebase
import FirebaseDatabase
struct PreferencesKeys{
static let savedItems = "savedItems"
}
class ViewController: UIViewController, CLLocationManagerDelegate{
let manager = CLLocationManager()
var coordinotes:[noteClass] = Array()
var latitude = Double()
var noteTime = noteBrain()
//Map
#IBOutlet weak var map: MKMapView!
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations[0]
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, noteTime.span)
map.setRegion(region, animated: true)
self.map.showsUserLocation = true
}
override func viewDidLoad()
{
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(handleLogout))
if FIRAuth.auth()?.currentUser?.uid == nil {
perform(#selector(handleLogout), with: nil, afterDelay: 0)
}
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
fetchUser()
loadAllCoordinotes()
}
func handleLogout() {
do {
try FIRAuth.auth()?.signOut()
} catch let logoutError {
print(logoutError)
}
let loginController = LoginController()
present(loginController, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func loadAllCoordinotes() {
let length = coordinotes.count - 1
map.addAnnotation(coordinotes[length])
}
func fetchUser(_ completion:#escaping ([noteClass] , _ success: Bool)-> Void){
let coordinotes = [noteClass]()
FIRDatabase.database().reference().child("notes").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = noteClass(dictionary: dictionary)
coordinotes.append(user)
}
completion(coordinotes, true)
}, withCancel: nil)
}
and then you call it in viewDidLoad like this:
fetchUser { (coordinotes, success) in
if success {
self.coordinotes = coordinote
self.loadAllCoordinotes()
}
}

Out of index range when grab string from array in collection view

I want my app to show data from mysql database in collectionview so i write down the mysql connection code in viewDidload() and make it in array format everything work fine except for showing data in collectionview Here is my code
var subjectlist = ""
var imgurllist = ""
var subjectarray = [String]()
var imgurllistarray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
collectionview.dataSource = self
collectionview.delegate = self
let request = NSMutableURLRequest(url: NSURL(string: [Point to file on my server])! as URL)
let urlsession = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
DispatchQueue.main.async {
do {
if let dataunwarp = data {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
if let subjectnotfound = parseJSON["subjectnotfound"]{
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "ไม่พบ subject ในเซิฟเวอร์ กรุณาลองใหม่อีกครั้ง", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
} else {
self.subjectlist = parseJSON["subjectlist"] as! String
self.imgurllist = parseJSON["imgurllist"] as! String
self.subjectarray = Array(self.subjectlist.components(separatedBy: ","))
print("subjectarray=\(self.subjectarray)")
print(self.subjectarray[0])
//self.imgurllistarray = Array(self.imgurllist.components(separatedBy: ","))
}
} else {
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "เกิดข้อผิดพลาดจากเซิฟเวอร์", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
}
} else {
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "เกิดข้อผิดพลาดจากเซิฟเวอร์", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
}
} catch {
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "ไม่สามารถติดต่อกับเซิฟเวอร์ได้ กรุณาตรวจสอบการเชื่อมต่ออินเตอร์เน็ตเเละลองใหม่อีกครั้ง", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
}
}})
urlsession.resume()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return subject.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! collectionviewcell
let subjectimgurl = NSMutableURLRequest(url: NSURL(string: "https://psmapps.com/psmatstamp/images/atstamp.png")! as URL)
let imgsession = URLSession.shared.dataTask(with: subjectimgurl as URLRequest, completionHandler: {data, response, error -> Void in
if let e = error {
print("Error downloading picture: \(e)")
} else {
// No errors found.
// It would be weird if we didn't have a response, so check for that too.
if let res = response as? HTTPURLResponse {
if let imageData = data {
// Finally convert that Data into an image and do what you wish with it.
let subjectimg = UIImage(data: imageData)
DispatchQueue.main.async {
cell.subjectimage.image = subjectimg
}
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
})
imgsession.resume()
cell.subjectlabel.text = subjectarray[indexPath.item]
print(self.subjectarray[indexPath.item])
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.layer.borderWidth = 0.5
return cell
}
An error occurred in this line before this view presented
cell.subjectlabel.text = subjectarray[indexPath.item]
with error message: "Index out of range" but when i uncomment the line that error occurred and print out the data from subjectarray, i can see my data with array type
How can i fix this problem?
Inside numberOfItemsInSection
return subject.count
Then inside cellForItemAt you use
subjectarray[indexPath.item]
Is there a guarantee that both will be same size at a time , also use SDWebImage to load images
Also after you get the data reload
self.subjectarray = Array(self.subjectlist.components(separatedBy: ","))
self.collectionView.reloadData()
plus spreading UIAlertController everywhere isn't a good way for compact code , consider making an extension / function and call it with the appropriate message

Passing global Arrays through functions is not working in swift

I have this program which takes values from firestore and puts them in an array. I have confirmed that these arrays have data inside of them by using test print functions. When I declare 2 global arrays ( gloabalGPA and globalSAT) and pass the value of the gpaColleges and the satColleges into them through a function everything works. I placed test print functions inside of the functions (the swithcSATArray and the switchGPAArray). However when I attempt to print these global variables again in a different function the print function prints out an empty array. like so: ( [] )
import UIKit
import FirebaseAuth
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class ScoresViewController: UIViewController {
var docRef: DocumentReference!
let defaultStore = Firestore.firestore()
var globalGPA = [String]()
var globalSAT = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let userID: String = (Auth.auth().currentUser?.uid)!
docRef = Firestore.firestore().document("Users/\(userID)")
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBOutlet weak var GpaScore: UITextField!
#IBOutlet weak var SATscore: UITextField!
#IBOutlet weak var ACT_Score: UITextField!
#IBAction func SubmitTapped(_ sender: Any) {
print("Submit Tapped")
let Sattext = SATscore.text
let Acttext = ACT_Score.text
let Gpatext = GpaScore.text
let gpaScore = Gpatext
let SatScore2 = Sattext
let Acttext2 = Acttext
let CombinedScores = Sattext! + Acttext!
if GpaScore.text == "" {
self.createAlert(titleText: "Error", messageText: "No Weighted GPA Entered")
}
else if CombinedScores == "" {
self.createAlert(titleText: "Error", messageText: "No SAT nor ACT Score Entered")
}
else{
let dataToSave: [String: Any] = ["GPA": gpaScore!, "SAT Score": SatScore2!, "ACT Score": Acttext2!]
docRef.setData(dataToSave) { (error) in
if let error = error {
print("error in sending data to fireStore: \(error.localizedDescription)")
}else {
print("Data was succesfully saved to FireStore")
}
}
self.presentLoggedInScreen()
sendToFireStore(gpa: gpaScore!, sat: SatScore2!)
self.addArrays()
}
}
func createAlert (titleText : String , messageText: String) {
let alert = UIAlertController (title: titleText, message: messageText, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Dissmis", style: .default, handler: { (action) in alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
func presentLoggedInScreen() {
let storyboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let HomeVC:HomeVC = storyboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeVC
self.present(HomeVC, animated: true, completion: nil)
}
func sendToFireStore(gpa: String, sat: String) {
let db = Firestore.firestore()
var gpaColleges = [String]()
let gpaRef = db.collection("Colleges")
let query1 = gpaRef
.whereField("Average GPA", isLessThanOrEqualTo: gpa)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
gpaColleges.append(document.documentID)
}
self.switchGPAArray(gpa: gpaColleges)
}
}
var satColleges = [String]()
let satRef = db.collection("Colleges")
let query2 = satRef
.whereField("Average SAT Score", isLessThanOrEqualTo: sat)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
satColleges.append(document.documentID)
}
self.swithcSATArray(sat: satColleges)
}
}
}
func swithcSATArray(sat: Array<Any>) {
self.globalSAT = sat as! [String]
print("Printing inside of SAT function \(self.globalSAT)")
}
func switchGPAArray(gpa: Array<Any>) {
self.globalGPA = gpa as! [String]
print("Printing inside of GPA funtion \(self.globalGPA)")
}
func addArrays() {
print("INSIDE OF ADD ARRAYS SAT \(self.globalSAT)")
print("INSIDE OF ADD ARRAYS GPA \(self.globalSAT)")
}
}
Any help would be greatly appreciated.
First of all there is lot of code but little information about your problem. We don't even know which function you call first and what is the second one that prints empty?
I can edit this answer after you provide more details. But as far as I see, you have closures and the compiler won't wait closure to complete, to run the next line. If this is the problem you can use threads and run your code in main thread so you can be sure it is completed. Or you can use delegate pattern to be notified when it's complete.
Or just do this:
let dataToSave: [String: Any] = ["GPA": gpaScore!, "SAT Score": SatScore2!, "ACT Score": Acttext2!]
docRef.setData(dataToSave) { (error) in
if let error = error {
print("error in sending data to fireStore: \(error.localizedDescription)")
}else {
self.presentLoggedInScreen()
sendToFireStore(gpa: gpaScore!, sat: SatScore2!)
self.addArrays()
print("Data was succesfully saved to FireStore")
}
}
If no one above works, provide more details about your issue! :)

tableview duplicating data on back button

I am creating individual members in my app, and everything works well. I validate user input, store it in a local array, and then pass the data on to Firebase when I tap 'next'. The app takes me from Step 2 to Step 3. So far so good.
This is what Step 2 looks like if I load the app from scratch: Step 2 Existing Users. The users are pulled from Firebase, and everything is correct.
If I add a new user, everything updates fine. I tap the 'next' button, and Firebase updates with 3 users, all with correct data, and I'm taken to Step 3. All is good still.
But if I tap the 'back' button in the upper left corner of Step 3 (provided by my navigation controller), the app takes me back to Step 2 and now a duplicate entry of the new user shows up in my tableview. Somehow a duplicate user is appended to my array. This only shows up AFTER I click the 'back' button. It doesn't show up in Firebase, but I don't know where it's coming from. I've spent all day trying to figure this out. I'm sure it's a simple problem that I'm overlooking, but I could really use the community's help on this one.
I've looked at this but it didn't help me. I did some tests where I printed out users.count and found that on ViewDidLoad, the count was 0, but on the unwind segue, the count updated to 3 when I added a new user (this was correct, because there were 3 users). But then when I went to Step 3 and tapped the 'back' button to get back to Step 2, Step 2's ViewDidLoad showed a users.count of 4. I have no idea why. I'm completely stumped.
Here is my Step 2 View Controller code in its entirety:
class Step2VC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var usersTableView: UITableView!
var users = [User]() // create variable called 'users' which is an array of type User (which is a class we created)
var firebaseUser: FIRUser!
var firebaseStorage: FIRStorage!
var ref: FIRDatabaseReference!
var cellStyleForEditing: UITableViewCellEditingStyle = .none
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "users"
usersTableView.dataSource = self
usersTableView.delegate = self
usersTableView.tableFooterView = UIView()
// --------
// Firebase
// --------
firebaseUser = FIRAuth.auth()?.currentUser
firebaseStorage = FIRStorage.storage()
ref = FIRDatabase.database().reference().child("users").child(firebaseUser.uid)
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "edit", style: .plain, target: self, action: #selector(editButtonTapped))
loadExistingUsers() // check to see if there are existing users, and if so, load them into tableview
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
usersTableView.reloadData()
}
// ----------
// Table View
// ----------
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! Step2Cell
cell.myLabel.text = users[indexPath.row].firstName
cell.userImage.image = users[indexPath.row].photo
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deleteUserConfirmationAlert(tableViewIndexPath: indexPath)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "EditUser", sender: users[indexPath.row])
}
// ----------
// Navigation
// ----------
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditUser" {
let nextContoller = segue.destination as! Step2UsersVC
// 'sender' is retrieved from 'didSelectRow' function above
nextContoller.user = sender as? User
nextContoller.navBarTitle = "edit user"
} else if segue.identifier == "AddUser" {
let nextController = segue.destination as! Step2UsersVC
nextController.navBarTitle = "add user"
} else {
print("Segue Initiated:",segue.identifier!)
}
}
#IBAction func unwindToStep2VC(sender: UIStoryboardSegue) {
let sourceVC = sender.source as! Step2UsersVC
let updatedUser = sourceVC.user
if let selectedIndexPath = usersTableView.indexPathForSelectedRow {
// Update an existing user
users[selectedIndexPath.row] = updatedUser!
usersTableView.reloadData()
} else {
// Add a new user
let newIndexPath = IndexPath(row: users.count, section: 0)
users.append(updatedUser!)
usersTableView.insertRows(at: [newIndexPath], with: .automatic)
users.sort(by: {$0.birthday < $1.birthday})
usersTableView.reloadData()
}
}
#IBAction func nextButtonTapped(_ sender: UIButton) {
// check for at least two users
if users.count < 2 {
createAlert(alertTitle: "Users", alertMessage: "You have not created enough users. Please enter in at least two users.")
} else {
// check for at least one parent
if numberOfParents() < 1 {
createAlert(alertTitle: "Users", alertMessage: "You must have at least one parent. Please enter in a parent.")
} else {
confirmationAlert()
}
}
}
// ---------
// Functions
// ---------
// if users exist on Firebase, load them
func loadExistingUsers() {
ref.child("members").observe(.childAdded) { (snapshot: FIRDataSnapshot) in
if let dict = snapshot.value as? [String : Any] {
let userPhotoUrl = dict["profileImageUrl"] as! String
let userFirstName = dict["firstName"] as! String
let userBirthday = dict["birthday"] as! Int
let userPasscode = dict["passcode"] as! Int
let userGender = dict["gender"] as! String
let isUserChildOrParent = dict["childParent"] as! String
let storageRef = FIRStorage.storage().reference(forURL: userPhotoUrl)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
let pic = UIImage(data: data!)
let user = User(profilePhoto: pic!,
userFirstName: userFirstName,
userBirthday: userBirthday,
userPasscode: userPasscode,
userGender: userGender,
isUserChildOrParent: isUserChildOrParent)
self.users.append(user)
self.users.sort(by: {$0.birthday < $1.birthday})
self.usersTableView.reloadData()
})
}
}
}
func saveUsersToFirebase() {
for user in users {
let storageRef = FIRStorage.storage().reference().child("users").child(firebaseUser.uid).child("members").child(user.firstName)
let profileImg = user.photo
let imageData = UIImageJPEGRepresentation(profileImg, 0.1) // compress photos
storageRef.put(imageData!, metadata: nil, completion: { (metadata, error) in
if error != nil {
return
}
// get Firebase image location and return the URL as a string
let profileImageUrl = (metadata?.downloadURL()?.absoluteString)!
// save user data to Firebase
self.ref?.child("members").child(user.firstName).setValue(["profileImageUrl" : profileImageUrl,
"firstName" : user.firstName,
"birthday" : user.birthday,
"passcode" : user.passcode,
"gender" : user.gender,
"childParent" : user.childParent])
})
}
}
func editButtonTapped() {
if cellStyleForEditing == .none {
cellStyleForEditing = .delete
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "done", style: .done, target: self, action: #selector(editButtonTapped))
} else {
cellStyleForEditing = .none
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "edit", style: .plain, target: self, action: #selector(editButtonTapped))
}
usersTableView.setEditing(cellStyleForEditing != .none, animated: true)
}
func deleteUserConfirmationAlert(tableViewIndexPath: IndexPath) {
// create alert for user to confirm user deletion
let alert = UIAlertController(title: "Delete User", message: "Are you sure you want to delete \(users[tableViewIndexPath.row].firstName)? This cannot be undone.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "okay", style: .default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
// remove user from Firebase
self.ref.child("members").child(self.users[tableViewIndexPath.row].firstName).removeValue()
self.users.remove(at: tableViewIndexPath.row)
self.usersTableView.deleteRows(at: [tableViewIndexPath], with: .fade)
}))
alert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
self.usersTableView.reloadData()
}))
present(alert, animated: true, completion: nil)
}
func numberOfParents() -> Int {
var parentCount = 0
for user in users {
if user.childParent == "parent" {
parentCount += 1
}
}
return parentCount
}
func createAlert(alertTitle: String, alertMessage: String) {
let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "okay", style: .cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
present(alert, animated: true, completion: nil)
}
func confirmationAlert() {
let alert = UIAlertController(title: "Users", message: "You have entered in \(users.count) users. Are you finished adding family members?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "continue", style: .default, handler: { (action) in
self.saveUsersToFirebase()
self.performSegue(withIdentifier: "GoToStep3", sender: self)
alert.dismiss(animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
present(alert, animated: true, completion: nil)
}
}

Resources