Static Table View with Cell that fill visible screen area - ios

I want to create a static table with 7 cells that fill the rest of the screen with taking into account the status bar and nav bar. I override the methods bellow but I am not getting the expected result. What I am missing?
override func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat {
let tableHeight = (CGFloat(tableView.bounds.height) - CGFloat(64))/7
return tableHeight }
the picture shows that my 7 cells do no occupy all the screen

try this
override func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat {
let tableHigh = (CGFloat(UIScreen.mainScreen.bounds.height) - CGFloat(64))/7
return tableHigh }

This is another good solution:
override func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat {
let tableHeight = (tableView.bounds.height - tableView.contentInset.top - tableView.contentInset.bottom) / 7
return tableHeight
}

Related

Make UIImage clipsToBound over UITableViewCell

I have a UITableViewController with a static table cell.
And I'm trying to make a cell With UIImageView and this image clipToBounds to this cell but it's not working.
This is a sample that I want to do.
But when I make a UITableView and set UIImage clipToBounds to TRUE
the result is :
Am I just need a way to make UIImageView like the first image anyway to do that with UITableView Cell?
And this is my UIImageView Constraints :
and here's my code :
class ProfileTableViewController: UITableViewController {
#IBOutlet weak var userImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
// image view
self.userImage.layer.cornerRadius = self.userImage.frame.width / 2
self.userImage.clipsToBounds = true
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
var number = 0
if section == 0
{
number = 1
}else if section == 1
{
number = 4
}else if section == 2
{
number = 1
}
return number
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100
}
}
just create outlet for image view make it corner radius itswidth/2 (make sure height and width same size) and make clip to bounds true. for ex:
imageView = imageView.frame.size.width/2;
imageView.clipsToBounds = true;
I Found the solution when i added this method and add custom Section header
and give it clipToBounds = false
problem solved
i'm just added :
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
view.clipsToBounds = false
return view
}
and it's working perfectly :))
thanks all for trying to help me
Why clipsToBounds = true ?
If the image must be outside the cell, then I guess you want the opposite:
yourView.clipsToBounds = false
this might solve your problem. In cell for row at indexPath
let height = CGFloat(cell.frame.size.height-10)
let leading = (cell.frame.size.width - height)/2
let top = CGFloat(18)
let myImage = UIImageView(frame: CGRect(x: leading, y: top, width: height, height:height))
myImage.layer.masksToBounds = true
myImage.layer.cornerRadius = (height)/2
cell.contentView.addSubview(myImage)
//OR
cell.myImage.layer.cornerRadius = cell.myImage.frame.width / 2
cell.myImage.clipsToBounds = true
try like this
enter image description here

How to calculate estimatedRowHeight for Dynamic Type

I have a custom subclass of UITableViewController. It has one section containing many rows. Each row corresponds to the same custom table view cell class. Each custom cell has two labels: myLabel1 & myLabel2, both subviews of the cell's contentView.
Every myLabel1 has one line of text, and every myLabel2 has one or two lines of text, but every cell should have the same height, as if every myLabel2 has two lines of text.
The labels use Dynamic Type.
myLabel1.font = UIFont.preferredFont(forTextStyle: .headline)
myLabel2.font = UIFont.preferredFont(forTextStyle: .subheadline)
According to Working with Self-Sizing Table View Cells, I've positioned each label with Auto Layout and "set the table view’s rowHeight property to UITableViewAutomaticDimension" so that the row height changes with Dynamic Type.
How do I make every cell have the same height?
How should I estimate the table view's row height?
UITableViewAutomaticDimension will estimate the cell size depending on cells content size. We must calculate the max height that a cell could possibly have, and return this height for all cells instead of using UITableViewAutomaticDimension.
CustomTableViewCell
let kMargin: CGFloat = 8.0
class CustomTableViewCell: UITableViewCell {
#IBOutlet var singleLineLabel: UILabel!
#IBOutlet var doubleLineLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
updateFonts()
}
override func prepareForReuse() {
updateFonts()
}
func updateFonts() {
singleLineLabel.font = UIFont.preferredFont(forTextStyle:.title3)
doubleLineLabel.font = UIFont.preferredFont(forTextStyle:.body)
}
func updateCellForCellWidth(_ width:CGFloat) {
doubleLineLabel.preferredMaxLayoutWidth = width - (2*kMargin)
}
func fillCellWith(_ firstString: String, _ secondString: String) {
singleLineLabel.text = firstString
doubleLineLabel.text = secondString
}
}
On View Controller
Setting up a dummy cell and listing to notifications for dynamic type
var heightCalculatorDummyCell: CustomTableViewCell!
var maxHeight: CGFloat = 0.0
override func viewDidLoad() {
super.viewDidLoad()
heightCalculatorDummyCell = tableView.dequeueReusableCell(withIdentifier: "cell_id") as! CustomTableViewCell
maxHeight = getMaxHeight()
NotificationCenter.default.addObserver(self, selector: #selector(AutomaticHeightTableViewController.didChangePreferredContentSize), name: .UIContentSizeCategoryDidChange, object:nil)
}
Getting max height using a dummy cell.
func getMaxHeight() -> CGFloat {
heightCalculatorDummyCell.updateFonts()
heightCalculatorDummyCell.fillCellWith("Title","A string that needs more than two lines. A string that needs more than two lines. A string that needs more than two lines. A string that needs more than two lines. A string that needs more than two lines.")
heightCalculatorDummyCell.updateCellForCellWidth(tableView.frame.size.width)
let size = heightCalculatorDummyCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return (size.height + 1)
}
Handling Table view reloads on notification
deinit {
NotificationCenter.default.removeObserver(self)
}
func didChangePreferredContentSize() {
maxHeight = getMaxHeight()
tableView.reloadData()
}
Final step, returning max heights in tableview delegate
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return maxHeight
}
Make myLabel2 a FixedHeightLabel so that its height is always two lines.
class FixedHeightLabel: TopAlignedLabel {
override var intrinsicContentSize: CGSize {
let oldText = text
text = "\n"
let height = sizeThatFits(CGSize(width: .max, height: .max)).height
text = oldText
return CGSize(width: UIViewNoIntrinsicMetric, height: height)
}
}
class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
super.drawText(in: textRect)
}
}

