Saving/Fetching CoreData Relationships Swift 3 - ios

I am making a budgeting app for the purposes of learning and I have a few questions about storing and fetching entities in CoreData.
I have two entities "Budget" and "Expense".
Every Budget has its own Expenses. As an example I can have an 'Entertainment' budget and it can have expenses such as 'Bowling' and 'Movies' etc.
I can create a Budget and save it. And then add expenses to it.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let expense = Expense(context: context)
.
. // Filling out the expense here
.
budget?.addToExpense(expense)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
I then retrieve the collection of Expenses and display the store name in a TableView
// Inside cellForRowAt
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
let myArray = Array((budget?.expense)!)
cell.textLabel?.text = (myArray[indexPath.row] as! Expense).store
return cell
So far so good. My issue is that when I store an expense it is stored in a Set. Which means the order is random when I retrieve that set and typecast it into an Array.
What I want is to store the Expenses and retrieve them in such a way that I can display the expenses in a FIFO order in the TableView. In other words the first expense I add in the budget should be the first element in the table view and so on and so forth.

There could be several ways to achieve that. The most straightforward would be to use Ordered relation for expense.
To do that,
Open expense relationship properties in DataModel editor.
Check Ordered option
Then budget.expense will be not Set, but OrderedSet, and you won't need to convert it to Array, but access it directly by index.

