I am trying to make an UIControl that is easier to make a grid than using the UICollectionView for certain small tasks. I am using UIStackView of UIStackViews. That is a horizontal container of vertical stacks(Columns). The problem is I need one column to be expansion column for filling extra space. Does UIStackView honor the setting of setContentHuggingPriority for itself ? , in this case one of my vertical columns.
I have included a playground to recreate this problem.
Link to visual showing the UILabel Grid where only one column should fill horizontally
Here is playground code:
import Foundation
import UIKit
import PlaygroundSupport
class ViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
let labMatrix = LabelMatrix( rows: 4, columns: 4)
self.view.addSubview( labMatrix)
labMatrix.labMatrix[0][0].text = "Test"
labMatrix.frame = CGRect(x: 0, y: 0, width: 360, height: 520)
}
}
class LabelMatrix : UIStackView {
let rowCount : Int
let colCount : Int
var labMatrix = [[UILabel]]()
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init ( rows: Int , columns : Int ) {
rowCount = rows
colCount = columns
super.init(frame:.zero)
create()
}
func create() {
var columns : [UIStackView] = [UIStackView]()
var rows : [UILabel] = [UILabel]()
for acol in 0..<colCount {
rows.removeAll()
for arow in 0..<rowCount {
let lab = UILabel()
// lab.translatesAutoresizingMaskIntoConstraints = false
rows.append( lab )
let str = "(\(arow),\(acol))"
lab.text = str
if !(arow%2 == 0) {
lab.backgroundColor = .lightGray
} else {
lab.backgroundColor = .yellow
}
if arow == rowCount-1 {
print("This should make last row stretch vertically to fill any extra vertical space")
// lab.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 20), for: .vertical)
lab.setContentHuggingPriority(UILayoutPriority(rawValue: 20), for:.vertical)
}
}
labMatrix.append( rows )
let curCol = UIStackView(arrangedSubviews: rows)
columns.append( curCol )
curCol.axis = .vertical
curCol.distribution = .fill
curCol.spacing = 2
curCol.alignment = .center
if acol == colCount-1 {
print("TODO Fix This. It should make only the last column(e.g. uistackview) strech to fill extra space")
curCol.setContentHuggingPriority(UILayoutPriority(rawValue: 20), for:.horizontal)
}
}
for col in columns {
self.addArrangedSubview( col )
}
self.axis = .horizontal
self.distribution = .fill
self.alignment = .top
self.spacing = 4
}
}
PlaygroundPage.current.liveView = ViewController()
Stack views behave a bit differently than other views in a number of ways - compression and hugging are two notable ones.
A stack view's primary function is to arrange its subviews. When one or more of its subviews has its own size / compression / hugging properties, the stack view's layout will be determined (in part) by its subviews.
To get your last column (and last row) to expand, the distribution needs to be fill and the views (labels) in that column / row need their hugging priorities modified.
You can get this playground result:
by modifying your code as below (comments should be clear):
import Foundation
import UIKit
import PlaygroundSupport
class ViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
let labMatrix = LabelMatrix( rows: 4, columns: 4)
self.view.addSubview( labMatrix)
labMatrix.labMatrix[0][0].text = "Test"
labMatrix.frame = CGRect(x: 0, y: 0, width: 360, height: 520)
}
}
class LabelMatrix : UIStackView {
let rowCount : Int
let colCount : Int
var labMatrix = [[UILabel]]()
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init ( rows: Int , columns : Int ) {
rowCount = rows
colCount = columns
super.init(frame:.zero)
create()
}
func create() {
var columns : [UIStackView] = [UIStackView]()
var rows : [UILabel] = [UILabel]()
for acol in 0..<colCount {
rows.removeAll()
for arow in 0..<rowCount {
let lab = UILabel()
rows.append( lab )
let str = "(\(arow),\(acol))"
lab.text = str
// set the label text alignment to center
lab.textAlignment = .center
if !(arow%2 == 0) {
lab.backgroundColor = .lightGray
} else {
lab.backgroundColor = .yellow
}
if acol == colCount-1 {
print("This will allow the last column to stretch horizontally")
lab.setContentHuggingPriority(UILayoutPriority(rawValue: 20), for:.horizontal)
}
if arow == rowCount-1 {
print("This will allow the bottom row stretch vertically")
lab.setContentHuggingPriority(UILayoutPriority(rawValue: 20), for:.vertical)
}
}
labMatrix.append( rows )
let curCol = UIStackView(arrangedSubviews: rows)
columns.append( curCol )
curCol.axis = .vertical
curCol.distribution = .fill
curCol.spacing = 2
// .alignment should be .fill -- let the label center its own text
curCol.alignment = .fill
// not needed - hugging priority is controlled by the stack view's arranged subviews
// if acol == colCount-1 {
// print("TODO Fix This. It should make only the last column(e.g. uistackview) strech to fill extra space")
// curCol.setContentHuggingPriority(UILayoutPriority(rawValue: 20), for:.horizontal)
// }
}
for col in columns {
self.addArrangedSubview( col )
}
self.axis = .horizontal
self.distribution = .fill
// .alignment needs to be .fill if you want the bottom row to expand vertically
self.alignment = .fill
self.spacing = 4
}
}
PlaygroundPage.current.liveView = ViewController()
I'm using the Collection view with vertical scroll direction for one my application. Here, I want to make my Collection view to display upside down without using any CGAffine Transform. Is it possible with the help of Flow Layout?
I like to implement the chat by using collection view
Thanks in advance
class ChatCollectionViewFlowLayout: UICollectionViewFlowLayout {
private var topMostVisibleItem = Int.max
private var bottomMostVisibleItem = -Int.max
private var offset: CGFloat = 0.0
private var visibleAttributes: [UICollectionViewLayoutAttributes]?
private var isInsertingItemsToTop = false
private var isInsertingItemsToBottom = false
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Reset each time all values to recalculate them
// ════════════════════════════════════════════════════════════
// Get layout attributes of all items
visibleAttributes = super.layoutAttributesForElements(in: rect)
// Erase offset
offset = 0.0
// Reset inserting flags
isInsertingItemsToTop = false
isInsertingItemsToBottom = false
return visibleAttributes
}
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
// Check where new items get inserted
// ════════════════════════════════════════════════════════════
// Get collection view and layout attributes as non-optional object
guard let collectionView = self.collectionView else { return }
guard let visibleAttributes = self.visibleAttributes else { return }
// Find top and bottom most visible item
// ────────────────────────────────────────────────────────────
bottomMostVisibleItem = -Int.max
topMostVisibleItem = Int.max
let container = CGRect(x: collectionView.contentOffset.x,
y: collectionView.contentOffset.y,
width: collectionView.frame.size.width,
height: (collectionView.frame.size.height - (collectionView.contentInset.top + collectionView.contentInset.bottom)))
for attributes in visibleAttributes {
// Check if cell frame is inside container frame
if attributes.frame.intersects(container) {
let item = attributes.indexPath.item
if item < topMostVisibleItem { topMostVisibleItem = item }
if item > bottomMostVisibleItem { bottomMostVisibleItem = item }
}
}
// Call super after first calculations
super.prepare(forCollectionViewUpdates: updateItems)
// Calculate offset of inserting items
// ────────────────────────────────────────────────────────────
var willInsertItemsToTop = false
var willInsertItemsToBottom = false
// Iterate over all new items and add their height if they go inserted
for updateItem in updateItems {
switch updateItem.updateAction {
case .insert:
if topMostVisibleItem + updateItems.count > updateItem.indexPathAfterUpdate!.item {
if let newAttributes = self.layoutAttributesForItem(at: updateItem.indexPathAfterUpdate!) {
offset += (newAttributes.size.height + self.minimumLineSpacing)
willInsertItemsToTop = true
}
} else if bottomMostVisibleItem <= updateItem.indexPathAfterUpdate!.item {
if let newAttributes = self.layoutAttributesForItem(at: updateItem.indexPathAfterUpdate!) {
offset += (newAttributes.size.height + self.minimumLineSpacing)
willInsertItemsToBottom = true
}
}
case.delete:
// TODO: Handle removal of items
break
default:
break
}
}
// Pass on information if items need more than one screen
// ────────────────────────────────────────────────────────────
// Just continue if one flag is set
if willInsertItemsToTop || willInsertItemsToBottom {
// Get heights without top and bottom
let collectionViewContentHeight = collectionView.contentSize.height
let collectionViewFrameHeight = collectionView.frame.size.height - (collectionView.contentInset.top + collectionView.contentInset.bottom)
// Continue only if the new content is higher then the frame
// If it is not the case the collection view can display all cells on one screen
if collectionViewContentHeight + offset > collectionViewFrameHeight {
if willInsertItemsToTop {
CATransaction.begin()
CATransaction.setDisableActions(true)
isInsertingItemsToTop = true
} else if willInsertItemsToBottom {
isInsertingItemsToBottom = true
}
}
}
}
override func finalizeCollectionViewUpdates() {
// Set final content offset with animation or not
// ════════════════════════════════════════════════════════════
// Get collection view as non-optional object
guard let collectionView = self.collectionView else { return }
if isInsertingItemsToTop {
// Calculate new content offset
let newContentOffset = CGPoint(x: collectionView.contentOffset.x,
y: collectionView.contentOffset.y + offset)
// Set new content offset without animation
collectionView.contentOffset = newContentOffset
// Commit/end transaction
CATransaction.commit()
} else if isInsertingItemsToBottom {
// Calculate new content offset
// Always scroll to bottom
let newContentOffset = CGPoint(x: collectionView.contentOffset.x,
y: collectionView.contentSize.height + offset - collectionView.frame.size.height + collectionView.contentInset.bottom)
// Set new content offset with animation
collectionView.setContentOffset(newContentOffset, animated: true)
}
}
}
also please check, it may set collection view As Chat behavior like upside down
https://gist.github.com/jochenschoellig/04ffb26d38ae305fa81aeb711d043068
For my first challenge using UIScrollView I modified this example to make UIScrollView display not just another background colour but another UIView and UILabel on each page. But I could have just as easily chosen to display objects like UITableView, UIButton or UIImage.
Potentially, UIScrollView could be much more than a giant content view where users scroll from one part to the next, e.g., some pages might have a UIButton that takes a user to a specific page, the same way we use books.
Code Improvements
My question has evolved since I first posted it. Initially the labels piled up on page 1 (as shown below) but this has now been corrected. I also included this extension to make the font larger.
Further improvement ?
As the code evolved I became more aware of other issues e.g. iPhone 5 images (below) appear differently on iPhone 7 where the UILabel is centred but not the UIView. So my next challenge is possibly to learn how to combine UIScrollView with Autolayout. I invite anyone to spot other things that might be wrong.
ViewController.swift (corrected)
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
var views = [UIView]()
var lables = [UILabel]()
var colors:[UIColor] = [UIColor.red, UIColor.magenta, UIColor.blue, UIColor.cyan, UIColor.green, UIColor.yellow]
var frame: CGRect = CGRect.zero
var pageControl: UIPageControl = UIPageControl(frame: CGRect(x: 50, y: 500, width: 200, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
initialiseViewsAndLables()
configurePageControl()
scrollView.delegate = self
self.view.addSubview(scrollView)
for index in 0..<colors.count {
frame.origin.x = self.scrollView.frame.size.width * CGFloat(index)
frame.size = self.scrollView.frame.size
self.scrollView.isPagingEnabled = true
views[index].frame = frame
views[index].backgroundColor = colors[Int(index)]
views[index].layer.cornerRadius = 20
views[index].layer.masksToBounds = true
lables[index].frame = frame
lables[index].center = CGPoint(x: (view.frame.midX + frame.origin.x), y: view.frame.midY)
lables[index].text = String(index + 1)
lables[index].defaultFont = UIFont(name: "HelveticaNeue", size: CGFloat(200))
lables[index].textAlignment = .center
lables[index].textColor = .black
let subView1 = views[index]
let subView2 = lables[index]
self.scrollView .addSubview(subView1)
self.scrollView .addSubview(subView2)
}
print(views, lables)
self.scrollView.contentSize = CGSize(width: self.scrollView.frame.size.width * CGFloat(colors.count), height: self.scrollView.frame.size.height)
pageControl.addTarget(self, action: Selector(("changePage:")), for: UIControlEvents.valueChanged)
}
func initialiseViewsAndLables() {
// Size of views[] and lables[] is linked to available colors
for index in 0..<colors.count {
views.insert(UIView(), at:index)
lables.insert(UILabel(), at: index)
}
}
func configurePageControl() {
// Total number of available pages is based on available colors
self.pageControl.numberOfPages = colors.count
self.pageControl.currentPage = 0
self.pageControl.backgroundColor = getColour()
self.pageControl.pageIndicatorTintColor = UIColor.black
self.pageControl.currentPageIndicatorTintColor = UIColor.green
self.view.addSubview(pageControl)
}
func getColour() -> UIColor {
let index = colors[pageControl.currentPage]
return (index)
}
func changePage(sender: AnyObject) -> () {
scrollView.setContentOffset(CGPoint(x: CGFloat(pageControl.currentPage) * scrollView.frame.size.width, y: 0), animated: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
pageControl.backgroundColor = getColour()
}
}
Extension
extension UILabel{
var defaultFont: UIFont? {
get { return self.font }
set { self.font = newValue }
}
}
The centre point of the lable on each frame must be offset by the origin of the content view (as Baglan pointed out). I've modified the following line of code accordingly.
lables[Int(index)].center = CGPoint(x: (view.frame.midX + frame.origin.x), y: view.frame.midY)
My application creates a UITableViewController that contains a custom tableHeaderView which may have an arbitrary height. I've been struggling with a way to set this header dynamically, as it seems the suggested ways have been cutting this header short.
My UITableViewController's relevant code:
import UIKit
import SafariServices
class RedditPostViewController: UITableViewController, NetworkCommunication, SubViewLaunchLinkManager {
//MARK: UITableViewDataSource
var post: PostData?
var tree: CommentTree?
weak var session: Session! = Session.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// Get post info from api
guard let postData = post else { return }
//Configure comment table
self.tableView.registerClass(RedditPostCommentTableViewCell.self, forCellReuseIdentifier: "CommentCell")
let tableHeader = PostView(withPost: postData, inViewController: self)
let size = tableHeader.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize)
let height = size.height
let width = size.width
tableHeader.frame = CGRectMake(0, 0, width, height)
self.tableView.tableHeaderView = tableHeader
session.getRedditPost(postData) { (post) in
self.post = post?.post
self.tree = post?.comments
self.tableView.reloadData()
}
}
}
This results in the following incorrect layout:
If I change the line: tableHeader.frame = CGRectMake(0, 0, width, height) to tableHeader.frame = CGRectMake(0, 0, width, 1000) the tableHeaderView will lay itself out correctly:
I'm not sure what I'm doing incorrectly here. Also, custom UIView class, if this helps:
import UIKit
import Foundation
protocol SubViewLaunchLinkManager: class {
func launchLink(sender: UIButton)
}
class PostView: UIView {
var body: UILabel?
var post: PostData?
var domain: UILabel?
var author: UILabel?
var selfText: UILabel?
var numComments: UILabel?
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented yet")
}
init(withPost post: PostData, inViewController viewController: SubViewLaunchLinkManager) {
super.init(frame: CGRectZero)
self.post = post
self.backgroundColor = UIColor.lightGrayColor()
let launchLink = UIButton()
launchLink.setImage(UIImage(named: "circle-user-7"), forState: .Normal)
launchLink.addTarget(viewController, action: "launchLink:", forControlEvents: .TouchUpInside)
self.addSubview(launchLink)
selfText = UILabel()
selfText?.backgroundColor = UIColor.whiteColor()
selfText?.numberOfLines = 0
selfText?.lineBreakMode = .ByWordWrapping
selfText!.text = post.selfText
self.addSubview(selfText!)
selfText?.sizeToFit()
//let attributedString = NSAttributedString(string: "Test"/*post.selfTextHtml*/, attributes: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType])
//selfText.attributedText = attributedString
body = UILabel()
body!.text = post.title
body!.numberOfLines = 0
body!.lineBreakMode = .ByWordWrapping
body!.textAlignment = .Justified
self.addSubview(body!)
domain = UILabel()
domain!.text = post.domain
self.addSubview(domain!)
author = UILabel()
author!.text = post.author
self.addSubview(author!)
numComments = UILabel()
numComments!.text = "\(post.numComments)"
self.addSubview(numComments!)
body!.translatesAutoresizingMaskIntoConstraints = false
domain!.translatesAutoresizingMaskIntoConstraints = false
author!.translatesAutoresizingMaskIntoConstraints = false
selfText!.translatesAutoresizingMaskIntoConstraints = false
launchLink.translatesAutoresizingMaskIntoConstraints = false
numComments!.translatesAutoresizingMaskIntoConstraints = false
let views: [String: UIView] = ["body": body!, "domain": domain!, "author": author!, "numComments": numComments!, "launchLink": launchLink, "selfText": selfText!]
//let selfTextSize = selfText?.sizeThatFits((selfText?.frame.size)!)
//print(selfTextSize)
//let metrics = ["selfTextHeight": selfTextSize!.height]
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[domain]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[author]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[numComments]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[launchLink]-[numComments]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[body][launchLink]|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[selfText][launchLink]|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[domain][author][numComments][launchLink]|", options: [], metrics: nil, views: views))
}
override func layoutSubviews() {
super.layoutSubviews()
body?.preferredMaxLayoutWidth = body!.bounds.width
}
}
Copied from this post. (Make sure you see it if you're looking for more details)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var headerFrame = headerView.frame
//Comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
}
}
}
Determining the header's frame size using
header.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
as suggested in the answers above didn't work for me when my header view consisted of a single multiline label. With the label's line break mode set to wrap, the text just gets cut off:
Instead, what did work for me was using the width of the table view and a height of 0 as the target size:
header.systemLayoutSizeFitting(CGSize(width: tableView.bounds.width, height: 0))
Putting it all together (I prefer to use an extension):
extension UITableView {
func updateHeaderViewHeight() {
if let header = self.tableHeaderView {
let newSize = header.systemLayoutSizeFitting(CGSize(width: self.bounds.width, height: 0))
header.frame.size.height = newSize.height
}
}
}
And call it like so:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.updateHeaderViewHeight()
}
More condensed version of OP's answer, with the benefit of allowing layout to happen naturally (note this solution uses viewWillLayoutSubviews):
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let header = tableView.tableHeaderView {
let newSize = header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
header.frame.size.height = newSize.height
}
}
Thanks to TravMatth for the original answer.
If you're still having problems with layout with the above code sample, there's a slight chance you disabled translatesAutoresizingMaskIntoConstraints on the custom header view. In that case, you need to set translatesAutoresizingMaskIntoConstraints back to true after you set the header's frame.
Here's the code sample I'm using, and working correctly on iOS 11.
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let headerView = tableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
if #available(iOS 9.0, *) {
tableView.layoutIfNeeded()
}
}
headerView.translatesAutoresizingMaskIntoConstraints = true
}
Based on #TravMatth and #NSExceptional's answer:
For Dynamic TableView Header, with multiple line of text(No matter have or not)
My solution is:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let footView = tableView.tableFooterView {
let newSize = footView.systemLayoutSizeFitting(CGSize(width: self.view.bounds.width, height: 0))
if newSize.height != footView.frame.size.height {
footView.frame.size.height = newSize.height
tableView.tableFooterView = footView
}
}
}
tableView.tableFooterView = footView to make sure that your tableview Header or Footer updated.
And if newSize.height != footView.frame.size.height helps you not to be called this method many times
I use the accepted answer for a long time and it always worked for me, until today, when I used a multiple lines label in a complex table header view, I ran into the same issue #frank61003 had:
it create a blank area with multiple lines label.
So in my case, there were big vertical margins around my label. If label text is just 1 line, then everything is fine. This issue only happens when the label has multiple lines of text.
I don't know the exact reason causing this, but I dug for a while and found a workaround to solve the issue, so I want to leave a reference here in case anyone runs into the same problem.
Optional first step, make sure your multiple lines label has the lowest Content Hugging Priority in your table header view, so it can auto increase to fit its text.
Then, add this calculate label height method to your view controller
private func calculateHeightForString(_ string: String) -> CGFloat {
let yourLabelWidth = UIScreen.main.bounds.width - 20
let constraintRect = CGSize(width: yourLabelWidth, height: CGFloat.greatestFiniteMagnitude)
let rect = string.boundingRect(with: constraintRect,
options: .usesLineFragmentOrigin,
// use your label's font
attributes: [.font: descriptionLabel.font!],
context: nil)
return rect.height + 6 // give a little extra arbitrary space (6), remove it if you don't need
}
And use the method above to configure your multiple lines label in viewDidLoad
let description = "Long long long ... text"
descriptionLabel.text = description
// manually calculate multiple lines label height and add a constraint to avoid extra space bug
descriptionLabel.heightAnchor.constraint(equalToConstant: calculateHeightForString(description)).isActive = true
This solved my issue, hope it can work for you too.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = self.tableView.tableHeaderView {
let headerViewFrame = headerView.frame
let height = headerView.systemLayoutSizeFitting(headerViewFrame.size, withHorizontalFittingPriority: UILayoutPriority.defaultHigh, verticalFittingPriority: UILayoutPriority.defaultLow).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
self.tableView.tableHeaderView = headerView
}
}
}
Problem in calculating label size when using horizontal or vertical fitting
If all constraint is added, this will work:
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
**My Working Solution is:
Add this function in viewcontroller**
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let headerView = myTableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
myTableView.tableHeaderView = headerView
if #available(iOS 9.0, *) {
myTableView.layoutIfNeeded()
}
}
headerView.translatesAutoresizingMaskIntoConstraints = true
}
**Add one line in header's view class.**
override func layoutSubviews() {
super.layoutSubviews()
bookingLabel.preferredMaxLayoutWidth = bookingLabel.bounds.width
}
Just implementing these two UITableView delegate methods worked for me:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
{
return 100;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return UITableViewAutomaticDimension;
}
I'm using Xamarin iOS and on iOS 9.2 devices the UITableView cells with cell style of UITableViewCellStyle.Value1 have the TextLabel overlapping the DetailTextLabel.
This doesn't happen on iOS 8. Anybody know what I can do to fix it without rolling my own cell? I just would like the TextLabel to ellipsis instead of overlapping the DetailTextLabel.
I couldn't find a fix. A blank project reproduced it with just a simple TableView so I rolled my own custom cell.
public class CustomTableViewCell : UITableViewCell
{
UILabel headingLabel, subheadingLabel;
public MTableViewCell (NSString cellId) : base (UITableViewCellStyle.Default, cellId)
{
SelectionStyle = UITableViewCellSelectionStyle.Default;
headingLabel = new UILabel ();
subheadingLabel = new UILabel () {
TextColor = UIColor.Gray,
TextAlignment = UITextAlignment.Center
};
ContentView.AddSubviews (new UIView[] { headingLabel, subheadingLabel });
}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
headingLabel.Frame = new CGRect (15, 0, ContentView.Bounds.Width - 130, ContentView.Bounds.Height);
subheadingLabel.Frame = new CGRect (ContentView.Bounds.Width - 110, 0, 95, ContentView.Bounds.Height);
}
public override UILabel TextLabel {
get {
return headingLabel;
}
}
public override UILabel DetailTextLabel {
get {
return subheadingLabel;
}
}
}
Pasudocode to decide if you want to dequeue value1 or sub type of cell
public static func isVerticalLayout(primary: String, secondary: NSAttributedString,
liveHostView: UIView) -> Bool
{
let csWidth = liveHostView.bounds.width
let headingLabel = UILabel()
let subheadingLabel = UILabel()
headingLabel.text = primary
headingLabel.numberOfLines = 0
headingLabel.lineBreakMode = .byWordWrapping
subheadingLabel.attributedText = secondary
subheadingLabel.textAlignment = .right
subheadingLabel.numberOfLines = 1
subheadingLabel.lineBreakMode = .byWordWrapping
let clipw = csWidth - P97GEL.Constants.leftTextMargin - P97GEL.Constants.rightTextMargin
let bounds = CGRect(x: 0, y: 0, width: clipw, height: CGFloat.greatestFiniteMagnitude)
let psRect = headingLabel.textRect(forBounds: bounds, limitedToNumberOfLines: 1)
let ps = psRect.size
let ssRect = subheadingLabel.textRect(forBounds: bounds, limitedToNumberOfLines: 1)
let ss = ssRect.size
headingLabel.frame.origin = CGPoint(x: P97GEL.Constants.leftTextMargin, y: P97GEL.Constants.verticalMargin)
headingLabel.frame.size = ps
subheadingLabel.frame.size = ss
if csWidth >= ps.width + ss.width + (3 * P97GEL.Constants.horizontalMargin) {
return false
} else {
return true
}
}