Automatically Resize UILabel - ios

In Xcode 6 Beta 5, I had a chat interface that looks like the iOS 7 messages app, where the UILabel that the text was inside sized to the width of the text itself. When I updated to Beta 6, I noticed an option for UILabel in interface builder that I hadn't noticed before:
When I have the explicit width set, the width doesn't change at all based on the width of the text. When I uncheck explicit, the width of the text is at least 234, so it expands out of the view.
I am using a UICollectionView inside of a UIViewController, and here is my cell for item at index path method:
func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell! {
let defaults = NSUserDefaults.standardUserDefaults()
let row = indexPath.row
var cell: UICollectionViewCell
let path = UIBezierPath()
let object: AnyObject = (messages[row] as NSDictionary).objectForKey("user_id")!
let uid: AnyObject = defaults.objectForKey("user_id")!
if "\(object)" == "\(uid)" {
cell = collectionView.dequeueReusableCellWithReuseIdentifier(right_chat_bubble, forIndexPath: indexPath) as UICollectionViewCell
path.moveToPoint(CGPointMake(0, 0))
path.addLineToPoint(CGPointMake(0, 10))
path.addLineToPoint(CGPointMake(12, 5))
path.addLineToPoint(CGPointMake(0, 0))
}
else {
cell = collectionView.dequeueReusableCellWithReuseIdentifier(left_chat_bubble, forIndexPath: indexPath) as UICollectionViewCell
path.moveToPoint(CGPointMake(0, 5))
path.addLineToPoint(CGPointMake(12, 10))
path.addLineToPoint(CGPointMake(12, 0))
path.addLineToPoint(CGPointMake(0, 5))
}
let initial_view = cell.viewWithTag(101) as UILabel
initial_view.layer.cornerRadius = 20
initial_view.layer.masksToBounds = true
let name = (messages[row] as NSDictionary).objectForKey("name")! as String
let name_array = name.componentsSeparatedByString(" ")
let first_initial = name_array[0]
let last_initial = name_array[1]
let first_char = first_initial[0]
let last_char = last_initial[0]
let initials = first_char + last_char
initial_view.text = initials
let circle: UIView = cell.viewWithTag(103)! as UIView
let mask = CAShapeLayer()
mask.frame = circle.bounds
mask.path = path.CGPath
circle.layer.mask = mask
let message = cell.viewWithTag(102) as ChatLabel
message.enabledTextCheckingTypes = NSTextCheckingType.Link.toRaw()
message.delegate = self
message.text = (messages[row] as NSDictionary).objectForKey("content")! as String
message.layer.cornerRadius = 15
message.layer.masksToBounds = true
message.userInteractionEnabled = true
return cell
}

take a look at sizeThatFits: and sizeToFit:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIView_class/index.html#//apple_ref/occ/instm/UIView/sizeThatFits:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIView_class/index.html#//apple_ref/occ/instm/UIView/sizeToFit
A UILabel can call sizeThatFits like :
myLabelLbl.text = #"some text"
CGSize maximumLabelSize = CGSizeMake(200, 800)
CGSize expectedSize = [myLabelLbl sizeThatFits:maximumLabelSize]
myLabelLbl.frame = CGRectMake(0, 0, expectedSize.width, expectedSize.height) //set the new size

Related

Swift give uicollectionviewcell width and dynamic height

