Unable to change static UITableViewCell height - ios

I'm using a static table view which contains 3 different cells. And when a button in the first cell is tapped, the height of the first cell should increase. Below is the function called when the button is tapped.
#IBAction func toggleExpandCamera(_ sender: Any) {
self.shouldShowCameraPreview = !self.shouldShowCameraPreview
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
}
And in the table view's delegate heightForRowAt()
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
let expandedHeight: CGFloat = 415
let collapsedHeight: CGFloat = 115
if self.shouldShowCameraPreview {
return expandedHeight
}
return collapsedHeight
} else if indexPath.row == 2 {
// If already premium, dont show purchases cell.
if AGTUserDefaultValues.isUserPremium {
return 0
}
// Last cell should be same height as the table view
return self.tableView.frame.height - (self.navigationController?.navigationBar.frame.height ?? 0)
- min(UIApplication.shared.statusBarFrame.height, UIApplication.shared.statusBarFrame.width)
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
I've verified that heightForRowAt() IS getting called, and it is working fine for the other cell heights when toggleExpandCamera() is called. It's just that the first cell that is behaving quite weird. It seems like it disappeared or something. I've attached screenshots down below, before and after expanding.
On further inspection, it looks like the cell still exist, but still has the same height. The only difference is there's now more space between the two cells. I also found out the alpha value of the cell is 0.
UPDATE
I've tried created a new project, with only the tableview and the function to expand the cell, and still on that project, the same thing happened. If anyone is curious to help I've uploaded the project here.

I've tried your project and found that changing :
self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
into :
self.tableView.reloadData()
Works as expected.

I figured out the answer. In my toggleExpandCamera() function instead of reloading the table view, I replaced that with:
#IBAction func toggleExpandCamera(_ sender: Any) {
self.shouldShowCameraPreview = !self.shouldShowCameraPreview
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
And the cell height animates as expected.

Related

ScrollToRow does not work when said row is near the bottom of the table view

I have a cell class which implements a textfield delegate. In this delegate I am calling a function to tell the tableview to scroll to a specific row based off an indexPath. This works in most cases but not when the row is at the bottom of the table view. The cell class has a table property which is passed in, in my main controllers cellForRow method. Code below:
extension IR_TextCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
util_textField.addHightlightedBorder(textField)
if let index = table?.indexPath(for: self)
{
scrollDelegate?.scrollToMe(at: index)
}
}
}
func scrollToMe(at index: IndexPath) {
self.tableV.scrollToRow(at: index, at: .middle, animated: false)
}
I have tried wrapping DispatchQueue.main.async around this and adding a deadline but it didn't make a difference.
Do I need to change my tableview's bottom constraint maybe?
My situation is a little different than yours but I had a same issue scrolling to cells that are near the bottom. It might now work for your exact situation but I hope this helps someone who comes across this posting. I suspected that it might be a timing issue so I ended up doing it like below:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == tableView.indexPathsForVisibleRows?.last?.row {
let scrollIndex = 0//set to your predetermined scrolled to index
let cellRect = tableView.rectForRow(at: indexPath)
let completelyVisible = tableView.bounds.contains(cellRect)
if scrollIndex >= indexPath.row && !completelyVisible {
let maxIndex = 10//number of elements in the array - 1
//in case you want a delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
self.tableView.scrollToRow(at: IndexPath(row: scrollIndex > maxIndex ? maxIndex : scrollIndex, section: 0), at: .middle, animated: false)
}
}
}
}

Decrease row height on click on searchbar

