I have an xCode project and I want to reload a label which contain the user score,
here is the code:
#IBOutlet weak var moneyLabel: UILabel!
#IBOutlet weak var imageMoneyMove: UIImageView!
var managedObjextContext:NSManagedObjectContext!
var coreDataMoney = [Money] ()
var money:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
managedObjextContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
loadData()
let moneyItem = Money(context: managedObjextContext)
moneyLabel.text! = String(moneyItem.allMoney)
}
func loadData() {
let presentRequest:NSFetchRequest<Money> = Money.fetchRequest()
do {
coreDataMoney = try managedObjextContext.fetch(presentRequest)
} catch {
print("Could not load data from database \(error.localizedDescription)")
}
}
#IBAction func addMoney(_ sender: Any) {
let moneyItem = Money(context: managedObjextContext)
money += 1
moneyItem.allMoney = Float(money)
moneyLabel.text! = String(moneyItem.allMoney)
print(moneyItem.allMoney)
do {
try self.managedObjextContext.save()
self.loadData()
}catch {
print("Could not save data \(error.localizedDescription)")
}
print(moneyItem.allMoney)
}`
I have an Entity called Money and an attribute of type Float and named allMoney. Thank you !
Assuming you have an Entity Money with is supposed to contain a single record I recommend to implement a computed property with custom getter and setter.
I renamed coreDataMoney with money and money with amount
First of all declare variable for the Money object and a lazy instantiated variable for the managed object context.
var money : Money!
lazy var managedObjextContext : NSManagedObjectContext = {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}()
Declare the variable amount.
The getter checks if the variable money containing the NSManagedObject is nil. If yes, the object is loaded, then the value is returned.
The setter updates the value in the NSManagedObject object, saves the context and updates the label.
var amount : Float {
get {
if money == nil {
loadData()
}
return money.allMoney
}
set {
money.allMoney = newValue
saveContext()
moneyLabel.text! = String(newValue)
}
}
Create a convenience method to save the context.
func saveContext() {
do {
try self.managedObjextContext.save()
} catch {
print("Could not save data \(error.localizedDescription)")
}
}
The loadData() method load the Money object. If the entity is empty create one.
func loadData() {
let presentRequest:NSFetchRequest<Money> = Money.fetchRequest()
do {
let coreDataMoney = try managedObjextContext.fetch(presentRequest)
if coreDataMoney.isEmpty {
let moneyItem = Money(context: managedObjextContext)
money = moneyItem
saveContext()
} else {
money = coreDataMoney.first!
}
} catch {
print("Could not load data from database \(error.localizedDescription)")
}
}
In viewDidLoad get the variable amount to load the data and update the label.
override func viewDidLoad() {
super.viewDidLoad()
moneyLabel.text! = String(amount)
}
Finally in the addMoney method just increment the amount property. The setter logic updates the CoreData stack and the label.
#IBAction func addMoney(_ sender: Any) {
amount += 1.0
}
Related
I'm writing an App, that takes information from an Excel document and saves the Data in Realm. My problem is, that every time I open the App, the Realm Database will save a copy of the Information. Now I get my TableViews with 3 times the same items.
Here is the code in my Main View Controller:
class ViewController: UIViewController {
let realm = try! Realm()
var importExcel = Import()
var xslxConvert = xslxConverter()
var currentString: [String] = []
var Name = ""
#IBOutlet weak var VRLabel: UIButton!
#IBOutlet weak var configurationLabel: UIButton!
#IBOutlet weak var Label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
currentString = importExcel.Results()
Label.text = Name
DispatchQueue.main.async {
self.saveData()
print("Data from Excel saved")
}
}
//MARK: - SaveData to the Realm
func saveData() {
do {
try realm.write {
for item in currentString {
let newItem = FunctionData()
newItem.functionName = item
realm.add(newItem)
}
}
} catch {
print ("Error trying to Realm data, \(error)")
}
}
}
How can I make a filter of something, to make that the App just save the Information from Excel ones?
Thanks a lot for the help!
Ok, I think it doesn't work with UUID(), because it will be different all time.
let filter = // choose what you need
if let items = Array(realm.objects(FunctionData.self).filter("parameterName == %#", filter)) {
// Do not save
} else {
// Save
}
And try to use realm.add(newItem, update: .modified) for saving
Hi everyone!
At the moment, I am taking a course at the Harvard computer science CS50.
My homework is almost ready, but has some incompleteness.
I cannot assign a value from a function to a variable in the class or pass
this value to the next function.
import UIKit
class PokemonViewController: UIViewController {
var url: String!
var name: String!
#IBOutlet var pokemonImage: UIImageView!
#IBOutlet var nameLabel: UILabel!
#IBOutlet var numberLabel: UILabel!
#IBOutlet var type1Label: UILabel!
#IBOutlet var type2Label: UILabel!
#IBOutlet var catchButton: UIButton!
#IBOutlet var descriptionLabel: UILabel!
// MARK: - additional properties
var currentDescURL: String!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadPokemon()
showPokemonDescription()
}
//MARK: - pokemon loading
func loadPokemon() {
guard let pokemonURL = URL(string: url) else { return }
URLSession.shared.dataTask(with: pokemonURL) { (data, _, error) in
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(PokemonResult.self, from: data)
DispatchQueue.main.async {
self.navigationItem.title = self.capitalize(text: result.name)
self.nameLabel.text = self.capitalize(text: result.name)
self.numberLabel.text = String(format: "#%03d", result.id)
for typeEntry in result.types {
if typeEntry.slot == 1 {
self.type1Label.text = typeEntry.type.name
}
else if typeEntry.slot == 2 {
self.type2Label.text = typeEntry.type.name
}
}
// Create Image and Update ImageView
guard let imageURL = URL(string: result.sprites.front_default) else { return }
if let data = try? Data(contentsOf: imageURL) {
self.pokemonImage.image = UIImage(data: data)
}
self.currentDescURL = result.species.url
print(self.currentDescURL)
}
} catch let error { print(error) }
}.resume()
}
// MARK: - Get the URL of a specific Pokémon
func showPokemonDescription() {
guard let pokemonDescriptionURL = URL(string: currentDescURL) else { return }
URLSession.shared.dataTask(with: pokemonDescriptionURL) { (data, _, error) in
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(PokemonDescription.self, from: data)
DispatchQueue.main.async {
// Check and get first pokemon description in English
for index in 0..<result.flavor_text_entries.count {
if result.flavor_text_entries[index].language.name == "en" {
self.descriptionLabel.text = result.flavor_text_entries[index].flavor_text
}
}
}
} catch let error { print(error) }
}.resume()
}
}
The first function loadPokemon() inside itself gets value from JSON and prints the value to the console -> print(self.currentDescURL). Moreover, if you display this value in viewWillAppear, then "nil" will be displayed in the console. I understand that the loadPokemon() function processes the values in the stream that occur at the very end. Perhaps because of this, the variable currentDescURL cannot get the value from the loadPokemon() function and the showPokemonDescription() function cannot use this value since currentDescURL is nil.
I ask you to explain to me what my mistake is and to help finish the assignment.
Move the call for method showPokemonDescription from viewWillAppear to loadPokemon after the currentDescURL property is set.
class PokemonViewController: UIViewController {
//...
var currentDescURL: String!
//...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadPokemon()
getPreferences()
// <- remove the call from here
}
//...
func loadPokemon() {
//...
self.currentDescURL = result.species.url
self.showPokemonDescription() // <- move the call here
}
//...
func showPokemonDescription() {
//...
}
}
I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[DataModelItem])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
func requestData() {
}
private func setDataWithResponse(response:[AnyObject]){
var data = [DataModelItem]()
for item in response{
if let tableViewModel = DataModelItem(data: item as? [String : String]){
data.append(tableViewModel)
}
}
delegate?.didRecieveDataUpdata(data: data)
}
}
And for DataModelItem:
class DataModelItem{
var name:String?
var id:String?
init?(data:[String:String]?) {
if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
self.name = serviceName
self.id = serviceId
}
else{
return nil
}
}
}
Controller:
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
How to implement simple MVC design pattern in Swift?
As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.
For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.
How to call a DataModel init function in "requestData" function
It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.
In the view controller:
let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()
I am just sharing my answer here and I am using a codable. It will be useful for anyone:
Model:
import Foundation
struct DataModelItem: Codable{
struct Result : Codable {
let icon : String?
let name : String?
let rating : Float?
let userRatingsTotal : Int?
let vicinity : String?
enum CodingKeys: String, CodingKey {
case icon = "icon"
case name = "name"
case rating = "rating"
case userRatingsTotal = "user_ratings_total"
case vicinity = "vicinity"
}
}
let results : [Result]?
}
NetWork Layer :
import UIKit
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[String])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
var theatreNameArray = [String]()
var theatreVicinityArray = [String]()
var theatreiconArray = [String]()
func requestData() {
Service.sharedInstance.getClassList { (response, error) in
if error != nil {
self.delegate?.didFailUpdateWithError(error: error!)
} else if let response = response{
self.setDataWithResponse(response: response as [DataModelItem])
}
}
}
private func setDataWithResponse(response:[DataModelItem]){
for i in response[0].results!{
self.theatreNameArray.append(i.name!)
self.theatreVicinityArray.append(i.vicinity!)
self.theatreiconArray.append(i.icon!)
}
delegate?.didRecieveDataUpdata(data: theatreNameArray)
print("TheatreName------------------------->\(self.theatreNameArray)")
print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
print("Theatreicon------------------------->\(self.theatreiconArray)")
}
}
Controller :
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
APIManager :
class Service : NSObject{
static let sharedInstance = Service()
func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
guard let gitUrl = URL(string: "") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(DataModelItem.self, from: data)
completion!([gitData],nil)
} catch let err {
print("Err", err)
completion!(nil,err as NSError)
}
}.resume()
}
}
I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application.
You may refer its documentation at :
Managing Shared resources using singleton
With this you wont need to initialise this class instance every time you need to access data.
I want to load Core Data objects in the background, and when it´s finished, should reload a TableView. The code below gives an empty tableView:
class ViewController: UIViewController {
var fetchRequest: NSFetchRequest<Venue>!
var venue:[Venue] = []
var coreDataStack: CoreDataStack!
override func viewDidLoad() {
super.viewDidLoad()
fetchRequest = Venue.fetchRequest()
coreDataStack.storeContainer.performBackgroundTask {(context) in
do {
self.venue = try context.fetch(self.fetchRequesrt)
} catch {
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
But if we write code in viewDidLoad() without chunk above, all works fine and tableView is populated with data:
venue = try! coreDataStack.managedContext.fetch(fetchRequesrt)
tableView.reloadData()
What is wrong with the code above?
For advices I would be very grateful!
Edit. That's the right solution:
class ViewController: UIViewController {
var fetchRequest: NSFetchRequest<Venue>!
var venue:[Venue] = []
var coreDataStack: CoreDataStack!
override func viewDidLoad() {
super.viewDidLoad()
fetchRequest = Venue.fetchRequest()
coreDataStack.storeContainer.performBackgroundTask {(context) in
do {
let venue = try context.fetch(self.fetchRequesrt)
venue.map {$0.objectID}.forEach{self.venue.append(self.coreDataStack.managedContext.object(with: $0) as! Venue)}
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
...
}
}
}
}
One more solution to use NSAsynchronousFetchRequest class. The example above would look like this:
class ViewController: UIViewController {
var fetchRequesrt: NSFetchRequest<Venue>!
var venue:[Venue] = []
var asyncFetchRequest: NSAsynchronousFetchRequest<Venue>!
var coreDataStack: CoreDataStack!
fetchRequest = Venue.fetchRequest()
override func viewDidLoad() {
super.viewDidLoad()
asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest:fetchRequest, completionBlock: { [unowned self]
(result: NSAsynchronousFetchResult) in
guard let venue = result.finalResult else { return }
self.venue = venue
self.tableView.reloadData()
})
do {
try coreDataStack.managedContext.execute(asyncFetchRequest)
} catch {
...
}
}
}
You should use NSFetchedResultController whenever interacting with Core Data and TableView itself. It is much stable and optimized than fetching data on your own.
I am very confused about your code, I have never seen a solution like that. Maybe you should make function with closure and on completion call reloadData, or put the whole block into beginUpdates like this:
self.tableView.beginUpdates()
coreDataStack.storeContainer.performBackgroundTask {(context) in
do {
self.venue = try context.fetch(self.fetchRequesrt)
} catch {/*error goes here*/}
}
self.tableView.endUpdates()
Anyway, I strongly recommend using NSFetchedResultsController because it is much easier for performing backgroundTasks.
How can I store an array of objects of type Goal which I have created in NSUserDefaults? (in swift)
Here is the code:
func saveGoalList ( newGoalList : [Goal] ){
let updatedGoalList = newGoalList;
NSUserDefaults.standardUserDefaults().setObject(updatedGoalList, forKey: "GoalList")
NSUserDefaults.standardUserDefaults().synchronize()
}
class GoalsViewController: MainPageContentViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: GoalsTableView!
var cell = GoalTableViewCell()
var goalsArray : Array<Goal> = [] //
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
if var storedGoalList: [Goal] = NSUserDefaults.standardUserDefaults().objectForKey("GoalList") as? [Goal]{
goalsArray = storedGoalList;
}
var goal = Goal(title: "Walk the Dog")
goalsArray.append(goal)
saveGoalList(goalsArray)
self.tableView?.reloadData()
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.xpnotificationView.alpha = 0.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goalsArray.count //to ensure there is always an extra cell to fill in.
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { //recreate the cell and try using it.
cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as GoalTableViewCell
cell.goalTextField.text = goalsArray[indexPath.row].title as String!
cell.checkmarkImageView.visible = goalsArray[indexPath.row].checkmarked as Bool!
if (cell.checkmarkImageView.visible == true) {
cell.blackLineView.alpha = 1.0
} else {
cell.blackLineView.alpha = 0.0
}
return cell
}
}
I understand that there are only certain data types that work with NSUserDefaults. Could anyone help me understand how I could do that?
Edit: Right now Goal inherits from NSObject.
I am posting code from a learning project I did to store objects using NSCoding. Fully functional and ready to use. A math game that was storing game variables, etc.
//********This class creates the object and properties to store********
import Foundation
class ButtonStates: NSObject {
var sign: String = "+"
var level: Int = 1
var problems: Int = 10
var time: Int = 30
var skipWrongAnswers = true
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(sign, forKey: "sign")
aCoder.encodeInteger(level, forKey: "level")
aCoder.encodeInteger(problems, forKey: "problems")
aCoder.encodeInteger(time, forKey: "time")
aCoder.encodeBool(skipWrongAnswers, forKey: "skipWrongAnswers")
}
init(coder aDecoder: NSCoder!) {
sign = aDecoder.decodeObjectForKey("sign") as String
level = aDecoder.decodeIntegerForKey("level")
problems = aDecoder.decodeIntegerForKey("problems")
time = aDecoder.decodeIntegerForKey("time")
skipWrongAnswers = aDecoder.decodeBoolForKey("skipWrongAnswers")
}
override init() {
}
}
//********Here is the data archiving and retrieving class********
class ArchiveButtonStates:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func ArchiveButtons(#buttonStates: ButtonStates) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("buttonStates.archive")
if NSKeyedArchiver.archiveRootObject(buttonStates, toFile: path) {
//println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func RetrieveButtons() -> NSObject {
var dataToRetrieve = ButtonStates()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("buttonStates.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? ButtonStates {
dataToRetrieve = dataToRetrieve2 as ButtonStates
}
return(dataToRetrieve)
}
}
the following is in my ViewController where the game is played. Only showing the relevant code for retrieving and storing objects
class mathGame: UIViewController {
var buttonStates = ButtonStates()
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//set inital view
//retrieving a stored object & placing property into local class variables
buttonStates = ArchiveButtonStates().RetrieveButtons() as ButtonStates
gameData.sign = buttonStates.sign
gameData.level = buttonStates.level
gameData.problems = buttonStates.problems
gameData.time = buttonStates.time
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
//storing the object
ArchiveButtonStates().ArchiveButtons(buttonStates: buttonStates)
}
}
You need your class to adopt the NSCoding protocol and encode and decode itself, like this:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch23p798basicFileOperations/ch36p1053basicFileOperations/Person.swift
Now you can transform an instance of your class into an NSData by calling NSKeyedArchiver.archivedDataWithRootObject: - and an NSData can go into NSUserDefaults.
This also means that an NSArray of instances of your class can be transformed into an NSData by the same means.
For Swift 2.1, your Goal class should look like :
import Foundation
class Goal : NSObject, NSCoding {
var title: String
// designated initializer
init(title: String) {
self.title = title
super.init() // call NSObject's init method
}
// MARK: - comply wiht NSCoding protocol
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(title, forKey: "GoalTitle")
}
required convenience init?(coder aDecoder: NSCoder) {
// decoding could fail, for example when no Blog was saved before calling decode
guard let unarchivedGoalTitle = aDecoder.decodeObjectForKey("GoalTitle") as? String
else {
// option 1 : return an default Blog
self.init(title: "unknown")
return
// option 2 : return nil, and handle the error at higher level
}
// convenience init must call the designated init
self.init(title: unarchivedGoalTitle)
}
}
and you should use it in your view controller like I did in this test code :
// create an array with test data
let goal1 = Goal(title: "first goal")
let goal2 = Goal(title: "second goal")
let goalArray = [goal1, goal2]
// first convert the array of custom Goal objects to a NSData blob, as NSUserDefaults cannot handle arrays of custom objects directly
let dataBlob = NSKeyedArchiver.archivedDataWithRootObject(goalArray)
// this NSData object can now be stored in the user defaults
NSUserDefaults.standardUserDefaults().setObject(dataBlob, forKey: "myGoals")
// sync to make sure they are saved before we retreive anytying
NSUserDefaults.standardUserDefaults().synchronize()
// now read back
if let decodedNSDataBlob = NSUserDefaults.standardUserDefaults().objectForKey("myGoals") as? NSData {
if let loadedGoalsArray = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSDataBlob) as? [Goal] {
for goal in loadedGoalsArray {
print("goal : \(goal.title)")
}
}
}
As a final remark : it would be easier to use NSKeyedArchiver instead of NSUserDefaults, and store your array of custom objects directly to a file. You can read more about the difference between both methods in another answer I posted here.