Swift: Change TableViewCell Height Dynamically and change WebView Height Dynamically

I have a TableViewController, inside the TableViewCell, I have a UIWebView. I want the UIWebView to display some content from the internet, but I don't want the scroll effect, I want the WebView to have a dynamic height based on the length of the content. In addition, I want the TableViewCell to be able to adjust its cell height dynamically based on the dynamic height of WebView. Is this possible?
This is how I implemented my TableViewController:
class DetailTableViewController: UITableViewController {
var passPost: Posts = Posts()
var author: Author = Author()
override func viewDidLoad() {
super.viewDidLoad()
getAuthor()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("detailCell")
let postImageUrlString = passPost.postThumbnailUrlString
let postImageUrl = NSURL(string: postImageUrlString)
let size = CGSize(width: 414.0, height:212.0 )
let filter = AspectScaledToFillSizeFilter(size: size)
(cell?.contentView.viewWithTag(1) as! UIImageView).af_setImageWithURL(postImageUrl!, filter: filter)
//Set Author Avatar
let authorAvatarUrlString = author.authorAvatarUrlString
let authorAvatarUrl = NSURL(string: authorAvatarUrlString)
//Mark - Give Author Avatar a Round Corner
let filter2 = AspectScaledToFillSizeWithRoundedCornersFilter(size: (cell?.contentView.viewWithTag(2) as! UIImageView).frame.size, radius: 20.0)
(cell?.contentView.viewWithTag(2) as! UIImageView).af_setImageWithURL(authorAvatarUrl!, filter: filter2)
//Set Post Title and Content and so on
(cell?.contentView.viewWithTag(4) as! UILabel).text = passPost.postTitle
(cell?.contentView.viewWithTag(6) as! UIWebView).loadHTMLString("\(passPost.postContent)", baseURL: nil)
for the heightForCellAtIndexPath I did
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! tableViewCell
tableView.rowHeight = cell.theWebView.scrollView.contentSize.height
return tableView.rowHeight
}
This is working fine, except the WebView has a scroll effect, and the height is limited due to the limitation of the TableViewCell. So, how to achieve what I need?
You need to set the height of your cell in the UITableViewDelegate method
tableView(_:heightForRowAt:)
You would do all your calculations on each individual cell height in this method and return it. If you want to display the UIWebView in it's whole without the need to scroll, you should return the height of the UIWebView's scroll view contentView – plus any height for anything else you might want to display in this cell.
Follow the steps:
1) set leading, trailing, top, bottom constraint 0(zero) from webview to tableviewCell
2) Now no need to call HeightForRow method of tableView.
3) in webview delegate method
func webViewDidFinishLoad(webView: UIWebView)
{
var frame = cell.webView.frame
frame.size.height = 1
let fittingSize = cell.webView.sizeThatFits(CGSizeZero)
frame.size = fittingSize
}
4) webview scrollenabled = false
You can return, in heightForRowAtIndexPath:
tableView.rowHeight = UITableViewAutomaticDimension
You also need to have a value setted in estimatedRowHeight, like
tableView.estimatedRowHeight = 85.0
If you have all the constraints defined correctly in the Storyboard (Or programmatically), you shouldn't get any error.
Reference: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html

How can I implement a method such that, when called, table view cells' height changes?

