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

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

Related

How to align UILabels with SF Symbols and custom images in UITableViewCell's imageView?

What I want to achive is something like list view in Files App. Document will display a little thumbnail if has one. Otherwise a SF Symbol with textStyle .callout will show up. All the rows’ labels should be left aligned. But currently the thumbnails are much bigger than the SF Symbols so that they push the labels away.
I emphasize textStyle because my app supports dynamic type, which means the imageView's frame calculation should base on SF Symbol.
I try to override layoutSubviews. But can't figure out how to do the calculation.
Files App
My App
For maximum control of your cell appearance, you should create a custom cell. But, if you want use a standard Subtitle cell, you can resize your images when setting them.
There are some excellent image scaling functions in this Stack Overflow answer: how to resize a bitmap on iOS
Add the extensions from that answer to your project. Specifically, for your needs, we'll use scaledAspectFit().
So, assuming you have a Subtitle cell prototype, with identifier of "cell", your cellForRowAt will look something like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// however you have your data stored
let title = "IMG_0001.JPG"
let subtitle = "Today at 10:15 AM, 2.5 MB"
let imgName = "IMG_0001"
cell.textLabel?.text = title
cell.detailTextLabel?.text = subtitle
if let img = UIImage(named: imgName) {
// default cell image size is 56x56 (points)
// so we'll proportionally scale the image,
// using screen scale to get the best resolution
let widthHeight = UIScreen.main.scale * 56
let scaledImg = img.scaledAspectFit(to: CGSize(width: widthHeight, height: widthHeight))
cell.imageView?.image = scaledImg
}
return cell
}
Edit
Takes only a little research to implement the use fo SF Symbols...
Add this func to your table view controller - it will return a UIImage of an SF Symbol at specified point size, centered in specified size:
private func drawSystemImage(_ sysName: String, at pointSize: CGFloat, centeredIn size: CGSize) -> UIImage? {
let cfg = UIImage.SymbolConfiguration(pointSize: pointSize)
guard let img = UIImage(systemName: sysName, withConfiguration: cfg) else { return nil }
let x = (size.width - img.size.width) * 0.5
let y = (size.height - img.size.height) * 0.5
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
img.draw(in: CGRect(origin: CGPoint(x: x, y: y), size: img.size))
}
}
Then change your cellForRowAt func as needed:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// however you have your data stored
let title = "IMG_0001.JPG"
let subtitle = "Today at 10:15 AM, 2.5 MB"
let imgName = "IMG_0001"
cell.textLabel?.text = title
cell.detailTextLabel?.text = subtitle
// defalt image view size
let wh = UIScreen.main.scale * 56
let targetSize = CGSize(width: wh, height: wh)
// for this example, if it's the first row, generate an image from SF Symbol
if indexPath.row == 0 {
if let img = drawSystemImage("doc.text", at: UIScreen.main.scale * 24, centeredIn: targetSize) {
cell.imageView?.image = img
}
} else {
if let img = UIImage(named: imgName) {
// default cell image size is 56x56 (points)
// so we'll proportionally scale the image,
// using screen scale to get the best resolution
let scaledImg = img.scaledAspectFit(to: targetSize)
cell.imageView?.image = scaledImg
}
}
return cell
}
I'll leave it up to you to tweak the point size as desired (and configure any other properties of your SF Symbol image such as weight, scale, color, etc).

UITableViewCell Height Issue

I'm actually trying to make the image view height dynamic
I have tried UITableViewAutomaticDimension
in the cell class I have set the image's dynamic height based on the aspect constraint
Well, you can't achieve the dynamic height of cell with UITableViewAutomaticDimension on the basis of image's constraints.
BUT image's height and width can help you in this. There is a plenty of help regarding this issue is available in following answer:
Dynamic UIImageView Size Within UITableView
make sure you have set top-bottom constraints in storyboard.
save the thumbnail image as data to local storage (I'm using core data)
using the thumb image, calculate the aspect of the image
customise the row height as you want in heightForRowAt method.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let entity = self.fetchedResultController.object(at: indexPath as IndexPath) as! Message
var newh:CGFloat = 100.00
if let fileThumbnails = entity.file_thumbnails as? NSArray{
if fileThumbnails.count != 0{
fileThumbnails.map { (thumbNail) in
newh = self.getImageHeight(image:UIImage(data: thumbNail as! Data)! , h: UIImage(data: thumbNail as! Data)!.size.height , w: UIImage(data: thumbNail as! Data)!.size.width)
}
}
}
if entity.fileStatus == "DeletedMedia" {
newh = 100
}
if entity.fileStatus == nil{
newh = 0.0
}
print ("newH " , newh)
return newh
}
func getImageHeight(image : UIImage, h : CGFloat, w : CGFloat) -> CGFloat {
let aspect = image.size.width / image.size.height
var newH = (messagesTV.frame.size.width * 0.6) / aspect
// customise as you want in newH
if(newH > 500){
newH = 500
//maximum height 500
}
if(newH < 100){
newH = 100
//minimum height = 100
}
return newH
}
if the thumb image is deleted in local storage then a placeholder image will be shown.
you can customise the newHvariable to get the desired output

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)
}

Dynamically changing CollectionView Cell Size Based on Downloaded Images Using Swift

I'm trying to build a collection view layout like Pinterest uses. Most of what is out there is in Objective C, so I've used this RW tutorial: http://www.raywenderlich.com/107439/uicollectionview-custom-layout-tutorial-pinterest
The problem is that the app in the RW tutorial uses local images, whereas I'm trying to base the cell size on images that are downloaded via PinRemoteImage but I cannot get the collectionView to properly lay itself out again once the images are downloaded.
Below is my attempt to modify the extension:
extension PinCollectionViewController : PinterestLayoutDelegate {
// 1
func collectionView(collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath: NSIndexPath,
withWidth width: CGFloat) -> CGFloat {
var pinterestLargestImage = UIImage()
if imageDownloads == 0 {
pinterestLargestImage = imageArray[indexPath.row]
} else {
pinterestLargestImage = UIImage(named: "testPic")!
}
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRectWithAspectRatioInsideRect(pinterestLargestImage.size, boundingRect)
return rect.size.height
}
// 2
func collectionView(collectionView: UICollectionView,
heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat {
var pinterestLargestImage = UIImage()
if pins.count == imageArray.count {
pinterestLargestImage = imageArray[indexPath.row]
} else { pinterestLargestImage = UIImage(named: "testPic")!}
let annotationPadding = CGFloat(4)
let annotationHeaderHeight = CGFloat(17)
let font = UIFont(name: "AvenirNext-Regular", size: 10)!
let commentHeight = CGFloat(10.0)
let height = annotationPadding + annotationHeaderHeight + commentHeight + annotationPadding
return height
}
}
Then I've tried to call self.collectionView(self.collectionView!, layout: (self.collectionView?.collectionViewLayout)!, sizeForItemAtIndexPath: indexPath) and self.collectionView!.reloadItemsAtIndexPaths([indexPath]) inside cellForRowAtIndexPath once the cell's image is downloaded, but neither properly call these methods to adjust the layout. Can anyone point me in the right direction here?

Automatically Resize UILabel

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

Resources