I'm having issues displaying some image views and text labels using a custom UITableViewCell.
This is what is happening:
A shot of the storyboard:
Some of the issues is that the UILabel text does not get replace and it's ghosting. Also, for the 2nd row, there should only be 2 UIImageViews but there's 3, because there's text for the middle that shouldn't have been shown.
AboutCustomCell.swift:
class AboutCustomCell: UITableViewCell
{
#IBOutlet var threePicRow_leftImageView: UIImageView!
#IBOutlet var threePicRow_leftNameLabel: UILabel!
#IBOutlet var threePicRow_leftPositionLabel: UILabel!
...
#IBOutlet var twoPicRow_leftImageView: UIImageView!
#IBOutlet var twoPicRow_leftNameLabel: UILabel!
#IBOutlet var twoPicRow_leftPositionLabel: UILabel!
...
}
cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell3Pic = tableView.dequeueReusableCellWithIdentifier("3PictureRowCell", forIndexPath: indexPath) as! AboutCustomCell
let cell2Pic = tableView.dequeueReusableCellWithIdentifier("2PictureRowCell", forIndexPath: indexPath) as! AboutCustomCell
switch (indexPath.row)
{
case 0:
cell3Pic.threePicRow_leftNameLabel.text = "Name"
cell3Pic.threePicRow_leftPositionLabel.text = "Position"
cell3Pic.threePicRow_leftImageView.image = UIImage(named: "icon_about")
cell3Pic.threePicRow_middleNameLabel.text = "Name"
cell3Pic.threePicRow_middlePositionLabel.text = "Position"
cell3Pic.threePicRow_middleImageView.image = UIImage(named: "icon_about")
cell3Pic.threePicRow_rightNameLabel.text = "Name"
cell3Pic.threePicRow_rightPositionLabel.text = "Position"
cell3Pic.threePicRow_rightImageView.image = UIImage(named: "icon_about")
return cell3Pic
case 1:
cell2Pic.twoPicRow_leftNameLabel.text = "Name"
cell2Pic.twoPicRow_leftPositionLabel.text = "Position"
cell2Pic.twoPicRow_leftImageView.image = UIImage(named: "icon_about")
cell2Pic.twoPicRow_rightNameLabel.text = "Name"
cell2Pic.twoPicRow_rightPositionLabel.text = "Position"
cell2Pic.twoPicRow_rightImageView.image = UIImage(named: "icon_about")
return cell2Pic
default:
break;
}
// Will never reach here
return cell3Pic
}
Is there something I'm not doing to the custom cell that would cause this? Thank you.
Here are your options:
Most close to your own design, make sure you hide and show the images and labels that should not be shown explicitly in cellForRowAtIndexPath, by setting their hidden property.
Better: create two different cell classes, one with 2 and one with three items per row. Dequeue only the cells you need and return those in cellForRowAtIndexPath.
Best: think about exploring UICollectionView instead.
First of all, I can't reproduce this behavior since I don't have any Apple hardware right here.
But I suspect the problem comes from the fact that table cells are reused or that always some outlets aren't properly connected but get nevertheless instantiated.
Three possible solutions:
The minimal/dirty one: Set the text of the unused outlet labels to the empty strings.
The elaborate one: Define two different cell classes for the two and three picture rows.
The completely different one: If you actually don't have dynamic content inside your table view don't use a table view, instead use a hand taylored layout.
Related
I'm making a chess app for swift with a chat function. I upload my messages to firebase firestore and then, download them into a [Message] array and display them in a UITableView.
I've been looking around for this on stackoverflow and found that several people are having trouble with sizes of a custom UITableViewCell. I've run into the same problem, but in my case the width of the cell seems to randomly change every time I call .reloadData(). To illustrate :
The first 2 times or so when I type something in my chat it should look like this (the cells in the circle)
But as you can see after a few times this happens :
My code :
Custom cell class
class MessageCustomCell: UITableViewCell {
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var bgView: UIView!
#IBOutlet weak var leftImage: UIImageView!
#IBOutlet weak var messageBubble: UIView!
#IBOutlet weak var messageLabel: UILabel!
#IBOutlet weak var rightImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
messageLabel.numberOfLines = 0
messageBubble.layer.cornerRadius = 5
}
}
TableView delegate methods (TableView = messageView)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allMessages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = messageView.dequeueReusableCell(withIdentifier: "MessageCustomCell") as! MessageCustomCell
//cell.width = tableView.frame.width
let msg = allMessages[indexPath.row]
//cell.frame.size.width = messageView.frame.width
//cell.contentView.frame.size.width = messageView.frame.width
let date = msg.time
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "d/M/yy, h:mm"
let realDate = dateFormatter.string(from: date)
print(msg.fromUserName!)
if msg.fromUserName! == currentUserName {
cell.messageBubble.backgroundColor = UIColor(named: "lightSquare")
cell.messageLabel.textColor = .black
cell.rightImage.isHidden = true
} else {
cell.messageBubble.backgroundColor = UIColor(named: "darkSquare")
cell.messageLabel.textColor = .white
cell.leftImage.isHidden = true
}
cell.dateLabel.text = realDate
cell.messageLabel.text = msg.text!
return cell
}
In my MessageCustomcell.xib file I have a few constraints :
I've put the leftImage, messageBubble (UIView), rightImage in a horizontal stackview. The images have only width and height constraints. The label in the messageBubble has constraints to its superview (top,bottom,trailing,leading)
The stackView has constraints to the bgView (UIView), also top bottom trailing and leading.
Same for the bgView to the safe area.
I've tried setting the frame for the custom cell, but that didn't seem to work, so I've commented that out.
Really hope someone can help me out. If you guys need more info then please do let me know :)
Thanks all.
EDIT :
Here are my constraints for the custom cell xib file.
Alright thanks to #SPatel, who pointed out to make 2 separate .xib files for the receiver chat bubble and the sender chat bubble. This actually worked for my situation and was the right way to go in the first place, instead of manipulating one custom cell for both sender and receiver.
I'm thinking the constraint issue had to do with me trying to hide either the right image or the left image, to change the custom cell appearance for both sides. That must have messed up the whole constraints situation for the custom cell.
Anyway, happy man. Moving on!
I'm trying to read data from CoreData into a custom cell. This is just a test app before I try moving to the real app that I've been working on. The data is there - I can print it to the console and see it. For some reason, even with constraints, all of the data is laid on top of each other in the cell. Can anyone see what's going on with this?
I've created constraints to keep the cells where they should be, but when the data is loaded from my 'show data' button, the data is laid on top of each other.
Here is my custom cell class:
import UIKit
class CustomCellClass: UITableViewCell {
#IBOutlet weak var txtNameLabel: UILabel!
#IBOutlet weak var txtAgeLabel: UILabel!
}
Here is the ShowData class: (partial)
class ShowData: UIViewController, NSFetchedResultsControllerDelegate {
#IBOutlet weak var personTableView: UITableView!
let appDelegate = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var personData = [Person]()
// Read the data
override func viewDidLoad() {
super.viewDidLoad()
personTableView.delegate = self
personTableView.dataSource = self
loadItems()
}
func loadItems() {
let request : NSFetchRequest<Person> = Person.fetchRequest()
do {
personData = try appDelegate.fetch(request)
} catch {
print("couldn't load")
}
}
}
extension ShowData : UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return personData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let person = personData[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "personNameAge", for: indexPath) as! CustomCellClass
cell.txtNameLabel?.text = person.name
cell.txtAgeLabel?.text = String(person.age)
return cell
}
Here is a screenshot of the tableview while running:
Edit:
I just deleted the app from the simulator and tried to rerun - now there isn't any data in the cells.
Just to clarify for other readers, as can be seen in your screenshot, the rows in your table are separated as expected but the different fields in each cell, what one might call the columns, are on top of one another.
You say that you have created constraints to keep the cells where they should be, I'm not sure what you mean by that. What you need is constraints for the fields within each cell – what I call intra-cell constraints. Either you have not added these constraints, or there is a mistake in them which causes all fields to be drawn at the left.
To show you what I mean, let's use the example of a little workout app of mine which has, in each table cell, from left to right, a Perform button, an Edit button, a Name field and a Duration field. The screenshot below shows, in the big yellow box, the intra-cell constraints. If you are using a storyboard, the problem with your app must be in that area. If you are not using a storyboard, the problem must be in the equivalent code (or lack of it).
Just to let everyone know. The issue is resolved. I removed the table view cell from the project, readded, and readded the constraints. Everything is working now. I'm not sure where the problem was, but I noticed I had weird wrapping happening. I moved one of the labels to the other side of the cell and constrained it to the right side, and the other to the left side. When I ran the app, the text appeared to word wrap. I decided to delete the cell and readd and relink my outlets. It worked the first time...
I am trying to figure out how to get textfield data from cells in a tableview in an ordered fashion. So far I am able to type into each textfield a retrieve the type data. To do so in the correct order though the user must edit the first cell...last cell, any other way and the data isn't in the correct order.
For Example:
I have the program create 5 cells with a textfield,
textfield1: I typed here second
textfield2: I typed here fourth
textfield3: I typed here first
textfield4: I typed here fifth
textfield5: I typed here third
the way I currently have it my dataArray would look identical to this one, because it is being stored based on when it is typed in and not the order of the cells.
I would like to type the above example, but my data come out like this:
textfield1: I typed here first
textfield2: I typed here second
textfield3: I typed here third
textfield4: I typed here fourth
textfield5: I typed here fifth
Here is my textfield editing code:
#IBAction func TitleEditBegin(_ sender: UITextField) {
}
#IBAction func TitleEditEnd(_ sender: UITextField) {
print(sender.tag) // Debug
titleArray.append(sender.text!)
}
I know for the time being that any other changes will be appended to the titleArray, but I want to solve the ordering issue first.
Thanks!
EDIT: I forgot to add in how I am creating the cells, the code is below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = TitleSessionTableView.dequeueReusableCell(withIdentifier: textCellIdentifier, for: indexPath) as! TitleSessionCell
cell.SessionTitleLabel.text = "Title"
// cell.SessionTitleField.text = "Default"
cell.SessionTitleField.tag = indexPath.row
cell.SessionTitleField.delegate = self
print(indexPath.row) // Debug
return cell
}
EDIT 2: Adding where I define the text fields.
import Foundation
import UIKit
class TitleSessionCell: UITableViewCell{
#IBOutlet weak var SessionTitleField: UITextField!
#IBOutlet weak var SessionTitleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}
The easiest way would be to use a dictionary I believe. I would assign all of the textFields a different tag. So for textField1 you would say textField1.tag = 1. I don't see the creation of your text fields, so it is hard to show the best way of adding that in, but then after handling that, I would create the dictionary as a class variable.
var textFieldDictionary: [Int: String] = [:]
and then add in the text to it like so:
if sender.text != nil && sender.text != "" {
textFieldDictionary[sender.tag] = sender.text
}
then when you want to retrieve the information, do something like this:
for i in 0..<biggestTextFieldNumber {
if let text = textFieldDictionary[i] {
//do something with text
print(text)
}
}
or you could just grab the specific number values out whenever you needed them by using:
textFieldDictionary[numberYouWant]
I hope this helps!
I currently have a ViewController with a TableView inside it called SelectedListItemsViewController. This ViewController's TableView is populated by a custom cell class called SelectedListItemsTableViewCell.
I have an array of Realm Model Objects called selectedListItems, each of which has several properties. The SelectedListItemsTableViewCell populates the TableView with the listItem property of that indexPath.row's object, and each row has a UIStepper with a UILabel next to it that (as of now) shows UIStepper.value for each row. Ideally, the label will reflect the listItemWeight property of each row, and change it when incrementing or decrementing that row.
This is my custom cell:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
}
}
And in my ViewController's cellForRowAtIndexPath, I've configured the cell like so:
// Configure the cell...
cell.selectedListItemLabel.text = selectedListItems[indexPath.row].listItem
cell.listItemWeightLabel.text = "\(selectedListItems[indexPath.row].listItemWeight)"
Which perfectly loads the listItem property, and the listItemWeight property shows up correctly, but as soon as I increment or decrement on the UIStepper it gets messed up.
How do I properly link my UILabel and UIStepper to the [indexPath.row].listItemWeight?
In the same method that gets called when your stepper updates, update your listItem. However, since this item is stored in your Realm database, you will have to get an instance of your Realm database and write the change to the database.
You can do this by having your TableViewCell hold on to an instance of the listItem. Your new TableViewCell class will look something like this:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
var listItem: Item?
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
if let listItem = listItem {
let realm = try! Realm
try! realm.write {
listItem.listItemWeight = Int(sender.value) ?? 0
}
}
}
}
The above answer was helpful in leading me to the actual solution of my issue, but if anyone in the future is curious - I ended up using a closure.
In my custom cell class, I did
#IBAction func stepperValueChanged(sender: UIStepper) {
selectedListItemLabel.text = Int(sender.value).description
tapped?(self)
}
And in my view controller, I did
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! SelectedListItemsTableViewCell
cell.stepperControl.value = selectedListItems[indexPath.row].listItemWeight
// Configure the cell...
cell.tapped = { (selectedCell) -> Void in
selectedListItems[indexPath.row].listItemWeight = cell.stepperControl.value
}
Which allowed me to access each cell's UIStepper in the view controller file.
It helped to read flashadvanced's Option 2 answer in this thread.
deriving from other posts about this topic i tried the following approach - but somehow "foo" as well as "bar" seem to be nil
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlowLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var foo = collectionView.cellForItemAtIndexPath(indexPath) as? messageCell
var bar = foo?.messageText.frame.height
return CGSizeMake(self.view.frame.width-20, bar!+20)
}
the class for the custom cell:
class messageCell: UICollectionViewCell,UIPopoverPresentationControllerDelegate,UITextViewDelegate{
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var timestampLabel: UILabel!
#IBOutlet weak var messageText: UITextView!
func setCell(authorLabelText:String, messageText:String,timestampLabelText:String) {
self.authorLabel?.text = authorLabelText
self.timestampLabel?.text = timestampLabelText
self.messageText?.text = messageText
self.messageText.delegate = self
self.messageText.sizeToFit() //works, i can see different heights when giving a custom color background
println(self.messageText.frame.height)
}
}
Am I supposed to set the height in my custom cell class? That would make no sense as it isnt rendered yet, wouldn't it?
€dit:
A Screenshot from stopping at "foo":
bar is nil because foo is nil - that's what the optional chaining "?" is doing in that expression. If collectionView.cellForItemAtIndexPath(indexPath) as? MessageCell† is nil, it would imply either:
There is no cell at that index path, or
The cell at indexPath is not an instance of MessageCell
I would suggest it's more likely to be the latter, as those delegate messages shouldn't really call with an invalid index path. Where you register your cell class (either in code, or in the storyboard), are you sure you're registering them as MessageCell cells?
† Types should begin with an uppercase character