I want to implement a method that looks something like this:
setCellHeightForIndexPath(someIndexPath, 80)
and then the table view cell at that index path will suddenly have a height of 80.
The reason I want to do this is because I want the height of the cell to be set to the height of the web view's content after it has finished loading the HTML. Since I can only get the web view's content size after it has finished loading, I can't just set the cell height right away.
See this question for more info.
So in the webViewDidFinishLoad method, I can just get the web view's content height, set the web view's height to that, and call this method to set the cell's height.
It seems like that the cell height can only change when heightForRowAtIndexPath is called. I think the method would use a similar approach as my answer to this question. I think I need to store an array of heights maybe? I just can't think of a way to implement this!
How can I implement such a method?
Note: don't tell me this is not possible. In the Instagram app, I can see different images that have different heights fit perfectly in a table view cell. And those images are similar to my web views. They both need time to load.
EDIT:
Let me show some of my attempts at this:
var results: [Entry] = []
var cellHeights: [CGFloat] = []
var webViews: [UIWebView] = []
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("resultCell")
let webView = cell!.contentView.viewWithTag(1) as! UIWebView
webView.loadHTMLString(results[indexPath.row].htmlDescriptionForSearchMode(.TitleOnly), baseURL: nil)
webView.delegate = self
webView.scrollView.scrollEnabled = false
webViews.append(webView)
cellHeights.append(400)
webView.stringByEvaluatingJavaScriptFromString("highlightSearch(\"day\")")
return cell!
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return indexPath.row < cellHeights.count ? cellHeights[indexPath.row] : 400
}
func webViewDidFinishLoad(webView: UIWebView) {
let height = CGFloat(webView.stringByEvaluatingJavaScriptFromString("document.height")!.toFloat()!)
webView.frame = CGRect(origin: webView.frame.origin, size: CGSizeMake(webView.frame.width, height))
print(height)
if let index = webViews.indexesOf(webView).first {
cellHeights[index] = height
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .None)
tableView.endUpdates()
}
}
results is the stuff that I want to show in the web views. cellHeights is used to store the height of each cell. I put all the web views into the webViews array so I can call indexOf in webViewDidFinishLoad to identify which web view is loaded.
EDIT:
So I wrote this code in my table view controller with reference to Andre's answer:
class SearchResultsController: UITableViewController, UIWebViewDelegate {
var entries: [Entry] = []
lazy var results: [Result] = {
return self.entries.map { Result(entry: $0) }
}()
var cellHeights: [CGFloat] = []
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let result = results[indexPath.section]
var cell = result.cell
if cell == nil {
print("cellForRow called")
cell = tableView.dequeueReusableCellWithIdentifier("resultCell") as! ResultCell
cell.webView.delegate = self
print(cell == nil)
print("loading \(result.entry.title)...")
cell.webView.loadHTMLString(result.entry.htmlDescriptionForSearchMode(.TitleOnly), baseURL: nil)
result.cell = cell
}
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return indexPath.row < cellHeights.count ? cellHeights[indexPath.row] : 400
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 169
tableView.rowHeight = UITableViewAutomaticDimension
tableView.tableFooterView = UIView()
}
func webViewDidFinishLoad(webView: UIWebView) {
print("didFinishLoad called")
if webView.loading {
return
}
guard let cell = webView.superview?.superview as? ResultCell else {
print("could not get cell")
return
}
guard let index = results.map({$0.cell}).indexOf(cell) else {
print("could not get index")
return
}
let result = results[index]
print("finished loading \(result.entry.title)...")
guard let heightString = webView.stringByEvaluatingJavaScriptFromString("document.height") else {
print("could not get heightString")
return
}
guard let contentHeight = Float(heightString) else {
print("could not convert heightString")
return
}
cell.webViewHeightConstraint.constant = CGFloat(contentHeight)
tableView.beginUpdates()
tableView.endUpdates()
}
}
class ResultCell: UITableViewCell {
#IBOutlet weak var webView: UIWebView!
#IBOutlet weak var webViewHeightConstraint: NSLayoutConstraint!
}
class Result {
let entry: Entry
var contentHeight: Float?
var cell: ResultCell!
init(entry: Entry) {
self.entry = entry
}
}
You cannot "push" the new cell height onto a table view. Instead, you need to make table view "pull" the new height from your heightForRowAtIndexPath, and be ready to supply the new height.
When the cell load finishes for row r, you need to update your model in such a way that it knows the new height of row r. After that you need to tell your table view to reload itself, like this:
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
tableView.endUpdates()
This will start the process of updating your cell. heightForRowAtIndexPath will be called. Your code will return the new height. After that cellForRowAtIndexPath will be called. Your code should be prepared to return the cell that has finished loading, without initiating a new data load.
i tried implementing it by using automatic autolayout and automatic cell height calculation.
maybe it helps to point you into the right direction:
https://github.com/andreslotta/WebViewCellHeight
just an excerpt:
func webViewDidFinishLoad(webView: UIWebView) {
if webView.loading {
return
}
guard let cell = webView.superview?.superview as? WebViewCell else {
print("could not get cell")
return
}
guard let index = websites.map({$0.cell}).indexOf(cell) else {
print("could not get index")
return
}
// get website
let website = websites[index]
print("finished loading \(website.urlString)...")
// get contentheight from webview
guard let heightString = webView.stringByEvaluatingJavaScriptFromString("document.height") else {
print("could not get heightString")
return
}
guard let contentHeight = Float(heightString) else {
print("could not convert heightString")
return
}
cell.webViewHeightConstraint.constant = CGFloat(contentHeight)
tableView.beginUpdates()
tableView.endUpdates()
}
You can implement like this
Take one global CGFloat for height and one indexpath
now when you need to change height set both values and use
[self.yourTableview beginUpdate]
[self.yourTableview endUpdate]
will update your cell
and in cellForRowAtIndexPath you should use dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *) will make sure you got updated cell every time
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath == yourIndexpath) {
return gloablVariable;
} else {
return defauleHeight
}
}
Hope it helps

