AutoLayout won't content fit UITableView cells - ios

I attempted to follow what I've read here on how to content-fit UITableView cells. However, the cells are still clipping content. See below:
The cell: (Connected to my custom cell class)
The constraints: (All constraints set to the ContentView)
The custom cell class:
import UIKit
class S360SSessionTableCell: UITableViewCell {
#IBOutlet var iconImg:UIImageView!
#IBOutlet var locationLbl:UILabel!
#IBOutlet var dateLbl:UILabel!
#IBOutlet var startTimeLbl:UILabel!
#IBOutlet var endTimeLbl:UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
The implementation:
These are the only table related methods. In addition, I have this in viewDidLoad: myTableView.estimatedRowHeight = 100.0
//Table Datasource
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:S360SSessionTableCell? = tableView.dequeueReusableCellWithIdentifier("S360SSessionTableCell") as? S360SSessionTableCell
if ((cell == nil)){
tableView.registerNib(UINib(nibName: XIBFiles.SESSIONTABLECELL, bundle: nil), forCellReuseIdentifier: "S360SSessionTableCell")
cell = tableView.dequeueReusableCellWithIdentifier("S360SSessionTableCell") as? S360SSessionTableCell
}
var session = sessions[indexPath.row]
cell!.locationLbl.text = (session["location"] as! String) + " - " + "Court " + (String(session["subname"]))
dateFormatter.dateFormat = "MMM. dd, yyyy"
cell!.dateLbl.text = dateFormatter.stringFromDate(session["startDate"] as! NSDate)
dateFormatter.dateFormat = "hh:mm a"
cell!.startTimeLbl.text = dateFormatter.stringFromDate(session["startDate"] as! NSDate)
cell!.endTimeLbl.text = dateFormatter.stringFromDate(session["endDate"] as! NSDate)
return cell!
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sessions.count
}
However, the cells still appear clipped, like so:
I am targeting iOS9.3, and it was my understanding that using AutoLayout, the default for cell height was an automatic content fit, one of the perks of the iOS8.0 changes made.
Where am I going wrong with this? I want the cells to fit the content they have and not clip it.

There should be a direct line of constraints from the top of the cell to the bottom. In other words, This image from Ray Wenderlich illustrates that very well
From Ray Wenderlich
Furthermore, you need a clear line of constraints going from the top to the bottom of the contentView. This ensures that auto layout correctly determines the height of the contentView based on its subviews.

Apart from given answer you could try other way as below,
Apply the constraints accordingly to your subviews and labels and in viewdidappear do the following,
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
Please refer the following link for more details,
Working with Self-Sizing Table View Cells.
You can also check the following for a sample
Table View Cells with Varying Row Heights

Related

why my autosizing table view cell doesn't work and it depends on the text size of my label in my storyboard?

