Best way to store and refrence lots data in Swift for use in a UITableView - ios

This is a bit confusing so I apologise. But:
I am trying to make an app where the user has the ability to add items to a list of individual items that will be displayed back to them(Something along the lines of a todo list app) as a table view.
However, I have hit a roadblock I need to store several different bits of data for each item(Some Strings, Some ints and a date) in the list.
I think that a class(or struct) would be the best way to do this where an instance of the class holds the information need for each item and then the name of that instance is stored in a list so it can be accessed via the indexPath in the table view.
However, I don't know how I am going to make a new instance of the class for every item because the app could have hundreds of individual items.
I'm sorry, this is so confusing and any help would be appreciated! Feel free to ask for more info
Edit: what I am looking for and I'm sure there's a stupidly easy way of doing it but I'm try to work out how to create an instance of a class when the name of the class is stored in a variable. Ecencialy I want the instance of the class to store the item. To be created when the user inputs the item to be added to the table.
Eg. They enter an item. item1 and the other data that goes along with then I want to be able to store that in instance of the item class but I don't know how to make the name of that instance because the name I want which is item 1 is stored in a variable.
Sorry that's so confusing that's the reason I need help

So first: You can't store the Name of a Class in a Variable and use this variable to get a new instance of a class.
What you need is an Array containing all the different Items. This array is unlimited so you can store as many items in it as you like and you don't need to set a name for each of these Instances.
First create an empty array containing all Items as a property:
var items : [Item] = []
Second call the function populateItemArray() in your viewDidLoad():
private func populateItemArray() {
// Populate the items Array (Are you using CoreData, Realm, ...?)
}
Third, use the Item Array to populate the TableView.
REMEMBER: If your only using one section in your tableView, indexPath.row is always equal to the corresponding item in the array.
E.G. items[indexPath.row]
Hope this helps you!
UPDATE:
Look at this example struct Item. As you can see you can also store a date in it:
struct Item {
// First create all the properties for your struct.
var data1: String
var data2: Int
var data3: String
// You can create properties of any type you want, also of type date.
var dateOfCreation : Date
// Then implement all the methods of your custom Struct Item.
func setDate(with date : Date) {
self.dateOfCreation = date
}
func returnDate() -> Date {
return self.dateOfCreation
}
}

Related

Value type design pattern to replace class

We are a looking for a value type design pattern in swift that will allow us to create a shopping cart to hold Products. We are currently using a class but that is a reference type and when we try to add two different version of the same product (i.e. with a different colors or sizes), the first item we added gets changed to the second item we added because it points to the same object in memory.
The design pattern needs to be “global” so we can access it from any page in the app. Right now this is our Cart class that stores all the items in the cart. What do we need to do to make this a value type or how does it need to be reengineered to use a struct without a class?
class Cart : NSObject {
var allProductsInCart = [MainProduct]()
override init() {
super.init()
}
class var sharedCart: Cart {
struct Static {
static let instance = Cart()
}
return Static.instance
}
}
The problem we are getting is that we need the products in the cart to be of custom class “MainProduct.” Right now as you can see, they are stored as “MainProduct.” Do we need to switch the products to a struct or other design pattern as well? How would we do that?
Yes, given the desired behavior between a value type vs. reference type you should use a Struct.
A commonly used "pattern" for doing this is called "Redux".
The idea is that you have one, immutable version of the "state" of your app.
It can be accessed from anywhere and the only way to update it is through "actions". These will reconstruct the entire state with the required updates.
ViewControllers and views, etc... subscribe to updates of various parts of the state.
So you could have an AppState that contains a ShoppingCartState. When a product is added to it your CartViewController will be informed of this update and can update its view etc...
There are many different frameworks that are built to use Redux so I won't recommend one as you should find the one that is right for you. But I believe this pattern best suits the usage you are after.

IGListKit with sections and multiple items

