UITableView load wrong cell height Swift - ios

I'm doing with Xcode 10. I have a tableview in ViewController.
The cell has label which is set as leading, tralling, top and bottom, line is 0
In ViewDidload() I added
override func viewDidLoad() {
super.viewDidLoad()
tableview.rowheight = UITableView.automaticDimension
tableview.estimateheight = 98
tableview.reloaddata()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CommentCell", for: indexPath) as! CommentCell
let idcmt: String
idcmt = self.CommentIDArray[indexPath.row]
ref.child("Post_theme/\(userid!)/\(id)/comment/\(idcmt)").observe( .value, with: {(snapshot) in
let value = snapshot.value as? NSDictionary
cell.Comment.text = value!["content"] as? String
})
// I have a button in the custom cell but tableview load height wrong even I did not click the button on cell
self.ref.child("Post_theme/\(self.userid!)/\(self.id)/comment/\(idcmt)/likecomment").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild(self.userid!){
cell.Heartcommentaction = {
cell.HeartCommentbutton.tintColor = .lightGray
self.TableCommentView.reloadData()
}
}
})
return cell
}
class CommentCell: UITableViewCell{
var Heartcommentaction : (() -> ()) = {}
#IBOutlet weak var Comment: UILabel!
#IBOutlet weak var HeartCommentbutton: UIButton!
#IBAction func HeartCommentaction(_ sender: Any) {
Heartcommentaction()
}
}
But when I click to Viewcontroller, tableview did not load the right height cell ( cell which is needed to autorize is not, cell which is not needed is resize)
Then I add this code
override func viewDidAppear(_ animated: Bool) {
tableview.reloadData()
}
the code run well just for initial few cells, but when I scroll down more (I have around over 100 cells), it's wrong height again, when I scroll up the initial cells get again wrong
I looked many solutions but not working for me
Really need help! thank you
UPDATE
this is the screenshot of wrong height cell, some timewrong, sometime right

Use :
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 98
}
Also remove tableview.estimateheight = 98

Give this a shot. My code here may not compile because I don't have your full source code. Remember, cellForRow should synchronously set all values on your cell. And you don't want to call reloadData because you will see glitches when scrolling. You want to call reloadRows. See below and see if that helps:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CommentCell", for: indexPath) as! CommentCell
let idcmt: String
idcmt = self.CommentIDArray[indexPath.row]
cell.Comment.text = "..."
cell.Heartcommentaction = {}
ref.child("Post_theme/\(userid!)/\(id)/comment/\(idcmt)")
.observe( .value, with: {(snapshot) in
let value = snapshot.value as? NSDictionary
if let value = value, let indexPath = tableView.indexPath(for: cell) {
cell.Comment.text = value!["content"] as? String
tableView.reloadRows(at: [indexPath], .fade)
} else {
print("nope")
}
})
self.ref.child("Post_theme/\(self.userid!)/\(self.id)/comment/\(idcmt)/likecomment")
.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild(self.userid!){
if let indexPath = tableView.indexPath(for: cell) {
cell.Heartcommentaction = {
cell.HeartCommentbutton.tintColor = .lightGray
tableView.reloadRows(at: [indexPath], .fade)
}
} else {
print("bad indexPath")
}
} else {
print("no child")
}
})
return cell
}

Related

Swift TableView insert row below button clicked

I am new to Swift and I am using Swift 4.2 . I have a TableView with a label and button . When I press a button I would like to add a new row directly below the row in which the button was clicked . Right now when I click a button the new row gets added to the bottom of the TableView every time. I have been looking at posts on here but haven't been able to get it working this is my code base . I have a method called RowClick I get the indexpath of the row that was clicked but do not know how to use that to get the new row to appear directly below the clicked row .
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
InsertVideoTitle(indexPath: indexPath)
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
This is how my table looks I clicked the Facebook Rows button and it appended the string SnapChat . The Snapchat label should appear in a row below Facebook instead . Any suggestions would be great !
I think the easiest solution without re-writing this whole thing would be adding 1 to the current row of the IndexPath you captured from the action.
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
I did this from memory because I am not near an IDE, so take a look at the change and apply that change if needed in any other location.
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
Your current code calls append to add the new item at the end of the array. What you want to do is insert a new row at indexPath.row+1. Array has an insert(element,at:) function.
You have to handle the case where the user has tapped the last row and not add 1 to avoid an array bounds error:
func InsertVideoTitle(indexPath: IndexPath)
{
let targetRow = indexPath.row < videos.endIndex ? indexPath.row+1 : indexPath.row
videos.insert("Snapchat" at:targetRow)
let newIndexPath = IndexPath(row: targetRow, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [newIndexPath], with: .automatic)
TableSource.endUpdates()
}

