I'm trying to add a subview to each cell (message) of my collectionView (JSQMessagesViewController) to display time of my message, something like this:
Here is my code:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
let timeLabel = UILabel()
timeLabel.frame = cell.textView.frame
timeLabel.text = "abc"
timeLabel.textColor = .blue
cell.addSubview(timeLabel)
if message.senderId == senderId { // 1
cell.textView?.textColor = UIColor.black // 3
cell.avatarImageView.image = self.avatars.0.image
cell.avatarImageView.layer.cornerRadius = cell.avatarImageView.frame.size.height / 2
cell.avatarImageView.clipsToBounds = true
} else {
cell.textView?.textColor = UIColor.black // 2
cell.avatarImageView.image = self.avatars.1.image
cell.avatarImageView.layer.cornerRadius = cell.avatarImageView.frame.size.height / 2
cell.avatarImageView.clipsToBounds = true
}
return cell
}
But it adds me 2 labels:
Why there are 2 labels? And how can I add this label particularly to the bottom-right of my message? Thanks in advance!
Check JSQMessagesCollectionViewCellIncoming.nib and JSQMessagesCollectionViewCellIncoming.nib and adjust the Cell bottom label as per your need to make it look like your design.Adjust Autolayout constraint and done.
Problem 1
Basically, you are creating every time new instance of label.
let timeLabel = UILabel()
timeLabel.frame = cell.textView.frame
timeLabel.text = "abc"
timeLabel.textColor = .blue
Due to the concept of reuses, the cell will reuse everything for the next time. So when you add the subview of timeLabel for the first time that is ready to reuse for the next time. and you are adding again it let timeLabel = UILabel() while the label already there and you are putting a new instance every time.
Solution 1
You have to add the subview once and reuse it by using the tag.
Declare the let timeLabel :UILabel? at class level means where your all variables are declare and check its reference in the cellForItemAt atIndexPath like
if timeLabel == nil {
timeLabel = UILabel()
timeLabel.frame = cell.textView.frame
timeLabel.text = "abc"
timeLabel.textColor = .blue
timeLabel.tag = 766
cell.addSubview(timeLabel)
}
And last get it with the tag in the cellForItemAt atIndexPath
Problem 2
That is not in the bottom right because after awakeFromNib() in JSQMessagesCollectionViewCell this is not adding means label adds before setupLayout.
Solution 2
A: You have to add the constraint manually.
B: OR you can try by setting the frame at last line before returning cell.
Related
Hi I am trying to make a home feed like facebook using UICollectionView But in each cell i want to put another collectionView that have 3 cells.
you can clone the project here
I have two bugs the first is when i scroll on the inner collection View the bounce do not bring back the cell to center. when i created the collection view i enabled the paging and set the minimumLineSpacing to 0
i could not understand why this is happening. when i tried to debug I noticed that this bug stops when i remove this line
layout.estimatedItemSize = CGSize(width: cv.frame.width, height: 1)
but removing that line brings me this error
The behavior of the UICollectionViewFlowLayout is not defined because: the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values
because my cell have a dynamic Height
here is an example
my second problem is the text on each inner cell dosent display the good text i have to scroll until the last cell of the inner collection view to see the good text displayed here is an example
You first issue will be solved by setting the minimumInteritemSpacing for the innerCollectionView in the OuterCell. So the definition for innerCollectionView becomes this:
let innerCollectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
let cv = UICollectionView(frame :.zero , collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .orange
layout.estimatedItemSize = CGSize(width: cv.frame.width, height: 1)
cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
return cv
}()
The second issue is solved by adding calls to reloadData and layoutIfNeeded in the didSet of the post property of OuterCell like this:
var post: Post? {
didSet {
if let numLikes = post?.numLikes {
likesLabel.text = "\(numLikes) Likes"
}
if let numComments = post?.numComments {
commentsLabel.text = "\(numComments) Comments"
}
innerCollectionView.reloadData()
self.layoutIfNeeded()
}
}
What you are seeing is related to cell reuse. You can see this in effect if you scroll to the yellow bordered text on the first item and then scroll down. You will see others are also on the yellow bordered text (although at least with the correct text now).
EDIT
As a bonus here is one method to remember the state of the cells.
First you need to track when the position changes so in OuterCell.swft add a new protocol like this:
protocol OuterCellProtocol: class {
func changed(toPosition position: Int, cell: OutterCell)
}
then add an instance variable for a delegate of that protocol to the OuterCell class like this:
public weak var delegate: OuterCellProtocol?
then finally you need to add the following method which is called when the scrolling finishes, calculates the new position and calls the delegate method to let it know. Like this:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let index = self.innerCollectionView.indexPathForItem(at: CGPoint(x: self.innerCollectionView.contentOffset.x + 1, y: self.innerCollectionView.contentOffset.y + 1)) {
self.delegate?.changed(toPosition: index.row, cell: self)
}
}
So that's each cell detecting when the collection view cell changes and informing a delegate. Let's see how to use that information.
The OutterCellCollectionViewController is going to need to keep track the position for each cell in it's collection view and update them when they become visible.
So first make the OutterCellCollectionViewController conform to the OuterCellProtocol so it is informed when one of its
class OutterCellCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, OuterCellProtocol {
then add a class instance variable to record the cell positions to OuterCellCollectionViewController like this:
var positionForCell: [Int: Int] = [:]
then add the required OuterCellProtocol method to record the cell position changes like this:
func changed(toPosition position: Int, cell: OutterCell) {
if let index = self.collectionView?.indexPath(for: cell) {
self.positionForCell[index.row] = position
}
}
and finally update the cellForItemAt method to set the delegate for a cell and to use the new cell positions like this:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OutterCardCell", for: indexPath) as! OutterCell
cell.post = posts[indexPath.row]
cell.delegate = self
let cellPosition = self.positionForCell[indexPath.row] ?? 0
cell.innerCollectionView.scrollToItem(at: IndexPath(row: cellPosition, section: 0), at: .left, animated: false)
print (cellPosition)
return cell
}
If you managed to get that all setup correctly it should track the positions when you scroll up and down the list.
I'm trying to make table view with random numbers of labels in. Everything is working till I try too scroll it. Than many some of cells appear in one place. It looks like this:
Screen from simulation
To make random row height in viewDidLoad() I put this:
tableView.estimatedRowHeight = 50.0
tableView.rowHeight = UITableViewAutomaticDimension
The code going to write randoms number of labels with random number of lines is here:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HarvestPlan", for: indexPath) as! HarvestPlanCell
let currentSpecies = harvestPlan[indexPath.row]
var kindLabels = [UILabel]()
cell.kindsNamesView.bounds.size.width = 100
for kind in currentSpecies.kinds {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.text = kind.fullName
label.bounds.size.width = 100
label.sizeToFit()
label.bounds.size.width = 100
cell.kindsNamesView.addSubview(label)
kindLabels.append(label)
}
var previous: UILabel!
for label in kindLabels {
label.widthAnchor.constraint(equalToConstant: 100).isActive = true
label.heightAnchor.constraint(equalToConstant: label.bounds.height).isActive = true
label.leadingAnchor.constraint(equalTo: cell.kindsNamesView.leadingAnchor).isActive = true
if previous == nil {
label.topAnchor.constraint(equalTo: cell.kindsNamesView.topAnchor).isActive = true
}
if previous != nil {
label.topAnchor.constraint(equalTo: previous.bottomAnchor).isActive = true
}
if label == kindLabels.last {
label.bottomAnchor.constraint(equalTo: cell.kindsNamesView.bottomAnchor).isActive = true
}
previous = label
}
return cell
Someone have some idea how to repair it? I'm looking for answer since one week and I did't find anything about it...
Thank you #Paulw11, prepareForReuse was this what I was looking for. If someone have similar problem, the answer is code below added to UITableViewCell:
override func prepareForReuse() {
super.prepareForReuse()
for view in kindsNames.subviews { //take all subviews from your view
view.removeFromSuperview() //delete it from you view
}
}
Cheers
I have a UICollectionView with 2 sections. I want to select the cell when the user taps on it.
My code runs correctly every time a user taps on the cell, the cell become smaller and a checkmark appears in it ( it's the imageView I add as subview of the cell). The problem is that if I tap a cell on the first section, it selects another cell in the second section. This is weird as I use the indexPath.
This is my code:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// handle tap events
let cell = collectionView.cellForItemAtIndexPath(indexPath)
let centerCell = cell?.center
if cell!.frame.size.width == cellWidth {
cell?.frame.size.width = (cell?.frame.size.width)!/1.12
cell?.frame.size.height = (cell?.frame.size.height)!/1.12
cell?.center = centerCell!
let imageView = UIImageView()
imageView.image = MaterialIcon.check?.imageWithColor(MaterialColor.white)
imageView.backgroundColor = MaterialColor.blue.accent2
imageView.frame = CGRectMake(1, 1, 20, 20)
imageView.layer.cornerRadius = imageView.frame.height/2
imageView.clipsToBounds = true
if indexPath.section == 0 {
imageView.tag = indexPath.row+4000
} else {
imageView.tag = indexPath.row+5000
}
print("IMAGEVIEW TAG: ",imageView.tag)
cell?.addSubview(imageView)
}
}
Be sure to have the multiple selection property on collectionView set to true in your viewDidLoad() or in storyboard
collectionView?.allowsMultipleSelection = true
So I have a TableView with 2 prototype cells. Here is my code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! ItemDetailsCell
cell.centerImage.image = mainImage
cell.heading.text = detailsHeadings[indexPath.row]
let headingString = detailsHeadings[indexPath.row]
cell.body.text = details[headingString]
tableView.rowHeight = cell.labelBlack.frame.height + 40
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath) as! ItemDetailsCell
cell.heading.text = detailsHeadings[indexPath.row]
let headingString = detailsHeadings[indexPath.row]
cell.body.text = details[headingString]
let bodyString = details[headingString]
let labelWidth = Int(cell.body.frame.width)
println(labelWidth)
let label = UILabel(frame: CGRect(x: 0, y: 0, width: labelWidth, height: 10000))
label.text = bodyString
label.numberOfLines = 100
label.font = UIFont(name: "OpenSans-Light", size: 12.0)
label.sizeToFit()
tableView.rowHeight = label.frame.height + 3
return cell
}
}
So the second prototype cell has just two labels with the values being assigned from a Dictionary. The cell size needs to expand or contract based upon how many lines of text there are. In auto layout I have the number of lines set to 0 so it will pick however many lines are needed. This works fine except when you scroll within the app it will snap the view up as users scroll back up from the bottom. Is there a way to avoid this?
Thanks in advance for any help.
I found this actually after spending some more time looking:
http://candycode.io/automatically-resizing-uitableviewcells-with-dynamic-text-height-using-auto-layout/
It gave me what I needed. I removed the parts of my code that set the rowHeight and then used the viewDidLoad method as well as auto layout to constrain my cell sizes and after a bit of trial and error it is working without snapping to place when you scroll.
You are changing the rowHeight of the tableview. That's most likely very wrong. The rowHeight of the tableView is the default for all rows that don't have their own height, so you are effectively changing the height of all cells.
You should use the delegate method tableView:heightForRowAtIndexPath:
Thanks in advance for the help.
I have a UITableView within a main view contoller. Within the prototype cell, I have a UIImageview. In the code below everything works until I add the 5 lines to apply a circular mask and border. Once I do that, the images will not load unless I scroll the cells. The mask and border do get applied perfectly however. Will be great when it works... but until then.
Certainly this has been seen before. I'm a swift/objective-C newbie.
Working in swift for this one.
Code below;
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mixerCell", forIndexPath: indexPath) as! MixerTableViewCell
// set label background color to clear
cell.textLabel?.backgroundColor = UIColor.clearColor()
// set highlight selection to none
cell.selectionStyle = .None
// set image for cell
let imageView = cell.viewWithTag(1) as! UIImageView
// put circular mask and border. This is the problem code that causes initial load of the tableview images to show up blank.
imageView.layer.cornerRadius = imageView.frame.size.width / 2;
imageView.clipsToBounds = true;
let color1 = UIColor(white: 1.0, alpha: 0.5).CGColor as CGColorRef
imageView.layer.borderWidth = 2;
imageView.layer.borderColor = color1
// assign image
imageView.image = UIImage(named: mixerSounds[indexPath.row])
return cell
}
initial view load
after scroll
your code is perfectly working for me. Here i am using Xcode-7. i think you are using Xcode-6.3 or less version. just upgrade it to Xcode- 7. and if you are using the same then just check your heightforRowAtIndexpath or other delegates there should be some issue.
thanks
Try changing the below lines,
// replace this
let imageView = cell.viewWithTag(1) as! UIImageView
// to
let imageView = cell.yourImageViewName
/* yourImageViewName is the outlet
reference name you have given in the
MixerTableViewCell custom class.
*/
Edit 2: Just for debugging purposes,
hardcode the image name and check if the image appears on the all the cells.
imageView.image = UIImage(named: "first1.png")
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
let cellIdentifier = "cell"
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as UITableViewCell
cell.image_View.image = UIImage(named: mixerSounds[indexPath.row])
println("The loaded image: \(image)")
cell.image_View.layer.masksToBounds = false
cell.image_View.layer.borderColor = UIColor.blackColor().CGColor
cell.image_View.layer.cornerRadius = image.frame.height/2
cell.image_View.clipsToBounds = true
return cell
}
Give imageview outlet to cell and not give imageview name because by default name is imageview so take diffrent name
It looks like the problem is using clipToBounds = true I am facing the same issue while making circular UIImageView inside UITableViewCell
I didn't find the exact solution but for now I found a way to do this
if (indexPath.row == indexPath.length && !isTableReloaded)
{
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.000000001 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.reloadTableView()
})
}
func reloadTableView()
{
isTableReloaded = true
self.tableViewContacts.reloadData()
}
Here isTableReloaded is a Bool type var which is initialized to false in viewDidLoad()
and the if condition is to be placed at the last of cellForRowAtIndexPath but before return statement
This will resolve our problem but do not rely on this as this is not the best approach.
Please post solution for this if any one found the better approach.
Here is a perfect and state away solution for circular image in UITableview Cell.
Simply modify your UITableviewCell (custom cell) class with below code.
override func awakeFromNib() {
super.awakeFromNib()
imgEvent.layer.frame = (imgEvent.layer.frame).insetBy(dx: 0, dy: 0)
imgEvent.layer.borderColor = UIColor.gray.cgColor
imgEvent.layer.cornerRadius = (imgEvent.frame.height)/2
imgEvent.layer.masksToBounds = false
imgEvent.clipsToBounds = true
imgEvent.layer.borderWidth = 0.5
imgEvent.contentMode = UIViewContentMode.scaleAspectFill
}
It will also helps to solve the problem of image circular only after scrolling table..(if any!)
let width = cell.frame.size.width
cell.img.layer.cornerRadius = width * 0.72 / 2
0.72 is the ratio of the cell width to image width, for eg. cellWidth = 125 and imageWidth = 90, so 125/90 would give 0.72. Do similar calculation for your image.
First: Images doesn't load until you scroll, because when cellForRowAtIndexPath methods called the constraints doesn't set for image until now, so when scrolling the constraints was added and the image appears, so if you set proportional width and height for imageView (width==height) in cell then
do that
let w = tableview.frame.width*(proportional value like 0.2)
imageView.layer.cornerRadius = w / 2
imageView.clipsToBounds = true;