I know that there is a thread dedicated for asking autosizing table view cell in here: why UITableViewAutomaticDimension not working?
but maybe may case little bit different.
so I want to make autosizing of my table view cell based on the text assigned of my label. I think I have tried to set UITableViewAutomaticDimension and also have tried to set the all constraints (top, bottom, right left) with respect to its superview/ cell container. and I have also set the number of line to be zero like this
here is the code of my VC:
class ChatLogVC: UIViewController {
#IBOutlet weak var chatLogTableView: UITableView!
var messageList = [ChatMessage]()
override func viewDidLoad() {
super.viewDidLoad()
listenForMessages()
chatLogTableView.estimatedRowHeight = UITableView.automaticDimension
chatLogTableView.rowHeight = UITableView.automaticDimension
}
private func listenForMessages() {
// Get data from firebase
let fromId = currentUser.uid
let toId = otherUser.uid
let ref = Database.database().reference(withPath: "/user-messages/\(fromId)/\(toId)")
ref.observe(DataEventType.childAdded, with: { (snapshot) in
let messageDictionary = snapshot.value as? [String : Any] ?? [:]
let message = ChatMessage(dictionary: messageDictionary)
self.messageList.append(message)
self.chatLogTableView.reloadData()
})
}
}
//MARK: - UI Table View Data Source & Delegate
extension ChatLogVC : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messageList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellFrom = tableView.dequeueReusableCell(withIdentifier: "fromChatViewCell", for: indexPath) as! fromChatViewCell
let chatMessage = messageList[indexPath.row]
cellFrom.chatMessageData = chatMessage
cellFrom.otherUserData = otherUser
return cellFrom
}
and here is the table view cell:
class fromChatViewCell: UITableViewCell {
#IBOutlet weak var fromChatLabel: UILabel!
#IBOutlet weak var fromProfilePictureImageView: UIImageView!
var chatMessageData : ChatMessage?
var otherUserData : User?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
updateUI()
}
func updateUI() {
guard let message = chatMessageData, let otherUser = otherUserData else {return}
guard let url = URL(string: otherUser.profileImageURL) else {return}
fromChatLabel.text = message.text
fromProfilePictureImageView.kf.setImage(with: url, options: [.transition(.fade(0.2))])
}
}
}
I have also tried to connect the datasource and delegate to the VC:
and here is my label constraint:
and I have also set the number of line to be zero:
and here is my problem:
if in my storyboard I set the text of my label using just few word, then the autosizing doesn't work, it seems that it will only show one line of text even though actually the text from firebase is more than one line
result (app running):
but if I set the text on my label using a lot of words, then the autosizing table view cell will work and will show all the string from the firebase database:
it will expand, but the autosizing still doens't work since the label still clipping
what went wrong in here? I am really confused
I have tried to change bottom label constraint priority to 200, sometimes the autosizing worked but the other time it will not. maybe my problem related to asynchrounous problem, I have tried to reload the table view rght after get the massge from firebase. but I don't know the root cause of my problem

Dynamically resizing an IOS tableview cells which have a textview embedded in swift

I have tried the following code which gives the correct textview frame height
func textViewDidChange(_ textView: UITextView) {
myToDoList[keyArray[sect]]![row] = textView.text
var frame = textView.frame
frame.size.height = textView.contentSize.height
textView.frame = frame
print(frame)
let indexPath = self.tableView.indexPathForView(textView)
print(inputActive,indexPath)
self.tableView.indexPathForView(inputActive)
self.tableView.rowHeight = frame.size.height
}
The answer from "Krunal" is missing a piece or two...
Start with the cell layout / constraints:
And use this code:
import UIKit
class WithTextViewCell: UITableViewCell, UITextViewDelegate {
#IBOutlet var theTextView: UITextView!
var callBack: ((UITextView) -> ())?
override func awakeFromNib() {
super.awakeFromNib()
// in case these were not set in IB
theTextView.delegate = self
theTextView.isScrollEnabled = false
}
func textViewDidChange(_ textView: UITextView) {
// tell controller the text changed
callBack?(textView)
}
}
class TableWithTextViewTableViewController: UITableViewController {
var cellData = [
"UITableViewController implements the following behaviors:",
"If a nib file is specified via the init(nibName:bundle:) method (which is declared by the superclass UIViewController), UITableViewController loads the table view archived in the nib file. Otherwise, it creates an unconfigured UITableView object with the correct dimensions and autoresize mask. You can access this view through the tableView property.",
"If a nib file containing the table view is loaded, the data source and delegate become those objects defined in the nib file (if any). If no nib file is specified or if the nib file defines no data source or delegate, UITableViewController sets the data source and the delegate of the table view to self.",
"When the table view is about to appear the first time it’s loaded, the table-view controller reloads the table view’s data. It also clears its selection (with or without animation, depending on the request) every time the table view is displayed. The UITableViewController class implements this in the superclass method viewWillAppear(_:). You can disable this behavior by changing the value in the clearsSelectionOnViewWillAppear property.",
"When the table view has appeared, the controller flashes the table view’s scroll indicators. The UITableViewController class implements this in the superclass method viewDidAppear(_:).",
"It implements the superclass method setEditing(_:animated:) so that if a user taps an Edit|Done button in the navigation bar, the controller toggles the edit mode of the table.",
"You create a custom subclass of UITableViewController for each table view that you want to manage. When you initialize the controller in init(style:), you must specify the style of the table view (plain or grouped) that the controller is to manage. Because the initially created table view is without table dimensions (that is, number of sections and number of rows per section) or content, the table view’s data source and delegate—that is, the UITableViewController object itself—must provide the table dimensions, the cell content, and any desired configurations (as usual). You may override loadView() or any other superclass method, but if you do be sure to invoke the superclass implementation of the method, usually as the first method call.",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "WithTextViewCell", for: indexPath) as! WithTextViewCell
// Configure the cell...
cell.theTextView.text = cellData[indexPath.row]
cell.callBack = {
textView in
// update data source
self.cellData[indexPath.row] = textView.text
// tell table view we're starting layout updates
tableView.beginUpdates()
// get current content offset
var scOffset = tableView.contentOffset
// get current text view height
let tvHeight = textView.frame.size.height
// telll text view to size itself
textView.sizeToFit()
// get the difference between previous height and new height (if word-wrap or newline change)
let yDiff = textView.frame.size.height - tvHeight
// adjust content offset
scOffset.y += yDiff
// update table content offset so edit caret is not covered by keyboard
tableView.contentOffset = scOffset
// tell table view to apply layout updates
tableView.endUpdates()
}
return cell
}
}
The "key" parts:
Add a "call back" closure to your cell, so we can tell the controller when the text has changed.
When the call back occurs, have the table view controller: update the dataSource with the edited text; tell the text view to resize itself; and adjust the content offset to avoid having the caret (the text insertion point) disappear behind the keyboard.
Set UITextview height according to your content (text) size using sizeToFit and enable translatesAutoresizingMaskIntoConstraints in cellForRowAtIndexPath
Try this and see
class TextViewCell: UITableViewCell {
#IBOutlet weak var textview: UITextView!
func adjustTextViewHeight(textview : UITextView) {
textview.translatesAutoresizingMaskIntoConstraints = true
textview.sizeToFit()
textview.isScrollEnabled = false
}
}
class TableController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "textview") as! TextViewCell
cell.adjustTextViewHeight(textview: cell.textview)
return cell
}
}
Here is Storyboard Layout:
And here is result:
Its very easy to implement dynamic cell height when you design cell with interfaceBuilder directly as prototype cell or xib, where you just need to set top an bottom constraints properly and rest of the thing is done by tableViewAutoDimension.
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableViewAutomaticDimension
}

