I have a UIViewController containing a UITableView that is populated via an array of custom class objects. These custom class objects have an array property. As you can see from my code below, the objects are equatable. When I segue to a second vC, the array of custom class objects (and obviously the array properties associated with each object) is passed over.
I have a function in the second vC that matches an object with one that is contained in the array. After matching, the property array of the object in the array that matched is updated. However, when I print what should be the updated property array, no change has been made. Below is a representation of my code:
class Object: Equatable {
var propertyArray: [String] = []
static func ==(lhs: object, rhs: object) -> Bool {
return lhs.someProperty == rhs.someProperty
}
}
class ArrayOfObjects {
var array: [Object] = []
}
class vC1: UIViewController, UITableViewDelegate, UITableViewDataSource {
var objectArray1 = ArrayOfObjects()
override viewDidLoad() {
//populate objectArray1.array
}
prepareForSegue(segue: UIStoryboardSegue) {
segue.destination.objectArray2 = objectArray1 //segue to vC2
}
}
class vC2: UIViewController {
var objectArray2 = ArrayOfObjects()
var someOtherObject = Object() //object that has same properties as an object in objectArray2
func someFunc() {
let indexOfMatchingObject = objectArray2.array.index(of: someOtherObject)
let matchingObject = objectArray2.array[indexOfSomeOtherObject]
matchingObject.propertyArray.append("TestString")
print("\(matchingObejct.propertyArray)") //prints []
}
}
Why doesn't it print ["TestString"]? The same goes for when I remove one of the values from the array, the update doesnt occur.
If you are wondering why I am doing this, it's because the objects are modified in the second vC and the UI of the tableView cells in the first vC are dependent upon the properties of the objects. Hence why my data is represented as a class (reference type).
Update your someFunc():
func someFunc() {
//try to find the same object
let indexOfMatchingObject = objectArray2.array.index(of: someOtherObject)
//obtain this common objec from array
let matchingObject = objectArray2.array[indexOfSomeOtherObject]
//update propertyArray from matchingObject
matchingObject.propertyArray.append("TestString")
}
Also update your first VC table view in viewWillApear.
I realised that the answer to my question is nothing to do with the class I had created or the segue, but is in actual fact to do with the state of the array. You cannot append to an array that has not been initialised. In order to append to it, you must first initialise it, then you may append to it. Playground code has been provided below to demonstrate this in the context of the question above:
//Create object class
class Object: Equatable {
var propertyArray: [String]?
var id: Int = Int()
static func ==(lhs: Object, rhs: Object) -> Bool {
return lhs.id == rhs.id
}
}
//Create class that will hold array of objects
class ArrayOfObjects {
var array: [Object] = []
}
//initialise class that will hold array of objects
var array = ArrayOfObjects()
//set the initial values for the array
func setArray() {
let object1 = Object()
object1.id = 1
object1.propertyArray = ["hello"]
let object2a = Object()
object2a.id = 2
object2a.propertyArray = ["bye"]
array.array = [object1, object2a]
}
setArray()
//Create new object that will be used to match with one already in the array
let object2b = Object()
object2b.id = 2
object2b.propertyArray = ["bye"]
//Find if the new object exists in the array
let index = array.array.index(of: object2b)
let matchingObject = array.array[index!]
matchingObject.propertyArray?.append("welcome")
//We were able to append to the matchingObject (object2a) because the property array had been initialised
print(matchingObject.propertyArray) //prints ["bye", "welcome"]
//Create new object with uninitialised propertyArray
let object3a = Object()
object3a.id = 4
//Append this new object to the array
array.array.append(object3a)
//Create another new object that will be used to match with object3a
var object3b = Object()
object3b.id = 4
//Find if object3b can be matched in the array
let index2 = array.array.index(of: object3b)
let matchingObject2 = array.array[index2!]
matchingObject2.propertyArray?.append("hello")
//A match was found for object3b, but the array had not been initialised and so we couldn't append to it
print(matchingObject2.propertyArray) //prints nil
//Initialise the array
matchingObject2.propertyArray = []
matchingObject2.propertyArray?.append("goodbye")
//We can now append to it as it has been initialised
print(matchingObject2.propertyArray) //prints ["goodbye"]
Related
I am trying to create an app where I have 4 buttons which each one corresponds to a different category. Now the categories are different Realm Objects saved in a swift file.
class HealthData: Object {
#objc dynamic var name : String = ""
}
class SelfImprovement: Object {
#objc dynamic var name : String = ""
}
class TopSecret: Object {
#objc dynamic var name : String = ""
}
class Ohter: Object {
#objc dynamic var name : String = ""
}
Now my problem is that I want a single view controller with a TableView to have different data that will get passed on to TableView from the corresponding category.
My idea was that I can create the var categories : Results<HealthData>! and use an if statement to change the categories to be Results etc using the prepare for a segue to know which button was pressed.
override func viewWillAppear(_ animated: Bool) {
if categoryNo == 1 {
title = "Health"
} else if categoryNo == 2 {
title = "Self Improvement"
categories = Results<SelfImprovement>!
}
}
But of course, XCode cannot assign the value of type 'Results?.Type' to type 'Results?'.
Any ideas?
Thank you all for your time!
So the issue is you want to re-use the tableView to display data from different tableView datasources. You're heading down the right path but the answer is to tell the tableView where to get it's data from.
I am pairing this down to really basic code so don't copy paste - trying to keep it short.
Assume we have a view controller with a tableView
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet weak var myTableView: NSTableView!
var myHealthArray = [String]() //one tableview datasource
var mySelfImprovementArray = [String]() //another tableview datasource
var tableType = "" //will tell the tableView what it should be displaying
then, further down, we have the tableView delegate methods that populate the tableview
func numberOfRows(in tableView: NSTableView) -> Int {
if self.tableType == "health" {
return self.myHealthArray.count
} else if self.tableType == "self_improvement" {
return self.mySelfImprovementArray.count
} //etc
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeView(withIdentifier: myIdentifier, owner: self) as! NSTableCellView
var s = ""
if self.tableType == "health" {
s = self.myHealthArray[row]
} else if self.tableType == "self_improvement" {
s = self.mySelfImprovementArray[row]
} //etc
cell.textField?.stringValue = s
return cell
}
So the idea here is to set the tableType to whatever is supposed to be displayed in the tableView then the logic within the tableView delegate methods will know which dataSource array to get its data from.
It's important to remember that Realm Results are homogenous so they can only store one type of object.
In this example, I defined the dataSources as strings but in your case they may be realm results objects. If they are strings, you could just re-use the array as it would only contain string objects. However, if the dataSources are Realm Results, you'll need separate arrays as shown in this answer.
Problem
I need to save a List in Realm, which is made up of a the properties of an Array of Struct Objects (which has been passed through a Segue and is popualating a tableview). This is in the form of an 'exercise name' and 'number of reps' on each row.
What have I tried?
I have matched the Realm Object with the Struct in terms of fields and format and attempted to save the array as a list e.g. "=List< array >" but this doesn't work ("use of undeclared type"). I've also tried various methods of trying to save the properties of each table row but again, couldn't get that to work (e.g. = cell.workoutname)
Research I found this How to save a struct to realm in swift? however, this isn't for saving arrays of objects I don't think. This did however (first answer), give me the idea of potentially saving the values contained within each row to Realm instead of the actual Struct array. I also found this Saving Array to Realm in Swift? but I think this is for when the array is already made up of Realm Objects, not Struct instances like in my case.
Code and details
Structs
I have a Struct as per below. Another struct, (Workout Generator) has a function which generates x number of instances of these objects. These are then passed via a Segue to a new VC TableView (each row displays a workout name and number of reps):
struct WorkoutExercise : Hashable, Equatable{
let name : String
let reps : Int
var hashValue: Int {
return name.hashValue
}
static func == (lhs: WorkoutExercise, rhs: WorkoutExercise) -> Bool {
return lhs.name == rhs.name
}
}
I then have the following Realm Objects. One is for saving a 'WorkoutSession'. This will contain a Realm List of WorkoutExercise Realm objects.
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutName = ""
let exercises = List<WorkoutExerciseObject>()
var totalExerciseCount: Int {
return exercises.count
}
}
class WorkoutExerciseObject: Object {
#objc dynamic var name = ""
#objc dynamic var reps = 0
}
I have tried the following code when trying to save the Workout details to Realm :
func saveToRealm() {
let workoutData = WorkoutSessionObject()
workoutData.workoutName = "test"
workoutData.workoutID = UUID().uuidString
workoutData.exercises = List<selectedWorkoutExerciseArray>
}
What I think I need to do from reading the other answers
Option 1 - instead of trying to save the actual array, save the 'name' and 'reps' from each table row instead?
Option 2 - somehow convert the 'selectedWorkoutExerciseArray' into a list of realm objects?
of course there might be other options! Any help/ideas appreciated!
Why populate 2 separate lists if it needs to be persistent anyway? Just use the list in Realm to populate your table view. Here's a simple example of populating the list using append (just like any array):
class SomeClass: Object {
#objc dynamic var id: String = ""
var someList = List<SomeOtherClass>()
convenience init(id: String) {
self.init()
self.id = id
}
}
#objcMembers class SomeOtherClass: Object {
dynamic var someValue: String = ""
convenience init(value: String) {
self.init()
someValue = value
}
}
func addToList(someOtherClass: SomeOtherClass) {
let realm = try! Realm()
if let someClass = realm.objects(SomeClass.self).last {
do {
try realm.write({
someClass.someList.append(someOtherClass)
})
} catch {
print("something went wrong")
}
}
}
I have a very similar functionality, that allow the user to select from a table view. What I do is create a List from the selection like:
var arrayForSelectedObjects = [CustomObject]()
...
let aList = List<CustomObject>()
aList.append(objectsIn: arrayForSelectedObjects)
//I then assign the created list to the main object and save it.
let realmObject = MainObject()
realmObject.list = aList
My CustomObject is also stored on the realm db.
My MainObject is defined like so:
class MainObject : Object {
#objc dynamic var title: String?
var list = List<CustomObject>()
}
class MySingleton{
static let shareInstance = MySingleton()
private init() {}
var myDetail = [Detail]()
}
class DetailTableViewController {
var expense = [Detail]()
override func viewDidLoad() {
super.viewDidLoad()
... put stuff in expense array ....
MySingleton.shareInstance.myDetail = expense //<--- doesn't work
// error is "cannot assign value of type '[Detail]' to type [MySingleton.Detail]"
}
}
How do I copy an array to my MySingleton?
right now i just pass my array around my classes using segue
From your error, it is likely you are defining Detail twice, once locally to the singleton, once globally for the viewController.
How can I check if a property is an Array (of any type)? This code always only prints "Worker". Is there a way (dynamically) to know if a property is an Array without inform the type?
final class Worker: NSObject {
var id: Int?
var array: Array<Worker>?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let worker = Worker()
worker.id = Int(2) as Int?
worker.array = [Worker(),Worker(),Worker()]
let mirror = reflect(worker)
for i in 0..<mirror.count {
let (name, childMirror) = mirror[i]
if childMirror.disposition == .Optional {
let (newName,subChildMirror) = childMirror[0]
if subChildMirror.valueType is Array<AnyClass>.Type {
println("AnyClass")
}
if subChildMirror.valueType is Array<AnyObject>.Type {
println("AnyObject")
}
if subChildMirror.valueType is Array<Any>.Type {
println("Any")
}
if subChildMirror.valueType is Array<NSObject>.Type {
println("NSObject")
}
if subChildMirror.valueType is Array<Worker>.Type {
println("Worker")
}
}
}
}
}
Ps.: I need to deal with Array<>
An array of any type can always be casted to a NSArray. So you could check if it's an array with code like this:
if _ = subChildMirror.valueType as? NSArray {
println("Array")
}
It's also possible to dynamically get the type of the objects of that array. In my EVReflection library I do something similar. I extended the Array in order to get a new element of an object what should be in that Array. In your case you could then get the .dynamicType from dat object.
So the code would become:
let arrayType = worker.array.getTypeInstance().dynamicType
Here is the Array extension
extension Array {
/**
Get the type of the object where this array is for
:returns: The object type
*/
public func getTypeInstance<T>(
) -> T {
let nsobjectype : NSObject.Type = T.self as! NSObject.Type
let nsobject: NSObject = nsobjectype.init()
return nsobject as! T
}
}
I've had this problem for a very long time. I'm trying to populate a UITableView with NSMutableArray. But I'm not getting all the objects out side of the loop. The total count of objects is 167. But instead it returns 167 of the same object.
I declared the array outside of the loop like this:
class TableViewController: UITableViewController {
var sermon = SermonLink()
var sermons: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
loadContent()
}
The whole thing happens in a method:
func loadContent() {
var elements: NSArray = []
let urlForHTML = NSURL(string:"http://ontherock.dk/?page_id=1141")
var dataForHTML: NSData = NSData(contentsOfURL: urlForHTML!)!
var htmlParser = TFHpple(HTMLData: dataForHTML)
var htmlXPathString = "//td[#class='lyf_td_filename']/a"
elements = htmlParser.searchWithXPathQuery(htmlXPathString) as NSArray
var sermonArr:NSMutableArray = []
for item in elements {
var element: TFHppleElement = item as! TFHppleElement
var firstChildItem: NSString = element.firstChild.content
var dashSeperatedSermonArray = firstChildItem.componentsSeparatedByString("-") as! [String]
sermon.subject = dashSeperatedSermonArray[4].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
sermon.speaker = dashSeperatedSermonArray[3].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
sermon.urlForAudioSermon = element.attributes["href"] as! String
sermonArr.addObject(sermon)//Here inside the loop I get the expected data
}
sermons = sermonArr //sermons contains only the object at the last index, 167 times
self.tableView.reloadData()
}
The below could be the problem.
Since you initialised the var sermon = SermonLink()
as a property of the class and you are updating same objects inside the for loop. Try to create this inside the for loop for each iteration.