I'm trying to implement a collection view with IGListKit. It can either have one or three sections. The datasource is populated in realtime and so needs to refresh the content when new data are available.
To do so, I've created a datasource object conform to IGListDiffable that represent each section as such:
final class DataSource: NSObject {
var title: String?
var items: [SJResult] = []
}
extension DataSource: IGListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
if object === self {
return true
}
guard let obj = object as? DataSource else {
return false
}
return obj.items.count == items.count
}
}
Inside each section, I would like to display a list of items. I've managed to create the section by reusing the same section controller, but the only way I've found to insert new result was to do call reloadData() on the adapter. This is pretty bad as it will reload the complete list of items (which can potentially be very long, >50 items). It also give no possibility to animate the insertion of new item. I've also tried to do a adapter.performUpdates(animated: true) with no luck as it only refresh my datasource but not the items inside the datasource.
I then tried to create a section controller which will display each section with one cell. The cell would contain a IGListCollectionView and will take care of display the items, but with this I had no luck as well as despite the second section controller receives the objects it never displays the cells.
I'm a bit struggling now and not sure how I could display three different section that use the same type of cells with IGListKit, by having the sections static and the items dynamic. I'm thinking of creating three IGListCollectionView and setup bottom / top constraints between them, but I'm having some doubt on the possibility of doing that.
I'm wondering if someone already came across a similar issue and/or if the guys from IGListKit could give me some hints of what's the best implementation to resolve that?
You should be returning unique instances of your section controllers for each object. Don't reuse them!
Another thing to note is that you're using self as the diff identifier which means that the instance of the object identifies its uniqueness. That means two DataSource objects wont ever be compared (obj.items.count == items.count will never happen). Not a deal-breaker, but just be aware that's how it'll behave.
You also might want to take a look at IGListBindingSectionController which takes your original model and breaks it down into view models that drive each cell in the section.
More details and an example in the pull request. Note that this requires using master if you're using CocoaPods.
https://github.com/Instagram/IGListKit/pull/494

Realm creation and population sequence

I'm trying to build up a Realm of a bunch of data. This wouldn't be a problem but I've hit a wall - a gap in experience shall we say.
Creating one record is fine. However, one of the fields of that record is an array (<List>) of records from another table. Now my 2 questions are:
Does Realm support that? A list or array of Objects as one of the fields for a record... Answering no here will leed me on to an answer of my question - I will simply need to make an array of "primary keys" and query with those when I need to. If the answer is yes, proceed to question 2.
How would I go about creating those lists, bearing in mind that those tables might be created at a fraction of a second later than the current one, meaning those records don't yet exist and therefore can't be added to the list...
Example:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
var films = List<Film>()
}
See how all the film objects need to be created first before the character objects? Otherwise I could try add a film which does not yet exist and then it all crashes and burns :( Reason I want to try find a better way is, I'm dealing with 10 tables, a few hundred records and variable connection speeds. It would be too long to wait for each data retrieval to finish before the next one starts. AND since they are all suffering from the same problem (inter-connections), regardless of which I start with, it won't work...
Thank you :)
As discussed, for the object that haven't been created, you should create an empty object with only the primary key, then re-fetch and add value after the other network request called
For Many-to-many relationship, you can use Realm's Inverse Relationships to create linking between these objects like:
class Character: baseRLMObject {
var name: String!
var films = LinkingObjects(fromType: Film.self, property: "characters")
}
Like it's being discussed in the comments, you should be able to use Realm's inverse relationships feature to automate most of this process:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
let films = LinkingObjects(fromType: Film.self, property: "characters")
}
When calling character.films, a List will be returned of all of the Film objects whose characters property contains that object.
This is a lot easier and error-free than trying to maintain two separate relational lists between object types.

Update dictionary with previously deleted items

