TableView animation on selection - ios

I got this code - I want to animate change in width of a UIView of custom table view cell - here is what I implemented in the CustomTableViewCell class:
var expanded: Bool = false
override func layoutSubviews() {
super.layoutSubviews()
if !expanded {
horizontalProgressView.hidden = true
} else {
horizontalProgressView.hidden = false
horizontalProgressView.frame.size.width = 100.0
}
}
Then in my viewController which has tableView and a delegate/datasource implemented I try:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomTableViewCell
cell.expanded = true
}
When I select the cell the the horizontalProgressView is visible (so the .hidden part is working) but the width is still the same I set in my storyboard. What am I missing?

Related

UITableView scroll to wrong position when keyboard shows

Although UITableViewController can automatically adjust table view when keyboard shows, it's not flexible enough. I try to use a UIViewController and UITableView to build my UI.
There are many cells in the table view. Among all the cells, there is a cell with a UITextField. When I tap that text field, the keyboard shows and the table view does not do anything even if the cell is overlaid by the keyboard. It's OK because this is the expected result.
The strange thing comes. If I give the table view a large contentInset.bottom, e.g. contentInset.bottom = 600, the table view will automatically scroll when keyboard shows.
I try to aviod using tableView.contentInsetAdjustmentBehavior = .never.
The following code shows this strange behavior. It can be reproduced on iOS 14.5, iPhone 12 mini Simulator.
class TestViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
override func loadView() {
view = tableView
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tableView.delegate = self
tableView.dataSource = self
tableView.contentInset.bottom = 600
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("scrolling contentOffset-Y: \(scrollView.contentOffset.y)")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Ugly code, only for showing the problem.
let cell = UITableViewCell()
if indexPath.row == 9 {
let textField = UITextField()
cell.contentView.addSubview(textField)
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.leadingAnchor),
textField.trailingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.trailingAnchor),
textField.topAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.topAnchor),
textField.bottomAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.bottomAnchor),
])
} else {
cell.textLabel?.text = "\(indexPath)"
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
You should add UITextfield delegate to the textfield in the tableview cell to check if keyboard has been launched and let it check for you as shown by the example below:
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
var pointInTable:CGPoint = textField.superview!.convertPoint(textField.frame.origin, toView:_tableview)
var contentOffset:CGPoint = _tableview.contentOffset
contentOffset.y = pointInTable.y
if let accessoryView = textField.inputAccessoryView {
contentOffset.y -= accessoryView.frame.size.height
}
_tableview.contentOffset = contentOffset
return true;
}
Hope that one helps.

Two tableViews in same ViewController won't show in swift 3

I am trying to have two UITableViews in the same ViewController. I am trying to do all of it programmatically but for some reason neither of the tableViews display at all.
I have noticed that the code never gets to
else if tableView == self.tableView2 {
}
in the cellForRowAt method. I have no idea why this is the case.
I appreciate any help to solve this.
import UIKit
class TestViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView1: UITableView?
var tableView2: UITableView?
let tableView1Data = ["Option 1", "Option 2", "Option 3", "Option 4", "Other"]
let tableView2Data = ["Cancel"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .apricot
// Initalize
tableView1 = UITableView()
tableView2 = UITableView()
// Register cells
// tableView1!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell1")
// tableView2!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell2")
tableView1!.register(UINib(nibName: "yourNib", bundle: nil), forCellReuseIdentifier: "Cell1")
tableView2!.register(UINib(nibName: "yourNib", bundle: nil), forCellReuseIdentifier: "Cell2")
// Set delegates
tableView1!.delegate = self
tableView1!.dataSource = self
tableView2!.delegate = self
tableView2!.dataSource = self
// Add to view
view.addSubview(tableView1!)
view.addSubview(tableView2!)
// Set size constraints
tableView1!.translatesAutoresizingMaskIntoConstraints = false
tableView1!.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView1!.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView1!.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView2!.translatesAutoresizingMaskIntoConstraints = false
tableView2!.topAnchor.constraint(equalTo: tableView1!.bottomAnchor, constant: 15).isActive = true
tableView2!.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView2!.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// Customize looks
tableView1!.layer.cornerRadius = 10
tableView2!.layer.cornerRadius = 10
// Reload data
tableView1!.reloadData()
tableView2!.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
tableView1!.reloadData()
tableView2!.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView1 {
return tableView1Data.count
}
else if tableView == self.tableView2 {
return tableView2Data.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell?
if tableView == self.tableView1 {
cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath)
guard let cell = cell else {return UITableViewCell()}
let cellLabel = UILabel()
cellLabel.text = tableView1Data[indexPath.row]
cellLabel.textColor = .black
cell.addSubview(cellLabel)
cellLabel.frame = cell.frame
}
else if tableView == self.tableView2 {
cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath)
guard let cell = cell else {return UITableViewCell()}
let cellLabel = UILabel()
cellLabel.text = tableView2Data[indexPath.row]
cellLabel.textColor = .black
cell.addSubview(cellLabel)
cellLabel.frame = cell.frame
}
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return view.frame.height * 0.1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//...
}
}
You need to somehow constrain the height of either tableView1 or tableView2 (Once you have established 1 height, the other can be inferred). At the moment you have told autolayout that the two table views must be 15 apart vertically, but not where that separation is in terms of the superview. As a result, autolayout is probably sizing tableView1 to the full height of the superview and the other tableview isn’t on screen, so it’s datasource methods don’t get called
For example, to set the table views to half the screen each:
TableView1!.bottomAnchor.constraint(equalTo:self.view.centerYAnchor, constant:-7).isActive=true
one caveat with tableView is that it's datasource method will not get called unless view has it's frame . If tableView try to render it self and doesn't find it's frame valid data source method will never get's called.
You need to check if both of the tableView has it's frame defined

