Setting text for uilabel collection from array - ios

I am having a UIScrollView with multiple UILabels inside, so I made a collection of IBOutlets and connected all the labels. Now the problem is am setting the label text from an array and the value in each label not in order as I have in the array.
let timeTables:[TimeTable] = TimeTable.TimeTableList(array:
respnse.responseArray)
print("From label count \(self.fromLabels.count)")
print("Time tabele count \(timeTables.count)")
for (index, item) in timeTables.enumerated() {//
print(index)
if index < self.fromLabels.count , index <
self.toLabels.count{
self.fromLabels[index].text = item.from
print(item.from as Any)
self.toLabels[index].text = item.to
print(item.to as Any)
}
}
How to make the label display the value in the array order?

Related

Iterate StackView, Swift

I have a custom cell composed by 3 StackView. Each one of them has a title, a description and an image, horizontally.
I have to fill this cell with an Array, it could be made of max 3 elements, but it could have 2 or 1.
So in my viewModel I'm treating this array like this ...
let firstItem = myArray[0]
let secondItem = myArray[1]
let thirdItem = myArray[2]
And I fill the field with firstItem.name firstItem.description ... For each one of them (not the best approach I guess)
Then I made some check if index exist, if it doesn't I delete the StackView, I set manually some constraints and I fit the cell to the content ( If I have 2 elements the cell is shorter, If I have 3 elements the cell is bigger).
This is a piece of code after I check that index 3 doesn't exist:
self.stackView.removeFromSuperview()
self.ownConstraints.constant = value (20 for example)
My question is, what is the best approach to achieve this? With cell I usually append item with a for cycle one by one, but I'm not familiar with this approach on StackView inside a Cell.
This is what I have done with cell (a series of cell with 1 name and 1 description):
for (element) in myArray {
self.cellArray.append( elementName , elementDescription )
}
// hide all items in stackView
stackView.arrangedSubviews.forEach({ $0.isHidden = true })
// add or update arrangedSubviews
for (index, element) in myArray.enumerated() {
if index >= stackView.arrangedSubviews.count - 1 {
stackView.addArrangedSubview(UILabel())
}
(stackView.arrangedSubviews[index] as? UILabel)?.text = element
stackView.arrangedSubviews[index].isHidden = false
}
I would hide all subviews in stackView and only show subviews if content is available in your viewModel.

Set Image When Word Appears In Label From Array

I am trying to make an image view set to a certain image when a label displays a certain word from an array which is being randomised.
The picture is supposed to set to the image view when specific words are the text of the label from the array.
Here is the array:
let freeMoodArray = ["Happy", "Sad", "Angry", "Annoyed", "Curious", "Bored", "Chilled", "Furious", "Excited", "Scared", "Emotionless", "Shocked", "Tired", "Sick", "Amused"]
Here is the random label text:"
self.moodAnswer.text = "\(self.freeMoodArray.randomElement()!)"
Now when I load the view, the image chooses a random word from the array which is working. Now, lets say it says Happyas the text of the label.
I want to set a certain image only when it says happy.
Here is my code, which doesn't work: (this func is called in the viewDidLoad())
func emojiMood() {
if moodAnswer.text == "Happy" {
emojiImg.image = UIImage(named: "happy.png")
}
}
how a but a variable and DidSet ?
// create a variable
var moodAnswer : string = "" {
didSet {
// if or switchCase, set image only if the text is equal to Happy
if moodAnswer == "Happy" {
emojiImg.image = UIImage(named: "happy.png")
}
// always set text field with new updated text
self.moodAnswerLabel.text = moodAnswer
}
}
// in your code always set moodAnswer variable, this is more clean and also you set label and image only in a single place
self.moodAnswer = "\(self.freeMoodArray.randomElement()!)"

How to maintain tableview scroll to stop reusing cell