I have two dictionaries. Both declared in a viewController, both based on a model structure class.
// ItemDictionary
var ItemDictionary = ItemModel()
var JSONDictionary = ItemModel()
JSON data is fed into the JSONDictionary and then this data is passed to ItemDictionary which feeds a table within ViewDidLoad.
self.ItemDictionary = self.JSONDictionary
All good. The table is nicely populated from JSON data. I can also delete items from the table and the ItemDictionary. However, when I try and add items back by referring to the original dictionary (JSONDictionary) the item has gone.
I understand this is expected. If Dictionary1 = Dictionary2, a new dictionary is not actually created. Only an second address. So if you change Dictionary1, Dictionary2 also changes.
A practical example could be setting a price range. You can reduce the range and have less items displayed on the table. But you can't replace previously deleted items if you wanted to increase the price range. I do not want to recall the JSON script each time I edit the range. Advice appreciated.
As confirmed by the OP, ItemModel is a class and not a Dictionary. To fix this you need to make ItemModel a real Dictionary and thus a value type. This is probably the preferred choice but will be more work.
An alternative would be to add an convenience initializer to the ItemModel class that instantiates a new copy of itself and call that instead of setting self.ItemDictionary = self.JSONDictionary.
Something like this.
init(model: ItemDictionary) -> ItemDictionary {
// copy properties
}
Then create the new copy.
self.ItemDictionary = ItemDictionary(self.JSONDictionary)
Later you can reinitialize ItemDictionary with the same call.
Try this code out-
var dictionary1 = ["name": "Magnus"]
var dictionary2 = dictionary1
dictionary2.removeAll()
print("\(dictionary2) \(dictionary1)")
The output is :-
[:] ["name": "Magnus"]
Thus 2 new dictionaries are being created. If you refer to the Swift documentation, You will find that in swift, references are hardly present.
Some other portion of code might be responsible. Hope this helps :-)

Class or Struct for a model similar to a relational database?

The application in question is built as follows:
A user selects a job
the job can have many components
each component can have many lineitems.
I am not clear on how this should be structured - should this be class or structs? Seeing that only one job is being processed at a time, I am fairly confident that jobs should be a class. However, when there are multiples of a certain object type, I am not exactly clear on how to form them, like the components and lineitem objects.
The application consists of ViewControllers and TableViewControllers. All the data is fetched from a server in JSON and populated into the appropriate view as needed. Here are the object types as they are currently setup:
A job object:
// Job Object
//
public struct Job {
static var globalId : String?
static var name : String?
static var status : String?
static var client = Client()
static var components = Array<Component>()
// etc..
}
A Component like so:
// JobComponent Object
//
public struct Component {
var name:String? = ""
var fmRecordId : String?
var startTS:NSDate?
var endTS:NSDate?
var notes:String? = ""
var items = Array<Lineitem>()
// etc...
}
and finally, a lineitem:
// Lineitem Object
//
public struct Lineitem {
var fmRecordId = String()
var itemName = String()
var itemNumber = String()
// etc...
}
All of these object are built within a public class called "PL".
When a user selects lineitem and edits it's values, the values are not available outside the VC in which they are edited because the VC isn't referencing the lineitem that is was passed, it is simply copying it. The same happens with components.
A workaround I found was to use the the Job struct PL.Job.self and always modify the components and lineitems like so where i = a desired index in the array:
PL.Job.components[i] to access a component
PL.Job.components[i].items[i] to access a specific item within that component.
However, this doesn't scale very well.
The desired behavior is to be able to pass a reference to a particular instance of an object around rather than pass around the index path of those objects in the PL.Job object.
I am well aware there is something wrong with how this is currently structured, but could someone please point me in the right direction?
A couple of points:
You can only pass class instances by reference. If you want to be able to pass a reference to a particular LineItem or Component or Job, and you want to be able to make changes to that object that are effective everywhere, then you need to define them as classes and not structs. Instances of struct types are always passed by value and not be reference. And when a value type is passed, it is copied, meaning that you create an entirely new copy of the object, and mutating the copy has no effect on the original.
Your Job struct only has static properties - i.e., there will only ever be one globalId, name, status etc. throughout your entire application. If you want to have multiple instances of Job, then these should not be static properties. You say that only one Job will be processed at a time, so maybe that was intentional. Either way, it is still often preferable to create an instance of a Job class that has those properties. It certainly would give you more flexibility later if you decide to make it possible to hold references to multiple jobs in memory, or to allow the user to select between different jobs, or switch between jobs, etc. For example, you may want to allow a user to switch to the Job they were processing earlier without necessarily destroying the Job that they are working on now.
But I think the main point is that you will need to define your objects as classes if you want to be able to pass them by reference. If you modify an object that is passed by reference, all other references to the same object will show the same changes (because, after all, they are just references to the same object). That doesn't work with value types, like structs.

Resources