VIEWCONTROLLER 1:
=====================>
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var txt_user: UITextField!
#IBOutlet weak var txt_password: UITextField!
var result = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
txt_password.isSecureTextEntry = true
}
#IBAction func login_action(_ sender: Any)
{
if(txt_user.text == "" || txt_password.text == "")
{
let alert = UIAlertController(title: "info", message: "fields are empty", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
self.present(alert, animated: true, completion: nil)
}
else
{
self.CheckForUserNameAndPasswordMatch(empName: txt_user.text! , empPwd: txt_password.text!)
}
}
func CheckForUserNameAndPasswordMatch(empName:String,empPwd:String)
{
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let fetchdata = NSFetchRequest<NSManagedObject>(entityName: "Employee")
let predicate = NSPredicate(format: "empName = %#",empName)
fetchdata.predicate = predicate
do
{
self.result = try context.fetch(fetchdata as! NSFetchRequest<NSFetchRequestResult>)as NSArray
if result.count>0
{
let objcetEntity = result.firstObject as! Employee
if objcetEntity.empName == empName && objcetEntity.empPwd == empPwd
{
print("Login Successfully")
// Entered Username & password matched
}
else
{
print("Wrong password/username")
//Wrong password/username
}
}
}
catch let error as NSError
{
print("error",error.localizedDescription)
}
}
#IBAction func unwindToVC1(sender:UIStoryboardSegue)
{
if sender.source is ViewController2
{
let secvc = sender.source as! ViewController2
txt_password.text = secvc.str1! as String
}
}
VIEWCONTROLLER 2:
=====================>
class ViewController2: UIViewController ,UITextFieldDelegate{
#IBOutlet weak var txt_name: UITextField!
#IBOutlet weak var txt_mail: UITextField!
#IBOutlet weak var txt_pwd: UITextField!
#IBOutlet weak var txt_cpwd: UITextField!
#IBOutlet weak var txt_phone: UITextField!
#IBOutlet weak var err: UILabel!
var str1:NSString!
var str2:NSString!
//var update:NSManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
/*if(update != nil)
{
txt_name.text = update.value(forKey: "empName") as? String
txt_mail.text = update.value(forKey: "empMail") as? String
txt_pwd.text = update.value(forKey: "empPwd") as? String
txt_cpwd.text = update.value(forKey: "empCpwd") as? String
txt_phone.text = update.value(forKey: "empPhone") as? String
}*/
txt_pwd.isSecureTextEntry = true
txt_cpwd.isSecureTextEntry = true
}
override func viewWillAppear(_ animated: Bool)
{
txt_name.becomeFirstResponder()
}
#IBAction func regis_clicked(_ sender: Any)
{
if(txt_name.text == "" || txt_mail.text == "" || txt_pwd.text == "" || txt_cpwd.text == "" || txt_phone.text == "" )
{
let alert = UIAlertController(title: "information", message: "fields are empty", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler:
{
(actionsheet) in
if(self.txt_name.text == "")
{
self.txt_name.becomeFirstResponder()
}
if(self.txt_mail.text == "")
{
self.txt_mail.becomeFirstResponder()
}
if(self.txt_pwd.text == "")
{
self.txt_pwd.becomeFirstResponder()
}
if(self.txt_cpwd.text == "")
{
self.txt_cpwd.becomeFirstResponder()
}
if(self.txt_phone.text == "")
{
self.txt_phone.becomeFirstResponder()
}
})
let cancel = UIAlertAction(title: "cancel", style: .default, handler: nil)
alert.addAction(ok)
alert.addAction(cancel)
self.present(alert, animated: true, completion: nil)
}
else if(txt_pwd.text != txt_cpwd.text)
{
let alert1 = UIAlertController(title: "information", message: "password mismatched", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler:nil)
let cancel = UIAlertAction(title: "cancel", style: .default, handler: nil)
alert1.addAction(ok)
alert1.addAction(cancel)
self.present(alert1, animated: true, completion: nil)
}
else
{
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let newuser = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: context)
newuser.setValue(txt_name.text, forKey: "empName")
newuser.setValue(txt_mail.text, forKey: "empMail")
newuser.setValue(txt_pwd.text, forKey: "empPwd")
newuser.setValue(txt_cpwd.text, forKey: "empCpwd")
newuser.setValue(txt_phone.text, forKey: "empPhone")
do
{
try context.save()
}
catch let error as NSError
{
print("error",error.localizedDescription)
}
}
// self.navigationController?.popViewController(animated: true)
performSegue(withIdentifier: "unwind", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if let newlabel = txt_pwd.text
{
str1 = newlabel as NSString
}
}

Related

xcode does not recognise paymentcontext, it shows found nil unexpectedy

This is my simulator-
It shows - Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value in both the below scenarios:
i) - when I click on "card", it shows error as shown in the screenshot below:-
ii)when I click on "checkout" first it shows the following on the simulator
when I click on "confirm, , it shows error as shown in the screenshot below:-
This is my "CheckoutVC" -
import UIKit
import Stripe
import Firebase
class CheckoutVC: UIViewController {
var product = Cart()
lazy var quantitylabel = product.totalQuantity
var Pro = Product(imagename:#imageLiteral(resourceName: "blue"), price: 5,unit: 5)
#IBOutlet weak var selectCardView: UIView!
#IBOutlet weak var cardIcon: UIImageView!
#IBOutlet weak var cardEndingIn: UILabel!
#IBOutlet weak var selectBankView: UIView!
#IBOutlet weak var bankIcon: UIImageView!
#IBOutlet weak var bankEndingIn: UILabel!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var currentSelectedPaymentType: PaymentType?
var paymentContext: STPPaymentContext!
override func viewDidLoad() {
super.viewDidLoad()
setupStripe()
setupTapGestures()
setupUi()
setCheckoutLabelDetails()
}
func setCheckoutLabelDetails() {
let pricelabel = Pro.price.formatToCurrencyString()
let processingFee = FeesCalculator.calculateFeesForCard(subtotal: Pro.price)
let formatprocessingfees = "Processing Fees: \(processingFee.formatToCurrencyString())"
let total = processingFee + Pro.price
let totallabel = "Total: \(total.formatToCurrencyString())"
}
func setupUi() {
let producttitle = Pro
let productprice = Pro.price
let qunatity = "\(Pro.unit) night accomodations"
let price = Pro.price.formatToCurrencyString()
}
func setupTapGestures() {
let selectCardTouch = UITapGestureRecognizer(target: self, action: #selector(selectCardTapped))
selectCardView.addGestureRecognizer(selectCardTouch)
let selectBankTouch = UITapGestureRecognizer(target: self, action: #selector(selectBankTapped))
selectBankView.addGestureRecognizer(selectBankTouch)
}
#objc func selectBankTapped() {
setBankPaymentView()
}
func setBankPaymentView() {
if currentSelectedPaymentType == .bank { return }
currentSelectedPaymentType = .bank
selectBankView.layer.borderColor = UIColor(named: AppColor.BorderBlue)?.cgColor
selectBankView.layer.borderWidth = 2
selectCardView.layer.borderColor = UIColor.lightGray.cgColor
selectCardView.layer.borderWidth = 1
bankIcon.tintColor = UIColor(named: AppColor.BorderBlue)
cardIcon.tintColor = UIColor.lightGray
}
func setupStripe() {
guard (UserManager.instance.user?.stripeId) != nil else { return }
let config = STPPaymentConfiguration.shared
paymentContext = STPPaymentContext(customerContext: Wallet.instance.customerContext,
configuration: config(),
theme: .default())
paymentContext.hostViewController = self
paymentContext.delegate = self
}
// MARK: Select Card
#objc func selectCardTapped() {
setCardPaymentView()
}
func setCardPaymentView() {
if currentSelectedPaymentType == .card { return }
currentSelectedPaymentType = .card
selectCardView.layer.borderColor = UIColor(named: AppColor.BorderBlue)?.cgColor
selectCardView.layer.borderWidth = 2
selectBankView.layer.borderColor = UIColor.lightGray.cgColor
selectBankView.layer.borderWidth = 1
cardIcon.tintColor = UIColor(named: AppColor.BorderBlue)
bankIcon.tintColor = UIColor.lightGray
}
#IBAction func changeCardClicked(_ sender: Any) {
self.paymentContext.pushPaymentOptionsViewController()
}
#IBAction func changeBankClicked(_ sender: Any) {
}
#IBAction func payBtnClicked(_ sender: Any) {
let total = Pro.price + FeesCalculator.calculateFeesForCard(subtotal: Pro.price)
let confirmPayment = UIAlertController(title: "Confirm Payment", message: "Confirm payment for \(total.formatToDecimalCurrencyString())", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Confirm", style: .default) { (action) in
self.paymentContext.requestPayment()
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel)
confirmPayment.addAction(confirmAction)
confirmPayment.addAction(cancel)
present(confirmPayment, animated: true)
}
}
extension CheckoutVC: STPPaymentContextDelegate {
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
// Triggers when the content of the payment context changes, like when the user selects a new payment method or enters shipping information.
if let card = paymentContext.selectedPaymentOption {
cardEndingIn.text = card.label
} else {
cardEndingIn.text = "No Card Selected"
}
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
simpleAlert(msg: "Sorry, but we are not able to load you credit cards at this time.")
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: #escaping STPPaymentStatusBlock) {
// Request Stripe payment intent, and return client secret.
guard let stripeId = UserManager.instance.user?.stripeId else { return }
let idempotency = UUID().uuidString.replacingOccurrences(of: "-", with: "")
let fees = FeesCalculator.calculateFeesForCard(subtotal: Pro.price)
let total = Pro.price + fees
let data: [String: Any] = [
"total": total,
"idempotency": idempotency,
"customer_id": stripeId
]
Functions.functions().httpsCallable("createPaymentIntent").call(data) { (result, error) in
if let error = error {
debugPrint(error)
self.simpleAlert(msg: "Sorry, but we are not able to complete your payment.")
return
}
guard let clientSecret = result?.data as? String else {
self.simpleAlert(msg: "Sorry, but we are not able to complete your payment.")
return
}
// Once the client secret is obtained, create paymentIntentParams
let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
paymentIntentParams.paymentMethodId = paymentResult.paymentMethod.stripeId
// Confirm the PaymentIntent
STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: paymentContext) { (status, paymentIntent, error) in
switch status {
case .succeeded:
completion(.success, nil)
case .failed:
completion(.error, nil)
case .canceled:
completion(.userCancellation, nil)
}
}
}
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
// Take action based on return status: error, success, userCancellation
switch status {
case .success:
let successAlert = UIAlertController(title: "Payment Success!", message: "\nYou will receive an email with all the travel details soon! \n\n Bon Voyage!", preferredStyle: .alert)
let ok = UIAlertAction(title: "Ok", style: .default) { (action) in
self.navigationController?.popToRootViewController(animated: true)
}
successAlert.addAction(ok)
present(successAlert, animated: true)
case .error:
simpleAlert(msg: "Sorry, something went wrong during checkout. You were not charged and can try again.")
case .userCancellation:
return
}
}
}
enum PaymentType {
case card
case bank
}
what am I doing wrong ?

