UITableViewCell resize after click, constraints are changing - ios

I have TableViewCell filled with data. I want to show only one label for example, and after user clicks cell will resize and show all data to user. I'm done with resizing part, but when cell is small it automaticly change my constraints. Can somebody help me ? code nd photos below.
Thanks!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if (i != indexPath.row) {
i = indexPath.row
}
else {
i = -1
}
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if (indexPath.row == i) {
return 250
}
else {
return 50
}
}

When the cell is at a height of 50 your constraints are being broken because there is not enough space to accommodate them all as you wanted them to be laid out. Therefore, the height of the cell is being honored before any other constraints you added to elements within the cell and thus they are squished together.
To eliminate constraint errors, play around with the priority and relation modes on your constraints.
Lastly, when your cells are collapsed, you can hide all other labels except the one you want to display by calling the following on ones that should be hidden:
label.hidden = true
Or animate their disappearance like so:
UIView.animate(withDuration: 0.2, animations: {
label.alpha = 0
}, completion: { completed -> Void in
label.hidden = true
})
And do the opposite to make them reappear when you expand the cells.

Related

How to narrow UITableView cell height if there is dynamic structure in Swift?

I have a tableView and cells. The Cells are loaded from a xib and they have a label with automatic height. I need to narrow one cell if the user taps on it.
I have tried hiding - doesn't work
I have tried removeFromSuperView()- doesn't work
Is there any alternative?
When setting up your tableViewCell store the height anchor you want to update
var yourLabelHeightAnchor: NSLayoutConstraint?
private func setupLayout() {
yourLabelHeightAnchor = yourLabel.heightAnchor.constraint(equalToConstant: 50)
// Deactivate your height anchor as you want first the content to determine the height
yourLabelHeightAnchor?.isActive = false
}
When the user clicks on a cell, notify the tableView that the cell is going to change, and activate the height anchor of your cell.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCellIdentifier") as? YourCell
self.tableView.beginUpdates()
cell?.yourLabelHeightAnchor?.isActive = true
self.tableView.endUpdates()
}
Did you try to do something like this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var result: CGFloat
if (indexPath.row==0) {
result = 50 }
else {result = 130}
return result
}
This is just an example where height is changed for the first row. I tested on my application and it gave result like this.

swift messaging app, uitableview not responding correctly to keyboard?

