I have a UICollectionView with a header of type UICollectionReusableView.
In it, I have a label whose length varies by user input.
I'm looking for a way to have the header dynamically resize depending on the height of the label, as well as other subviews in the header.
This is my storyboard:
This the result when I run the app:
Here's an elegant, up to date solution.
As stated by others, first make sure that all you have constraints running from the very top of your header view to the top of the first subview, from the bottom of the first subview to the top of the second subview, etc, and from the bottom of the last subview to the bottom of your header view. Only then auto layout can know how to resize your view.
The following code snipped returns the calculated size of your header view.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
// Get the view for the first header
let indexPath = IndexPath(row: 0, section: section)
let headerView = self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: indexPath)
// Use this view to calculate the optimal size based on the collection view's width
return headerView.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height),
withHorizontalFittingPriority: .required, // Width is fixed
verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed
}
Edit
As #Caio noticed in the comments, this solution will cause a crash on iOS 10 and older.
In my project, I've "solved" this by wrapping the code above in if #available(iOS 11.0, *) { ... } and providing a fixed size in the else clause. That's not ideal, but acceptable in my case.
This drove me absolutely crazy for about half a day.
Here's what finally worked.
Make sure the labels in your header are set to be dynamically sizing, one line and wrapping
Embed your labels in a view. This will help with autosizing.
Make sure the constraints on your labels are finite. ex: A greater-than constraint from the bottom label to the reusable view will not work. See image above.
Add an outlet to your subclass for the view you embedded your labels in
class CustomHeader: UICollectionReusableView {
#IBOutlet weak var contentView: UIView!
}
Invalidate the initial layout
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
Lay out the header to get the right size
extension YourViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionElementKindSectionHeader).first as? CustomHeader {
// Layout to get the right dimensions
headerView.layoutIfNeeded()
// Automagically get the right height
let height = headerView.contentView.systemLayoutSizeFitting(UILayoutFittingExpandedSize).height
// return the correct size
return CGSize(width: collectionView.frame.width, height: height)
}
// You need this because this delegate method will run at least
// once before the header is available for sizing.
// Returning zero will stop the delegate from trying to get a supplementary view
return CGSize(width: 1, height: 1)
}
}
Swift 3
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize{
return CGSize(width: self.myCollectionView.bounds.width, height: self.mylabel.bounds.height)
}
https://developer.apple.com/reference/uikit/uicollectionviewdelegateflowlayout/1617702-collectionview
You could achieve it by implementing the following:
The ViewController:
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
// the text that you want to add it to the headerView's label:
fileprivate let myText = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "customHeader",
for: indexPath) as! CustomCollectionReusableView
headerView.lblTitle.text = myText
headerView.backgroundColor = UIColor.lightGray
return headerView
default:
fatalError("This should never happen!!")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
cell.backgroundColor = UIColor.brown
// ...
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
// ofSize should be the same size of the headerView's label size:
return CGSize(width: collectionView.frame.size.width, height: myText.heightWithConstrainedWidth(font: UIFont.systemFont(ofSize: 17)))
}
}
extension String {
func heightWithConstrainedWidth(font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: UIScreen.main.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox.height
}
}
The custom UICollectionReusableView:
class CustomCollectionReusableView: UICollectionReusableView {
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// setup "lblTitle":
lblTitle.numberOfLines = 0
lblTitle.lineBreakMode = .byWordWrapping
lblTitle.sizeToFit()
}
}
#Pim's answer worked for me but as #linus_hologram in the comment section mentioned this solution makes AutoLayout complain about unsatisfiable constraints. I found a simple workaround:
In collectionView(_:layout:referenceSizeForHeaderInSection:) instead of getting a reference to the view that your instance of UICollectionView wants to reuse using collectionView(_:viewForSupplementaryElementOfKind:at:) just create an instance of your header view class on the fly and add a width constraint so that AutoLayout will be able to calculate your header's width:
let headerView = YourCustomHeaderClass()
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.widthAnchor.constraint(equalToConstant: collectionView.frame.width).isActive = true
return headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
Related
I'm trying to have collection view with 2 column but dynamic height.
I have used Autolayout and given required constraints to the Cell
By this way I can calculate the dynamic height but its column grids fails.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MenuListCollectionViewCell
cell.frame.size.width = collectionView.frame.width/2
cell.menuList = self.menuList[indexPath.row]
let resizing = cell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.fittingSizeLevel)
return resizing
}
This is how I want it to look
Not sure what you mean by "its column grid fails".
Anyway, you need to write a custom collection view layout. The default one (UICollectionViewFlowLayout) allows you to change height of the cells by providing the sizeForItemAt, but that won't change the behavior of the layout that will always arrange cells in rows of the same height (the height of the highest cell).
If I understood correctly, you just want the same layout of this raywenderlich tutorial.
Basically:
Create a subclass of UICollectionViewLayout implementing it's methods:
collectionViewContentSize: return width and height of the collection view content
prepare: where you can calculate the sizes of cells and
collectionView content
layoutAttributesForElements: where you
return an array of UICollectionViewLayoutAttributes in the given
rect
layoutAttributesForItem: where you return the same kind
of attributes, this time specific for an item
assign an object of this class to the collection view layout property
you can use this
This code is somehow written that you can change section inset or minimumInteritemSpacing and this calculate and resize with this parameters
you can use this code or download project from Github
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let numberofItem: CGFloat = 2
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let collectionViewWidth = self.collectionView.bounds.width
let extraSpace = (numberofItem - 1) * flowLayout.minimumInteritemSpacing
let inset = flowLayout.sectionInset.right + flowLayout.sectionInset.left
let width = Int((collectionViewWidth - extraSpace - inset) / numberofItem)
return CGSize(width: width, height: width)
}
I was able to achieve your desired outcome by doing the following.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (self.view.frame.size.width/2 - 5), height: 100)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = customCollectionView.dequeueReusableCell(withReuseIdentifier: "test", for: indexPath)
cell.layer.backgroundColor = UIColor.red.cgColor
return cell
}
Make sure you have implemented all the correct delegates
UICollectionViewDelegate
UICollectionViewDatasource
UICollectionViewDelegateFlowLayout
where you see height: make it your own desired height as you have specified in your question.
KEY: Make sure in your storyboard you have set the estimate size of the collection view to NONE -> otherwise the code will not work as expected
I am trying to create a custom table cell that will have rounded corners and a shadow. The end goal being to create a "card" look and feel. My storyboard setup is below. I created a separate view onto the content view of the cell so that I could manipulate that view to have the effects that I want. The problem is that the effects only seem to be affecting the top portion of the view. The bottom corners and not rounded and you can also see that the border is surrounding the entire view. I have included the custom cell class file as well. Any ideas on how to get the rest of the view to have the rounded corners and border?
class EventTableCell: UITableViewCell {
#IBOutlet weak var collectionView: UIView!
#IBOutlet weak var address: UILabel!
#IBOutlet weak var statusImage: UIImageView!
#IBOutlet weak var customerName: UILabel!
#IBOutlet weak var customerTime: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
//collectionView.backgroundColor = UIColor.gray
collectionView.clipsToBounds = false
collectionView.layer.cornerRadius = 10
collectionView.layer.borderWidth = 1
collectionView.layer.borderColor = UIColor.gray.cgColor
collectionView.layer.shadowColor = UIColor.lightGray.cgColor
collectionView.layer.shadowOpacity = 1
collectionView.layer.shadowOffset = CGSize.zero
collectionView.layer.shadowRadius = 3
}
}
Screen Shot of UiTableview in Main Storyboard:
Screen Shot of output:
Use this method:
extension UIView {
func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float) {
layer.masksToBounds = false
layer.shadowOffset = offset
layer.shadowColor = color.cgColor
layer.shadowRadius = radius
layer.shadowOpacity = opacity
let backgroundCGColor = backgroundColor?.cgColor
backgroundColor = nil
layer.backgroundColor = backgroundCGColor
}
}
Call this method like this:
view.addShadow(offset: CGSize(width: 5, height: 5), color: .lightGray, radius: 5, opacity: 0.5)
You can call this method in the method 'awakeFromNib' of tableCell.
Note: Give proper height of table row also.
If your subviews of UICollectionView constraints are correct then set height automatic dimension in heightForRowAt and estimatedHeightForRowAt in UITableView delegate methods and reload the UICollectionView after start rendering.
Maintain some padding to all sides between cell contentView and containerView (*from your attached image) to visible shadow in all sides.
You can add following method where size will be yours preference and set "UICollectionViewDelegateFlowLayout" delegate to your class
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let cellSize = CGSize(width: 100, height: 100)
return cellSize
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
{
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
let sectionInset = UIEdgeInsetsMake(10, 10, 10, 10)
return sectionInset
}
By applying these method you can able to display cell with proper way , currently your size of cell is exceeded then default size so this is happening , by implementing size method will remove default height and replace with your requested cell size.
Hope this will solve your problem.
I am trying to develop an iOS application that has 03 UICollectionViews handled by the same ViewController. (The reason why I am opting for 03 UICollectionViews rather than one with sections and different prototype cells is because I may need to add additional content not relevant to collection views between the sections in the future)
________________
| _ _
| | | | |
| |_| |_| . . . (UICollectionView1)
|_______________
_______________
| _ _
| | | | |
| |_| |_| . . . (UICollectionView2)
|_______________
My problem is as follows:
No of cells in each of the CollectionViews is variable and if the number exceeds the width constraint it wraps down (so far so good). However, the height constraint of the UICollectionView causes a scroll bar to appear rather than simply laying out the cells if the number of cells causes to wrap beyond the height constraint
I have tried a couple of things to get this to work, most of which revolve around the advice given in the following questions
how to set dynamic height of a Collection View, the 'view' not the 'cells'?
How to adjust height of UICollectionView to be the height of the content size of the UICollectionView?
In the end I tried this
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.contentSize.height)
}
But then the content in the UICollectionViewCell stretched wierdly and still problem of scrolling exists.
I do not mind that the main view of the view controller (the one on which all other UICollectionViews are placed) becomes scrollable (actually that is part of the requirement), I just don't want the UICollectionViews to act like some HTML iframe and allow scrolling but just layout the cells in order for as much as constrained by width of the UICollectionView
In pseudo code, something like this
array = (cell1, cell2, cell3)
for i in array
if currentCollectionViewRow is filled
wrapToNextLine()
add cell{i} to view controller
Any help is appreciated. Even help that suggests better ways to achieve this functionality along with best practices rather than hacking code
EDIT
I carried out the instructions as #Saad Chaudhry mentioned but to no avail. My layout is as follows: CollectionView Layout
As you can see, the stack view encloses both collection views as suggested. Now the IDE gives the following complaints: Ambiguous Layout
I tried adding constraints intuitively, then tried the IDEs options to add constraints automatically to no avail. Most times, there is no data cells on the screen.
For more information, without stack view but just two collection views, I get the following simulation: Without Stack Views
And with stack views I get the simulation: With stack views and note that the second collection view is missing
If I then constraint the stack view at 0,0,0,0, this brings back the issues where each of the collection view heights are ambiguous. Providing heights causes scrolling "within that collection view" if the number of cells are large (from datasource).
I simply want all the black squares to be rendered first and then the yellow squares. The parent view may scroll and that's fine bu not the individual collection views
My code for the controller:
import UIKit
class DemoCollectionViewController: UICollectionViewController {
#IBOutlet weak var nonPriorityCollectionView: UICollectionView!
#IBOutlet weak var priorityCollectionView: UICollectionView!
private let reuseIdentifier = "priorityCell"
fileprivate let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0)
fileprivate var priorityItems = [PriorityItem]()
fileprivate let itemsPerRow: CGFloat = 3
private let nonPriorityReuseIdentifier = "nonPriorityCell"
fileprivate var nonPriorityItems = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: nonPriorityReuseIdentifier)
loadPriorityItems()
loadNonPriorityItems()
}
func loadPriorityItems(){
let item1 = PriorityItem(image: #imageLiteral(resourceName: "User"))
let item2 = PriorityItem(image: #imageLiteral(resourceName: "User"))
let item3 = PriorityItem(image: #imageLiteral(resourceName: "User"))
let item4 = PriorityItem(image: #imageLiteral(resourceName: "User"))
priorityItems = [item1, item2, item3, item4, item1, item2, item3, item4]
}
func loadNonPriorityItems(){
let item1 = "Item 1"
let item2 = "Item 2"
let item3 = "Item 3"
let item4 = "Item 4"
nonPriorityItems = [item1, item2, item3, item4]
}
}
// MARK: UICollectionViewDataSource
extension DemoCollectionViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.priorityCollectionView {
return priorityItems.count
}
else{
return nonPriorityItems.count
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.priorityCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = UIColor.black
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: nonPriorityReuseIdentifier, for: indexPath)
cell.backgroundColor = UIColor.yellow
return cell
}
}
}
extension DemoCollectionViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return sectionInsets
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return sectionInsets.left
}
}
Please help me in figuring out this issue. I now feel like collection view is not the ideal way to achieve this due to the complexity I'm facing...
I have a cell that contains a few stackviews, bottom stackView contains a textView and a custom separator.
I want to create an option, when user tap on cell, it shows whole text of tapped text view, so number of maximum lines in that cell is 0 and in other cells should be 3.
I used this tutorial http://www.roostersoftstudios.com/2011/04/14/iphone-uitableview-with-animated-expanding-cells/ and I modified it a little, my code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(iden_tableViewCell4) as! TableViewCell4
if let selectedCellIP = selectedIndexPath {
if indexPath == selectedCellIP {
cell.textTextVIew.textContainer.maximumNumberOfLines = 0
}
else {
cell.textTextVIew.textContainer.maximumNumberOfLines = textVIewMaxNumberOfLines
}
}
else {
cell.textTextVIew.textContainer.maximumNumberOfLines = textVIewMaxNumberOfLines
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
//The user is selecting the cell which is currently expanded
//we want to minimize it back
if selectedIndexPath == indexPath {
selectedIndexPath = nil
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
return
}
//First we check if a cell is already expanded.
//If it is we want to minimize make sure it is reloaded to minimize it back
if let selIndexPath = selectedIndexPath {
let previousPath = selIndexPath
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths([previousPath], withRowAnimation: .Fade)
}
//Finally set the selected index to the new selection and reload it to expand
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
in my viewDidLoad I set
tableView.estimatedRowHeight = 160
tableView.rowHeight = UITableViewAutomaticDimension
Expansion and contraction work well, but textView height of other cells has a strange behavior. When first cell is extended, an I scroll down, the last is extended too, and should not be.
Dynamically Resize UITableViewCell upon Selection
Playing with individual selection indexes is dangerous business. Not only are you likely to miss corner case conditions in didSelectRowAtIndexPath, it cannot possibly work for multiple selections.
You should split the cell expansion/compression notification into 2 distinct blocks (no need to keep track of selectedIndexPath, more robust code):
Expand
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView,
indexPath:indexPath)
}
Contract
override func tableView(_ tableView: UITableView,
didDeselectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView,
indexPath:indexPath)
}
Selection is destroyed by selectRowAtIndexPath
This prevents didDeselectRowAtIndexPath from ever being invoked.
A workaround is to cache the entire selection, not individual indexes, and to restore such selection after reload.
Complete code:
Notice when and how cacheSelectedRows is maintained. This uses a plain UITableViewCell. All it needs is a reuseIdentifier.
class TableViewController: UITableViewController {
var cacheSelectedRows:[IndexPath]? = nil
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 160
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
if let textLabel = cell.textLabel {
textLabel.backgroundColor = UIColor.clear
textLabel.numberOfLines = 1
if let cacheSelectedRows = cacheSelectedRows {
textLabel.numberOfLines = (cacheSelectedRows.contains(indexPath)) ? 0 : 1
}
textLabel.text = "\(1 + indexPath.row), Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView, indexPath:indexPath)
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView, indexPath:indexPath)
}
func reloadRowsAtIndexPaths(_ tableView: UITableView, indexPath: IndexPath) {
cacheSelectedRows = tableView.indexPathsForSelectedRows
tableView.reloadRows(at: [indexPath], with: .fade)
// Restore selection
if let cacheSelectedRows = cacheSelectedRows {
for path in cacheSelectedRows {
self.tableView.selectRow(at: path, animated: false, scrollPosition: .none)
}
}
}
}
Demo
► Find this solution on GitHub and additional details on Swift Recipes.
I didn't find answer, why the strange behavior of textView and cell height is happening, but I have solution for my problem.
So it looks like tableView.estimatedRowHeight = 160 and tableView.rowHeight = UITableViewAutomaticDimension don't respect textView's maximum number of lines for all cells. cellForRow works fine, it sets the limitation, but the rows height is sometimes expanded and sometimes contracted.
So I unwrapped textView and the bottom separator from StackView, replaced TextView with Label and set autolayaut constraints. Now it works fine, without strange behavior.
Is it possible to self-size a UITableViewCell in iOS 8 without creating a Custom UITableViewCell?
I thought that the standard UITableViewCell types (UITableViewCellStyleDefault, UITableViewCellStyleSubtitle, UITableViewCellStyleValue1, UITableViewCellStyleValue2) had built in auto layout constraints. This is confirmed by the fact that the constraints for non-custom cells cannot be changed in Storyboard.
But when I use a non-custom cell of type UITableViewCellStyleValue1, set it up in Storyboard, set numberOfRows for textLabel and detailTextLabel to 0, and set the viewDidLoad code as below, only the textLabel of the cell is accounted for in the autosizing of the height of the cell. If the detailTextLabel ends up displaying on more lines than the textLabel, the text for detailTextLabel spills out over the top and bottom edges of the cell. Again, the cell does resize properly for the textLabel, but seems to ignore the detailTextLabel in its resizing process.
The main thing I need to know is - do I need to create a custom cell even for the rows that can use a standard cell if I want to properly support Dynamic Text and Self-Sizing?
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView setEstimatedRowHeight:DEFAULT_ROW_HEIGHT];
[self.tableView setRowHeight:UITableViewAutomaticDimension];
}
I just tried this in iOS 10/XCode 8 (same results in iOS 9/XCode 7) with the different cell types and it looks like it's possible ONLY for the textLabel and not for the detailTextLabel.
(basically repeating the issue that the OP mentioned)
ViewController code that sets some text alternately on detailTextLabel and textLabel.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.row % 2 == 0 {
cell.textLabel?.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
cell.detailTextLabel?.text = "<- textLabel"
} else {
cell.textLabel?.text = "detailTextLabel ->"
cell.detailTextLabel?.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
Make sure you set the textLabel and textDetailLabel's line property to 0 and here are the results.
Basic Cell
Right Detail Cell
Left Detail Cell
Subtitle Cell
I'll report this as a bug.