UITableViewCell doesn't update height after adding a view to UIStackView

I have a UIStackView inside UITableViewCell's contentView. Based on user interaction, I add/remove items in the UIStackView. After modifying the items in UIStackView, I expect the cell to update it's height accordingly. But, it doesn't update it's height unless I call tableView.reloadData(). But, calling reloadData() in cellForRowAtIndexPath / willDisplayCell becomes recursive.
What is the proper way to adjust the cell height at run time based on items in UIStackView?
I use UITableViewAutomaticDimension
Updating the Problem:
Here is a simple prototype of what I am trying to do.
My actual problem is dequeuing the cell.
In the prototype, I have 2 reusable cells and 3 rows. For row 0 and 2, I dequeue cellA and for row 1, I dequeue cellB. Below is the overview on the condition I use.
if indexPath.row == 0 {
// dequeue cellA with 2 items in stackView
}
if indexPath.row == 1 {
// dequeue cellB with 25 items in stackView
}
if indexPath.row == 2 {
// dequeue cellA with 8 items in stackView
}
But the output is,
row 0 contains 2 items in stackView - expected
row 1 contains 25 items in stackView - expected
row 2 contains 2 items in stackView - unexpected, row 0 is dequeued
I also tried removing all arranged subViews of stackView in cellForRowAtIndexPath. But, doing so, flickers the UI when scrolling. How can I manage to get the desired output?
I believe the problem is when you are adding views to the stack view.
In general, adding elements should take place when the cell is initialized.
willDisplay cell: is where one handles modifying attributes of cell contents.
If you move your code from willDisplay cell: to cellForRowAt indexPath: you should see a big difference.
I just made that one change to the code you linked to, and the rows are now auto-sizing based on the stack view contents.
Edit: Looked at your updated code... the issue was still that you are adding your arrangedSubviews in the wrong place. And you compound it by calling reloadData().
Second Edit: Forgot to handle previously added subviews when the cells are reused.
Updated code... replace your ViewController code with:
//
// ViewController.swift
//
import UIKit
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
tableView.estimatedRowHeight = 56
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if indexPath.row == 0 || indexPath.row == 2 {
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
if let stackView = cell.viewWithTag(999) as? UIStackView {
let numberOfItemsInStackView = (indexPath.row == 0) ? 2 : 8
let color = (indexPath.row == 0) ? UIColor.gray : UIColor.black
// cells are reused, so clear out any previously added subviews...
// but leave the first view that is part of the cell prototype
while stackView.arrangedSubviews.count > 1 {
stackView.arrangedSubviews[1].removeFromSuperview()
}
// use "i" so we can count
for i in 1...numberOfItemsInStackView {
// use label instead of view so we can number them for testing
let newView = UILabel()
newView.text = "\(i)"
newView.textColor = .yellow
// add a border, so we can see the frames
newView.layer.borderWidth = 1.0
newView.layer.borderColor = UIColor.red.cgColor
newView.backgroundColor = color
let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 54)
heightConstraint.priority = 999
heightConstraint.isActive = true
stackView.addArrangedSubview(newView)
}
}
}
if indexPath.row == 1 {
cell = tableView.dequeueReusableCell(withIdentifier: "lastCell")!
if let stackView = cell.viewWithTag(999) as? UIStackView {
let numberOfItemsInStackView = 25
// cells are reused, so clear out any previously added subviews...
// but leave the first view that is part of the cell prototype
while stackView.arrangedSubviews.count > 1 {
stackView.arrangedSubviews[1].removeFromSuperview()
}
// use "i" so we can count
for i in 1...numberOfItemsInStackView {
// use label instead of view so we can number them for testing
let newView = UILabel()
newView.text = "\(i)"
newView.textColor = .yellow
// add a border, so we can see the frames
newView.layer.borderWidth = 1.0
newView.layer.borderColor = UIColor.red.cgColor
newView.backgroundColor = UIColor.darkGray
let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 32)
heightConstraint.priority = 999
heightConstraint.isActive = true
stackView.addArrangedSubview(newView)
}
}
}
return cell
}
// override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// if cell.reuseIdentifier == "cell" {
// if let stackView = cell.viewWithTag(999) as? UIStackView {
// let numberOfItemsInStackView = (indexPath.row == 0) ? 2 : 8
// let color = (indexPath.row == 0) ? UIColor.gray : UIColor.black
// guard stackView.arrangedSubviews.count == 1 else { return }
// for _ in 1...numberOfItemsInStackView {
// let newView = UIView()
// newView.backgroundColor = color
// let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 54)
// heightConstraint.priority = 999
// heightConstraint.isActive = true
// stackView.addArrangedSubview(newView)
// }
// tableView.reloadData()
// }
// }
//
// if cell.reuseIdentifier == "lastCell" {
// if let stackView = cell.viewWithTag(999) as? UIStackView {
// let numberOfItemsInStackView = 25
// guard stackView.arrangedSubviews.count == 1 else { return }
// for _ in 1...numberOfItemsInStackView {
// let newView = UIView()
// newView.backgroundColor = UIColor.darkGray
// let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 32)
// heightConstraint.priority = 999
// heightConstraint.isActive = true
// stackView.addArrangedSubview(newView)
// }
// tableView.reloadData()
// }
// }
// }
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
}
Try to reload only the cell using: https://developer.apple.com/documentation/uikit/uitableview/1614935-reloadrows
Example code
Here is an example. We have basic table view cells (TableViewCell) inside a view controller. The cells have 2 labels inside a stack view. We can hide or show the second label using the collapse/reveal methods.
class TableViewCell : UITableViewCell {
#IBOutlet private var stackView: UIStackView!
#IBOutlet private var firstLabel: UILabel!
#IBOutlet private var secondLabel: UILabel!
func collapse() {
secondLabel.isHidden = true
}
func reveal() {
secondLabel.isHidden = false
}
}
class ViewController : UIViewController {
#IBOutlet var tableView: UITableView!
fileprivate var collapsedCells: Set<IndexPath> = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 128
}
#IBAction private func buttonAction(_ sender: Any) {
collapseCell(at: IndexPath(row: 0, section: 0))
}
private func collapseCell(at indexPath: IndexPath) {
if collapsedCells.contains(indexPath) {
collapsedCells.remove(indexPath)
} else {
collapsedCells.insert(indexPath)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
extension ViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
if collapsedCells.contains(indexPath) {
cell.collapse()
} else {
cell.reveal()
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}

Remove the UIlabel text from table view cells

i Have a table view and use custom cells for this. now i have set a clear button in my Bar. now on click of that UIBarButton i want to clear all the text inside the text field in the cells. How can i do this..??
var DataSource = [NewAssessmentModel]()
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.DataSource.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let model = self.DataSource[indexPath.row]
switch(model.assessmentControlType)
{
case .text:
let cell = (tableView.dequeueReusableCellWithIdentifier("QuestionWithTextField", forIndexPath: indexPath) as? QuestionWithTextField)!
cell.model = model
cell.indexPath = indexPath
cell.txtAnswer.delegate = self
cell.lblQuestion.text = model.labelText
cell.indexPath = indexPath
return cell
}
}
now cell contains a txtAnswer as a UITextField. How can i clear the text fields of txtAnswer.
for clearing the fields:
func clearView(sender:UIButton)
{
print("Clear Button clicked")
}
The above code applies only for the cells that are visible.The cell values won't be cleared if it is not visible in the phone.
For this you need to loop through every table view cells. I think this one is the one of the good option for you.
func clearView(sender:UIButton)
{
print("Clear Button clicked")
for view: UIView in tableView.subviews {
for subview: Any in view.subviews {
if (subview is UITableViewCell) {
let cell = subview as? UITableViewCell
// do something with your cell
if let questioncell = cell as? QuestionWithTextField
{
questioncell.txtField.text = ""
}
// you can access any cells
}
}
}
}
You can get all visible cell of tableView.
#IBAction func deleteText(_ sender: Any) {
for cell in tableView.visibleCells {
if let questionCell = cell as? QuestionWithTextField {
// Hide your label here.
// questionCell.lblQuestion.hidden = true
}
}
}