A similar question is this, except I don't have estimated row heights, I store the actual heights into a dictionary, and either use those, or use UITableViewAutomaticDimension.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeightDict[indexPath.row] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cellHeightDict[indexPath.row] {
return height
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cellHeightDict[indexPath.row] {
return height
} else {
return UITableViewAutomaticDimension
}
}
So I'm making a messaging app in Swift 4 right now. When the user shows the keyboard, I want the messages to shift up with the keyboard. So in order to do this, I have a variable keyboardHeight which correctly gets the height of the keyboard:
let keyboardHeight: CGFloat = ((userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height)!
I change the height of the table view by keyboardHeight:
self.messageTableViewHeight.constant -= keyboardHeight
So the users can see all of the messages. And to shift up the messages with the keyboard animation, I changed the contentOffset of the table view:
self.messageTableView.contentOffset.y += keyboardHeight
All of this is in a UIView.animationWithDuration, and I call self.view.layoutIfNeeded() after, so the shifting and everything works fine. But, when I send a message without scrolling to the bottom, the messageTableView content jumps down??? And it seems to shift down by keyboardHeight, at least when I eye it. I am using messageTableView.reloadData(). Here are some images.
Before message sent
It jumps up in between the "Hi" and "Testing" messages.
After message sent
As soon as you reload the data of the tableview messageTableView.reloadData(). Every row is reloaded but it is not able to calculate the actual content size somehow. You can try reloading the data in the main queue.
DispatchQueue.main.async {
messageTableView.reloadData()
}
If it still doesn't works, you can add one more thing to the above code. Suppose you have an array of messages in your class. var messages then you can use this code to display data correctly in the tableview.
DispatchQueue.main.async {
messageTableView.reloadData()
if messages.count > 0 {
let indexPath = IndexPath(row: messages.count - 1, section: 0)
self.messageTableView.scrollToItem(at: indexPath, at: .bottom, animated: true)
}
}
Hope this helps. Happy coding.

Cannot work out vertically expandable UITableViewCells to show buttons

I have my UITableViewCells working fine. I want to be able to click the menu button to expand the height of the cell to reveal a set of buttons as per the image:
I have code as follows:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == Globals.selectedRowIndex {
return 335
} else {
return 125
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if indexPath.row == Globals.selectedRowIndex {
Globals.selectedRowIndex = -1
} else {
Globals.selectedRowIndex = indexPath.row
}
tableView.reloadData()
}
The buttons all get squished together on the smaller size though. I thought they would just be cropped out of the view. I think I can get around this by having them hide on click. I haven't tried this yet.
I also cannot set a height for them as my constraints seem to not be available for anything inside the cell. I don't know why. I can set autoresizing but not constraints.
I think though there's probably a completely better way to do this which I'm missing. Any pointers will be greatly appreciated.
First iterate upto tableCount and assign false. For Example,
for i=0;i<tablecount;i++
{
self.isSelected.append(false)
}
For Example, If you have 5 cells means,You will be having Values like this:
isSelected = [false, false, false, false, false]
Then, While Expanding Cells Assign True.Otherwise assign False
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedIndex[indexPath.row] = indexPath.row;
if isSelected[indexPath.row]
{
isSelected[indexPath.row] = false
self.tableView.reloadRowsAtIndexPaths([tableView.indexPathForSelectedRow!], withRowAnimation: .Fade)
}
else
{
isSelected[indexPath.row] = true
self.tableView.reloadRowsAtIndexPaths([tableView.indexPathForSelectedRow!], withRowAnimation: .Fade)
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if isSelected[indexPath.row]
{
return 275.0
}
else
{
return 110.0
}
}

Swift What is the best way to replace content in TableViewCell

I want to do something like in the GIF
I tried 2 ways, one was hiding the elements on selecting the row and showing others, but that's not really elegant and doesn't work very well
and second was creating 2 views, one with labels, another with buttons, adding them as subviews to cell.contentView but that caused some issues with other cells as they were displaying wrong data. How can I recreate something like this?
I think something like this would work:
Use 2 different UITableViewCells: add them to the table view in your storyboard and design them separately, also you can use 2 different UITableViewCell subclasses for them
Have an array in the tableview's datasource class that will define the type of the cell from each row (e.g. the simplest solution would be an array of integers, with values: 0 representing the first cell, 1 representing the second cell)
Initialise that array with 0s for each row
In tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell :
if cellTypes[indexPath.row] == 0 --> return a cell of first type
if cellTypes[indexPath.row] == 1 --> return a cell of the second type
In tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) :
switch the cell type in the array
reload the row with animation, e.g. you can use .fade or .left or .right etc.
tableView.reloadRows(at: [indexPath], with: .fade)
EDIT: Your solution is also a good one, but it can cause problems when the cells are dequeued, so if a cell with the wrong subviews is dequeued then you need to switch the subviews back in the cellForRowAt indexPath datasource method.
EDIT2: I took some time and I have tried my solution in Xcode. Here is the code of my tableview controller:
class TableViewController: UITableViewController {
private var cellTypes: [Int] = [1, 1, 1, 1, 1, 1]
public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.cellTypes.count
}
public override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 56.0
}
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if self.cellTypes[indexPath.row] == 1 {
return tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath)[![enter image description here][1]][1]
} else {
return tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath)
}
}
public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if self.cellTypes[indexPath.row] == 1 {
self.cellTypes[indexPath.row] = 2
tableView.reloadRows(at: [indexPath], with: .fade)
} else {
self.cellTypes[indexPath.row] = 1
tableView.reloadRows(at: [indexPath], with: .right)
}
}
}
And here is how it is working in the iOS simulator:
I believe you are on the right track about creating 2 separate views inside the cell; one for showing 3 buttons (Play Now, Play Next etc.) and, one for showing the song's details (song name, singer name etc.).
In order not to mess with frames or constraints (in case you are using Autolayout), the main trick here is to create a snapshot of the view containing the buttons and move it to the end of the cell.
As I said above, you should have 2 separate views. I'll call them:
infoView: View that has 2 labels showing the song's and the singer's name.
actionsView: View that has 3 buttons for play actions. (Now, Next, Last etc.)
Here are things that you should do when user taps on a cell:
Check if cell is not selected. If it is not, then hide infoView and show actionView.
If cell is selected:
Deselect the cell.
Create a snapshot out of actionsView, set its frame accordingly so it'll shadow the real actionsView.
Set actionView's isHidden property to true.
Set infoView's isHidden property to false.
Set frame.origin.x value of the snapshot to contentView's maxX in an animation block so it'll move to the right side of the cell smoothly.
At the end of the animation, remove the snapshot from view hierarchy.
I've created a cell class and defined a method that executes those steps:
public class SongCell: UITableViewCell {
#IBOutlet fileprivate weak var infoView: UIView!
#IBOutlet fileprivate weak var actionsView: UIView!
...
public func showActions(_ show: Bool) {
switch show {
case true:
infoView.isHidden = true
actionsView.isHidden = false
case false:
if let snapshot = actionsView.snapshotView(afterScreenUpdates: true) {
snapshot.frame = actionsView.frame
contentView.addSubview(snapshot)
actionsView.isHidden = true
infoView.isHidden = false
UIView.animate(withDuration: 0.25, animations: {
snapshot.frame.origin.x = self.contentView.frame.maxX
}, completion: { _ in
snapshot.removeFromSuperview()
})
}
else {
infoView.isHidden = false
actionsView.isHidden = true
}
}
}
}
Here is how it looks on my simulator:
You can download the project from here.