How do I update values stored in Core Data on second screen and display updated values on first screen?

I have three view controllers(two UIViewControllers and 1 UITableViewController). I want to display all the data on the tableView and add/update data on two separate view controllers.
The two UIViewControllers have three textFields each (for name, email and phone number) and one button to save/update the data in CoreData and on the tableViewController.
One section in the table view consists of three/two rows (number text field can be empty). On swiping a row from a section, the user can delete the whole section or edit the data in the section.
I have created "Person" entity and three attributes ("name","email","number", all of String data type).
But I get the following error on the line
let objectUpdate = test[0] as! NSManagedObject
Error: Fatal error: Index out of range
import UIKit
import CoreData
class RootTableViewController: UITableViewController {
//Array to display the data in table:
var array_of_person_data_array : [PersonData] = []
//Variable with index of selected row/section:
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
let rightBarButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(nextScreen))
navigationItem.rightBarButtonItem = rightBarButton
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
}
override func viewWillAppear(_ animated: Bool) {
retrieveData()
tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return array_of_person_data_array.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if array_of_person_data_array[section].number == nil || array_of_person_data_array[section].number == ""
{return 2}
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! TableViewCell
if indexPath.row == 0
{cell.personLabel.text = array_of_person_data_array[indexPath.section].name}
else if indexPath.row == 1
{cell.personLabel.text = array_of_person_data_array[indexPath.section].email}
else
{cell.personLabel.text = array_of_person_data_array[indexPath.section].number}
return cell
}
//Row actions when swiped:
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
index = indexPath.section
//Cancel row action:
let cancelRowAction = UITableViewRowAction(style: .normal, title: "Cancel", handler: {(action : UITableViewRowAction,indexPath : IndexPath) in
})
//Update row action:
let updateRowAction = UITableViewRowAction(style: .default, title: "Update", handler: {(action: UITableViewRowAction, indexPath: IndexPath) in
let sbObj = UIStoryboard(name: "Main", bundle: nil)
let svcObj = sbObj.instantiateViewController(withIdentifier: "UpdateViewControllerSB") as! UpdateViewController
svcObj.index = self.index
svcObj.personDataObject = self.array_of_person_data_array[indexPath.section]
self.navigationController?.pushViewController(svcObj, animated: true)
})
//Delete row action:
let deleteRowAction = UITableViewRowAction(style: .destructive, title: "Delete", handler: {(alert : UITableViewRowAction, indexPath : IndexPath) in
//Delete controller:
let deleteController = UIAlertController(title: "Delete", message: nil, preferredStyle: .actionSheet)
//Delete action:
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: {(UIAlertAction)in
self.deleteData()
})
deleteController.addAction(deleteAction)
//Cancel action:
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
deleteController.addAction(cancelAction)
//Present the controller:
self.present(deleteController,animated: true,completion: nil)
})
return [cancelRowAction,updateRowAction,deleteRowAction]
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 1000, height: 1000))
return view
}
#objc func nextScreen()
{
let sbObj = UIStoryboard(name: "Main", bundle: nil)
let svcObj = sbObj.instantiateViewController(withIdentifier: "AddViewControllerSB") as! AddViewController
self.navigationController?.pushViewController(svcObj, animated: true)
}
//Function to retrieve data from core data:
func retrieveData() {
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Prepare the request of type NSFetchRequest for the entity
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
// fetchRequest.fetchLimit = 1
// fetchRequest.predicate = NSPredicate(format: "username = %#", "Ankur")
// fetchRequest.sortDescriptors = [NSSortDescriptor.init(key: "email", ascending: false)]
//
do {
let result = try managedContext.fetch(fetchRequest)
for data in result as! [NSManagedObject] {
array_of_person_data_array.append(PersonData(personName: data.value(forKey: "name") as! String, personEmail: data.value(forKey: "email") as! String, personNumber: data.value(forKey: "number") as? String))
}
} catch {
print("Failed")
}
}
//Function to delete data from Core Data:
func deleteData(){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name = %#", array_of_person_data_array[index].name!)
fetchRequest.predicate = NSPredicate(format: "email = %#", array_of_person_data_array[index].email!)
fetchRequest.predicate = NSPredicate(format: "number = %#", array_of_person_data_array[index].number!)
do
{
let test = try managedContext.fetch(fetchRequest)
let objectToDelete = test[0] as! NSManagedObject
managedContext.delete(objectToDelete)
array_of_person_data_array.remove(at: index)
do{
try managedContext.save()
}
catch
{
print(error)
}
}
catch
{
print(error)
}
tableView.reloadData()
}
}
Add View Controller:
import UIKit
import CoreData
class AddViewController: UIViewController {
#IBOutlet weak var nameTF: UITextField!
#IBOutlet weak var emailTF: UITextField!
#IBOutlet weak var numberTF: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addButtonAction(_ sender: Any) {
createData()
navigationController?.popToRootViewController(animated: true)
}
func createData(){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Now let’s create an entity and new user records.
let userEntity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
//Get data ready to be set into CORE DATA:
let user = NSManagedObject(entity: userEntity, insertInto: managedContext)
user.setValue(nameTF.text, forKeyPath: "name")
user.setValue(emailTF.text, forKey: "email")
user.setValue(numberTF.text, forKey: "number")
//Save the set data to CORE DATA:
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
Update view controller:
class UpdateViewController: UIViewController {
#IBOutlet weak var nameTF: UITextField!
#IBOutlet weak var emailTF: UITextField!
#IBOutlet weak var numberTF: UITextField!
var index : Int?
var personDataObject=PersonData(personName: "sample", personEmail: "sample#sample", personNumber: "xxxx-xxx-xxx")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func updateButtonAction(_ sender: Any) {
self.updateData()
navigationController?.popToRootViewController(animated: true)
}
//Update the data in CoreData:
func updateData(){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name = %#", "Ankur1")
fetchRequest.predicate = NSPredicate(format: "email = %#", "Ankur1")
fetchRequest.predicate = NSPredicate(format: "number = %#", "Ankur1")
do
{
let test = try managedContext.fetch(fetchRequest)
let objectUpdate = test[0] as! NSManagedObject
objectUpdate.setValue(nameTF.text, forKey: "name")
objectUpdate.setValue(emailTF.text, forKey: "email")
if let no = numberTF.text
{objectUpdate.setValue(no, forKey: "number")}
do{
try managedContext.save()
}
catch
{
print(error)
}
}
catch
{
print(error)
}
}
}
PersonData class is defined as:
class PersonData
{
var name : String?
var email: String?
var number : String?
init(personName : String, personEmail : String, personNumber : String?) {
name = personName
email = personEmail
number = personNumber
}
}
How do I update the existing data or add new data to CoreData and display the new data on the Table View Controller?
You should look into NSFetchedResultsController: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/nsfetchedresultscontroller.html
In essence, you want to create/update/delete values on your second screens in your CoreData context, save those changes, and then the fetched results controller on your main screen is setup (by following the above documentation) to automatically listen for those changes and reflect them in the table.
This is good practice because you don't have to worry about updating the view yourself and keeping state synchronized - it's done automatically by the view retrieving data when it is updated.
Declare a callback property and a property for the person data in AddViewController. Use the NSManagedObject object rather than the custom class PersonData
var callback : ((Person) -> Void)?
var person : Person?
In RootTableViewController assign a closure in nextScreen before presenting the controller
svcObj.callback = { person in
// execute the code you need
}
In AddViewController at some point assign the modified NSManagedObject to person
In viewWillDisappear call the closure
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let person = person { callback?(person) }
}