I have some tableviewcells loaded on my tableview. Each of those cells has an arrow button at the top right on the click of which the height of the row is increased and some more buttons are exposed. Clicking on the button again hides the button & the height is decreased as before. The code for that is given like so...
func moreOptionsBtnTapped(cell: CustomersTableViewCell) {
if i == 0 {
if let indexPath = tableView?.indexPath(for: cell) {
selectedIndex = indexPath as NSIndexPath
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.fade)
}
i += 1
} else {
if let indexPath = tableView?.indexPath(for: cell) {
selectedIndex = indexPath as NSIndexPath
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.fade)
}
i = 0
}
}
The heightForRowAtIndexPath is given as:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == selectedIndex as IndexPath {
if i == 0 {
return 92
} else {
return 66
}
}
return 66
}
Now the issue is I also have a search bar on top and on click of that searchbar, if the additional buttons are exposed on the tableview cell then I want them to be minimised. Mere hiding them doesn't work. That has been tried.
Hope somebody can help...
When you are clicking on search bar you will get the call back on UISearchBarDelegate methods like
optional public func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool // return NO to not become first responder
in this method you can change the i value and reload the table view. and in this method you are not getting the indexPath for rows , thats why you use this...
self.tableview.relaodData()

UIWebView's entire contents embedded in UITableView

I've been struggling with this for two days, so I come hat in hand to the wise people of the internet.
I am showing an article as part of my UITableView. For this to display correctly, I need to give the delegate a height for the cell, which I want to be the same as the UIWebView's height, so I can disable scroll on the WebView and display the web content in its entirety as a static cell.
My first approach was to render it in the heightForRowAtIndexpathmethod, but this did obviously not work as I need the wait for the UIWebViewDelegate to tell me when the web view is fully loaded and has a height. After a while I found a working solution, which used the web view delegate to refresh the cell height when the web view was loaded.
The works fine until the screen size changes. Either from rotate or from full-screening my UISplitView. I forced an update on it in the didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation), but this causes it to flash about 10 times before settling into the correct height. I logged this change, and it seems the WebView is calling itself multiple times, causing a loop.
As seen in this log, starting from when I rotated the screen.
It flashes once every time it reloads, and as you can see, it reloads itself a bunch of times.
So. I need a way to show an entire web views content inside a uitableview, and reliably get the height when the screen size changes. If anyone has managed this in any way before, please tell me. I will give a bounty and my firstborn child to anyone who can resolve this, as it's driving me insane.
Here's my relevant code.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch indexPath.row {
case 4:
//Content
print("Height for row called")
return CGFloat(webViewHeight)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch (indexPath.row){
//HTML Content View
case 4:
let cell = tableView.dequeueReusableCellWithIdentifier("ContentCell", forIndexPath: indexPath)
var contentCell = cell as? ContentCell
if contentCell == nil {
contentCell = ContentCell()
}
contentCell?.contentWebView.delegate = self
contentCell?.contentWebView.scrollView.userInteractionEnabled = false
contentCell?.contentWebView.loadHTMLString((post?.contentHTML)!, baseURL: nil)
print("Cell For row at indexpath called")
return contentCell!
}
func webViewDidFinishLoad(webView: UIWebView) {
updateHeight()
}
func updateHeight(){
let webView = (self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 4, inSection: 0)) as! ContentCell).contentWebView
if self.webViewHeight != Double(webView.scrollView.contentSize.height) {
print("Previous WV Height = \(self.webViewHeight), New WV Height = \(webView.scrollView.contentSize.height)")
self.webViewHeight = Double(webView.scrollView.contentSize.height)
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .Automatic)
} else {
return
}
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
print("rotated")
self.updateHeight()
//tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .Automatic)
}
I solved this by changing the .Automatic to .None in the row change animation. Its still a bad solution, but at least it doesn't flicker anymore.
I would recommend that you calculate the web view height independently of the table view and store the dimension as part of the data itself and use it return in heightForRowAtIndexPath call. Its a easier that way since you don't have to deal with calculating the table height during table view display. When the html content is not loaded use a standard height and a message for the web view.
I don't see a problem in your implementation. Trey few things
There are few things you can check
func webViewDidFinishLoad(webView: UIWebView) {
updateHeight()
//This function may get called multiple times depending on webpage.
}
//Use
self.tableView.beginUpdates()
self.tableView.endUpdates()
//Instead of
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .None)
func updateHeight(){
let webView = (self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 4, inSection: 0)) as! ContentCell).contentWebView
if self.webViewHeight != Double(webView.scrollView.contentSize.height)
{
print("Previous WV Height = \(self.webViewHeight), New WV Height = \(webView.scrollView.contentSize.height)")
self.webViewHeight = Double(webView.scrollView.contentSize.height)
self.tableView.beginUpdates()
self.tableView.endUpdates()
// tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .None)
} else {
return
}
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation)
{
print("rotated")
let webView = (self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 4, inSection: 0)) as! ContentCell).contentWebView
webView.reload();
}
You will need to reload webview on orientation change.