Make UITableViewCell actions overlap content

I was trying to go about making the actions for a UITableViewCell overlap the content of the cell, like in the National Geographic app:
table view
table view when swiped
I tried using a listener on the contentView of the cell to track the frame and keep it constant, but I was unable to get that to work (although it's possible it would, I'm kinda new to iOS).
If anyone has any suggestions for creating a similar effect, they would be much appreciated!
u can make your own custom cell and add a delete button and swipe gestures to make like button overlap the contents of the cell for example, try it out yourself, first create a sample project with single view app, and proceed
subclass the tabview cell with xib option selected and name it something like CustomCell , and in CustomCell.swift class past below code
import UIKit
class CustomCell: UITableViewCell {
var deleteButton:UIButton!
//create a custom cell if not in the reuse pool
class func customCell() -> CustomCell?
{
let aVar:Array = NSBundle.mainBundle().loadNibNamed("CustomCell", owner: nil, options: nil)
if aVar.last!.isKindOfClass(UITableViewCell)
{
return aVar.last as? CustomCell
}
return nil
}
//handle your left swipe
func swipeLeft()
{
print("swipe Left")
self.contentView.bringSubviewToFront(deleteButton!)
var frameRect:CGRect! = deleteButton!.frame
frameRect.origin.x = self.contentView.bounds.size.width - self.deleteButton!.frame.size.width
UIView.animateWithDuration(0.5) { () -> Void in
self.deleteButton!.frame = frameRect
}
}
//aslo the right swipe
func swipeRight()
{
print("swipe right")
var rect:CGRect! = deleteButton?.frame
rect.origin.x = self.contentView.bounds.size.width
UIView.animateWithDuration(0.5) { () -> Void in
self.deleteButton!.frame = rect
}
}
//hear we are adding the delete button in code, if u want add it in xib or (if u are using storyboard )
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
deleteButton = UIButton.init(type: .Custom)
deleteButton.setTitle("Delete", forState: .Normal)
deleteButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
deleteButton.backgroundColor = UIColor.redColor()
self.contentView.addSubview(deleteButton)
let gestureLeft:UISwipeGestureRecognizer = UISwipeGestureRecognizer.init(target: self, action: "swipeLeft")
gestureLeft.direction = .Left
self .addGestureRecognizer(gestureLeft)
let gestureRight:UISwipeGestureRecognizer = UISwipeGestureRecognizer.init(target: self, action: "swipeRight")
gestureRight.direction = .Right
self .addGestureRecognizer(gestureRight)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
//set the initial frame of delete button
override func layoutSubviews() {
super.layoutSubviews()
var rect:CGRect! = deleteButton?.frame
rect.size = CGSizeMake(100, 100)
rect.origin.x = self.contentView.bounds.size.width
deleteButton?.frame = rect
}
}
and make sure the tableview cell height to be of 100pt and in view controller set up a tableview in storyboard with datasource and delegate and implement the required delegate and datasource methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomCell? = tableView.dequeueReusableCellWithIdentifier("CELL") as? CustomCell
if cell == nil
{
cell = CustomCell .customCell()
}
return cell as CustomCell!
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100
}

Resources