I've read around so don't knock this as a duplicate unless my searching has been completely awful.
Basically I have a small table (5 Fixed rows) however they do have indexes associated with them in a tuple (this is why I'm using the dynamic way rather than a static way.
The players goal is to swap the cells around. I am not using a nib to get these cells populated, this is how.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellID = "Cell"
let customCell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! DefinitionMatchTableViewCell
customCell.background.layer.borderWidth = CGFloat(2.0)
if tableView == nameTableLeft{
customCell.textToDislay!.text = nameList[indexPath.row].0
customCell.background.layer.borderColor = UIColor(red: 255/255, green: 192/255, blue: 1/255, alpha: 1.0).cgColor
let cellIDFromTuple = nameList[indexPath.row].1
customCell.tag = cellIDFromTuple
}
if tableView == definitionTableRight{
customCell.textToDislay!.text = definitionList[indexPath.row].0
customCell.background.layer.borderColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.2).cgColor
let cellIDFromTupleForDefinition = definitionList[indexPath.row].1
customCell.tag = cellIDFromTupleForDefinition
}
customCell.background.backgroundColor = UIColor.clear
customCell.backgroundColor = UIColor.clear
return customCell
}
Now the problem is that on an iPhone SE the 5 cells can't fit which results in the top or bottom cell losing it's reference ID when checking if the values match. This makes the game impossible to complete, I need a way so I can populate the table with an index value associated with each string but so the table doesn't reuse the cell but it remains in memory.
The only method I can think of is writing a static table with the data but then providing each cell with an invisible index that it carries associated with the string. However I'd much rather do it dynamically.
Any advice on to keep the state of these cells would be greatly appreciated. Thanks!
EDIT 1
Here is the way I check the answers based on my index values in the table. I imagine the problem comes from .indexPathsForVisibleRows as some aren't visible
for index in nameTableLeft.indexPathsForVisibleRows!{
let leftCell = nameTableLeft.cellForRow(at: index) as? DefinitionMatchTableViewCell
let rightCell = definitionTableRight.cellForRow(at: index) as? DefinitionMatchTableViewCell
if nameTableLeft.cellForRow(at: index)?.tag == definitionTableRight.cellForRow(at: index)?.tag{
leftCell?.background.layer.borderColor = UIColor.green.cgColor
rightCell?.background.layer.borderColor = UIColor.green.cgColor
counter += 1
}else{
leftCell?.background.layer.borderColor = UIColor.red.cgColor
rightCell?.background.layer.borderColor = UIColor.red.cgColor
}
}
As you can see I then check the values match up on the tags. However I'm struggling with this concept if I used something Different.
My Model
var nameList : [(String, Int)] = [("Trojan", 0),
("Virus", 1),
("Spyware", 2),
("Ransomware", 3),
("Leakware", 4)]
var definitionList : [(String, Int)] = [("Attaches onto another program. Replicates itself. Something loaded onto your device without your knowledge. Continues to spread", 0),
("When malware is disguised as something else. Usually an e-mail attachment", 1),
("Tracks a user's actions, this includes login details, internet habits and any secure accounts", 2),
("Locks a device or encrypts files and demands money to unlock it", 3),
("Threatens to distribute your information", 4)]
Edit 2
Thanks to some help in the answers I had decided to add this to my answer checking.
for answerIndex in 0...4{
if definitionList[answerIndex].1 == nameList[answerIndex].1{
print("MATCH")
counter += 1
}else{
print("NO MATCH")
}
}
I jumble both the definition and name lists to start with. I then decide to look at both of the indexes placed in the tuple based on the tables total length. Unfortunately I had to resort to magic numbers are you cannot iterate through a tuple.
So this little loop decides if the indexes line up in my tuples and if they do it's correct otherwise it's not. Thanks again everyone for the help.
I also solved a similar problem. My advise is to keep reusing the cells. Store the data corresponding to each cell externally in a separate model class. Then store it in a mutable array. When you shuffle the cells, shuffle the data in the mutable array.
It can be done in the following way.
The model class
class model: NSObject {
var name: String = ""
var definition: String = ""
var type: String = ""
required init(name: String, definition: String, type: String) {
super.init()
self.name = name
self.definition = definition
self.type = type
}
}
Custom UITableViewCell - trivial example for different layouts
class CustomTableViewCell: UITableViewCell {
var nameLabel: UILabel = UILabel()
var definitionLabel: UILabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
nameLabel.textColor = UIColor.black
nameLabel.font = UIFont.systemFont(ofSize: 15)
nameLabel.textAlignment = NSTextAlignment.left
self.contentView.addSubview(nameLabel)
definitionLabel.textColor = UIColor.brown
definitionLabel.font = UIFont.systemFont(ofSize: 15)
definitionLabel.textAlignment = NSTextAlignment.right
self.contentView.addSubview(definitionLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
nameLabel.frame = CGRect(x: 10, y: 10, width: self.contentView.frame.size.width - 20, height: self.contentView.frame.size.height - 20)
definitionLabel.frame = CGRect(x: 10, y: 10, width: self.contentView.frame.size.width - 20, height: self.contentView.frame.size.height - 20)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Registering the cell:
self.tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
Using the CustomTableViewCell according to model:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell...
if model.type == "name" {
cell.nameLabel.text = model.name
cell.definitionLabel.isHidden = true
}else if model.type == "definition" {
cell.definitionLabel.text = model.definition
cell.nameLabel.isHidden = true
}
return cell
}
The moving cells part:
let mutableArray:[model] = [model1, model2, model3, model4, model5] // model class holds data for each cell
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let source = sourceIndexPath.row;
let destination = destinationIndexPath.row;
let sourceModel = mutableArray[source]
let destinationModel = mutableArray[destination]
mutableArray[source] = destinationModel
mutableArray[destination] = sourceModel
}
Feel free to comment if you have any doubts regarding this implementation. Feel free to suggest edits to make this better :)
Edit:-
Even if it's a tuple with it's index position, you can change the values when the rows moved accordingly.
var nameList : [(String, Int)] = [("Trojan", 0),
("Virus", 1),
("Spyware", 2),
("Ransomware", 3),
("Leakware", 4)]
let source = sourceIndexPath.row
let destination = destinationIndexPath.row
var sourceTuple: (String, Int) = nameList[source]
var destinationTuple: (String, Int) = nameList[destination]
sourceTuple.1 = destination
destinationTuple.1 = source
nameList[destination] = sourceTuple
nameList[source] = destinationTuple
First of all you should expect cells to be reused and not store state in your cells. But if you want a quick answer then set a different reuse identifier for every cell.
Related
I am attempting to pass data from a the UITableView function cellForRowAt to a custom UITableViewCell. The cell constructs a UIStackView with n amount of UIViews inside of it. n is a count of items in an array and is dependent on the data that is suppose to be transferred (a count of items in that array). Something very confusing happens to me here. I have checked in the VC with the tableView that the data has successfully passed by using the following snippet of code
print("SELECTED EXERCISES: ", self.selectedExercises)
cell.randomSelectedExercises = self.selectedExercises
print("PRINTING FROM CELL: ", cell.randomSelectedExercise)
I can confirm that both of these print statements return non-empty arrays. So to me, this means that the custom cell has the data I require it to have. But, when I try to print out the very same array in the UITableViewCell swift file (randomSelectedExercises) , it returns empty to me. How is this possible? From what I understand, the cell works on creating the property initializers first, then 'self' becomes available. I had a previous error telling me this and to fix it, I turned my UIStackView initializer to lazy, but this is how I ended up with my current problem.
Here is the code in beginning that is relevant to the question that pertains to the table view. I have decided to present this code incase the issue is not in my cell but in my table view code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineTableViewCell") as! RoutineTableViewCell
let workout = selectedWorkouts[indexPath.row]
//get the information we need - just the name at this point
let name = workout["name"] as! String
var randomInts = [Int]()
//perform a query
let query = PFQuery(className: "Exercise")
query.includeKey("associatedWorkout")
//filter by associated workout
query.whereKey("associatedWorkout", equalTo: workout)
query.findObjectsInBackground{ (exercises, error) in
if exercises != nil {
//Created an array of random integers... this code is irrelevant to the question
//Picking items from parent array. selectedExercises is a subarray
for num in randomInts {
//allExercises just contains every possible item to pick from
self.selectedExercises.append(self.allExercises[num-1])
}
//now we have our selected workouts
//Both print statements successfully print out correct information
print("SELECTED EXERCISES: ", self.selectedExercises)
cell.randomSelectedExercises = self.selectedExercises
print("PRINTING FROM CELL: ", cell.randomSelectedExercises)
//clear the arrays so we have fresh ones through each iteration
self.selectedExercises.removeAll(keepingCapacity: false)
self.allExercises.removeAll(keepingCapacity: false)
} else {
print("COULD NOT FIND WORKOUT")
}
}
//***This works as expected - workoutName is visible in cell***
cell.workoutName.text = name
//clear the used arrays
self.allExercises.removeAll(keepingCapacity: false)
self.selectedExercises.removeAll(keepingCapacity: false)
return cell
}
Below is the code that gives me a problem in the cell swift file. the randomSelectedExercise does not have any data in it when I enter this area. This is an issue because in my for loop I am iterating from 1 to randomSelectedExercise.count. If this value is 0, I receive an error. The issue is focused in the UIStackView initializer:
import UIKit
import Parse
//Constants
let constantHeight = 50
//dynamic height number
var heightConstantConstraint: CGFloat = 10
class RoutineTableViewCell: UITableViewCell {
//will hold the randomly selected exercises that need to be displayed
//***This is where I thought the data would be saved, but it is not... Why???***
var randomSelectedExercises = [PFObject]()
static var reuseIdentifier: String {
return String(describing: self)
}
// MARK: Overrides
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
contentView.addSubview(containerView)
containerView.addSubview(workoutName)
containerView.addSubview(stackView)
NSLayoutConstraint.activate(staticConstraints(heightConstantConstraint: heightConstantConstraint))
//reset value
heightConstantConstraint = 10
}
//MARK: Elements
//Initializing workoutName UILabel...
let workoutName: UILabel = {...}()
//***I RECEIVE AN EMPTY ARRAY IN THE PRINT STATEMENT HERE SO NUM WILL BE 0 AND I WILL RECEIVE AN ERROR IN THE FOR LOOP***
lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.backgroundColor = .gray
stackView.translatesAutoresizingMaskIntoConstraints = false
//not capturing the data here
print("rSE array:", randomSelectedExercises)
var num = randomSelectedExercises.count
for i in 1...num {
let newView = UIView(frame: CGRect(x: 0, y: (i*50)-50, width: 100, height: constantHeight))
heightConstantConstraint += CGFloat(constantHeight)
newView.backgroundColor = .purple
let newLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
newLabel.text = "Hello World"
newView.addSubview(newLabel)
stackView.addSubview(newView)
}
return stackView
}()
//initializing containerView UIView ...
let containerView: UIView = {...}()
//Setting the constraints for each component...
private func staticConstraints(heightConstantConstraint: CGFloat) -> [NSLayoutConstraint] {...}
}
Why is my data not properly transferring? How do I make my data transfer properly?
Here is what you're doing in your cellForRowAt func...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// get a cell instance
let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineTableViewCell") as! RoutineTableViewCell
// ... misc stuff
// start a background process
query.findObjectsInBackground { (exercises, error) in
// nothing here will happen yet... it happens in the background
}
// ... misc stuff
return cell
// at this point, you have returned the cell
// and your background query is doing its work
}
So you instantiate the cell, set the text of a label, and return it.
The cell creates the stack view (with an empty randomSelectedExercises) and does its other init /setup tasks...
And then - after your background find objects task completes, you set the randomSelectedExercises.
What you most likely want to do is run the queries while you are generating your array of "workout" objects.
Then you will already have your "random exercises" array as part of the "workout" object in cellForRowAt.
Each row of a table has a position control implemented as a segmented control. When the table is initialized, the selectedSegment is unknown. I want to update the selectedSegment from a different function.
Currently the code creates an array of segmentControls which can be accessed by a function. The program runs OK, but the segmentControl does not updated.
Here is the code to create the table cells:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as UITableViewCell
cell.textLabel?.text = self.shades[indexPath.row]
let shadePositionControl = UISegmentedControl (items: [#imageLiteral(resourceName: "Icon-Closed").withRenderingMode(.alwaysOriginal),
#imageLiteral(resourceName: "Icon-Half").withRenderingMode(.alwaysOriginal),
#imageLiteral(resourceName: "Icon-Open").withRenderingMode(.alwaysOriginal)])
shadePositionControl.frame = CGRect(x: cell.bounds.origin.x + 200, y: cell.bounds.origin.y + 6, width: 90, height: 30)
shadePositionControl.tag = indexPath.row
shadePositionControl.addTarget(self, action: #selector(self.shadePositionControlAction), for: .valueChanged)
shadePositionControls.append(shadePositionControl)
cell.contentView.addSubview(shadePositionControl)
return cell
}
and here is the function to update the selectedSegment:
func statusPoll() {
myShade.getShadow() //gets the state from AWS-IOT
if myShade.gotState() {
let shadeIndex = 0 //hard-coded during proof-of-concept testing
let position = myShade.getDesiredState(key: "position") as! String
switch position {
case "closed": shadePositionControls[shadeIndex].selectedSegmentIndex = 0
case "half": shadePositionControls[shadeIndex].selectedSegmentIndex = 1
case "open": shadePositionControls[shadeIndex].selectedSegmentIndex = 2
default: shadePositionControls[shadeIndex].selectedSegmentIndex = -1
}
}
}
What am I missing to have the segmentedControl get updated, and have the view refreshed to reflect the update? Is there a better way to update the per-cell segmentControl than creating an array of them? I tried using shadeTableView.reloadData() to refresh the display of the segmentedControl, but it had no effect.
This method of updating controls in a table cell works.
The reason it didn't appear to work was there was a bug in instantiating the shadePositionControls array. It was instantiated as follows, which added an unwanted segmentedControl at the zero position of the array.
var shadePositionControls = [UISegmentedControl()]
Hence, the code in statusPoll was updating the bogus segmentedControl, not the intended one.
The fix was change the array creation to:
var shadePositionControls:[UISegmentedControl] = []
I know this question has been asked many times.
I'm using a UICollectionView with custom cells which have a few properties, most important being an array of UISwitch's and and array of UILabel's. As you can guess, when I scroll, the labels overlap and the switches change state. I have implemented the method of UICollectionViewCell prepareForReuse in which I empty these arrays and reset the main label text.
I have tried to combine solutions from different answers and I have reached a point where my labels are preserved, but the state of my switches in the cells isn't. My next step was to create an array to preserve the state before removing the switches and then set the on property of a newly created switch to a value of this array at an index. This works, until after I scroll very fast and switches in cells which were not selected previously become selected(or unselected). This is creating a huge problem for me.
This is my collectionView cellForItemAtIndexPath method:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("pitanjeCell", forIndexPath: indexPath) as! PitanjeCell
// remove views previously created and create array to preserve the state of switches
var selectedSwitches: [Bool] = []
for item: UIView in cell.contentView.subviews {
if (item.isKindOfClass(UILabel) && !item.isEqual(cell.tekstPitanjaLabel)){
item.removeFromSuperview()
}
if (item.isKindOfClass(UISwitch)){
selectedSwitches.append((item as! UISwitch).on)
item.removeFromSuperview()
}
}
// get relevant data needed to place cells programmatically
cell.tekstPitanjaLabel.text = _pitanja[indexPath.row].getText()
let numberOfLines: CGFloat = CGFloat(cell.tekstPitanjaLabel.numberOfLines)
cell.setOdgovori(_pitanja[indexPath.row].getOdgovori() as! [Odgovor])
var currIndex: CGFloat = 1
let floatCount: CGFloat = CGFloat(_pitanja.count)
let switchConstant: CGFloat = 0.8
let switchWidth: CGFloat = cell.frame.size.width * 0.18
let heightConstant: CGFloat = (cell.frame.size.height / (floatCount + 2) + (numberOfLines * 4))
let labelWidth: CGFloat = cell.frame.size.width * 0.9
for item in _pitanja[indexPath.row].getOdgovori() {
// create a switch
let odgovorSwitch: UISwitch = UISwitch(frame: CGRectMake((self.view.frame.size.width - (switchWidth * 2)), currIndex * heightConstant , switchWidth, 10))
odgovorSwitch.transform = CGAffineTransformMakeScale(switchConstant, switchConstant)
let switchValue: Bool = selectedSwitches.count > 0 ? selectedSwitches[Int(currIndex) - 1] : false
odgovorSwitch.setOn(switchValue, animated: false)
// cast current item to relevant class
let obj: Odgovor = item as! Odgovor
// create a label
let odgovorLabel: UILabel = UILabel(frame: CGRectMake((self.view.frame.size.width / 12), currIndex * heightConstant , labelWidth, 20))
odgovorLabel.text = obj.getText();
odgovorLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
odgovorLabel.font = UIFont(name: (odgovorLabel.font?.fontName)!, size: 15)
// add to cell
cell.addSwitch(odgovorSwitch)
cell.addLabel(odgovorLabel)
currIndex++
}
return cell
}
My custom cell also implements methods addSwitch and addLabel which add the element to the contentView as a subview.
Is there any way I can consistently preserve the state of switches when scrolling?
EDIT: As per #Victor Sigler suggestion, I created a bydimensional array like this:
var _switchStates: [[Bool]]!
I initialized it like this:
let odgovori: Int = _pitanja[0].getOdgovori().count
_switchStates = [[Bool]](count: _pitanja.count, repeatedValue: [Bool](count: odgovori, repeatedValue: false))
And I changed my method like this:
for item: UIView in cell.contentView.subviews {
if (item.isKindOfClass(UILabel) && !item.isEqual(cell.tekstPitanjaLabel)){
item.removeFromSuperview()
}
if (item.isKindOfClass(UISwitch)){
_switchStates[indexPath.row][current] = (item as! UISwitch).on
current++
selectedSwitches.append((item as! UISwitch).on)
item.removeFromSuperview()
}
}
And in the end:
let switchValue: Bool = _switchStates.count > 0 ? _switchStates[indexPath.row][Int(currIndex) - 1] : false
First of all as you said in your question regarding the default behavior of the cell in the UICollectionView you need to save the state of each cell, in your case with the UISwitch, but you need to preserve the state in a local property, not in the cellForRowAtIndexPath method because this method is calles every time a cell is going to be reused.
So first declare the array where you going to save the state of the UISwitch's outside this function, something like this:
var selectedSwitches: [Bool] = []
Or you can declare it and then in your viewDidLoad instantiate it, it's up to you, I recommend you instantiate it in your viewDidLoad and only declare it as a property like this:
var selectedSwitches: [Bool]!
Then you can do whatever you want with the state of your UISwitch's always of course preserving when change to on or off.
I hope this help you.
I have done it!
In the end I implemented this:
func collectionView(collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
var current: Int = 0
var cell = cell as! PitanjeCell
for item: UISwitch in cell.getOdgovoriButtons() {
if(current == _switchStates[0].count) { break;}
_switchStates[indexPath.row][current] = (item).on
current++
}
}
In this function I saved the state.
This in combo with prepareForReuse:
public override func prepareForReuse() {
self.tekstPitanjaLabel.text = nil
self._odgovoriButtons.removeAll()
self._odgovoriLabels.removeAll()
self._odgovori.removeAll()
super.prepareForReuse()
}
Finally did it!
I'd like to use an instance of a separate class as my UITableView's datasource & delegate. I create a new instance of the class that should handle this role and assign it via my tableview's properties:
autoMakesTable is defined as property:
var autoMakesTable : UITableView?
Further down:
func nextBttnTouched(sender: AnyObject) {
switch self.autoState {
case .NewOrEdit:
self.autoState = .YearEntered
// If the picker wasn't used create a new Auto with the default year value
self.auto = Auto(year: NSNumber(integerLiteral: self.yearPickerValues.count - 2), make: "", model: "", body: "", trim: "", downPaymentBudget: 0, monthlyPaymentBudget: 0)
// Prepare the make & model fields
backgroundFieldView = UIView(frame: CGRectMake(0, 0-self.view.frame.size.height/2, self.view.frame.width, self.view.frame.height/2))
backgroundFieldView!.backgroundColor = UIColor(red: 251/255, green: 251/255, blue: 251/255, alpha: 1.0)
let makeField = UITextField(frame: CGRectMake(self.view.center.x - 100, self.navigationController!.navigationBar.frame.size.height + 34, 200, 30))
makeField.delegate = self
makeField.placeholder = "Make"
makeField.addTarget(self, action: Selector("makeTextChanged:"), forControlEvents: UIControlEvents.EditingChanged)
let modelField = UITextField(frame: CGRectMake(self.view.center.x - 100, makeField.frame.origin.y + makeField.frame.size.height + 8, 200, 30))
modelField.placeholder = "Model"
let newAutoMakesTableData = AutoMakesTableData()
autoMakesTable = UITableView(frame: CGRectMake(makeField.frame.origin.x, makeField.frame.origin.y + makeField.frame.height + 2, makeField.frame.width, 100))
autoMakesTable!.dataSource = newAutoMakesTableData
autoMakesTable!.delegate = newAutoMakesTableData
self.backgroundFieldView?.addSubview(self.autoMakesTable!)
self.autoMakesTable!.reloadData()
makeField.becomeFirstResponder()
self.addBorderToTextField([makeField,modelField])
self.view.addSubview(backgroundFieldView!)
// Move year picker with animation
// Update next button location while loading make / model input fields with animation
UIView.animateWithDuration(0.8, animations: { () -> Void in
self.newYearPicker?.frame.origin.y = 0
self.backgroundFieldView!.frame.origin.y = 0
self.backgroundFieldView!.addSubview(makeField)
self.backgroundFieldView?.addSubview(modelField)
//self.backgroundFieldView?.addSubview(self.autoMakesTable!)
// self.autoMakesTable!.reloadData()
})
case .YearEntered:
self.autoState = .MakeModelEntered
case .MakeModelEntered:
self.autoState = .DetailsEntered
case .DetailsEntered:
self.autoState = .BudgetEntered
case .BudgetEntered:
performSegueWithIdentifier("next", sender: self)
}
}
It's added onto the view hierarchy (I intact see an empty tableview with a few rows) later on. I also try to call reloadData() on it to no effect.
AutoMakesTableData is defined like so:
import UIKit
class AutoMakesTableData: NSObject, UITableViewDataSource, UITableViewDelegate {
// Auto makes URL
// This should be hosted online so changes can be made w/out having to re-submit the app
let urlForMakes = NSBundle.mainBundle().URLForResource("makes", withExtension: "JSON")
var autoMakes : [String]?
override init() {
super.init()
self.populateAutoMakesFromURL(urlForMakes!)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let aCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
print("cell created")
return aCell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("selected row")
}
private func populateAutoMakesFromURL(url: NSURL) {
do {
let jsonAutoMakesData = try NSData(contentsOfURL: url, options: NSDataReadingOptions.DataReadingMappedIfSafe)
do {
let jsonAutoMakes = try NSJSONSerialization.JSONObjectWithData(jsonAutoMakesData, options: NSJSONReadingOptions.MutableContainers) as! [String:[String]]
self.autoMakes = jsonAutoMakes["makes"]
// Sort array alphabetically
self.autoMakes?.sortInPlace(<)
} catch let error as NSError {
print(error.userInfo.debugDescription)
}
} catch let error as NSError {
print(error.userInfo.debugDescription)
}
}
}
I can access the autoMakes array property on this instance without a problem.
The table view shows up but none of the print statements fire (for a cell at index path or for did select row).
I don't see anywhere where you install the table view into the view hierarchy. If you don't install the view controller into the active view hierarchy it won't do anything.
That is my guess as to what's going wrong.
It's ok to have a separate object serve as the data source/delegate of the view controller.
EDIT:
Based on discussion below this answer, it turns out the problem was that the OP was creating his AutoMakesTableData in a method and assigning it to a local variable but not saving any other strong references to it.
When the method that created the AutoMakesTableData object returned, the local variable went out of scope, there were no more strong references to the object, and it was deallocated.
A table view's delegate and dataSource properties are weak, so those get zeroed out when the object is deallocated. The table view no longer has a data source, so nothing happens.
I got a strange issue (in my opinion;)) considering a subclass of uitableview cell. The code for my subclass is:
class SubLevelTableCell: UITableViewCell {
var subLevelLabel:UILabel
var subLevelBack:UIView
var subLevelScore:UIImageView
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
self.subLevelBack = UIView()
self.subLevelLabel = UILabel()
self.subLevelScore = UIImageView()
super.init(style: UITableViewCellStyle.Value1, reuseIdentifier: reuseIdentifier)
self.subLevelLabel.textColor = whiteColor
self.subLevelLabel.font = UIFont(name: subLevelLabel.font.fontName, size: 20)
self.addSubview(self.subLevelBack)
self.subLevelBack.addSubview(self.subLevelLabel)
self.subLevelBack.addSubview(self.subLevelScore)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
self.subLevelBack.frame = CGRectMake(10, 10, self.bounds.size.width-20, self.bounds.size.height-20)
self.subLevelLabel.frame = CGRectMake(5, 0, subLevelBack.frame.size.width/2-10, subLevelBack.frame.size.height)
self.subLevelScore.frame = CGRectMake(subLevelBack.frame.size.width-120, 15, 100, subLevelBack.frame.size.height-30)
}
}
Now I create these cells in my view controller as follows:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:SubLevelTableCell? = SubLevelTable?.dequeueReusableCellWithIdentifier("Cell") as? SubLevelTableCell
if (cell == nil)
{
cell = SubLevelTableCell(style: UITableViewCellStyle.Subtitle,
reuseIdentifier: "Cell")
cell!.backgroundColor = grayColor
}
if (indexPath.row == 0){nextLevelCanBePlayed = true}
if(nextLevelCanBePlayed){
canBePlayedArray[indexPath.row] = true
cell!.subLevelBack.backgroundColor = blueColor
}else{
canBePlayedArray[indexPath.row] = false
cell!.subLevelBack.backgroundColor = redColor
}
cell!.subLevelLabel.text = "Stage \(indexPath.row+1)"
let data = db.query("SELECT * FROM LEVEL_DETAIL WHERE MAIN_LEVEL =\(MainLevelNr!) AND SUB_LEVEL = \(indexPath.row+1)")[0]
var levelScore: Int = 0
if let columnValue = data["COMPLETED"]{
levelScore = columnValue.asInt()
if (levelScore > 0){nextLevelCanBePlayed = true}else{nextLevelCanBePlayed = false}
cell!.subLevelScore.image = UIImage(named: "\(levelScore)Stars.png")
}
return cell!
}
My problem is that when the view loads for the first time, the subLevelBack.backgroundColor is set properly, ie. the subview's color is correct.
Though when I start scrolling, it becomes a bit of a mess with different cells having incorrect background colors, and I don't know how to solve this issue. I don't have this same issue with the image displayed in the UIImageView btw.
I hope someone will point me in the right direction. Kind regards, Sander
if (indexPath.row == 0){nextLevelCanBePlayed = true}
if(nextLevelCanBePlayed){ ...
The above code seems incorrect. You're trying to keep track of how many levels have been played, but each time the first cell gets re-rendered, nextLevelCanBePlayed will be reset. Perhaps try just if(nextLevelCanBePlayed || indexPath.row == 0){.
The other thing is you're making the assumption that the table view will re-render its cells near the viewport in order, but isn't documented anywhere and very likely isn't reliably true, and is especially likely if you're scrolling in the reverse direction. So when the table view re-builds cell 4 (nextLevelCanBePlayed calculates and sets to false) and maybe goes back to cell 3 next, and even if 3 is actually playable, nextLevelCanBePlayed will be false and 3 will show up incorrect. It then starts making sense that your colors start erratically changing as you scroll up and down.
My suggestion is to properly use your UITableViewDataSource methods and use those to work with the db and return proper data objects that represent the state and data of your cells.