Optional array is empty after appending elements inside it in Swift - arrays

My context is as follows: I have a view controller that draws different room shapes. Each wall is a subclass of UIView. The model for the walls is as follows:
struct Wall {
let name: String
let number: Int
var length: CGFloat?
var attachedObjects: [WallObject]?
var isSelected: Bool
}
So each wall has a property that gets assigned one of the above ones.
When a tap happens on one wall the sender gets stored into an optional variable:
#objc fileprivate func changeWallProperties(sender: UITapGestureRecognizer) {
guard let tappedWall = sender.view as? RoomShapeDrawingV else {print("could not get wall"); return}
tappedWall.wallModel?.isSelected = true
selectedWallModel = tappedWall.wallModel
I have a collection view that displays objects that can go on the wall. When a cell is selected I get a WallObject type that is another struct for the contents of the collection view, the selection needs to be placed into the model for the selected wall:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let aWallHasBeenSelected = isWallSelected else {print("could not get the selected wall"); return}
if aWallHasBeenSelected {
print("a wall has been selected")
let wallObject = wallObjects[indexPath.item]
selectedWallModel?.attachedObjects?.append(wallObject)
The last line does nothing, [attachedObjects] is nil inside the model for that selected wall. I have found a solution on this post: Adding elements to optional arrays in Swift
But I want to know why this happens, if there is a logical error on my behalf or what. Is it because I use structs and I make copies of them, or there is something else happening?

Related

Passing a Model between View Controllers

I am struggling to pass a model of data from one controller to the next. I am sure I am just missing something very simple but hoping I can get some help.
Below I create the code to select all the data from the row that was selected:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print(indexPath.row)
let passingData = arrivals[indexPath.row]
print(passingData)
let controller = storyboard?.instantiateViewController(withIdentifier: "FlightInfoVC") as! FlightInfoViewController
controller.flightDataPassedIn = passingData
//Code incomplete - Will add execution to show VC
}
That print statement is reflected below:
Now where I am struggling is to get that data to go to my next view controller. Setting the array of data in my new VC doesn't seem to work because it is expecting a type of 'FlightModel' but I cannot seem to figure out how to declare that. I have tried this but I cannot convert type [Any] to 'FlightModel'
var flightDataPassedIn: FlightModel = []
Appreciate any help you can give!
It sounds like the issue is that you're trying to initialize flightDataPassedIn with a value of [], which is of incompatible type [Any].
If you're okay with the flightDataPassedIn property on FlightInfoViewController being optional, that might be the most straightforward solution. It would automatically be initialized to nil, so would not require you to set an initial value - just declare it like this:
var flightDataPassedIn: FlightModel?
If you don't want it to be optional, initialize it to a default value of type FlightModel (add an initializer to FlightModel if necessary) and overwrite it with passingData from the parent ViewController.
Hope this helps!
The issue was that I was not passing the data via the main required function that everyone uses Prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TrackingToInfo" {
if let destinationVC = segue.destination as? FlightInfoViewController {
destinationVC.flightDataPassedIn = readyToPass
}
}
}
Within the tableview I had to set the data to a var in my currentVC of type FlightModel? then the data passed

What is the best way to approach a 'How many items do you have selected' type counter?

What I'm meaning is: I have a Table View and Populated Cells, I want each cell, when selected, to link to its on or off state that is represented in its bool value. When it's in an 'On' State, it adds +1 to a counter, when it's clicked to it's 'Off' state it takes back that count (so subtracting). I have the bool within the class that populates the table view so that it doesn’t mix up selections when filtering data via search bar
Imagine a 'How many items do you have selected' type of counter.
What I'm unsure about is how to connect the bool to the selection itself so it’s not ‘mixed up’. Examples are welcome!
Thankyou
Propably easiest solution is using simple variable "counter" and count your selections in UITableViewDelegate method didSelectRowAt (counting up) and method didDeselectRowAt (counting down)
Here are some steps and example code:
create UITableview and conform your UIViewController to UITableViewDelegate protocol . Then add methods to your UIViewController and conform yourTableView.delegate = yourViewController (propably just self).
Adding delegate methods:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.counter += 1
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.counter -= 1
}
To approach this question, I would have a custom table view cell. A great tutorial on this is here: https://www.youtube.com/watch?v=OQYqGM5_wVY&list=LL&index=16&t=752s
It actually shows the use of a switch in a tableview.
To have a counter, I would use UINotification Center. In the custom tableview cell swift file, I would add something like the code below to the action for your switch:
if switch.isOn{
NotificationCenter.default.post(Notification(name: Notification.Name("tableSwitchClicked"), object: nil, userInfo: ["isOn":true]))
} else {
NotificationCenter.default.post(Notification(name: Notification.Name("tableSwitchClicked"), object: nil, userInfo: ["isOn":false]))
}
"switch" is whatever the outlet name of your switch is.
Then add this to your viewcontroller swift file in the viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(changeSwitchCounter(_:)), name: Notification.Name("tableSwitchClicked"), object: nil)
and finally, this in your code after viewDidLoad in the main view controller:
#objc func changeSwitchCounter(_ notification: Notification){
let positiveChange = notification.userInfo!["isOn"] as! Bool
if positiveChange == true {
switchOnCount += 1
} else if positiveChange == false {
switchOnCount -= 1
}
}
where switchOnCount is the variable you use to track the amount of switches on. I hope this helps you! Please comment if you have any questions.