UITableView SIGBRT error when trying to "swipe left to delete " at indexpath.row

I have a simple app that populates a UITableView based on data inputed in a different ViewController. I am trying to implement the "swipe left to delete"
My problem is that this UITableView is a dropdown table view. That is when I click on one cell of the UITableView the cells open up and show me the internal cells associated with that one cells.
I think I am missing something simple as my code to delete the row does not work, it just throws a SIGBRT error. I think because maybe I trying to remove the wrong array maybe? I think it is messed up because it is a dropdown UITableView, so I am left with a bunch of extra UITableview rows?
Code to added delete button and remove selected row.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// remove the item from the data model
tableViewData.remove(at: indexPath.row)
// delete the table view row
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Whole code in the UITableViewController is as follows:
import Foundation
import UIKit
private let reuseidentifier = "Cell"
struct cellData {
var opened = Bool()
var title = String()
var exerciseData = [String]()
var repsSetsData = [String]()
}
//here
struct Contact {
var fullname: String
var exercises : [Exercise]
}
class Exercise : NSObject , NSSecureCoding{
static var supportsSecureCoding: Bool = true
var excerciseName: String
var excerciseReps: String
var excerciseSets: String
init(Name : String, Reps : String, Sets : String) {
excerciseName = Name
excerciseReps = Reps
excerciseSets = Sets
}
func encode(with aCoder: NSCoder) {
aCoder.encode(excerciseName, forKey: "excerciseName")
aCoder.encode(excerciseReps, forKey: "excerciseReps")
aCoder.encode(excerciseSets, forKey: "excerciseSets")
}
required convenience init?(coder aDecoder: NSCoder) {
let excerciseName = aDecoder.decodeObject(forKey: "excerciseName") as! String
let excerciseReps = aDecoder.decodeObject(forKey: "excerciseReps") as! String
let excerciseSets = aDecoder.decodeObject(forKey: "excerciseSets") as! String
self.init(Name: excerciseName, Reps: excerciseReps, Sets: excerciseSets)
}
}
class ContactController: UITableViewController {
//new
var tableViewData = [cellData]()
var contacts = [Contact]()
override func viewDidLoad() {
super.viewDidLoad()
//getting data from CoreData
self.contacts = CoreDataManager.sharedInstance.retrieveDataFromCoreData()
tableView.register(UINib(nibName: "ExerciseCell", bundle: nil), forCellReuseIdentifier: "ExerciseCell")
for contact in contacts{
var exerciseData = [String]()
var repsSetsData = [String]()
for exercise in contact.exercises{
let name = exercise.excerciseName
let sets = exercise.excerciseSets
let reps = exercise.excerciseReps
exerciseData.append(name)
repsSetsData.append("Reps: " + reps + " Sets: " + sets)
}
self.tableViewData.append(cellData.init(opened: false, title: contact.fullname, exerciseData:exerciseData, repsSetsData: repsSetsData))
}
self.tableView.reloadData()
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.title = "Workouts"
view.backgroundColor = .white
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseidentifier)
}
#IBAction func handleAddContact(_ sender: Any) {
let controller = AddContactController()
controller.delegate = self
self.present(UINavigationController(rootViewController: controller), animated: true, completion: nil)
}
//UITABLEVIEW
//all new
override func numberOfSections(in tableView: UITableView) -> Int {
//new
return tableViewData.count
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
//new
if tableViewData[section].opened == true {
return tableViewData[section].exerciseData.count + 1
}else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseidentifier, for: indexPath)
cell.textLabel?.text = tableViewData[indexPath.section].title
return cell
}else {
//use a different cell identifier if needed
let cell = tableView.dequeueReusableCell(withIdentifier: "ExerciseCell", for: indexPath) as! ExerciseCell
cell.exerciseLabel.text = tableViewData[indexPath.section].exerciseData[indexPath.row - 1]
cell.repsSetsLabel.text = tableViewData[indexPath.section].repsSetsData[indexPath.row - 1]
cell.repsSetsLabel.sizeToFit()
return cell
}
}
//did select row new
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableViewData[indexPath.section].opened == true {
tableViewData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none) //play around with animation
}else {
tableViewData[indexPath.section].opened = true
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none) //play around with animation
}
}
//being able to delete a row
// this method handles row deletion
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// remove the item from the data model
tableViewData.remove(at: indexPath.row)
// delete the table view row
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
//this is an extention to addContactController. this is what happens whent he done button is clicked in addcontactcontroller
extension ContactController: AddContactDelegate {
func addContact(contact: Contact) {
self.dismiss(animated: true) {
//Saving Data to CoreData
CoreDataManager.sharedInstance.addContactsToCoreData(contact: contact)
self.contacts.append(contact)
//Settings values in table view
var exerciseData = [String]()
var repsSetsData = [String]()
for exercise in contact.exercises{
let name = exercise.excerciseName
let sets = exercise.excerciseSets
let reps = exercise.excerciseReps
exerciseData.append(name)
repsSetsData.append("Reps: " + reps + " Sets: " + sets)
}
self.tableViewData.append(cellData.init(opened: false, title: contact.fullname, exerciseData:exerciseData, repsSetsData: repsSetsData))
self.tableView.reloadData()
}
}
}
After deleting the rows can you try to reload the tableview cells like this: self.tableView.reloadData()
It's supposed that you delete a row not an entire section , so replace
tableViewData.remove(at: indexPath.row)
with
tableViewData[indexPath.section].exerciseData.remove(at: indexPath.row)
also make sure exerciseData is mutable ( declared as var )

