UITableView scroll to wrong position when keyboard shows - ios

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.

Related

How to make stackview disappear when scrolling down and re-appear when scrolling up?

I created a stack view with 2 buttons called
Services and Customize
My goal is to make this stack view disappear when I scroll down and make it appear again when I scroll up. Everything works as it should be when I scroll down but when I scroll up, the stack view, I named it
menuStack
it overlaps with the tableview. The tableView is the one that contains images of a shoe as you can see in the screenshot.
I tried this delegate for scrollview but I think something is missing in my anchors. Here are 3 screenshots of each events.
This is the default view
When I scroll down
When I scroll up the menuStack does not reappear
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
self.menuStack.translatesAutoresizingMaskIntoConstraints = false
if targetContentOffset.pointee.y < scrollView.contentOffset.y {
//scroll up
self.menuStack.isHidden = true
if #available(iOS 11.0, *) {
let safeGuide = self.view.safeAreaLayoutGuide
self.menuStack.topAnchor.constraint(equalTo: safeGuide.topAnchor).isActive = true
self.menuStack.bottomAnchor.constraint(equalTo: self.tableView.topAnchor, constant: 50).isActive = true
self.tableView.topAnchor.constraint(equalTo: safeGuide.topAnchor, constant: 100).isActive = true
} else {
// Fallback on earlier versions
}
self.menuStack.isHidden = false
} else {
//scroll down
self.menuStack.isHidden = true
if #available(iOS 11.0, *) {
let safeGuide = self.view.safeAreaLayoutGuide
self.tableView.topAnchor.constraint(equalTo: safeGuide.topAnchor).isActive = true
} else {
// Fallback on earlier versions
}
}
}
It seems like the scrollViewDidEndDragging function is called when the user lifts their finger off the tableView, this happens before the tableView starts it's deceleration animation. I think you should use scrollViewDidScroll instead. I tried this and it's working for me:
class ViewController: UIViewController {
#IBOutlet weak var menuStackView: UIStackView!
#IBOutlet weak var tableViewTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
}
extension ViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
menuStackView.isHidden = scrollView.contentOffset.y > CGPoint.zero.y
tableViewTopConstraint.constant = (scrollView.contentOffset.y > CGPoint.zero.y) ? 0 : 40
//CGPoint.zero.y is when the contentOffset is at the top
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
}
I'll post a screenshot of the UI as well so you can see how my design is set up.

UITextView inside tableview not resizing according to content