Trying to create a custom UITableView and cell for for some reason table is showing bars

I'm trying to create a custom UITableView for some reason the table is showing the bars below. Does anyone know how to get rid of those bars?
Here's what my code for my TableViewController looks like as well.
//
// TableTableViewController.swift
// CarApp
//
// Created by Lillybridge, Kevin on 2/10/17.
// Copyright © 2017 RS Design Lab. All rights reserved.
//
import UIKit
struct cellData {
let cell : Int!
let text : String!
let image : UIImage!
}
class TableTableViewController: UITableViewController {
var arrayOfCellData = [cellData]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("TableViewCell", owner: self, options: nil)?.first as! TableViewCell
print("how many times is this being called")
return cell
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorStyle = .none //gets rid of separator lines
tableView.rowHeight = 75 //the height of the nib view
}
U need to setup the cell height. Otherwise better solution is drag and drop a UIView on the header of TableView thats it.
There should be no reason the image takes up the size that it does. Cell height will not help because it is taking up not one cell, but many. It seems like you are adding the image to the view itself, rather than the cell.
As a solution,
Make sure that an individual cell has the image added t it, rather than the view
Once that is done, set up constraints on the image so that is is bound to the dimensions of its parent cell.
Make sure you have set the cell height using tableview.rowHeight or even tableview.estimatedRowHeight.
I think I figured it out. Changing the "separator" in the inspector seemed to do the trick. I will try setting the height though as well since I want to add more cells. Thanks!

Xcode - Swift - UILabel in TableViewCell Expansion