Change properties of previous cells in tableView

I have a tableView, designed in storyboard, that mimics a chat UI. A cell consists of:
A TextView for the message text
A Profile Image of the sender
Right now, the profile image is displayed in every cell, next to the text bubble. This is fine, but if the same users send two or more messages directly after the other, the profile image should only appear on the last bubble and not on the previous one.
I tried calling cellForRowAtIndexPath to get the previous cell's properties and change the hidden property of the profile image, but this gave me two problems:
I'm calling cellForRowAtIndexPath inside cellForRowAtIndexPath, because that's where I make the cell UI and decide wether the profile image has to be hidden or not. I don't think it's a good idea to call this Method inside itself.
Sometimes (when scrolling up and down very fast) this does not work properly.
I also tried to store all the cells in an dictionary (indexPath.row: Cell), so I can access it faster later, but this gave me the same problem namely that it does not work when scrolling up and down really fast.
This is an illustration of how it should be: http://tinypic.com/view.php?pic=2qavj9w&s=8#.Vfcpi7yJfzI
You need to both look ahead inside of your cellForRowAtIndexPath method and, as Paulw11 recommended, call reloadRowsAtIndexPaths after inserting the cell:
import UIKit
struct MyMessage {
let sender: String
let text: String
}
class MyTableViewCell: UITableViewCell {
var message: MyMessage?
var showProfileImage: Bool = false
}
class MyTableViewController: UITableViewController {
private var _messages: [MyMessage] = []
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self._messages.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = self._messages[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyTableViewCell
cell.message = message
if self._messages.count > indexPath.row + 1 {
let nextMessage = self._messages[indexPath.row + 1]
cell.showProfileImage = message.sender != nextMessage.sender
} else {
cell.showProfileImage = true
}
return cell
}
func addMessage(message: MyMessage) {
let lastIndexPath = NSIndexPath(forRow: self._messages.count - 1, inSection: 0)
let indexPath = NSIndexPath(forRow: self._messages.count, inSection: 0)
self._messages.append(message)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Bottom)
self.tableView.reloadRowsAtIndexPaths([lastIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}
}

disappearing headers in swift

I managed a way to animate my cells. I created a global variable named rowHeight in type of CGFloat and implicitly assigned it as 40. I wrote this code down:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return rowHeight
}
In the touch event:
#IBAction func lessonCollapser(sender: AnyObject) {
if rowHeight == 40 {
self.tableView.beginUpdates()
UIView.animateWithDuration(1, animations: {self.rowHeight = 0})
self.tableView.endUpdates()
} else if rowHeight == 0 {
self.tableView.beginUpdates()
UIView.animateWithDuration(1, animations: {self.rowHeight = 40})
self.tableView.endUpdates()
}
}
So far so good. Even though it is being animated, there are a few problems.
First: My custom headers disappear.
Second: I get an error: "no index path for table cell being reused". I think because there are no index paths, the headers are disappeared. That is expected. What is not expected is that, why my tables' index paths disappeared when I used the animation? I think that is because of the beginUpdates() and endUpdates(). Can someone advice me something to do?
The code for my header view:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! lessonHeaderTableViewCell
switch (section) {
default:
headerCell.lessonHeader.setTitle("TEST HEADER", forState: .Normal)
}
return headerCell
}
What happens on the code above is this: I created a prototype cell with "headerCell" identifier. I threw a button inside it. I created a swift file named "lessonHeaderTableViewCell. I assigned it as class of the prototype cell so it could control it. Lastly, I created an outlet named "lessonHeader" for the button, inside the swift file that I created.

Resources