saving array data as user default and loading array in table view

I created an array and I'm populating the data (type Double) onto a table view. But when the app closes, all the data goes away! Im having trouble saving the array as a User Default so that when the user closes the app and opens it again, all of the info is still populated and available to see.
I set up an array called moneySpentArray, and this array holds all of a users entered transactions. After user clicks "submit", the IB action takes the input and sends it thru a function called addToMoneySpentArray
#IBAction func submitNewInfo(_ sender: UIButton) {
AddSubtractMoneyController.addToMoneySpentArray(amountISpent: Double(outputLabel.text!)!)
}
Below is the addToMoneySpentArray function that I send the data too, which then appends that amount into the 'moneySpentArray'.
class func addToMoneySpentArray( amountISpent: Double) {
moneySpentArray.append(amountISpent)
print(moneySpentArray)
}
Now in my SpendingHistoryVC- when the View loads, I use the array.reduce to add up the sum of the values in the array and display it in a text label called "youSpentThisMuch"- and Below this label will be the table view, which holds all the individual indexed amounts from the array also. Im confused on where and when to implement user defaults so that when a user enters a new amount through the IBAction above, it automatically stores and updates the array as a new user default. So even if the app is closed, their array history is still available to see.
override func viewDidLoad() {
super.viewDidLoad()
let arraySum: Double = AddSubtractMoneyController.moneySpentArray.reduce(0.0, +)
youSpentLabel.text = "\(arraySum)"
}
In addToMoneySpentArray save the data:
class func addToMoneySpentArray( amountISpent: Double) {
moneySpentArray.append(amountISpent)
UserDefaults.standard.set(moneySpentArray, forKey: "moneySpent")
print(moneySpentArray)
}
and in viewDidLoad load it
override func viewDidLoad() {
super.viewDidLoad()
let moneySpentArray = UserDefaults.standard.array(forKey: "moneySpent") as? [Double] ?? [Double]()
AddSubtractMoneyController.moneySpentArray = moneySpentArray
let arraySum = moneySpentArray.reduce(0.0, +)
youSpentLabel.text = "\(arraySum)"
}

Fetch Parse Data based on Array

I have a PFQueryTableViewController.
I also have an array of numbers in a randomly generated order, from 1 to the Parse class count.
In Parse, each object in the class is assigned a number (1,2 ,3, etc.)
I want to fetch objects from this class in Parse to table view cells,and the order depends on the order of the array.
For example, my array would be [3, 2, 5, 1, 4]
I want to query where key "number" is equal to those numbers, in that order..
So far I have this:
// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery{
var query = PFQuery(className: "Stores")
for i in 1...10 {
query.whereKey("number", equalTo: numArray[i])
}
return query
}
and
//override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ExploreCell!
if cell == nil {
cell = ExploreCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
}
however, whenever i run, it only grabs information from 1 objects. My table view only has 1 cell that grabbed data.
I suppose that's Swift Code (I actually don't know Swift).
Anyway I don't see the need to (try to) query the objects in the order you want from array. Actually using query.whereKey more times you will just use the last one.
What you should do, is actually query the objects and then sort them (unless you want an ascending/descending order).
Also keep in mind that a Parse Query is asynchronous so probably structuring the function like you did won't work like you want.
EDIT: This is the kind of code you may want to use:
var query = PFQuery(className:"GameScore")
query.whereKey("playerName", equalTo:"Sean Plott")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
The objects variable is the array you want to sort.
The Parse Documentation has been always useful to me to actually understand better Parse

NSIndexPath in Swift