I have this uicollectionviewcell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = commentSection.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CommentCell
return cell
}
CommentCell:
let CommenterprofileImage: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let commentText: UILabel = {
let l = UILabel()
l.numberOfLines = 0
l.text = "some text"
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blue
designCell()
}
func designCell(){
addSubview(CommenterprofileImage)
addSubview(commentText)
addCellConstraints()
}
func addCellConstraints(){
CommenterprofileImage.topAnchor.constraint(equalTo: self.topAnchor,constant:10).isActive = true
CommenterprofileImage.leftAnchor.constraint(equalTo: self.leftAnchor,constant:20).isActive = true
CommenterprofileImage.widthAnchor.constraint(equalToConstant: 70).isActive = true
CommenterprofileImage.heightAnchor.constraint(equalToConstant: 70).isActive = true
commentText.topAnchor.constraint(equalTo: CommenterprofileImage.bottomAnchor,constant:20).isActive = true
commentText.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
commentText.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
}
And i want this cell's width to be equal to the view's width,but i want it's height to depend on it's content.
I tried doing this
layout.estimatedItemSize = CGSize(width: view.frame.width, height: 100)
But then my cell's height is 100 and width is less than 100.
You Can do it this way-:
Calculate label height with boundingRect method first-:
func estimatedFrameHeight(text:String) -> CGRect{
let size = CGSize(width: (view.frame.width/2 - 8), height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attribute = [NSFontAttributeName:UIFont.systemFont(ofSize: 11)]
return NSString(string: text).boundingRect(with: size, options: options, attributes: attribute, context: nil)
}
Keep the exact same font you providing to your label,And keep .usesLineFragmentOrigin for multi line label.
Now in CollectionView sizeForItemAt indexPath do this-:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (collectionView.frame.width)
let subDict = subCategoryListArray[indexPath.row]
if let product_name = subDict["product_name"] as? String {
let height = estimatedFrameHeight(text:product_name)
return CGSize(width: width, height: height.height + 70 + 10 + 20)
}
return CGSize(width: 0, height: 0)
}
let height = estimatedFrameHeight(text:product_name) . This will provide you with estimated height as well as width if you need. Now you have your label height calculated you need to add imageView height , y position constraints to make it work.You can get it from here-:
CommenterprofileImage.heightAnchor.constraint(equalToConstant: 70).isActive = true
Height = 70 you have.
And,
CommenterprofileImage.topAnchor.constraint(equalTo: self.topAnchor,constant:10).isActive = true
commentText.topAnchor.constraint(equalTo: CommenterprofileImage.bottomAnchor,constant:20).isActive
Top Constraint is = 10
Top Constraint is = 20
So you need to add this now as you can see I did in answer.
Remark-:
only add ImageView Height if it is above label else not require, just
add height and y padding for what ever you have above label.
Same to calculate exact width for label subtarct leading or trailing
space.
If label covers complete width (No insets subtraction required)-:
func estimatedFrameHeight(text:String) -> CGRect{
let size = CGSize(width: (view.frame.width), height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attribute = [NSFontAttributeName:UIFont.systemFont(ofSize: 11)]
return NSString(string: text).boundingRect(with: size, options: options, attributes: attribute, context: nil)
}
If label is 20 points from leading then subtract that-:
func estimatedFrameHeight(text:String) -> CGRect{
let size = CGSize(width: (view.frame.width - 20), height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attribute = [NSFontAttributeName:UIFont.systemFont(ofSize: 11)]
return NSString(string: text).boundingRect(with: size, options: options, attributes: attribute, context: nil)
}

Swift: tableview cell content is added again and again with each reload?

Ok, I am fairly this Objective C question had the same problem = Cell Label text overlapping in cells but I haven't found any answers in Swift. Im also very new to tableviews/cells and would just like to know the proper way to do this as clearly Im doing it wrong-
I have custom cells in my tableview that I created in storyboard. I need to add the content of my cells (labels, etc) programmatically. I have done this here -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("eventCell", forIndexPath: indexPath) as! EventTableCellTableViewCell
// cell.eventTitle.text = names[indexPath.row]
// cell.eventDescription.text = descriptions[indexPath.row]
cell.contentView.clipsToBounds = false
//cell UIX
let eventTitleLabel = UILabel()
let dateLabel = UILabel()
let authorLabel = UILabel()
let locationLabel = UILabel()
let categoryView = UIImageView()
//border
let botBorder: CALayer = CALayer()
botBorder.frame = CGRectMake(0.0, cell.frame.height-1, cell.frame.width, 1.0)
botBorder.backgroundColor = colorWithHexString("#C5C7C9").CGColor
//initalize cell items
eventTitleLabel.text = names[indexPath.row]
eventTitleLabel.frame = CGRectMake(0, 0, cell.frame.width * 0.5, cell.frame.height * 0.3)
eventTitleLabel.tag = indexPath.row
eventTitleLabel.textAlignment = .Left
eventTitleLabel.font = UIFont(name: "Montserrat-Bold", size: screenSize.height * (24/568))
eventTitleLabel.textColor = UIColor.blackColor()
eventTitleLabel.center = CGPointMake(cell.contentView.frame.width * 0.35, cell.contentView.frame.height * 0.35)
dateLabel.textColor = colorWithHexString("#C5C7C9")
let dateString = "\(dates[indexPath.row]) \(times[indexPath.row])"
dateLabel.text = dateString
dateLabel.frame = CGRectMake(0, 0, cell.frame.width * 0.5, cell.frame.height * 0.3)
dateLabel.tag = indexPath.row
dateLabel.textAlignment = .Left
dateLabel.font = UIFont(name: "Montserrat-Regular", size: screenSize.height * (10/568))
dateLabel.center = CGPointMake(cell.contentView.frame.width * 0.35, cell.contentView.frame.height * 0.6)
//for setting bottom label
//Code sets label (yourLabel)'s text to "Tap and hold(BOLD) button to start recording."
let boldAttribute = [
//You can add as many attributes as you want here.
NSFontAttributeName: UIFont(name: "Montserrat-Bold", size: 11.0)!]
let regularAttribute = [
NSFontAttributeName: UIFont(name: "Montserrat-Regular", size: 11.0)!]
let beginningAttributedString = NSAttributedString(string: authors[indexPath.row], attributes: boldAttribute )
//let boldAttributedString = NSAttributedString(string: locationNames[indexPath.row], attributes: boldAttribute)
let boldAttributedString = NSAttributedString(string: "Monterey, CA USA", attributes: regularAttribute)
let fullString = NSMutableAttributedString()
fullString.appendAttributedString(beginningAttributedString)
fullString.appendAttributedString(NSAttributedString(string: " ", attributes: regularAttribute)) //space
fullString.appendAttributedString(boldAttributedString)
//------
authorLabel.attributedText = fullString
authorLabel.textColor = colorWithHexString("#C5C7C9")
authorLabel.frame = CGRectMake(0, 0, cell.frame.width, cell.frame.height * 0.3)
authorLabel.tag = indexPath.row
authorLabel.textAlignment = .Left
authorLabel.center = CGPointMake(cell.contentView.frame.width * 0.5, cell.contentView.frame.height * 0.8)
categoryView.frame = CGRectMake(0, 0, screenSize.width * (50/screenSize.width), screenSize.width * (50/screenSize.width))
categoryView.layer.cornerRadius = categoryView.frame.width * 0.5
categoryView.center = CGPointMake(cell.contentView.frame.width * 0.7, cell.contentView.frame.height * 0.35)
categoryView.backgroundColor = colorWithHexString("#3dccff")
cell.contentView.addSubview(categoryView)
cell.contentView.addSubview(eventTitleLabel)
cell.contentView.addSubview(dateLabel)
cell.contentView.addSubview(locationLabel)
cell.contentView.addSubview(authorLabel)
cell.contentView.layer.addSublayer(botBorder)
print("called cell")
return cell
}
And this works the first time. However I learned from the print to console that this is called every time you scroll, and also after I add new items that take up new cells in my tableview. When that happens I get this overlapping -
How do I fix this? I looked also at TableViewCell is piled up and appear again and again and tried putting cell.contentView.removeFromSuperView() at the beginning of this function so it would clear out the old content but that resulted in absolutely nothing showing up.
What is the right way to add content programmatically?
The tableview cells are recycled, therefore each time a cell is presented its going to have whatever you put in it last, you will need to appropriately handle a cell that comes back filled with the labels you have put in. Probably should have some kind of init method of the cell that is called only once per new cell, and is ignored when the cell is recycled, then just edit the labels and what ever else as normal. This kind of functionality should be built into the cells custom class itself instead of inside the cellForRowAtIndexPath

Shadows in tableview keep stacking (Swift)

I currently have a list of 70 questions in a tableview. Once a question is solved, the table is updated and shows a checkmark (all works OK).
The issue I am having is with the shadow that I add to each of my cells in the tableview. For some reason they keep stacking when scrolling up and down. Also when putting the device in landscape, the new CGRect is drawn, but the old one is still there (also overlap).
The code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:QuestionCell = tableView.dequeueReusableCellWithIdentifier("cell") as! QuestionCell
let bgColorView = UIView()
bgColorView.frame = CGRectMake(5, 5, (tableView.frame.width)-10, (cell.frame.height)-10)
bgColorView.backgroundColor = UIColor.clearColor();
cell.selectedBackgroundView = bgColorView
let myBackView=UIView(frame:cell.frame)
myBackView.frame = CGRectMake(5, 5, (tableView.frame.width)-10, (cell.frame.height)-10)
myBackView.backgroundColor = UIColor.whiteColor();
myBackView.layer.masksToBounds = false
myBackView.clipsToBounds = false
myBackView.layer.cornerRadius = 3
myBackView.layer.shadowOffset = CGSizeMake(-1, 1)
myBackView.layer.shadowRadius = 2
myBackView.layer.shadowOpacity = 0.4;
let test:CGRect = myBackView.layer.bounds
myBackView.layer.shadowPath = UIBezierPath(rect: test).CGPath
cell.addSubview(myBackView)
cell.sendSubviewToBack(myBackView)
if (question.showAfter == "true") {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
//give header of correct question blue color
cell.backgroundColor = UIColor.lightTextColor()
cell.headerQuestion.textColor = UIColor(red:0.01, green:0.53, blue:0.82, alpha:1.0)
}
else
{
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
cell.backgroundColor = UIColor.whiteColor()
cell.headerQuestion.textColor = UIColor(red:0.38, green:0.49, blue:0.55, alpha:1.0)
}
return cell
}
I have already tried the following (it was a solution from someone on an objective-C topic):
let tag = 120
if (cell.tag != 120) {
//the above code here
cell.tag = tag
}
This solves the shadow issue, BUT when I turn the device to landscape mode it does not redraw.
Any ideas? All are welcome
Your adding a new shadow every time a cell is reused. You should only add the shadow once at creation time.
Make a subclass of UITableViewCell and do your common customizations (background color, shadow) there. Then register your class with the tableview. Only do stuff that's different per cell in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
Try this :
if(cell.viewWithTag(120)==nil)
{
let myBackView=UIView(frame:cell.frame)
myBackView.frame = CGRectMake(5, 5, (tableView.frame.width)-10, (cell.frame.height)-10)
myBackView.backgroundColor = UIColor.whiteColor();
myBackView.layer.masksToBounds = false
myBackView.clipsToBounds = false
myBackView.layer.cornerRadius = 3
myBackView.layer.shadowOffset = CGSizeMake(-1, 1)
myBackView.layer.shadowRadius = 2
myBackView.layer.shadowOpacity = 0.3;
let test:CGRect = myBackView.layer.bounds
myBackView.layer.shadowPath = UIBezierPath(rect: test).CGPath
myBackView.tag = 120;
cell.addSubview(myBackView)
}
cell.sendSubviewToBack(myBackView)

