Looping Core Data Objects outside a Table/ Collection View - swift

I want to loop through the objects in CoreData without using a tableView or CollectionView... But using a ViewController
I tried something like:
for var i = 0; i < numberOfExerciseItems; i++ {
let exerciseItemsfromDay = fetchedResultController.objectAtIndexPath(i) as! ExerciseItemModel
}
This obviously doesn't work since it is not of NSIndexPath type as you'd get in a table or CollectionView. Are there ways to do this outside a Table / Collection View? Thanks for the ideas in advance.
Edit
From this answer, I'd want to access an entity from an item in the items array in the loop :
let request = NSFetchRequest(entityName: "ExerciseItemModel")
let items = (try? context.executeFetchRequest(request)) as? [ExerciseItemModel] ?? []
In this loop
items.forEach {
print(items) //Displays all the objects in the console.
print(items[1].attribute //Throws the error Value of type'AnyObject' has no member 'attribute' and I cannot access an attribute from the items array . Not sure why!
}
Edit My ExerciseModel Class
import Foundation
import CoreData
#objc(ExerciseItemModel)
class ExerciseItemModel: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
extension ExerciseItemModel {
#NSManaged var exerciseType: String?
#NSManaged var exerciseName: String?
#NSManaged var durationOrSets: String?
#NSManaged var distanceOrReps: String?
#NSManaged var weight: String?
#NSManaged var backgroundImage: NSData?
#NSManaged var dayID: String?
#NSManaged var date: NSDate?
}

let request = NSFetchRequest(entityName: "ExerciseItemModel")
let items = (try? context.executeFetchRequest(request)) as? [ExerciseItemModel] ?? []
items.forEach {
// Do your stuff
}
A small example. Not exactly an answer so I'll delete it when a proper answer has been posted. This example will not show you any error messages if there are any, just keep that in mind.
Edit:
You're using forEach() incorrectly:
items.forEach {
print($0) // Print's the object in items
print($0.attribute) // Should print the attribute
}
print(items[0].attribute) // Probably same error, but it should be used outside the loop

Related

Swift : retrieving struct values in swiftui view is not working

I am new to swift and need to define some form of Global Dictionary that I can access the content throughout my project. My understanding is that struct class can be used for that so
I created a struct and appended values to it, now I want to access each of those values in view
this is my product struct
struct Product {
let name: String
let aisleNo:Int
let location_section: Int
let location_zone: String
let productPrice: Int
}
then created a global
import Foundation
struct Global {
static var productList = [Product]()
}
this is how I append many products to Product
class SearchResult : ObservableObject {
var productList = [Product]()
//There could be hundreds of product in the array
for product in productArray {
let productName = product.productName!
let aisleNo = product.productLocation_aisle.value!
let location_section = product.productLocation_section.value!
let location_zone = product.productLocation_zone!
let productPrice = product.productPrice.value!
let product_real_id = product._id!
Global.productList.append(Product(name: productName, aisleNo: aisleNo, location_section: location_section, location_zone: location_zone, productPrice: Int(productPrice)))
}
this is my search result view where I want to display the content of the Product
struct SearchResultView: View {
var searchResults = Global.productList
var body: some View {
VStack {
List {
ForEach(model.searchResults, id: \.self) { text in
Text(text)
}
}
}
}
}
I can seem to get it to show in the searchResultView. What am doing wrong ?
I keep getting this error
Generic struct 'ForEach' requires that 'Product' conform to 'Hashable'
Initializer 'init(_:)' requires that 'Product' conform to 'StringProtocol'
You need to set "searchResults" equal to your "productList"
Right now your searchResults is EMPTY. It simply exists as an instance of your struct with no data in it.
one option is to make the variable scope global and then set your new variable = to it
self.searchResults = Global.productList
--EDIT
You are close.
Where you set your var here
var searchResults = Global.productList
it needs to be like this.
var searchResults = [Product]() // ->Creates an instance of the struct object
Then set it equal to your global array.
self.searchResults = Global.productList
ALSO you should remove you redundant variable var productList = [Product]()
Furthermore, some things to note
for product in productArray {
let productName = product.productName!
let aisleNo = product.productLocation_aisle.value!
let location_section = product.productLocation_section.value!
let location_zone = product.productLocation_zone!
let productPrice = product.productPrice.value!
let product_real_id = product._id!
Global.productList.append(Product(name: productName, aisleNo: aisleNo, location_section: location_section, location_zone: location_zone, productPrice: Int(productPrice)))
}
you are doing extra work by using all the let variables.
A better way is doing it like so.
for product in productArray {
Global.productList.append(Product(name: product.name, aisleNo: product.aisleNo, location_section: product.location_section, location_zone: product.location_zone, productPrice: Int(product.productPrice)))
}
EDIT - Hashable Erorr
try this out
struct Product: Hashable {
let name: String
let aisleNo:Int
let location_section: Int
let location_zone: String
let productPrice: Int
}

Saving custom object as core data property

I am trying to save a custom class as a property of a core data object.
The Core Data object:
#objc(Safer)
class Safer: NSObject
{
#NSManaged var perakimFiles: [prayer]? //This line gives me an error saying that it cannot save a type that is not Obj. C
#NSManaged var titleEnglish: String?
#NSManaged var titleHebrew: String?
#NSManaged var parshaPerakim: [Int]?
#NSManaged var saferCode: String?
var titles: [[String]]
{
get
{
var ret = [[String]]()
var i = 0
for file in perakimFiles!
{
ret.append([file.title, "\(i+1)"])
i++
}
return ret
}
}
init(_ _saferCode: SefarimCodes)
{
super.init()
self.saferCode = _saferCode.rawValue
}
init(_perakimFiles: [prayer], _titleEnglish: String, _titleHebrew: String)
{
super.init()
self.perakimFiles = _perakimFiles
self.titleEnglish = _titleEnglish
self.titleHebrew = _titleHebrew
}
init(_perakimFiles: [prayer], _titleEnglish: String, _titleHebrew: String, _parshaPerakim: [Int])
{
super.init()
self.perakimFiles = _perakimFiles
self.titleEnglish = _titleEnglish
self.titleHebrew = _titleHebrew
self.parshaPerakim = _parshaPerakim
self.saferCode = setTorahSaferCode()!.rawValue
let config = self.configFromCode()
self.perakimFiles = config.perakimFiles
}
}
Here is the prayer class that I am trying to save in the core data object:
class prayer
{
var url: NSURL
var title: String
var detail: String?
init(initURL: NSURL, initTitle: String)
{
print("1")
print("2")
self.title = initTitle
print("3")
self.url = initURL
print("4")
}
init(initURL: NSURL, initTitle: String, initDetail: String)
{
self.url = initURL
self.title = initTitle
self.detail = initTitle
}
}
So what can I do to the prayer class to make it savable by the core data object? I need to also be able to use instances of the prayer class in other places of the code.
As mentioned, have your prayer class subclass NSObject and conform to NSCoding which requires two methods : -initWithCoder: and encodeWithCoder:
Once those are implemented, you can use NSKeyedArchiver/NSKeyedUnarchiver class methods to transform your objects into NSData objects and back, thus allowing you to store your objects as CoreData properties under the supported NSData type.
Let class prayer conforms to NSObject and NSCoding and see whether that addresses the error.

Swift Struct Identity

I have a following struct which defines a shoppingList.
struct ShoppingList {
var shoppingListId :NSNumber
var title :String
}
let shoppingList = ShoppingList(shoppingListId: NSNumber, title: String) // I don't want to assign shoppingListId
Now, the problem is that it auto-generates the constructors which takes in the shoppingListId as parameter. Since shoppingListId is an identity or unique key I don't want to pass in with the constructor. It will be set later by the ShoppingListService after inserting shoppingList into the database.
Is this a place where class will make more sense than structures?
UPDATE:
struct ShoppingList {
var shoppingListId :NSNumber
var title :String
init(title :String) {
self.title = title
// but now I have to assign the shoppingListId here unless I make it NSNumber? nullable
}
}
let shoppingList = ShoppingList(title: "Hello World")
UPDATE 3:
let shoppingList = ShoppingList()
// THE FOLLOWING CODE WILL NOT WORK SINCE IT REQUIRES THE shoppingList to // BE var instead of let
shoppingList.shoppingListId = NSNumber(int: results.intForColumn("shoppingListId"))
shoppingList.title = results.stringForColumn("title")
shoppingLists.append(shoppingList)
If you don't want the default constructor, then make one of your own. However, it is important to keep in mind that unless your property is an Optional, it has to be initialized to some value. In this case, you will have to give shoppingListId a value, or you will need to make it an Optional. Also, I don't see any reason why a class would be better than a struct for this scenario.
struct ShoppingList {
var shoppingListId: NSNumber
var title: String
init(title: String) {
self.title = title
shoppingListId = NSNumber(integer: 0)
}
}
let newList = ShoppingList(title: "Groceries")
Update:
Just saw you updated your question. If you are not able to pick a reasonable initial value for your shoppingListId, then make it an Optional like so:
struct ShoppingList {
var shoppingListId: NSNumber?
var title: String
init(title: String) {
self.title = title
}
}
let newList = ShoppingList(title: "Groceries")
Just realize shoppingListId will be nil till you set it to something, and everywhere you use it you should bind the value in an if let.

CoreData - One-to-many relationship

I'll post the following code then explain my struggle
This function saves a day (like sunday, monday, tuesday, etc):
func appendDaysToArray() {
let dayLabel = dayName.text
daysArray.append(dayLabel)
let dayEntity = NSEntityDescription.entityForName("TrainingDay", inManagedObjectContext: moc!)
let trainingday = TrainingDay(entity: dayEntity!, insertIntoManagedObjectContext: moc)
trainingday.day = dayName.text
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Day #\(dayName.text) saved successfully!")
}
}
and this one saves details as a name, a number of sets and a number of repetitions (like gym exercises):
func appendTrainingDetails () {
let nameLabel = exerciseName.text
namesArray.append(nameLabel)
let numberOfSets = setsNumber.text?.toInt()
setsArray.append(numberOfSets!)
let numberOfReps = repsNumber.text?.toInt()
repsArray.append(numberOfReps!)
let detailsEntity = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
let trainingdetails = TrainingDetails(entity: detailsEntity!, insertIntoManagedObjectContext: moc)
trainingdetails.exerciseName = exerciseName.text
trainingdetails.setsNumber = setsNumber.text!
trainingdetails.repsNumber = repsNumber.text!
var error: NSError?
moc?.save(&error)
if let err = error {
var status = err.localizedFailureReason
println("\(status)")
} else {
println("Exercise: #\(exerciseName.text) saved successfully!")
println("Number of sets: #\(setsNumber.text) saved successfully!")
println("Number of reps: #\(repsNumber.text) saved successfully!")
}
}
My app is working ok, but what I actually need is this: for each DAY, I will have multiple exerciseNames, setsNumber and repsNumber. I set a one-to-many relationship, but I don't know how to attribute the TrainingDetails to each day in the daysArray.
Here are my 2 models:
import Foundation
import CoreData
class TrainingDay: NSManagedObject {
#NSManaged var day: String
#NSManaged var relationship1: NSSet
}
and
import Foundation
import CoreData
class TrainingDetails: NSManagedObject {
#NSManaged var exerciseName: String
#NSManaged var repsNumber: String
#NSManaged var setsNumber: String
#NSManaged var relationship2: TrainingDay
}
Later, I'll have a button for each day and, when pressed, they will update a tableView with the list of exercises for that specific day. That's why I need to set this one-to-many relationship.
How can I achieve this?
Sorry for any mistakes. Thanks in advance!!
The documentation for what you want to do is in this link, under the heading "To-Many Relationships".
Here is a short example. First, I recommend changing the names of your relationships to something more intuitive. It will really help:
class TrainingDay: NSManagedObject {
#NSManaged var day: String
#NSManaged var trainingDetails: NSSet
}
class TrainingDetails: NSManagedObject {
// ... other stuff here
#NSManaged var trainingDay: TrainingDay
}
Make sure any changes you make to the code are also made in the model graph. Make sure that the relationships are configured to be the inverse of each other in the model graph.
You can set the TrainingDay for a given TrainingDetails like this:
// This assumes you've created a TrainingDay called "trainingDay1", and a
// TrainingDetails object called "details".
details.trainingDay = trainingDay1
Core Data takes care of creating the inverse relationship as well, so the trainingDay1 object will automatically add details to its trainingDetails set.
If you are trying to add objects to the trainingDetails set in TrainingDay, you need to use the mutableSetValueForKey: method described in the documentation that I linked to. Basically, it looks like this:
var details = trainingDay1.mutableSetValueForKey("trainingDetails")
details.addObject(newTrainingDetails)
The mutableSetValueForKey: creates a proxy object. Any changes made to the set that it returns are effective on the set you are trying to modify.
Hope this helps!

How do I create global object in swift?

I think my previous question was too vague. Let me try again.
I'm trying to hold user information by creating a singleton.
After a bit of research, this is what I understood.
Class UserInfo {
var userFirstName: String?
var userLastName: String?
/*I'll add all other variables here*/
}
let sharedInfo = UserInfo()
Am I on right path?
EDIT: For Swift 1.2 and higher (Thanks Will M)
class UserInfo {
static let sharedUserInfo = UserInfo()
var firstName: String?
var lastName: String?
}
For Swift earlier than 1.2
Something along the lines of:
class UserInfo
{
// Singleton Setup
private static var info: UserInfo?
class func sharedUserInfo() -> UserInfo
{
if self.info == nil
{
self.info = UserInfo()
}
return self.info!
}
// Class properties
var firstName: String?
var lastName: String?
}
let user = UserInfo.sharedUserInfo()
user.firstName = "Fred"
user.lastName = "Smith"
I would also recommend making sure that a singleton is really what you want, because depending on how you use it, you could run into some race conditions with threading.