I have multiple sections in tableview. I have multiple questions and multiple answers of each question. In multiple answers, I have one option and that is other (option). when I select the button of other, then it shows the text field for advice. Now i need to maintain the data of text field and that selected option's (Other) text when scrolling in tableview.I am using below code for all answer.
if (indexPath.section == 2)
{
let cellidentifier="cell3"
let cell=tableView.dequeueReusableCell(withIdentifier: cellidentifier,for:indexPath as IndexPath) as! TextfieldTableViewCell
let object_3:AnswerBaseClass = arrobject_answer[0][indexPath.row]
//print("arrobject is\(arrobject_answer[0][indexPath.row])")
if object_3.answer == "O"
{
// cell.lbl_answer.isHidden = true
cell.btn_selected.isHidden=true
//cell.lbl_answer_height.constant = 0
cell.Other_textfield.tag = 101
cell.Other_textfield.borderStyle = .line
cell.Other_textfield_top.constant = -30
cell.Height_2.constant = 30
}
else
{
cell.lbl_answer?.text = object_3.answer!
cell.Other_textfield_top.constant = 12
cell.Height_2.constant = 0
cell.lbl_answer.isHidden = false
cell.btn_selected.isHidden=false
if answer_main_data[0][indexPath.row] == true
{
cell.lbl_answer.tag = indexPath.row
cell.btn_selected.isSelected=true
if cell.lbl_answer.text == "Other"
{
for subview in cell.contentView.subviews
{
subview.removeFromSuperview()
}
if arrOtherTextfield_2.indices.contains(indexPath.row)
{
cell.addSubview(arrOtherTextfield_2[indexPath.row])
}
else
{
cell.Other_textfield.tag = 1100
cell.Other_textfield.borderStyle = .line
cell.Height_2.constant = 30
arrOtherTextfield_2.append(cell.Other_textfield)
}
}
else
{
cell.Height_2.constant = 0
}
}
else
{
cell.Other_textfield_top.constant = 12
cell.btn_selected.isSelected=false
cell.Height_2.constant = 0
}
}
cell.Other_textfield.borderStyle = .line
return cell
}
You will have to retain (store in some dictionary) the data entered in textfield, otherwise it will be lost when you scroll table and cell is reloaded. If you don't want to retain then you should use scroll view instead of table view. In scrollview it will not redraw UI even if you scroll up and down.
You need to separate UI and data. You embed data into your cell and when cells are reused, you lost data.
You can do 2 things:
Create a ViewModel class which contains data of cell: text, color, etc. Of course you need to update your ViewModel as you receive input You can google "MVVM pattern" for more information. Even if your cell is reused, your data is safe in ViewModel object.
You can keep your cells in an Array so that they won't be reused.

SearchBar with UITableViewController contains wrong amount of cells

I am setting up a UISearchController in my UITableViewController like this.
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.delegate = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = self.resultSearchController.searchBar
In my willPresentSearchController I am counting the amount of cells and putting them into an array in order to make the filtered cells equal to the original ones.
{
print("Will Present")
cells.removeAll()
print(numberOfSectionsInTableView(table))
// Iterate over all the rows of a section
for (var row = 0; row < tableView.numberOfRowsInSection(0); row++) {
if let cell:chatUebersichtCell = table.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: 0)) as? chatUebersichtCell{
cells.append(cell)
}
}
}
Now, counting the cells, I am able to get a number equal to the visible amount of cells I am having in my TVC, but not more (5 on iPhone 5).
Any ideas, how to change this?
Edit:
NumberOfSections = 1
NumberOfRows is equal to number of cells (10 in my example)
After Row nr.5 I cannot cast to my cell anymore
The reason why you are getting only 5 cells is because you are using the same view controller to present your results (the table view gets altered after the search). In order to achieve what you are looking for, you can do one of two things:
Keep a reference of the number of rows before the search happens and run your loop against this number.
OR
Use another table view controller for displaying your search results: self.resultSearchController = UISearchController(searchResultsController: newTableViewController)

Adding subviews to only one UICollectionViewCell on button tap