I have a tableView and a UITextView inside it. I am trying to add data from JSON to the table view but it is not shrinking/expanding according to size. I have tried a lot of forums and methods.
The main problem is - if it starts expanding/shrinking with height, it stops shrinking/expanding and vice versa. Bot height and width are not working.
This is because, when I set the trailing and leading constraints of the UITextView to the cell, it starts working with height but stops working with width. When I remove the leading/trailing constraints, the content of the UITextView goes beyond the screen and does not come as multiline i.e. does not expand with height. I have tried -
How do I size a UITextView to its content?
How to resize table cell based on textview?
and a lot many like these.
A little of my code-
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Message") as! TextViewCell
cell.customTextView.textColor = UIColor.white
// Give a source to table view cell's label
cell.customTextView.text = requestResponseArr[indexPath.row]
cell.translatesAutoresizingMaskIntoConstraints = true
cell.sizeToFit()
if (indexPath.row % 2 == 0) {
cell.customTextView.backgroundColor = ConstantsChatBot.Colors.iMessageGreen
cell.customTextView.textAlignment = .right
} else {
cell.customTextView.backgroundColor = ConstantsChatBot.Colors.ButtonBlueColor
cell.customTextView.textAlignment = .left
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
And in viewDidLoad()-
override func viewDidLoad() {
// RandomEstimatedRowHeight
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 300
}
I do not want the textview to be editable. Please reply with swift as I am not familiar with Objective C. Thanks
EDIT: Custom cell code
class TextViewCell: UITableViewCell {
#IBOutlet weak var customTextView: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
customTextView.isScrollEnabled = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I have tried with UITextView inside UITableViewCell.
Constraints
UITextView, top, bottom and right as 2, 2 and 5, Width as 50. Font Size as 13, Alignment as Center. Give Outlet connection for Width constraints as txtVwWidthConst
UIViewController
#IBOutlet weak var tblView: UITableView!
var textWidthHeightDict = [Int : CGSize]()
override func viewDidAppear(_ animated: Bool) {
stringValue = [0 : "qwer\nasdasfasdf", 1 : "qwe", 2 : "123123\nasdas\nwqe", 3 : "q\n3\n4", 4 : "klsdfjlsdhfjkhdjkshfjadhskfjhdjksfhkdjsahfjksdhfkhsdfhjksdfjkasklsdfjlsdhfjkhdjkshfjadhskfjhdjksfhkdjsahfjksdhfkhsdfhjksdfjkasklsdfjlsdhfjkhdjkshfjadhskfjhdjksfhkdjsahfjksdhfkhsdfhjksdfjkas"]
for i in 0..<stringValue.count
{
GettingTextViewSize(getStr: stringValue[i]!, fontSize: 14, loopValue: i)
}
tblView.reloadData()
}
func GettingTextViewSize(getStr : String, fontSize: CGFloat, loopValue : Int)
{
var textSize = (getStr as! NSString).size(withAttributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: fontSize)])
if textSize.width > self.tblView.frame.width
{
// IF STRING WIDTH GREATER THAN TABLEVIEW WIDTH
let multipleValue = textSize.width / self.tblView.frame.width
textSize.height = (textSize.height * (multipleValue + 1.0))
textSize.width = self.tblView.frame.width
textWidthHeightDict[loopValue] = textSize
}
else
{
textSize.height = textSize.height + 10 //ADDING EXTRA SPACE
textSize.width = textSize.width + 10 //ADDING EXTRA SPACE
textWidthHeightDict[loopValue] = textSize
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stringValue.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "table", for: indexPath) as! TblTableViewCell
cell.backgroundColor = cellBGColr[indexPath.row]
cell.txtVw.inputAccessoryView = toolBar
cell.txtVw.text = stringValue[indexPath.row]
cell.txtVw.tag = indexPath.row
cell.txtVwWidthConst.constant = (textWidthHeightDict[indexPath.row]?.width)!
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var heightVal = (textWidthHeightDict[indexPath.row])
return heightVal!.height + 8 //ADDING EXTRA SPACE
}
UITableViewCell
class TblTableViewCell: UITableViewCell {
#IBOutlet weak var txtVw: UITextView!
#IBOutlet weak var txtVwWidthConst: NSLayoutConstraint!
// TEXTVIEW WIDTH CONSTRAINTS
override func awakeFromNib() {
super.awakeFromNib()
}
}
Output
Note
UITextView only, we have to calculate String size. But, in UILabel, this concept is very simple. Let me know, if you have any queries.
Disable UITextView isScrollEnabled is false inside cellForRowAt
Demo Example
You need to disable UItextView Scrolling for that.
textView.isScrollingEnabled = false
and in the cell add top, bottom, right and left constraints of textview.
And add height constraint >= 10
You need to set all four constraints for textview i.e. leading, trailing, top and bottom lets say all are set to 8 from margin.
It will look something like below:
check demo on GitHub here

UiView fixed on top of UiTableViewController

I need to put an UIView fixed on top of UITableViewController (like a header). I've tried this:
override func scrollViewDidScroll (scrollView: UIScrollView) {
var fixedFrame: CGRect = self.uiTopView.frame;
fixedFrame.origin.y = scrollView.contentOffset.y;
self.uiTopView.frame = fixedFrame;
}
But it does not work and I don't know why. Someone have any idea?
This can not be done, one way to accomplish this is to add the UITableViewController insideUIContainerView
So the structure will be as follows:
ViewController1 contains aUIContainerView this container view has embedded segue
to your tableViewController.
Then you can add the view to the ViewController1.
Why do you actually use UITableViewController instead of UIViewController with a tableView inside?
Maybe you should add your header view first then add you tableview depending on the header's frame.
for example: `import UIKit
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var fixedLabel : UILabel!
var tableView : UITableView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tableView.frame = CGRectMake(0, self.fixedLabel.frame.maxY, self.view.frame.width, self.view.frame.height-70)
self.fixedLabel.frame = CGRectMake(0,0,self.view.bounds.width,70)
}
override func viewDidLoad() {
super.viewDidLoad()
self.fixedLabel = UILabel()
self.fixedLabel.backgroundColor = UIColor.blueColor()
self.fixedLabel.text = "This is a fixedLabel"
self.fixedLabel.textAlignment = .Center
self.tableView = UITableView()
self.tableView.delegate = self
self.tableView.dataSource = self
self.view.addSubview(fixedLabel)
self.view.addSubview(tableView)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("cell")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
}
cell?.textLabel?.text = "Your text"
return cell!
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
}
`

When I swipe a UITableViewCell, the header view moves as well