Keyboard not showing when tableView.reloadData() inside textFieldDidBeginEditing

I put UITextField inside UITableViewCell and want to make highlight tableViewCell and unselected tableViewCell goes original color if user key-in inside each UITextField. So, I did like that.
func textFieldDidBeginEditing(_ textField: UITextField) {
defaultIndex = textField.tag
dynamicFormTable.reloadData()
}
But problem is Keyboard is not showing when I've added dynamicFormTable.reloadData(). Please let me know how to resolve it. Thanks.
Following code will give good result, to avoid reloads
var cellBGColr = [Int : UIColor]()
var previouselectedRow = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<70 // numberOfRows
{
cellBGColr[i] = UIColor.white
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 70
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "table", for: indexPath) as! TblTableViewCell
cell.backgroundColor = cellBGColr[indexPath.row]
cell.selectionStyle = .none
return cell
}
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
let cellPosition = textView.superview?.convert(CGPoint.zero, to: tblView)
let indPath : IndexPath = tblView.indexPathForRow(at: cellPosition!)!
let cell = tblView.cellForRow(at: indPath) as! TblTableViewCell
var previousSelectedCellRow : Int = -1 // FOR VALIDATION
if previouselectedRow.count == 0 // FIRST EDIT
{
previouselectedRow.append(indPath.row)
}
else
{
previousSelectedCellRow = previouselectedRow[0]
if previousSelectedCellRow == indPath.row // SAME ROW EDITING AGAIN
{
}
else // NEW ROW
{
let previousIndPath : IndexPath = IndexPath(row: previousSelectedCellRow, section: 0)
if (tblView.indexPathsForVisibleRows?.contains(previousIndPath))!
{
let previousCell = tblView.cellForRow(at: previousIndPath) as! TblTableViewCell
previousCell.backgroundColor = UIColor.white
cellBGColr[previousSelectedCellRow] = UIColor.white
}
else
{
cellBGColr[previousSelectedCellRow] = UIColor.white
}
previouselectedRow.remove(at: 0)
previouselectedRow.append(indPath.row)
}
}
cell.backgroundColor = UIColor.red // HERE YOU CAN CHANGE UR CELL COLOR
cellBGColr[indPath.row] = UIColor.red // HERE STORED IN DICT
return true
}
On scrolling your tableview, or somewhere you try to reload, cell background color will not change / reuse.
When reloadData is called it resigns first responder. But you can use beginUpdates/endUpdates methods:
dynamicFormTable.beginUpdates()
dynamicFormTable.reloadRows(at: [IndexPath(row: defaultIndex, section: 0)], with .none)
dynamicFormTable.endUpdates()

Dynamically resize TableViewCell with and with out images