Each UICollectionViewCell has its own button hooked up to the following action:
#IBAction func dropDown(sender:UIButton){
var pt = sender.bounds.origin
var ptCoords : CGPoint = sender.convertPoint(pt, toView:sender.superview);
var ptCoords2 : CGPoint = sender.convertPoint( ptCoords, toView: collectionView!);
var cellIndex: NSIndexPath = self.collectionView!.indexPathForItemAtPoint(ptCoords2)!
//var i : NSInteger = cellIndex.row;
//var i2 : NSInteger = cellIndex.section;
var selectedCell = collectionView?.cellForItemAtIndexPath(cellIndex) as CollectionViewCell!
selectedCell.button.backgroundColor = UIColor.blackColor()
for (var i = 0; i < 3; i++){
var textView : UITextView! = UITextView(frame: CGRectMake(self.view.frame.size.width - self.view.frame.size.width/1.3, CGFloat(50 + (30*(i+1))), CGRectGetWidth(self.view.frame), CGFloat(25)))
textView.backgroundColor = UIColor.whiteColor()
selectedCell.contentView.addSubview(textView)
}
}
What I want to do is add 3 subviews to only the cell that's been tapped. The subviews are added successfully, but as soon as I scroll, cells that come into view & correspond to the previously set indexPath are loaded with 3 subviews. I figure this is due to the dequeueReusableCellWithReuseIdentifier method, but I can't figure out a way around it. I considered removing the subviews on scrollViewDidScroll, but ideally I would like to keep the views present on their parent cell until the button is tapped again.
EDIT:
Okay, I ditched the whole convertPoint approach and now get the cell index based on button tags:
var selectedCellIndex : NSIndexPath = NSIndexPath(forRow: cell.button.tag, inSection: 0)
var selectedCell = collectionView?.cellForItemAtIndexPath(selectedCellIndex) as CollectionViewCell!
Regardless, when I try to add subviews to only the cell at the selected index, the subviews are duplicated.
EDIT:
I've created a dictionary with key values to track the state of each cell like so:
var cellStates = [NSIndexPath: Bool]()
for(var i = 0; i < cellImages.count; i++){
cellStates[NSIndexPath(forRow: i, inSection: 0)] = false
}
which are set by cellStates[selectedCellIndex] = true within the dropDown function. Then in the cellForItemAtIndexPath function, I do the following check:
if(selectedIndex == indexPath && cellStates[indexPath] == true){
for (var i = 0; i < 3; i++){
var textView : UITextView! = UITextView(frame: CGRectMake(cell.frame.size.width - cell.frame.size.width/1.3, CGFloat(50 + (30 * (i+1))), CGRectGetWidth(cell.frame), CGFloat(25)))
textView.backgroundColor = UIColor.whiteColor()
cell.contentView.addSubview(textView)
println("display subviews")
println(indexPath)
}
} else {
println("do not display subviews")
println(indexPath)
}
return cell
where selectedIndex, the NSIndexPath of the active cell set via the dropDown function, is compared to the indexPath of the cell being created & the cellState is checked for true.
Still no luck - the subviews are still displayed on the recycled cell. I should mention that "display subviews" and "do not display subviews" are being logged correctly while scrolling, so the conditional statement is being evaluated successfully.
MY (...hack of a...) SOLUTION!
Probably breaking a bunch of best coding practices, but I assigned tags to all the created subviews, remove them at the beginning of the cellForItemAtIndexPath method, and create them again if the cellState condition returns true.
No problem. Basically, you need to store program state OUTSIDE your UI components in what is commonly called a "model". Not sure what your app is so I am going to make up an example. Assume you want to show a grid where each cell is initially green and they toggle to red when the user taps it. You would need to store the state (I.e., whether a cell has been tapped or not) in some two dimensional array, which is going to contain a Boolean for ALL cells, and not just the ones that are currently showing (assuming you have enough cells to make the grid scroll). When the user taps a cell you set the flag in corresponding array element. Then, when the iOS calls you back to provide a cell (in the dequeue method) you check the state in the array, apply the appropriate color to the UIView of the cell, then return it. That way, iOS can reuse the cell view objects for efficiency, while at the same time you apply your model state to corresponding cells dynamically. Let me know if this clear.
One of two things:
- Disallow pooling of cells.
- Maintain sufficient info in your mode to be able to draw cells depending on the model rather than on their location on screen. That is, store a bit in your model that determines whether or not to show the three views for each "logical" cell. Then, when asked to dequeue a cell, check its model and add/remove the backgrounds dynamically.

Resources