So, I have a few Swipe actions like delete, block, etc in my UITableView. I wanted to add headers to separate my two sections. So, I added a prototype cell, named it HeaderCell and then went to the view. I added one label, named headerLabe. My problem is that when I swipe for the actions, the header cells were moving as well, which looked bad. I researched, and found a solution to just return the contentView of the cell. However, when I do this, the label has not shown up. I have tried a dozen different solutions, and nothing has worked, so I have turned to SO. Can anyone help me?
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell : CustomHeaderTableViewCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! CustomHeaderTableViewCell
if section == 0 {
headerCell.headerLabel.text = "Thank You's"
} else if section == 1 {
headerCell.headerLabel.text = "Conversations"
}
return headerCell.contentView
}
Thanks so much.
You can use a section Header as #ozgur suggest.If you still want to use a cell.
Refer to this datasource method
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath = YourHeaderCellIndexPath{
return false
}
return true
}
check the following methods
In your UIViewController use the following
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! WishListHeaderCell
headerCell.lblTitle.text = cartsData.stores_Brand_Name
let imgVw = UIImageView()
imgVw.frame = CGRectMake(8, 18, 25, 25)
imgVw.image = UIImage(named: "location.png")
let title = UILabel()
title.frame = CGRectMake(41, 10, headerCell.viwContent.frame.width - 49, 41)
title.text = cartsData.stores_Brand_Name
title.textColor = UIColor.whiteColor()
headerCell.viwContent.addSubview(imgVw)
headerCell.viwContent.addSubview(title)
return headerCell.viwContent
}
In your UITableViewCell use the following
import UIKit
class HeaderCell: UITableViewCell {
#IBOutlet weak var viwContent: UIView!
#IBOutlet weak var imgIcn: UIImageView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.viwContent.backgroundColor = UIColor.grayColor()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//UITableViewCell
let headerCell = tableView.dequeueReusableCellWithIdentifier("headerCell") as! SecJobCCHeaderTableViewCell
// Cell Rect
var cellRect : CGRect = headerCell.frame
cellRect.size.width = screenBounds.width
// Header Footer View
let headerFooterView = UITableViewHeaderFooterView(frame : cellRect)
//Adding Gesture
let swipeGestRight = UISwipeGestureRecognizer(target: self, action:#selector(AddSecJobCostCentreViewController.draggedViewRight(_:)))
swipeGestRight.enabled = true
swipeGestRight.direction = UISwipeGestureRecognizerDirection.Right
headerFooterView.addGestureRecognizer(swipeGestRight)
// Update Cell Rect
headerCell.frame = cellRect
// Add Cell As Subview
headerCell.tag = 1000
headerFooterView.addSubview(headerCell)
// Return Header Footer View
return headerFooterView
}
func draggedViewRight(sender:UISwipeGestureRecognizer) {
// Swipe Gesture Action
let currentHeaderView = sender.view?.viewWithTag(1000) as! SecJobCCHeaderTableViewCell
}

Programmatically added UITableView won't populate data

I wish to create UITableView and a custom cell, and add them to my UIViewController.
If I layout UITableView from the storyboard onto my UIViewController, everything works fine with custom cell.
But I realized that animation regarding its height is not very smooth - sometimes the table view disappears
So I decided to create/destroy a UITableView everytime I wish to do some animation, and add it as a subview to my UIViewController.
But the programmatically-added UITableView won't populate data.
What am I doing wrong here?
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var dataArr = [String]() // Holds data for UITableView
var tv: UITableView! // Notice it's not #Outlet
override func viewDidLoad() {
super.viewDidLoad()
dataArr = ["one", "two", "three"]
// Create and add UITableView as drop down
tv = UITableView()
tv.delegate = self
tv.dataSource = self
createTableView()
}
func createTableView() {
let w = UIScreen.mainScreen().bounds.size.width
tv.frame = CGRectMake(0, 50, w, 0)
tv.rowHeight = 25.0
// Register custom cell
var nib = UINib(nibName: "searchCell", bundle: nil)
tv.registerNib(nib, forCellReuseIdentifier: "searchCell")
self.view.addSubview(tv)
UIView.animateWithDuration(0.2, delay: 0.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {
self.tv.frame.size.height = 100
}, completion: { (didFinish) in
self.tv.reloadData()
})
}
func destroyTableView() {
UIView.animateWithDuration(0.2, animations:
{
// Hide
self.tv.frame.size.height = 0
}, completion: { (didFinish) in
self.tv.removeFromSuperview()
})
}
// MARK: - UITableView
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArr.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: SearchCell = self.tv.dequeueReusableCellWithIdentifier("searchCell") as! SearchCell
cell.cellLabel.text = dataArr[indexPath.row]
return cell;
}
}
I think the problem is with this line.
self.tv.frame.size.height = 100
Try setting the whole frame instead of just the height. Pretty sure a UIView's Frame's properties are read only.
See here
If that doesn't work. Maybe try implementing
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat

Resources