This is my first question so please do excuse me if I have broken guidelines.
I will post code down below and would appreciate any input. Easy technique is preferred.
So, to the question.
I have a custom cell and a label inside of it. I want the size of the label to change. I have already got a way for the table cell height to change but I haven't worked out how to change the size of the label.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("messageCell") as! MessageTableViewCell
let receiveTextSize: CGFloat = cell.receivedMessage.intrinsicContentSize().width
let sendTextSize: CGFloat = cell.sentMessage.intrinsicContentSize().width
cell.receivedMessage.alpha = 0
cell.sentMessage.alpha = 0
if messageReceived[indexPath.row] {
if receiveTextSize >= 220 {
cell.sentMessage.frame.size = CGSizeMake(cell.sentMessage.frame.width, 100)
}
cell.receivedMessage.text = messageContent[indexPath.row] as String
cell.receivedMessage.alpha = 1
} else {
if sendTextSize >= 220 {
cell.sentMessage.frame.size = CGSizeMake(cell.sentMessage.frame.width, 100)
cell.sentMessage.numberOfLines = 0
}
cell.sentMessage.text = messageContent[indexPath.row] as String
cell.sentMessage.alpha = 1
}
cell.receivedMessage.sizeToFit()
cell.receivedMessage.frame.size = CGSizeMake(100, 100)
return cell
}
Edit Solution was to set auto-constraints and then change the heightForRowAtIndexPath function for the tableView
Related
I'm calculating trailing constraints for stack view which is inside table view. It seems that calculation doesn't give exact value. Please refer to screenshot below. Is my calculation/logic incorrect? How to fix this so that I can get exact value?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableCellID", for: indexPath) as? TableViewCell else {
fatalError("Can't find cell")
}
let profile = array[indexPath.row]
if profile.isDisabled1 || profile.isDisabled2 || profile.isDisabled3 {
let totalHiddenViews: Int = (profile.isDisabled1 ? 1 : 0) + (profile.isDisabled2 ? 1 : 0) + (profile.isDisabled3 ? 1 : 0)
let singleViewWidth = (UIScreen.main.bounds.size.width - 32)/3 // 32 is sum of leading and trailing constraints
cell.stackViewTrailing.constant = CGFloat(totalHiddenViews) * (singleViewWidth + 8)
} else {
cell.stackViewTrailing.constant = 8
}
cell.view1.isHidden = profile.isDisabled1
cell.view2.isHidden = profile.isDisabled2
cell.view3.isHidden = profile.isDisabled3
return cell
}
You also have issues with the 2-cell layout. My instincts say that one of your magic numbers is incorrect and the one I see here that is directly contingent on the number of visible cells is this line.
cell.stackViewTrailing.constant = CGFloat(totalHiddenViews) * (singleViewWidth + 8)
I would start by checking the sanity of that constant and work back from there starting with code that depends on the number of visible cells.
I am supporting iOS 7 and I am not using autolayout. Is there a way I can have dynamic height for cell labels doing it this way?
Thanks
EDIT:
Here is the code I am using to define a dynamic height in iOS 7, it seems I can get it kinda working with auto layout but it cuts off the last cell at the bottom, it is weird.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = offScreenCells.objectForKey("gcc") as? GalleryCommentCell
if cell == nil {
cell = commentsTable.dequeueReusableCellWithIdentifier("GalleryCommentCustomCell") as? GalleryCommentCell
offScreenCells.setObject(cell!, forKey: "gcc")
}
let comment :GalleryCommentInfo = commentResults[indexPath.row]
setCellCommentInfo(cell!, data: comment)
cell!.bounds = CGRectMake(0, 0, CGRectGetWidth(commentsTable.bounds), CGRectGetHeight(cell!.bounds))
cell!.setNeedsLayout()
cell!.layoutIfNeeded()
let height = cell!.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return height + 1
}
func setCellCommentInfo(cell :GalleryCommentCell, data :GalleryCommentInfo) {
cell.commentDate.text = data.galleryCommentDate
cell.comment.text = data.galleryComment
}
In your custom cell implement method like this:
+ (CGFloat)heightForContactName:(NSString *)name
{
CGFloat height = 0.0f;
if (name) {
CGFloat heightForText;
// Calculate text height with `textBoundingRect`...
height += heightForText;
}
return height;
}
I want to hide the label in a cell that was tapped and instead show an image. But I want to do this only if a cell with a certain index has already been set to the imageView.
What is the best way to address the cells and store if they are set to imageView or not? How do I use the prepareForReuse method?
This is the way I do it until now, but as the cells are reused. The image is shown in other cells at scrolling.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("user tapped on door number \(indexPath.row)")
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCollectionViewCell
if (cell.myLabel.text == "1") {
one = true
if(seven = true) {
if (cell.myLabel.hidden) {
cell.myLabel.hidden = false
cell.MyImageView.image = nil
}
else {
cell.myLabel.hidden = true
cell.MyImageView.image = UIImage(named:"1")!
}
}
}
You didn't say if your collection view has exactly 7 cells or if it can have "N" (e.g. 100) cells in the collection, so if this were my problem and I had to solve it, I would make the state of your "seven" cell a property of the class (e.g. "var sevenState : Bool") and then I could display the button or image of other cells depending on what sevenState is.
In my app I have to configure a UICollectionReusableView based on the index path, if the indexPath has a particular value then I send an array which is used to set labels and images.
I use a function in the custom UICollectionReusableView, if I call it with an array it populates the labels and images and if I call it with nil it resets these.
func collectionView(collectionView: UICollectionView!, viewForSupplementaryElementOfKind kind: String!, atIndexPath indexPath: NSIndexPath!) -> UICollectionReusableView! {
.... [logic around selecting index path based on data returned]
....
if filteredEvents != nil{
reusableView.populateCalendarDayDates(sortedEvents)
}else{
reusableView.populateCalendarDayDates(nil)
}
In the function in the custom UICollectionReusableView I reset labels back to default values before possibly updating them :
func populateCalendarDayDates(arrayEvents: NSArray?){
let firstDayTag = tagStartDay()
var dayDate = 1
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.delegate = callingCVC
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
You can get the same effect, and it is probably a bit more readable, by moving this code to prepareForReuse in the custom UICollectionReusableView :
override func prepareForReuse() {
super.prepareForReuse()
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
}
}
Hope that helps.
The basic setup is like this: I have a UITableView with two proto cells that I am working with (think of a messaging app, where one cell type shows messages you send and the other, messages you recieve). Now obviously the message length can vary from one line to even 100+ lines thus I need variable cell heights.
First attempt:
tableView.estimatedRowHeight = 75
tableView.rowHeight = UITableViewAutomaticDimension
I used estimatedRowHeight in my viewDidLoad(). This works perfectly and computes cell heights very nicely. But because this is a messaging app I need to scroll the tableView all the way to bottom on viewDidLoad() and whenever a new message is recieved / sent. But the estimatedRowHeight messes with the tableView scrolling all the way to bottom. Some say it's a bug, some say it's to be expected. Nonetheless, this way won't work for me at all.
Second Attempt:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
I thought to compute heights manually as so:
let measuerWidth = UIScreen.mainScreen().bounds
var w = measuerWidth.size.width // this is so that we can limit label width to screen width so the text is forced to go to multiple lines
// I probably should use my custom cell width here, but if I try to `dequeue` that cell it's frame contents are always `zero`. Is that a problem?
let lbl = UILabel(frame: CGRect.zeroRect)
lbl.text = chatMessages[indexPath.row][ChatRoomKeys.MESSAGE_TEXT] as? String
lbl.font = UIFont.systemFontOfSize(20)
lbl.numberOfLines = 0
lbl.lineBreakMode = NSLineBreakMode.ByWordWrapping
lbl.frame = CGRectMake(0, 0, w, 0)
lbl.sizeToFit()
return lbl.frame.height + 20
This way works almost perfectly however at times cell height isn't what it should be. Meaning sometimes if the text is one line plus one word, that one word won't show because the cell height was only for one line.
Is there some better way to calculate the cell height?
UPDATE:
Here's a screenshot of kinda what happens
as you can see the label ends at x but the original text goes upto y.
UPDATE 2:
These are the proto cells I am using, the bottom cell is simply a mirror of the top one.
I use this one:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 2)
{
NSString *cellText = #"init some text";
CGSize labelSize = [self calculateTextSize:cellText];
return labelSize.height + 20.0f;
}
return 45;
}
- (CGSize) calculateTextSize: (NSString*) text
{
UIFont *cellFont = [UIFont fontWithName:#"HelveticaNeue" size:12.0];
CGFloat width = CGRectGetWidth(self.tableView.frame) - 40.0f;
CGSize constraintSize = CGSizeMake(width, MAXFLOAT);
CGRect labelRect = [cellText boundingRectWithSize:constraintSize options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:cellFont} context:NSLineBreakByWordWrapping];
CGSize labelSize = labelRect.size;
return labelSize;
}
Edit:
CGFloat width = CGRectGetWidth(self.tableView.frame) - 40.0f;
Calculation of the maximum available width of the text, you can use self.view.frame or etc. -40.0f - because i have indentation from the edge = 20.0f
You can do this with the automatic height as you were doing. See the code below, when your view is about to appear, ask the UITableView for the number of rows in the section (0) in this case. Then you can create an NSIndexPath for the last row in that section then ask the UITableView to scroll that index path into view. Using the code below you can still have the UITableView calculate the heights for you.
import UIKit
final class ViewController: UIViewController {
// MARK: - Constants
let kFromCellIdentifier = "FromCell"
let kToCellIdentifier = "ToCell"
// MARK: - IBOutlets
#IBOutlet private weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 75.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let numRows = tableView.numberOfRowsInSection(0) - 1 // -1 because numbering in the array starts from 0 not 1
let indexPath = NSIndexPath(forRow: numRows, inSection: 0)
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: false)
}
}
extension ViewController: UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row % 2 == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier(kFromCellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
cell.configureCell(UIImage(named: "Talk")!, text: "From Some text \(indexPath.row)")
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(kToCellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
cell.configureCell(UIImage(named: "Email")!, text: "To Some text \(indexPath.row)")
return cell
}
}
}
I have a vertically scrolling UICollectionView that uses a subclass of UICollectionViewFlowLayout to try and eliminate inter-item spacing. This would result in something that looks similar to a UITableView, but I need the CollectionView for other purposes. There is a problem in my implementation of the FlowLayout subclass that causes cells to disappear when scrolling fast. Here is the code for my FlowLayout subclass:
EDIT: See Comments For Update
class ListLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
if var answer = super.layoutAttributesForElementsInRect(rect) {
for attr in (answer as [UICollectionViewLayoutAttributes]) {
let ip = attr.indexPath
attr.frame = self.layoutAttributesForItemAtIndexPath(ip).frame
}
return answer;
}
return nil
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let currentItemAtts = super.layoutAttributesForItemAtIndexPath(indexPath) as UICollectionViewLayoutAttributes
if indexPath.item == 0 {
var frame = currentItemAtts.frame
frame.origin.y = 0
currentItemAtts.frame = frame
return currentItemAtts
}
let prevIP = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section)
let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIP).frame
let prevFrameTopPoint = prevFrame.origin.y + prevFrame.size.height
var frame = currentItemAtts.frame
frame.origin.y = prevFrameTopPoint
currentItemAtts.frame = frame
return currentItemAtts
}
}
One other thing to note: My cells are variable height. Their height is set by overriding preferredLayoutAttributesFittingAttributes in the subclass of the custom cell:
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes! {
let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as UICollectionViewLayoutAttributes
attr.frame.size = CGSizeMake(self.frame.size.width, myHeight)
return attr
}
And I set the layout's estimated size on initialization:
flowLayout.estimatedItemSize = CGSize(width: UIScreen.mainScreen().bounds.size.width, height: 60)
Here is a GIF that demonstrates this problem:
Does anybody have an idea as to what's going on? Your help is much appreciated.
Thanks!