UITableViewCell height incorrect, sizeToFit sizes incorrectly

I am attempting to create a custom UITableViewCell, and having issues with the cell frame having the proper height. This is troubling because the cell sizes correctly for iPhones 4s/5s running iOS 8.4, but not for iPhones 6/6+ running the same OS.
Chaos ensues around calling sizeToFit on messageLabel. Some of the labels almost appear to have extra, blank lines below, but clearly are not as tall as the cell makes them out to be.
Below is the custom cell. The label that appears to cause the trouble is the messageLabel. To view the frames of the labels, let borders = true
//
// NotesTableViewCell.swift
// urchin
//
// Created by Ethan Look on 6/17/15.
// Copyright (c) 2015 Tidepool. All rights reserved.
//
import Foundation
import UIKit
let noteCellHeight: CGFloat = 128
let noteCellInset: CGFloat = 16
let labelSpacing: CGFloat = 6
class NoteCell: UITableViewCell {
let borders = false
var cellHeight: CGFloat = CGFloat()
let usernameLabel: UILabel = UILabel()
let timedateLabel: UILabel = UILabel()
var messageLabel: UILabel = UILabel()
func configureWithNote(note: Note) {
usernameLabel.text = note.user!.fullName
usernameLabel.font = UIFont(name: "OpenSans-Bold", size: 17.5)!
usernameLabel.textColor = UIColor.blackColor()
usernameLabel.sizeToFit()
let usernameX = noteCellInset
let usernameY = noteCellInset
usernameLabel.frame.origin = CGPoint(x: usernameX, y: usernameY)
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEEE M.d.yy h:mm a"
var dateString = dateFormatter.stringFromDate(note.timestamp)
dateString = dateString.stringByReplacingOccurrencesOfString("PM", withString: "pm", options: NSStringCompareOptions.LiteralSearch, range: nil)
dateString = dateString.stringByReplacingOccurrencesOfString("AM", withString: "am", options: NSStringCompareOptions.LiteralSearch, range: nil)
timedateLabel.text = dateString
timedateLabel.font = UIFont(name: "OpenSans", size: 12.5)!
timedateLabel.textColor = UIColor.blackColor()
timedateLabel.sizeToFit()
let timedateX = contentView.frame.width - (noteCellInset + timedateLabel.frame.width)
let timedateY = usernameLabel.frame.midY - timedateLabel.frame.height / 2
timedateLabel.frame.origin = CGPoint(x: timedateX, y: timedateY)
messageLabel.frame.size = CGSize(width: contentView.frame.width - 2 * noteCellInset, height: CGFloat.max)
let hashtagBolder = HashtagBolder()
let attributedText = hashtagBolder.boldHashtags(note.messagetext)
messageLabel.attributedText = attributedText
messageLabel.adjustsFontSizeToFitWidth = false
messageLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
messageLabel.numberOfLines = 0
messageLabel.sizeToFit()
let messageX = noteCellInset
let messageY = usernameLabel.frame.maxY + 2 * labelSpacing
messageLabel.frame.origin = CGPoint(x: messageX, y: messageY)
contentView.addSubview(usernameLabel)
contentView.addSubview(timedateLabel)
contentView.addSubview(messageLabel)
cellHeight = noteCellInset + usernameLabel.frame.height + 2 * labelSpacing + messageLabel.frame.height + noteCellInset
if (borders) {
usernameLabel.layer.borderWidth = 1
usernameLabel.layer.borderColor = UIColor.redColor().CGColor
timedateLabel.layer.borderWidth = 1
timedateLabel.layer.borderColor = UIColor.redColor().CGColor
messageLabel.layer.borderWidth = 1
messageLabel.layer.borderColor = UIColor.redColor().CGColor
self.contentView.layer.borderWidth = 1
self.contentView.layer.borderColor = UIColor.blueColor().CGColor
}
self.contentView.frame.size = CGSize(width: self.contentView.frame.width, height: cellHeight)
}
}
And heightForRowAtIndexPath:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = NoteCell(style: .Default, reuseIdentifier: nil)
cell.configureWithNote(notes[indexPath.row])
return cell.cellHeight
}
The project is open source and on Github, so feel free to clone the repository and check out all of the code yourself.
Thank you!
Unfortunately, you can't do it that way because tableView(_:heightForRowAtIndexPath) is called first and the value you return is used to create the cell that you will dequeue in tableView(_:cellForRowAtIndexPath). The cell can't set its own size because by the time it could do so (e.g. awakeFromNib or prepareForResuse), the table view will already have a height value for it. There are some whacky workarounds for this that I've used, but it's easier to just use self-sizing table view cells.
Check it:
http://www.appcoda.com/self-sizing-cells/
Instead of creating an entirely new cell in heightForRowAtIndexPath:, I simply create the UI elements that determine the cell height (usernameLabel and messageLabel), size them appropriately with sizeToFit, then do a simple calculation to determine the cell height.
By doing this, I never create a new cell which is later dequeued.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let usernameLabel = UILabel(frame: CGRectZero)
usernameLabel.font = UIFont(name: "OpenSans-Bold", size: 17.5)!
usernameLabel.text = notes[indexPath.row].user!.fullName
usernameLabel.sizeToFit()
let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.view.frame.width - 2*noteCellInset, height: CGFloat.max))
let hashtagBolder = HashtagBolder()
let attributedText = hashtagBolder.boldHashtags(notes[indexPath.row].messagetext)
messageLabel.attributedText = attributedText
messageLabel.adjustsFontSizeToFitWidth = false
messageLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
messageLabel.numberOfLines = 0
messageLabel.sizeToFit()
let cellHeight = noteCellInset + usernameLabel.frame.height + 2 * labelSpacing + messageLabel.frame.height + noteCellInset
return cellHeight
}