Swift: UITableView Cell Disappears When Using UIView.animateWithDuration

I am programmatically adding a UITableView as a subview of a view that uses UIView.animateWithDuration to expand the view when a button is clicked from a single point to a full window. Basically, a box that starts as a point and expands to full size with an animation. I am having difficulties getting the table to populate with cells. At first, a cell was being created, but would disappear after quickly after the animation completed, after playing around with it, I have gotten the cell to remain after the animation is complete, but now the cell disappears when I tap on it. I don't understand what is going on here. Can someone please help?
Here is my code. Note, I have removed what I believe to be irrelevant to this problem to make the code easier to read.
class PokerLogSelectionView: UIViewController {
let logSelectionTableViewController = LogSelectionTableViewController()
let logSelectionTableView = UITableView()
// Irrelevant class variables removed
init(btn : PokerLogSelectionButton){
// Irrelevant view initialization code removed
// Display the subviews
self.displayLogListScrollView()
}
func displayLogListScrollView() {
// Frame is set to (0,0,0,0)
let frame = CGRect(x: self.subviewClosed, y: self.subviewClosed, width: self.subviewClosed, height: self.subviewClosed)
logSelectionTableView.delegate = self.logSelectionTableViewController
logSelectionTableView.dataSource = self.logSelectionTableViewController
// Set the frame of the table view
logSelectionTableView.frame = frame
// Give it rounded edges
logSelectionTableView.layer.cornerRadius = 10
// Remove the cell divider lines
logSelectionTableView.separatorStyle = UITableViewCellSeparatorStyle.None
logSelectionTableView.backgroundColor = logSelectionViewContentScrollViewColor
self.view.addSubview(logSelectionTableView)
//self.logSelectionTableView.reloadData()
//self.addChildViewController(logSelectionTableViewController)
}
override func viewDidAppear(animated: Bool) {
// Create animation
let timeInterval : NSTimeInterval = 0.5
let delay : NSTimeInterval = 0
UIView.animateWithDuration(timeInterval, delay: delay, options: UIViewAnimationOptions.CurveEaseOut, animations: {
// Irrelevant code removed
// Set the size and position of the view and subviews after the animation is complete
self.view.frame = CGRect(x: self.frameXopen, y: self.frameYopen, width: self.frameWopen, height: self.frameHopen)
self.logSelectionTableView.frame = CGRect(x: self.subviewXopen, y: self.svYopen, width: self.subviewWopen, height: self.svHopen)
}, completion: { finished in
self.addChildViewController(self.logSelectionTableViewController)
})
}
}
class LogSelectionTableViewController : UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(LogSelectionCell.self, forCellReuseIdentifier: "logCell")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokerLibrary.logNames.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 20
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("Selected row: \(indexPath.row)")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell : LogSelectionCell = self.tableView.dequeueReusableCellWithIdentifier("logCell") as? LogSelectionCell {
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.textLabel!.text = pokerLibrary.logNames[indexPath.row].name
return cell
}
fatalError("Could not dequeue cell of type 'LogSelectionCell'")
}
}
Note: I can see the tableview after the animation is complete. The color is different than the view in the background view and the tableview does not disappear, just the cell. I expect there to be 1 cell, and I have printed out the number of rows in section 0 and it always returns 1.
Thanks for the help!
Edit:
Here is a screenshot of the view hierarchy before the cell disappears.
Here is a screenshot of the view hierarchy after I tap the cell and it disappears.
I overrode the touchesBegan method in my custom cell and did not call its superclass method. This stopped the cell from disappearing when I tap it, but it still disappears when I scroll the tableView.

Resources