I have a UILabel in a custom UITableViewCell called "ScheduleCell", like so:
Here is the code for ScheduleCell:
import UIKit
class ScheduleCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I want the tileLabel to expand based on its text and I want the ScheduleCell to expand with it. Here are the attributes of the titleLabel:
Here are the attributes of the cell prototype, which is linked to the ScheduleCell class:
Here is some relevant code for the ViewController:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
And
func tableView(tableView: UITableView!,cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ScheduleCell
cell.backgroundColor = UIColor(rgb: 0xF6FBFE)
cell.titleLabel.text = (Globals.scheduleArr[indexPath.row][3] as! String)
cell.titleLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
return cell
}
All of the titles from "Globals.scheduleArr" fit on one line except one, which is "Jane Richards Grey Reads "To Kill a Mockingbird"". Here is what the ViewController looks like in the iOS Simulator:
As you can see, the label does not expand as necessary. Strangely enough, when I add a "\n" to the end of each of the titles, the label does expand. However, the cell doesn't seem to expand with the label:
Any help in resolving this issue would be greatly appreciated!
If you want to use UITableViewAutomaticDimension, you need to add Label-ContentView bottom constraint.
Add new bottom constraint and try again!

How do I create a parallax effect in UITableView with UIImageView in their prototype cells

I'm building an app in iOS 8.4 with Swift.
I have a UITableView with a custom UITableViewCell that includes a UILabel and UIImageView. This is all fairly straight forward and everything renders fine.
I'm trying to create a parallax effect similar to the one demonstrated in this demo.
I currently have this code in my tableView.cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = self.tableView.dequeueReusableCellWithIdentifier("myitem", forIndexPath: indexPath) as! MixTableViewCell
cell.img.backgroundColor = UIColor.blackColor()
cell.title.text = self.items[indexPath.row]["title"]
cell.img.image = UIImage(named: "Example.png")
// ideally it would be cool to have an extension allowing the following
// cell.img.addParallax(50) // or some other configurable offset
return cell
}
That block exists inside a class that looks like class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource { ... }
I am also aware that I can listen to scroll events in my class via func scrollViewDidScroll.
Other than that, help is appreciated!
I figured it out! The idea was to do this without implementing any extra libraries especially given the simplicity of the implementation.
First... in the custom table view Cell class, you have to create an wrapper view. You can select your UIImageView in the Prototype cell, then choose Editor > Embed in > View. Drag the two into your Cell as outlets, then set clipToBounds = true for the containing view. (also remember to set the constraints to the same as your image.
class MyCustomCell: UITableViewCell {
#IBOutlet weak var img: UIImageView!
#IBOutlet weak var imgWrapper: UIView!
override func awakeFromNib() {
self.imgWrapper.clipsToBounds = true
}
}
Then in your UITableViewController subclass (or delegate), implement the scrollViewDidScroll — from here you'll continually update the UIImageView's .frame property. See below:
override func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY = self.tableView.contentOffset.y
for cell in self.tableView.visibleCells as! [MyCustomCell] {
let x = cell.img.frame.origin.x
let w = cell.img.bounds.width
let h = cell.img.bounds.height
let y = ((offsetY - cell.frame.origin.y) / h) * 25
cell.img.frame = CGRectMake(x, y, w, h)
}
}
See this in action.
I wasn't too happy with #ded's solution requiring a wrapper view, so I came up with another one that uses autolayout and is simple enough.
In the storyboard, you just have to add your imageView and set 4 constraints on the ImageView:
Leading to ContentView (ie Superview) = 0
Trailing to ContentView (ie Superview) = 0
Top Space to ContentView (ie Superview) = 0
ImageView Height (set to 200 here but this is recalculated based on the cell height anyway)
The last two constraints (top and height) need referencing outlets to your custom UITableViewCell (in the above pic, double click on the constraint in the rightmost column, and then Show the connection inspector - the icon is an arrow in a circle)
Your UITableViewCell should look something like this:
class ParallaxTableViewCell: UITableViewCell {
#IBOutlet weak var parallaxImageView: UIImageView!
// MARK: ParallaxCell
#IBOutlet weak var parallaxHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var parallaxTopConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
clipsToBounds = true
parallaxImageView.contentMode = .ScaleAspectFill
parallaxImageView.clipsToBounds = false
}
}
So basically, we tell the image to take as much space as possible, but we clip it to the cell frame.
Now your TableViewController should look like this:
class ParallaxTableViewController: UITableViewController {
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return cellHeight
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! ParallaxTableViewCell
cell.parallaxImageView.image = … // Set your image
cell.parallaxHeightConstraint.constant = parallaxImageHeight
cell.parallaxTopConstraint.constant = parallaxOffsetFor(tableView.contentOffset.y, cell: cell)
return cell
}
// Change the ratio or enter a fixed value, whatever you need
var cellHeight: CGFloat {
return tableView.frame.width * 9 / 16
}
// Just an alias to make the code easier to read
var imageVisibleHeight: CGFloat {
return cellHeight
}
// Change this value to whatever you like (it sets how "fast" the image moves when you scroll)
let parallaxOffsetSpeed: CGFloat = 25
// This just makes sure that whatever the design is, there's enough image to be displayed, I let it up to you to figure out the details, but it's not a magic formula don't worry :)
var parallaxImageHeight: CGFloat {
let maxOffset = (sqrt(pow(cellHeight, 2) + 4 * parallaxOffsetSpeed * tableView.frame.height) - cellHeight) / 2
return imageVisibleHeight + maxOffset
}
// Used when the table dequeues a cell, or when it scrolls
func parallaxOffsetFor(newOffsetY: CGFloat, cell: UITableViewCell) -> CGFloat {
return ((newOffsetY - cell.frame.origin.y) / parallaxImageHeight) * parallaxOffsetSpeed
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY = tableView.contentOffset.y
for cell in tableView.visibleCells as! [MyCustomTableViewCell] {
cell.parallaxTopConstraint.constant = parallaxOffsetFor(offsetY, cell: cell)
}
}
}
Notes:
it is important to use tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) and not tableView.dequeueReusableCellWithIdentifier("CellIdentifier"), otherwise the image won't be offset until you start scrolling
So there you have it, parallax UITableViewCells that should work with any layout, and can also be adapted to CollectionViews.
This method works with table view and collection view.
first of all create the cell for the tableview and put the image view in it.
set the image height slightly more than the cell height. if cell height = 160 let the image height be 200 (to make the parallax effect and you can change it accordingly)
put this two variable in your viewController or any class where your tableView delegate is extended
let imageHeight:CGFloat = 150.0
let OffsetSpeed: CGFloat = 25.0
add the following code in the same class
func scrollViewDidScroll(scrollView: UIScrollView) {
// print("inside scroll")
if let visibleCells = seriesTabelView.visibleCells as? [SeriesTableViewCell] {
for parallaxCell in visibleCells {
var yOffset = ((seriesTabelView.contentOffset.y - parallaxCell.frame.origin.y) / imageHeight) * OffsetSpeedTwo
parallaxCell.offset(CGPointMake(0.0, yOffset))
}
}
}
where seriesTabelView is my UItableview
and now lets goto the cell of this tableView and add the following code
func offset(offset: CGPoint) {
posterImage.frame = CGRectOffset(self.posterImage.bounds, offset.x, offset.y)
}
were posterImage is my UIImageView
If you want to implement this to collectionView just change the tableView vairable to your collectionView variable
and thats it. i am not sure if this is the best way. but it works for me. hope it works for you too. and let me know if there is any problem
After combining answers from #ded and #Nycen I came to this solution, which uses embedded view, but changes layout constraint (only one of them):
In Interface Builder embed the image view into a UIView. For that view make [√] Clips to bounds checked in View > Drawing
Add the following constraints from the image to view: left and right, center Vertically, height
Adjust the height constraint so that the image is slightly higher than the view
For the Align Center Y constraint make an outlet into your UITableViewCell
Add this function into your view controller (which is either UITableViewController or UITableViewControllerDelegate)
private static let screenMid = UIScreen.main.bounds.height / 2
private func adjustParallax(for cell: MyTableCell) {
cell.imageCenterYConstraint.constant = -(cell.frame.origin.y - MyViewController.screenMid - self.tableView.contentOffset.y) / 10
}
Note: by editing the magic number 10 you can change how hard the effect will be applied, and by removing the - symbol from equation you can change the effect's direction
Call the function from when the cell is reused:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCellId", for: indexPath) as! MyTableCell
adjustParallax(for: cell)
return cell
}
And also when scroll happens:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
(self.tableView.visibleCells as! [MyTableCell]).forEach { cell in
adjustParallax(for: cell)
}
}

Resources