Core data id and Web JSON api id does not match?

I am not able to match Core Data Bookmark ID and JSON Web API ID, and I am not able to save image From JSON API to Core Data.
Mainclass
import UIKit
import MapKit
import CoreLocation
import SDWebImage
import CoreData
class InfoViewController: UIViewController , MKMapViewDelegate,CLLocationManagerDelegate{
var get_details : schools?
var bookmark : [Bookmark] = []
var bookmark_details : Bookmark?
override func viewDidLoad() {
super.viewDidLoad()
}
func bookmarktoCoredata(){
let alert = UIAlertController(title: "Add BookMark", message: "", preferredStyle: .alert)
let add = UIAlertAction(title: "Add", style: .default){
(action) in
// for bkms in self.bookmark
// {
// if ((bkms.name)) == ((self.get_details?.name)!){
//
// print("same name")
//
// }else{
// print("not same")
// }
// }
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Bookmark(context: context)
if (task.bookmark_id) == Int16((self.get_details?.schoolid)!){
print("same id")
}
else
{
print("not same id")
task.bookmark_id = Int16((self.get_details?.schoolid)!)
task.name = self.get_details?.name
task.address = self.get_details?.address
task.district = self.get_details?.district
task.country = self.get_details?.country
task.phone = self.get_details?.phone
task.email = self.get_details?.email
task.website = self.get_details?.website
task.institution = self.get_details?.institution_type
task.establishment = self.get_details?.establishment_date
task.admission_open = self.get_details?.admission_open_from
task.admission_end = self.get_details?.admission_open_from
task.latitude = (self.get_details?.latitude)!
task.longitude = (self.get_details?.longitude)!
task.bookmark_type = "school"
// let img = UIImage(named: "App.png") //Sending direct image its working
let img = UIImage(named: (self.get_details?.logo)!) //by Json it sendong nil
let imgdata = UIImageJPEGRepresentation(img!, 1)
task.logo = imgdata! as NSData
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel){
(alert) in
// JLToast.makeText("Your Bookmark Cancel !!").show()
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
I can't figure out why my code is wrong.
You are not fetching a Bookmark with id. Try this:
import UIKit
import MapKit
import CoreLocation
import SDWebImage
import CoreData
class InfoViewController: UIViewController {
var get_details : schools?
var bookmark : [Bookmark] = []
var bookmark_details : Bookmark?
override func viewDidLoad() {
super.viewDidLoad()
}
func bookmarktoCoredata(){
let alert = UIAlertController(title: "Add BookMark", message: "", preferredStyle: .alert)
let add = UIAlertAction(title: "Add", style: .default) {
(action) in
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchId = Int16((self.get_details?.schoolid)!
let bookmarkFetch: NSFetchRequest<Bookmark> = NSFetchRequest(entityName: "Bookmark")
bookmarkFetch.predicate = NSPredicate(format: "bookmark_id == %d", fetchId)
do {
let fetchedBookmarks = try context?.fetch(bookmarkFetch)
// you should find 1
if fetchedBookmarks?.count == 1 {
print("same id")
} else if fetchedBookmarks?.count == 0 {
// There is no Bookmark with this id so create a new one
let task = Bookmark(context: context)
// Populate all the properties here and save
task.bookmark_id = Int16((self.get_details?.schoolid)!)
task.name = self.get_details?.name
task.address = self.get_details?.address
task.district = self.get_details?.district
task.country = self.get_details?.country
task.phone = self.get_details?.phone
task.email = self.get_details?.email
task.website = self.get_details?.website
task.institution = self.get_details?.institution_type
task.establishment = self.get_details?.establishment_date
task.admission_open = self.get_details?.admission_open_from
task.admission_end = self.get_details?.admission_open_from
task.latitude = (self.get_details?.latitude)!
task.longitude = (self.get_details?.longitude)!
task.bookmark_type = "school"
// I don't know the type of get_details?.logo
if let imgdata = self.get_details?.logo as? NSData {
task.logo = UIImage(data: imgdata)
}
// Save your changes
(UIApplication.shared.delegate as! AppDelegate).saveContext()
} else {
// If you get here you have more than one with the same id. Fix error
}
} catch {
fatalError("Failed to fetch bookmark: \(error)")
}
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel){
(alert) in
// JLToast.makeText("Your Bookmark Cancel !!").show()
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
// MARK: - MKMapViewDelegate
extension InfoViewController: MKMapViewDelegate {
//Put your MKMapViewDelegate functions here
}
// MARK: - CLLocationManagerDelegate
extension InfoViewController: CLLocationManagerDelegate {
//Put your CLLocationManagerDelegate functions here
}

How to use a detail disclosure button in UICollectionViewCell?

I am trying to use the detail disclosure button on the view controller pictured below (PhotoSearchViewController) to show a UIAlertController that shows the photo description from the ImageUrlItem.
When I click the button, it shows that it was pressed, but the alert containing the description does not appear.
My code for the ImageUrlItem:
import Foundation
import Firebase
struct ImageUrlItem {
//Should I add my description in here?
let key: String
let imageUrl: String
let watsonCollectionImageUrl: String
let score: Double
let description: String
let ref: FIRDatabaseReference?
init(imageUrl: String, key: String = "", watsonCollectionImageUrl: String = "", score: Double = 0, description: String) {
self.key = key
self.imageUrl = imageUrl
self.watsonCollectionImageUrl = watsonCollectionImageUrl
self.ref = nil
self.score = score
self.description = description
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
imageUrl = snapshotValue["ImageUrl"] as! String // must map to firebase names
watsonCollectionImageUrl = ""
score = 0
description = snapshotValue["Description"] as! String
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"imageUrl": imageUrl,
"Description": description
]
}
}
My code for the collection view cell (PhotoCell) is as follows:
class PhotoCell: UICollectionViewCell, UIAlertViewDelegate
{
var photos: [ImageUrlItem] = []
var ref = FIRDatabase.database().reference(withPath: "Photos")
#IBOutlet weak var imgPhoto: UIImageView!
#IBOutlet weak var lblScore: UILabel!
#IBAction func btnDesc(_ sender: UIButton)
{
let alertTitle = "Description"
let alertMessage = photos.description
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title:"Ok", style: UIAlertActionStyle.default)
{
(result : UIAlertAction) -> Void in
print("OK")
}
alertController.addAction(okAction)
self.parentViewController?.present(alertController, animated: true, completion: nil)
}
}
Code for the cellForRowAt in my PhotoSearchViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseIdentifier = "PhotoCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! PhotoCell
cell.backgroundColor = UIColor(red:0.74, green:0.76, blue:0.78, alpha: 1.0)
// Do any custom modifications you your cell, referencing the outlets you defined in the Custom cell file // if we have a label IBOutlet in PhotoCell we can customize it here
// on page load when we have no search results, show nothing
if similarImageUrls.count > 0 {
//print(indexPath.row)
//print(similarImageUrls.count)
if (indexPath.row < similarImageUrls.count){
let image = self.similarImageUrls[indexPath.row]
// get image asynchronously via URL
let url = URL(string: image.imageUrl)
DispatchQueue.global().async {
// make an asynchonorous call to load the image
DispatchQueue.main.async {
cell.imgPhoto.af_setImage(withURL: url!) // show image using alamofire
}
}
cell.lblScore.isHidden = false
cell.lblScore.text = "Score: \(NSString(format: "%.2f", (image.score * 100)) as String)%"
}
else
{
// show the placeholder image instead
cell.imgPhoto.image = UIImage(named: "person")
cell.lblScore.isHidden = true
cell.lblScore.text = "0.00%"
}
}
else
{
// show the placeholder image instead
cell.imgPhoto.image = UIImage(named: "person")
cell.lblScore.isHidden = true
cell.lblScore.text = "0.00%"
// when we get to the last image, and it is not the first time load
if (indexPath.row == 8 && !firstTimeSearch){
// show nothing found alert here
let ac = UIAlertController(title: "Photo Search Completed!", message:"No macthing photo found!", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
return cell
}
}
You can use custom delegate to show the alert
// here is the protocol for creating the delegation:
protocol BtnDesc : class {
func btnDecAlert(alertTitle: String, message: String)
}
class PhotoCell: UICollectionViewCell
{
// MARK:- Delegate
weak var btnDescDelegate : BtnDesc?
var photos: [ImageUrlItem] = []
var ref = FIRDatabase.database().reference(withPath: "Photos")
#IBOutlet weak var imgPhoto: UIImageView!
#IBOutlet weak var lblScore: UILabel!
#IBAction func btnDesc(_ sender: UIButton)
{
let alertTitle = "Description"
let alertMessage = photos.description
btnDescDelegate?.btnDecAlert(alertTitle: alertTitle, message: alertMessage)
}
}
Code for the cellForRowAt in PhotoSearchViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseIdentifier = "PhotoCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! PhotoCell
//Mark set the delegate.
cell.btnDescDelegate = self
.....
//conform delegate
extension PhotoSearchViewController : BtnDesc {
func btnDecAlert(alertTitle: String, message: String) {
let alert = UIAlertController(title: alertTitle, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: {(_ action: UIAlertAction) -> Void in
// here is your action
print("OK")
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
}

Swift: Index out of range error for a table view while using firebase

I'm creating an app just to learn the language mostly. basically I am trying to make an app that allows you to bet fake money on which CSGO team will win. So right now I am at the part of the app that allows a user to create a bet. then the user can see all of the bets he can then accept another users bet. I had all this working before, but I noticed a bug so I decided to fix it. The problem is now it crashes.
I need the app to go to a different screen where it allows you to make a bet on a team. then you click the box and set how much you want to bet on that specific team. Then when you click the submit Button at the bottom, it will upload the users information and the bet information to a Firebase Database. then it will go back to the previous screen that shows all of the bets that have not been filled yet. so his should too.
the problem is when I use POPView controller to go back to the previous view controller that shows the bets it gives me an index out of range error for the table view. the weird thing is if I move the method getBetsFor to viewDidLoad instead of view will appear then it works just fine. The problem with that is it will glitch and it won't show the bet that the user just made.
I know my code is horrific I have tried to put some of it into MVC format, but without further ado here is my code.
the code below is the code that gets the bets from the Firebase Database then puts that data into an array of objects then displays the objects onto the TableView
//
// BetView.swift
// EDraft
//
// Created by Devin Tripp on 3/20/17.
// Copyright © 2017 Devin Tripp. All rights reserved.
//
import UIKit
import Foundation
import Firebase
class BetView: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var teamOne: UILabel!
#IBOutlet weak var teamTwo: UILabel!
let datRef = FIRDatabase.database().reference(fromURL: "https://edraft-77b47.firebaseio.com/")
var userMoney = String()
let getData = GatherData()
var testies: String?
var teamTOne = String()
var teamTTwo = String()
let screenSize: CGRect = UIScreen.main.bounds
let navBar: UINavigationBar = UINavigationBar(frame: CGRect(x: 0, y: 60, width: UIScreen.main.bounds.width, height: 50))
var navItem = UINavigationItem(title: "Money: ")
let doneItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.add, target: nil, action: #selector(sayHello(sender:)))
let backItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: nil, action: #selector(goBack(sender:)))
override func viewWillAppear(_ animated: Bool) {
self.getData.betObjects.removeAll()
self.getData.getBetsFor(completion: { (result) in
if result == true {
//show the spinning wheel thing
self.tableView.reloadData()
} else {
// error
}
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(navBar)
self.tableView.separatorStyle = .none
navItem.rightBarButtonItem = doneItem
navItem.leftBarButtonItem = backItem
navBar.setItems([navItem], animated: false)
teamOne.text = teamTOne
teamTwo.text = teamTTwo
/*
getData.getBetsFor(completion: { (result) in
if result == true {
self.tableView.reloadData()
} else {
}
})
*/
getData.getMoneyFromUser(username: self.getData.userName, completion: { (money) in
if money == true {
let usermoney = String(self.getData.userMoney)
//update the UI
self.navItem.title = "$" + usermoney
print(self.getData.userMoney)
} else {
print("no money found")
}
})
}
func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext) -> Bool {
return true
}
func updateBet(_ index: Int, completion: #escaping (_ something: Bool) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String
self.getData.userName = username!
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.getData.userName)")
return
}
let values = ["OpposingUsername": self.getData.userName,"Show": "no"]
//var val = self.getData.betObjects[index]
//val.opposingUserName = self.getData.userName
self.datRef.child("Bets").child(self.getData.tieBetToUser[index]).observeSingleEvent(of: .value, with: { (snapshot) in
let anothaValue = snapshot.value as? NSDictionary
let user = anothaValue?["Username"] as? String
if user == self.getData.userName {
// let the user know he cannot bet his own bet
completion(false)
} else {
self.datRef.child("Bets").child(self.getData.tieBetToUser[index]).updateChildValues(values)
completion(true)
}
})
})
}) { (error) in
print(error.localizedDescription)
}
}
func getOpoosingUserNames(_ username: String,_ index: Int, completion: #escaping (_ result: Bool) -> Void ) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.datRef.child("Bets").child(self.getData.tieBetToUser[index]).observeSingleEvent(of: .value, with: { snapshot in
let thisValue = snapshot.value as? NSDictionary
if let thisUserName = thisValue?["Username"] as? String {
self.getData.opposingUserName = thisUserName
completion(true)
} else {
completion(false)
}
})
}) { (error) in
print(error.localizedDescription)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//if makeEmpty == true {
// return 0
//} else {
print(self.getData.betObjects.count)
return self.getData.betObjects.count
//}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cellB", for: indexPath) as! CellB
print(self.getData.betObjects[indexPath.row].userName)
cell.userNameLabel?.text = getData.betObjects[indexPath.row].userName
cell.amountOfBet?.text = getData.betObjects[indexPath.row].betAmount
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + getData.amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
// let them know to wait a second or the bet won't go through
var waitController = UIAlertController(title: "Please Wait", message: "Your bet is being processed", preferredStyle: .alert)
self.present(waitController, animated: true, completion: nil)
//take away the usersMoney
self.takeAwayMoney(self.getData.amountBets[indexPath.row],index: indexPath.row, completion: { (result) in
if result == true {
self.updateBet(indexPath.row, completion: { (result) in
if result == true {
self.getOpoosingUserNames(self.getData.userName, indexPath.row, completion: { (anothaResult) in
if anothaResult == true {
self.dismiss(animated: true, completion: {
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.getData.opposingUserName, preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
let pathIndex = IndexPath(item: indexPath.row, section: 0)
self.getData.betObjects.remove(at: indexPath.row)
self.tableView.deleteRows(at: [pathIndex], with: .fade)
self.tableView.reloadData()
print("Second")
})
self.getData.getMoneyFromUser(username: self.getData.userName, completion: { (money) in
if money == true {
self.userMoney = String(self.getData.userMoney)
//update the UI
self.navItem.title = "$" + self.userMoney
print(self.userMoney)
} else {
print("no money found")
}
})
} else {
}
//wait for the first view to load in case it uploads to fast
})
} else {
self.dismiss(animated: true, completion: {
let cannotBet = UIAlertController(title: "Failed", message: "You cannot bet with yourself dummy", preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
cannotBet.addAction(okButt)
self.present(cannotBet, animated: true, completion: nil)
})
}
})
} else {
// user doesn't have money
//display a alert that lets the user know hes broke
print("this should print once")
self.dismiss(animated: true, completion: {
let brokeController = UIAlertController(title: "Failed", message: "Reason: You don't have enough money!", preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
brokeController.addAction(okButt)
self.present(brokeController, animated: true, completion: nil)
})
}
var getResult = ""
print("You have taken away the users money")
print("you made it this far almost there")
//let delayInSeconds = 3.0 // 1
//DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
})
return
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if(editingStyle == UITableViewCellEditingStyle.delete) {
self.getData.betObjects.remove(at: indexPath.row)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//let indexPath = self.tableView.indexPathForSelectedRow!
//var DestViewController : addBets = segue.destination as! addBets
//DestViewController.teamOne = self.teamOne.text!
//DestViewController.teamOne = self.teamTTwo
if segue.identifier == "gotobetview" {
if let destination = segue.destination as? addBets {
destination.teamOne = self.teamOne.text!
destination.teamTwo = self.teamTwo.text!
}
}
}
func takeAwayMoney(_ howMuch: String, index: Int, completion: #escaping (Bool)-> ()) -> Void{
if let notMuch = Int(howMuch) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let money = value?["money"] as? String ?? ""
//convert money to int
if let conMoney = Int(money) {
var conMoreMoney = conMoney
if conMoreMoney < notMuch {
print(" You don't have enough money")
completion(false)
return
} else {
conMoreMoney -= notMuch
let values = ["money": String(conMoreMoney)]
//update the users money
self.datRef.child("User").child(userID!).updateChildValues(values)
completion(true)
}
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
func getUserName() -> String {
let userID = FIRAuth.auth()?.currentUser?.uid
var useruser: String!
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.getData.userName = username
useruser = username
// ...
}) { (error) in
print(error.localizedDescription)
}
return useruser
}
func sayHello(sender: UIBarButtonItem) {
let userInput = self.teamOne.text
var userInputArray: [String] = []
userInputArray.append(self.teamOne.text!)
userInputArray.append(self.teamTwo.text!)
performSegue(withIdentifier: "gotobetview", sender: userInputArray)
//let storyboard = UIStoryboard(name: "Main", bundle: nil)
//let homeView = storyboard.instantiateViewController(withIdentifier: "addBets")
//self.present(homeView, animated: true, completion: nil)
}
func goBack(sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let backView = storyboard.instantiateViewController(withIdentifier: "tabview")
self.present(backView, animated: true, completion: nil)
}
}
Since I cannot post anymore code I will update the project on github and post a link here.
https://github.com/devintrippprojects/EDraft
if you download the project you can always create your own account you will start out with 100 dollars or you could use the main testing account the email is
k#k.com
and the password is
kkkkkk
I will post how the database looks and how the app looks tomorrow thanks have a good night.
I downloaded and ran your project.
Seems like the issue here is you should reload the tableView when you remove all the data source.
change this:
self.getData.betObjects.removeAll()
self.getData.getBetsFor(completion: { (result) in
if result == true {
//show the spinning wheel thing
self.tableView.reloadData()
} else {
// error
}
})
to this:
self.getData.betObjects.removeAll()
self.tableView.reloadData()
self.getData.getBetsFor(completion: { (result) in
if result == true {
//show the spinning wheel thing
self.tableView.reloadData()
} else {
// error
}
})
With an extra line:
self.tableView.reloadData()

Resources