Using swift3, I want to allow users to create posts of either pictures or simple text posts. I have everything working fine except for when I create a post of just text, the UIImageView in the cell fills out space in the TableViewCell. Ideally, if the user creates a post of just text, the TableViewCell will only include everything down to the caption label but not the UIImageView (see image). How can I go about this.
Research: https://www.youtube.com/watch?v=zAWO9rldyUE,
https://www.youtube.com/watch?v=TEMUOaamcDA, https://www.raywenderlich.com/129059/self-sizing-table-view-cells
Current Code
func configureCell(post: Post){
self.post = post
likesRef = FriendSystem.system.CURRENT_USER_REF.child("likes").child(post.postID)
userRef = FriendSystem.system.USER_REF.child(post.userID).child("profile")
self.captionText.text = post.caption
self.likesLbl.text = "\(post.likes)"
self.endDate = Date(timeIntervalSince1970: TimeInterval(post.time))
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
let snap = snapshot.value as? Dictionary<String, Any>
self.currentUser = MainUser(uid: post.userID, userData: snap!)
self.userNameLbl.text = self.currentUser.username
if let profileImg = self.currentUser.profileImage {
self.profileImg.loadImageUsingCache(urlString: profileImg)
} else {
self.profileImg.image = #imageLiteral(resourceName: "requests_icon")
}
})
// This is where I belive I need to determine wether or not the cell should have an image or not.
if let postImg = post.imageUrl {
self.postImg.loadImageUsingCache(urlString: postImg)
}
I see you're using storyboard to create your UI, in that case you can add a height constraint to your UIImageView (make sure to connect it your cell in order to use it in code) and change the constraint and the height of your tableview when required.
class MyCell: UITableViewCell {
#IBOutlet var postImage: UIImageView!
#IBOutlet var postImageHeight: NSLayoutConstraint!
}
class ViewController: UITableViewController {
var dataSource: [Model] = []
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//Cell without image
if dataSource[indexPath.row].image == nil {
return 200
}
//Cell with image
return 350
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
//Adjust the height constraint of the imageview within your cell
if dataSource[indexPath.row].image == nil {
cell.postImageHeight.constant == 0
}else{
cell.postImageHeight.constant == 150
}
return cell
}
}

Value of type '_' has no member

In the cb.check(self.rowChecked[indexPath.row]) line under cellForRowAt I'm getting a "Value of type 'LolFirstTableViewController' has no member 'rowChecked'" even though I set up rowChecked to be an array of Booleans with tasks.count number of items. Do I need to initialize rowChecked somewhere else besides cellForRowAt or what am I doing wrong here? The point of this code is to make a checkbox show up in each cell of a table where you can click it to change the accessory to a check mark, and click it again to uncheck it. The check box itself is a separate custom class called CheckButton. I'm still learning Swift so any help would be greatly appreciated! Thank you!
import UIKit
class LoLFirstTableViewController: UITableViewController {
var tasks:[Task] = taskData
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 60.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
#IBAction func cancelToLoLFirstTableViewController(_ segue:UIStoryboardSegue) {
}
#IBAction func saveAddTask(_ segue:UIStoryboardSegue) {
if let AddTaskTableViewController = segue.source as? AddTaskTableViewController {
if let task = AddTaskTableViewController.task {
tasks.append(task)
let indexPath = IndexPath(row: tasks.count-1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell
let task = tasks[indexPath.row]
cell.task = task
var rowChecked: [Bool] = Array(repeating: false, count: tasks.count)
if cell.accessoryView == nil {
let cb = CheckButton()
cb.addTarget(self, action: #selector(buttonTapped(_:forEvent:)), for: .touchUpInside)
cell.accessoryView = cb
}
let cb = cell.accessoryView as! CheckButton
cb.check(self.rowChecked[indexPath.row])
return cell
}
func buttonTapped(_ target:UIButton, forEvent event: UIEvent) {
guard let touch = event.allTouches?.first else { return }
let point = touch.location(in: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: point)
var tappedItem = tasks[indexPath!.row] as Task
tappedItem.completed = !tappedItem.completed
tasks[indexPath!.row] = tappedItem
tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.none)
}
You are declaring rowChecked as a local variable and calling it with self.rowChecked as if it were a class property.
To solve this issue, remove the self. before rowChecked.
Old:
cb.check(self.rowChecked[indexPath.row])
New:
cb.check(rowChecked[indexPath.row])
There might be further issues, but that's the reason for the error as your code currently stands.
You have the line: var rowChecked: [Bool] = Array(repeating: false, count: tasks.count) inside the tableView:cellForRowAt method, so it's a local variable, it's not a property of the LolFirstTableViewController class.
That means you need to change this line: cb.check(self.rowChecked[indexPath.row]) to cb.check(rowChecked[indexPath.row]) (Removed self.).

Resources