I have a ViewController which saves user inputs to CoreData and after the save is attempted displaying MBProgressHUD to state if the save was successful or not.
I have an AddNewViewController class
class AddNewViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate {
#IBOutlet weak var inputErrorMessage: UILabel!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var amountLabel: UILabel!
#IBOutlet weak var dayPicker: UIPickerView!
#IBOutlet weak var durationPicker: UIPickerView!
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var amountTextField: UITextField!
#IBOutlet weak var notesTextField: UITextField!
//variable to contrain the origin view controller
var originVC: String?
// variables to hold user input
var name: String?
var amount: Double?
var notes: String?
var durationDay: Double?
var durationType: String?
// The days and duration options to display in the pickers
var durationPickerDataSource = ["Day(s)","Week(s)","Month(s)","Year(s)"];
var dayPickerDataSource = ["1","2","3","4","5","6","7","8","9","10","11","12"];
#IBAction func saveButton(sender: AnyObject) {
CoreDataStatic.data.saveIncomeBudgetAndExpenses(originVC!, name: name!, amount: amount, durationDay: durationDay!, durationType: durationType!, notes: notes!)
}
/**
The number of columns in the picker view.
*/
func numberOfComponentsInPickerView(dayPickerView: UIPickerView) -> Int {
return 1
}
/**
The number of items in the picker view. Equal to the number of days(12) and duration options(4) .
*/
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView == durationPicker {
return durationPickerDataSource.count;
}
else {
return dayPickerDataSource.count;
}
}
/**
Gets the titles to use for each element of the picker view.
*/
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView == durationPicker{
durationType = durationPickerDataSource[row]
return durationType
}
else {
durationDay = Double(dayPickerDataSource[row])
return dayPickerDataSource[row]
}
}
/**
Display acknowledgement if the Income, Budget or Fixed Expense saved.
*/
func displayMessage(origin: String) {
var message : String
//Changes the message depending on what the user was trying to save.
if CoreDataStatic.data.saved == true {
message = "\(origin) saved!"
}
else if CoreDataStatic.data.saved == false {
message = "Error: \(origin) failed to save!"
}
else {
message = "Error!"
}
print(message)
//displays acknowledgement for 2 seconds.
/*let acknowledgement = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
acknowledgement.mode = MBProgressHUDMode.Text
acknowledgement.label.text = message
acknowledgement.hideAnimated(true, afterDelay: 2)*/
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.durationPicker.dataSource = self;
self.durationPicker.delegate = self;
self.dayPicker.dataSource = self;
self.dayPicker.delegate = self;
}
A CoreData class:
struct CoreDataStatic {
static let data = CoreData()
}
public class CoreData {
var appDel : AppDelegate
//Manage a collection of managed objects.
let context : NSManagedObjectContext
//Describes an entity in Core Data.
let incomeEntity : NSEntityDescription
let budgetEntity : NSEntityDescription
let fixedExpenseEntity : NSEntityDescription
//Retrieve data from Core Data with the entity 'Scores'.
let income = NSFetchRequest(entityName: "Income")
let budget = NSFetchRequest(entityName: "Budget")
let fixedExpense = NSFetchRequest(entityName: "FixedExpenses")
//Set the key that needs updating which is always 'score'
let nameKeyToUpdate = "name"
let amountDayKeyToUpdate = "amountDay"
let amountWeekKeyToUpdate = "amountWeek"
let amountMonthKeyToUpdate = "amountMonth"
let amountYearKeyToUpdate = "amountYear"
let durationDayKeyToUpdate = "durationDay"
let durationTypeKeyToUpdate = "durationType"
let notesKeyToUpdate = "notes"
var saved : Bool?
func saveIncomeBudgetAndExpenses(origin: String, name: String, amountDay: Double, amountWeek: Double, amountMonth: Double, amountYear: Double, durationDay: Double, durationType: String, notes: String) {
//saving in enity depending on origin view controller
let entity : NSEntityDescription
if origin == "Income" {
entity = NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
}
else if origin == "Budget" {
entity = NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
}
else {
entity = NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
}
let saveNew = NSManagedObject(entity: entity,
insertIntoManagedObjectContext:context)
// add user input to the relevant entity
saveNew.setValue(name, forKey: nameKeyToUpdate)
saveNew.setValue(amountDay, forKey: amountDayKeyToUpdate)
saveNew.setValue(amountWeek, forKey: amountWeekKeyToUpdate)
saveNew.setValue(amountMonth, forKey: amountMonthKeyToUpdate)
saveNew.setValue(amountYear, forKey: amountYearKeyToUpdate)
saveNew.setValue(durationDay, forKey: durationDayKeyToUpdate)
saveNew.setValue(durationType, forKey: durationTypeKeyToUpdate)
saveNew.setValue(notes, forKey: notesKeyToUpdate)
do {
try context.save()
print("saved")
saved = true
}
catch _ {
print("didnt save")
saved = false
}
AddNewViewController().displayMessage(origin)
}
init(){
appDel = (UIApplication.sharedApplication().delegate as! AppDelegate)
context = appDel.managedObjectContext
incomeEntity = NSEntityDescription.entityForName("Income", inManagedObjectContext: context)!
budgetEntity = NSEntityDescription.entityForName("Budget", inManagedObjectContext: context)!
fixedExpenseEntity = NSEntityDescription.entityForName("FixedExpenses", inManagedObjectContext: context)!
}
}
This code runs and as expected however when the commented out section in the displayMessage() function is uncommented I get the following error:
"fatal error: unexpectedly found nil while unwrapping an Optional value"
due to the line self.durationPicker.dataSource = self; in the override viewDidLoad()
Any help would be appreciated.
Note* if i call the displayMessage() within the saveButton function the code works so unsure why it isn't working when calling the message from the CoreData class.
I am unsure if this is the correct way about it but i found a fix.
a variable (bool) was created called attemptSave which is defaulted to false.
within the saveIncomeBudgetAndExpenses try and catch, the attemptSave is changed to true.
The displayMessage() function is now called within both button clicks using an if statement to check if an attemptSave is yes, if so, call function.
Related
I am using MySQL and PHP to download a restaurants menu but the user of the app should be able to add a certain amount to which item from the menu they want. Currently I am using a stepper to indicate the amount and adding that amount to a UserDefaults key which gets called when the menu is downloaded again.
This makes me have to download the menu again when I go to another viewController which sums up the order but I can't seem to filter out only them items which do have an amount.
What is a better way to add that amount to the downloaded data and how can I filter these items in my cart ViewController to only show and use the items which have an amount.
My current downloadModel, MenuModel, cellViewController (for the menu tableview) look like this:
MenuDownload.swift:
import UIKit
protocol MenuDownloadProtocol: class {
func productsDownloaded(products: NSArray)
}
class MenuDownload: NSObject {
//properties
weak var delegate: MenuDownloadProtocol!
func downloadProducts() {
let urlPath = "http://server.com/download.php" // Fake URL obviously
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Menu downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let products = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let restomenu = MenuModel()
//the following insures none of the JsonElement values are nil through optional binding
if let product = jsonElement["product"] as? String,
let price = jsonElement["price"] as? String,
let info = jsonElement["info"] as? String,
let imageurl = jsonElement["imageurl"] as? String
{
let productandprice = product + " " + "€" + price
let quantityy = UserDefaults.standard.object(forKey: productandprice) as? String
restomenu.product = product
restomenu.price = price
restomenu.info = info
restomenu.imageurl = imageurl
restomenu.quantity = quantityy
}
products.add(restomenu)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.productsDownloaded(products: products)
})
}
}
extension String {
func chopPrefix(_ count: Int = 1) -> String {
return substring(from: index(startIndex, offsetBy: count))
}
func chopSuffix(_ count: Int = 1) -> String {
return substring(to: index(endIndex, offsetBy: -count))
}
}
MenuModel.swift:
import UIKit
class MenuModel: NSObject {
//properties
var product: String?
var price: String?
var info: String?
var imageurl: String?
var quantity: String?
//empty constructor
override init()
{
}
init(product: String, price: String, info: String, imageurl: String, quantity: String) {
self.product = product
self.price = price
self.info = info
self.imageurl = imageurl
self.quantity = quantity
}
//prints object's current state
override var description: String {
return "product: \(String(describing: product)), price: \(String(describing: price)), info: \(String(describing: info)), imageurl: \(String(describing: imageurl)), quantity: \(String(describing: quantity))"
}
}
tableViewCell.swift:
import UIKit
class productTableViewCell: UITableViewCell {
#IBOutlet weak var productLabel: UILabel!
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var cellView: UIView!
#IBOutlet weak var orderCount: UILabel!
#IBOutlet weak var stepper: UIStepper!
var amount: String?
#IBAction func stepperValueChanged(_ sender: UIStepper) {
amount = Int(sender.value).description
orderCount.text = amount
// let defaultkey = String(productLabel.text!)
UserDefaults.standard.setValue(amount, forKey: productLabel.text!)
if amount == "0"
{
orderCount.isHidden = true
UserDefaults.standard.removeObject(forKey: productLabel.text!)
}
else
{
orderCount.isHidden = false
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
EDIT: after trying filtering options and many different ways I still haven't found how to fix this. I think I'm overthinking it too much.
I'm trying to create a leaderboard, but the player names and scores are not permanently saved, the "leaderboard" only contains the data from the most recent game in its text view. I tried making arrayOfData initially hold playerName and finalScore instead of being an empty array, but the problem still remains. How can I display playerName and playerScore in the leaderboard permanently and hove more names and scores added as more people play?
var finalScore = Int()
var playerName = String()
var allMyStoredData = UserDefaults.standard
var arrayOfData: [Any] = []
class secondVC: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet weak var nameTF: UITextField!
#IBOutlet weak var doneButton: UIButton!
var playerScore = 0
var arrayOfData: [Any] = []
override func viewDidLoad() {
super.viewDidLoad()
scoreLabel.text = "Your score is: \(finalScore)"
loadData()
}
#IBAction func donePressed(_ sender: Any) {
saveData()
performSegue(withIdentifier: "toLeaderboard", sender: self)
}
func saveData () {
playerName = nameTF.text!
playerScore = finalScore
arrayOfData.append(playerName)
arrayOfData.append(playerScore)
allMyStoredData.set(playerName, forKey: "saveTheName")
allMyStoredData.set(playerScore, forKey: "saveTheScore")
allMyStoredData.set(arrayOfData, forKey: "saveTheArray")
}
func loadData () {
if let loadPlayerName:String = UserDefaults.standard.value(forKey: "saveTheName") as? String {
playerName = loadPlayerName
}
if let loadTheScore:Int = UserDefaults.standard.value(forKey: "saveTheName") as? Int {
playerScore = loadTheScore
}
}
}
//this is the code in the leaderboard's view controller
class leaderboardViewController: UIViewController {
#IBOutlet weak var theTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
theTextView.text = "\((arrayOfData).map{"\($0)"}.joined(separator: " Score: "))"
}
}
You are overwriting your saveTheArray item UserDefaults each time with a new array.
You want to fetch the saved array first, append the new game data, then re-save as so:
//Create user defaults instance
let userDefaults = UserDefaults.standard
//User defaults key
let historicalGameDataKey = "historicalGameData"
//Fetch game data array
var historicalGameData = userDefaults.array(forKey: historicalGameDataKey)
//Create array if it is nil (i.e. very first game)
if historicalGameData == nil {
historicalGameData = []
}
//Create game data dictionary
let gameData = [
"playerName": playerName,
"playerScore": playerScore
]
//Append game data dictionary to the array
historicalGameData?.append(["playerName": gameData])
//Save the updated array
userDefaults.set(historicalGameData, forKey: historicalGameDataKey
Core Data would be a better way to store this data, but this example should solve your problem using UserDefaults.
I got error when I stored data Thread 1: Fatal error: init(realm:schema:) has not been implemented
in my button I want storage the data which user has been choce it and at the same time I want display it
but when I click on the button to storage my data I got error
and I'm sure there is not any nil in my data
I hope that my explanation of the problem is clear .
my code :
model :
import Foundation
import UIKit
import RealmSwift
import Realm
class Foods : Object {
#objc dynamic var name = ""
#objc dynamic var price = 0.0
#objc dynamic var descriptionn = ""
#objc dynamic var time = ""
#objc dynamic var rating = 0.0
var image:UIImage?
#objc dynamic var count = 0.0
init(name : String,price : Double , count:Double ,description : String,time : String,rating : Double,image : UIImage) {
self.name = name
self.price = price
self.descriptionn = description
self.time = time
self.rating = rating
self.image = image
self.count = count
super.init()
}
init(name : String,price : Double) {
self.name = name
self.price = price
super.init()
}
required init() {
name = ""
price = 0.0
super.init()
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
fatalError("init(realm:schema:) has not been implemented")
}
required init(value: Any, schema: RLMSchema) {
fatalError("init(value:schema:) has not been implemented")
}
}
my UITableViewCell :
import UIKit
import HCSStarRatingView
import GMStepper
import RealmSwift
class FoodsSecoundTableViewCell: UITableViewCell {
#IBOutlet weak var foodTitle: UILabel!
#IBOutlet weak var foodPrice: UILabel!
#IBOutlet weak var foodRating: HCSStarRatingView!
#IBOutlet weak var foodImage: UIImageView!
#IBOutlet weak var steperCount: GMStepper!
#IBOutlet weak var btnSend: UIButton!
var result : Double?
let realm = try! Realm()
weak var delegate: FoodsSecoundTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
btnSend.layer.cornerRadius = 23
btnSend.layer.borderColor = UIColor.white.cgColor
if steperCount.value == 0 {
btnSend.isEnabled = false
} else if steperCount.value > 0{
btnSend.isEnabled = true
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func myStepper(_ sender: GMStepper) {
delegate?.stepper(sender, at: sender.tag, didChangeValueTo: sender.value)
}
#IBAction func btnSendIt(_ sender: Any) {
print(foodPrice.text)
let myFood = Foods(name: foodTitle.text!, price: Double(foodPrice.text!)!)
try! realm.write {
realm.add(myFood)
}
let result = realm.objects(Foods.self)
for food in result {
print(food.name)
print(food.price)
}
}
}
You don't need to implement all the init() functions you have above. Instead, try this:-
Remove the required init functions, as I assume you are adding these to make it compile. They're not normally needed.
For the initialisers that you want to implement (which I assume are the first two above), add the keyword convenience before init.
Use self.init() at the start of your own initialisers.
So your entire initialiser code should be as below.
convenience init(name : String,price : Double , count:Double ,description : String,time : String,rating : Double,image : UIImage) {
self.init()
self.name = name
self.price = price
self.descriptionn = description
self.time = time
self.rating = rating
self.image = image
self.count = count
}
convenience init(name : String,price : Double) {
self.init()
self.name = name
self.price = price
}
In detail, I am retrieving data from firebase and I am creating an array of restaurants objects from each entry received and Im then performing a count on the number of elements are in the array, which should be 5,23: (restaurant #1)has 5 elements in the array,(restaurant #2) 23 (there's only two restaurants in the dataset). This is being done on my model controller which looks like this
class OverviewModelController {
//MARK: - SharedController
static let sharedController = OverviewModelController()
//MARK: - Source
var restaurantList = [Restaurant]()
var filteredList = [Restaurant]()
//MARK: - FireBase Reference
var ref: DatabaseReference?
func fetchAllData() {
self.ref = Database.database().reference()
self.ref?.child("restaurants").observe( .childAdded , with: { (snap) in
guard let topArray = snap.value as? [[String:Any]] else {print(":(") ; return }
var restaurantArray = [Restaurant]()
for dictionary in topArray {
guard let address = dictionary["address"] as? String,
let city = dictionary["city"] as? String,
let inspectionDate = dictionary["inspectionDate"] as? String,
let name = dictionary["name"] as? String,
let major = dictionary["major"] as? Int,
let minor = dictionary["minor"] as? Int,
let violationTitle = dictionary["violationTitle"] as? String else { continue }
//MARK: - creates restaurants from the list above
let restaurant = Restaurant(address: address, city: city, inspectionDate: inspectionDate, name: name, major: major, minor: minor, violationTitle: violationTitle)
//MARK: - Adds a restaurant to restaurant array instance
restaurantArray.append(restaurant)
}
self.restaurantList = restaurantArray
print(self.restaurantList.count)
})
}
Once I run the method above on a viewcontroller, it prints right count from the view did load but if I check the restaurantList count from a method in the viewController I only get 23 as oppose to 5, 23. I guess I am a bit loss on what I'm doing wrong. I know im doing something that only allows me to receive the last restaurant. I assumed I passed the array of data through the shared controller but it doesn't give me the result I want. The viewcontroller is below:
class MapViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var homeSearchBar: DesignableSearchBar!
var finalArray = [Restaurant]()
override func viewDidLoad() {
super.viewDidLoad()
FirebaseApp.configure()
homeSearchBar.delegate = self
fetch()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func fetch() {
OverviewModelController.sharedController.fetchAllData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print(OverviewModelController.sharedController.restaurantList.count)
self.homeSearchBar.resignFirstResponder()
DispatchQueue.main.async {
self.homeSearchBar.text = ""
}
}
How can i passing data uiviewController from uiview
I am Using function but it was not working
protocol name is startcalldelegate and function name is startcall
UIView Code
protocol StartCallDelegate: class {
func startCall(localNickname :String, remoteNickname :String)}
class CardView: UIView {
let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
weak var delegate: CardViewDelegate?
weak var socketdelegate: StartCallDelegate?
#IBOutlet weak var UserPhoto: UIImageView!
#IBOutlet weak var UserNickName: UILabel!
#IBOutlet weak var UserAge: UILabel!
#IBOutlet weak var UserPeople: UILabel!
var localNickname: String = ""
var remoteNickname: String = ""
#IBAction func SendMessage(_ sender: Any) {
print("SendMessage")
//print(localNickName)
//print(UserNickName.text!)
}
#IBAction func SendVideoCall(_ sender: Any) {
print("SendVideoCall")
let entityDescription = NSEntityDescription.entity(forEntityName: "Profile", in: managedObjectContext)
let request = NSFetchRequest<NSFetchRequestResult>()
request.entity = entityDescription
do {
let objects = try managedObjectContext.fetch(request)
if objects.count > 0 {
let match = objects[0] as! NSManagedObject
localNickname = match.value(forKey: "nick") as! String
} else {
print("Nothing founded")
}
} catch {
print("error")
}
remoteNickname = UserNickName.text!
socketdelegate?.startCall(localNickname: localNickname, remoteNickname: remoteNickname)
delegate?.VideoChatSegue()
}
}
UIViewcontroller Code
class ViewController: UIViewcontroller, StartCallDelegate {
var localNickname: String = ""
var remoteNickname: String = ""
override func viewDidLoad() {
super.viewDidLoad()
print(localNickname)
print(remoteNickname)
}
func startCall(localNickname: String, remoteNickname: String) {
print("Action startcall func")
self.localNickname = localNickname
self.remoteNickname = remoteNickname
}
startCall func not working
You need to define delegate in viewcontroller' ViewDidLoad
let objOardView = CardView() // this is only test purpose
objOardView.socketdelegate = self