I know, hopefully, that class NSIndexPath deals with arrays which has arrays.
I have this code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
let devCourses = [
("iOS App Dev with Swift Essential Training","Simon Allardice"),
("iOS 8 SDK New Features","Lee Brimelow"),
("Data Visualization with D3.js","Ray Villalobos"),
("Swift Essential Training","Simon Allardice"),
("Up and Running with AngularJS","Ray Villalobos"),
("MySQL Essential Training","Bill Weinman"),
("Building Adaptive Android Apps with Fragments","David Gassner"),
("Advanced Unity 3D Game Programming","Michael House"),
("Up and Running with Ubuntu Desktop Linux","Scott Simpson"),
("Up and Running with C","Dan Gookin") ]
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return devCourses.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
let (courseTitle, courseAuthor) = devCourses[indexPath.row]
cell.textLabel?.text = courseTitle
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
What I don't understand how NSIndexPath works. I have read documentation but is it still too hard for me to understand at this point of my learning how this works. How can I know NSIndexPath has property of row? Cans somebody explain every part of this line please:
let (courseTitle, courseAuthor) = devCourses[indexPath.row]
How does NSIndexPath know that courseTitle in this constant refers to index number 0 whitin array index 0 ("iOS App Dev with Swift Essential Training","Simon Allardice")
What I don't understand how NSIndexPath works.
For iOS, you can think of NSIndexPath as a read-only structure that contains two Int properties section and row if you're working with UITableViews or section and item if you're working with UICollectionViews.
You create them with the NSIndexPath:forRow:inSection: factory method:
let indexPath = NSIndexPath(forRow: 1, inSection: 0)
and read them by accessing their properties:
print("row is \(indexPath.row)")
How can I know NSIndexPath has property of row?
Xcode has some nice features to help you understand the code you are looking at. In Xcode, Option-click on row in the above statement line, and it will tell you what it is. In the pop-up, if you click on NSIndexPath UIKit Additions next to Reference, it will bring up the documentation.
Can somebody explain every part of this line please:
let (courseTitle, courseAuthor) = devCourses[indexPath.row]
devCourses is an array of tuples containing two values of type String. You can see this by option-clicking on devCourses and it shows [(String, String)]. Had it been an array of array of String it would have said [[String]] or [Array<String>] (which is two ways of saying the same thing).
indexPath.row is just an Int indicating the selected row of the tableView.
devCourses[indexPath.row] then is the tuple at that index. For example, if the row were 0, then the tuple would be ("iOS App Dev with Swift Essential Training","Simon Allardice").
Finally, you can initialize two variables simultaneously by declaring them as a tuple and initializing them with a tuple. For example:
let (a, b) = (3, 4)
creates two Int constants a and b and assigns 3 to a and 4 to b.
So this statement is retrieving the tuple from the array indicated by the integer indexPath.row and creating two variables, assigning the first variable courseTitle the value of the first value in the tuple and assigning the second variable courseAuthor the value of the second value in the tuple.
Update for Swift 3
NSIndexPath still exists in Foundation, but when working with indexPaths in Swift 3, a new type IndexPath is now used. This type is a structure.
You can still create an NSIndexPath in Swift 3, with the following updated syntax, but you can't change the properties:
var ip = NSIndexPath(row: 0, section: 0)
ip.row = 5 // Cannot assign to property: 'row' is a get-only property
but you should use the new IndexPath structure instead.
IndexPath is created with a similar syntax:
var ip2 = IndexPath(row: 0, section: 0)
ip2.row = 5 // this works
You can convert between IndexPath and NSIndexPath by casting with as:
let ip = NSIndexPath(row: 0, section: 0)
let ip2 = ip as IndexPath // convert to IndexPath
let ip3 = ip2 as NSIndexPath // convert back to NSIndexPath
All of the Cocoa and Cocoa Touch API's that use indexPaths have been converted to use IndexPath in Swift.
Slightly off topic, but I'd like to encourage everybody to use custom classes for the model rather than "primitive" types. There's a little effort but big benefit.
Simple class with two properties and a description (the description variable is helpful while debugging)
class DevCourse : Printable {
var author : String
var title : String
init(author : String, title : String) {
self.author = author
self.title = title
}
var description : String { return "DevCourse \(title) by \(author)"}
}
The devCourses array can be easily mapped to create the DevCourse instances with one line
let rawDevCourses = [
("iOS App Dev with Swift Essential Training","Simon Allardice"),
("iOS 8 SDK New Features","Lee Brimelow"),
("Data Visualization with D3.js","Ray Villalobos"),
("Swift Essential Training","Simon Allardice"),
("Up and Running with AngularJS","Ray Villalobos"),
("MySQL Essential Training","Bill Weinman"),
("Building Adaptive Android Apps with Fragments","David Gassner"),
("Advanced Unity 3D Game Programming","Michael House"),
("Up and Running with Ubuntu Desktop Linux","Scott Simpson"),
("Up and Running with C","Dan Gookin") ]
let devCourses = rawDevCourses.map { DevCourse(author: $0.1, title: $0.0) }
the lines to apply the properties look much clearer
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyIdentifier", forIndexPath: indexPath) as! UITableViewCell
let course = devCourses[indexPath.row]
cell.textLabel?.text = course.title
return cell
}

Resources