TableView Auto Scrolling misbehaving after adding cells

View Setup:
My TableView has 3 sections with 4 or 9 cell each. Each Cell has a Label and TextField.
On Starting to edit a cell at index 2 of each section, I reload the section which will now consist of 9 cells(update model to dequeueCell so that 5 more cells will be added).
Problem:
The tableView scrolls as expected(brings textfield to visible part of the screen) for the unexpanded state of the section. But after I add cells by beginning to edit the textfield of cell at index 2 of any section, the tableView scrolls such that it hides the textfield. The weird scrolling occurs for any cells in the tableview once any section has expanded numbers of cells. Also, while weird scroll is happening, the tableView is reloaded(which is leading to lose the focus away from textfield). I have included tableView.reloadSection(_:) in the didBeginEditing(:_) custom delegate of the cell.
I have seen this problem in iOS 9 and 10
Sorry for poor explanation. Thanks
Heres the Github Repo
And Problem is here
P.S. I am using Swift 3 and Xcode 8.3.3 with deployment target iOS 10
Please do not suggest answer in Swift 4 and Xcode 9
You can try another approach: change the height of cells instead of insert / delete.
Change number of cells to always return all items:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let sectionEnum = Sections(rawValue: section) else { return 0 }
return sectionEnum.getRows(forExpanded: true).count
}
Set height of 'hidden' items to 0:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let sectionEnum = Sections(rawValue: indexPath.section) else { return 0 }
let isExpanded = expandedSectionData[indexPath.section]
if (!isExpanded) {
let object = sectionEnum.getRows(forExpanded: true)[indexPath.row]
if (!sectionEnum.getRows(forExpanded: false).contains(object)) {
return 0;
}
}
return self.tableView.estimatedRowHeight
}
Set cell to clip subviews to its bounds:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
....
cell.clipsToBounds = true;
return cell
}
And change updating code to (remove tableView.reloadSections, change indexPath):
func didBeginEditing(textField: UITextField, cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: cell), let section = Sections(rawValue: indexPath.section) else { return }
if indexPath.row == 7 && !expandedSectionData[indexPath.section] {
expandedSectionData[indexPath.section] = true
tableView.beginUpdates()
tableView.endUpdates()
tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.none, animated: true)
textField.becomeFirstResponder()
}
}
You need to make textfield as first responder again, after reloading section text field no longer remains first responder.
You might need to change something like -
func didBeginEditing(textField: UITextField, cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
if indexPath.row == 2 && !expandedSectionData[indexPath.section] {
tableView.beginUpdates()
expandedSectionData[indexPath.section] = true
tableView.reloadSections(IndexSet(integer: indexPath.section), with: .automatic)
tableView.endUpdates()
// after tableview is reloaded, get cell again
let cell = tableView.cellForRow(at: IndexPath(row: 2, section: indexPath.section)) as? TestCell
cell?.textField.becomeFirstResponder()
}
}
I have tried running this, kind of looks fine to me.
This issue has to do with your use of self-sizing tableview cells. To fix the issue, comment out these two lines in your viewDidLoad and consider defining the height of your cells with tableView:heightForRowAtIndexPath:.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
Since the self-sizing tableview documentation states,
To define the cell’s height, you need an unbroken chain of constraints
and views (with defined heights) to fill the area between the content
view’s top edge and its bottom edge
I also tried changing the bottomMargin = textField.bottom constraint from priority 750 to 1000, but this did not fix the issue.

Resources