Spacing between uiimage and label inside uicollectionviewcell. Dynamic height of cell

I'm trying to get my label and image to dynamically change the height of a uicollectionviewcell. How can I estimate the correct height of what the cell should be from each image obtained? It is expecting a width and height, but I don't want to distort the image.
I'm following this cocoapod: https://github.com/ecerney/CollectionViewWaterfallLayout
Or would there be a better approach to setting the label/text?
Currently the spacing between the label and image is not set...
The cell inside the storyboard looks like the following with UIImageView set to Aspect Fill:
Code to obtain an image set
lazy var imageSet: [UIImage] = {
var _imageSet = [UIImage]()
for x in 0...10{
let array = [600,800,900]
let array2 = [1000,1200,1400]
let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
let randomIndex2 = Int(arc4random_uniform(UInt32(array2.count)))
let urlString:String = String(format: "http://lorempixel.com/%#/%#/", String(array[randomIndex]),String(array2[randomIndex2]))
let image = UIImage(data: NSData(contentsOfURL: NSURL(string: urlString)!)!)
print(urlString)
print("\(x)\n")
_imageSet.append(image!)
}
return _imageSet
}()
Code for setting the size of each cell...
func collectionView(collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let thisLayout = layout as! CollectionViewWaterfallLayout
var globalImage = imageSet[indexPath.row]
var finalWith = globalImage.size.width
var finalHeight = globalImage.size.height
var newSize = CGSize(width: finalWith, height: finalHeight )
return newSize
}
My repo https://github.com/rlam3/waterfallCocaopod

Resources