Failure while attempt to find specific cell in table view swift - swift

I want to find specific cell class in my table view. I use following code:
func setConfirmEnabledIfNeed(){
let ip = IndexPath(row: 8, section: 0)
if let cell = tableView.cellForRow(at: ip) as? ConfirmBtnCell {
print("find confirm cell")
}
let c = tableView.cellForRow(at: ip) as? ConfirmBtnCell
print("type of cell \(type(of: c))")
}
print("find confirm cell") is never called, however, second print output: type of cell Optional<ConfirmBtnCell>, what is obviously what i need. But why first print not called?
My cell for row look like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = viewModel.items[indexPath.row]
switch item {
case .selectable(let value, let placeholder, let type):
let selectionCell = tableView.dequeueReusableCell(withIdentifier: "SelectionCell") as! SelectionCell
if let placeholder = placeholder { selectionCell.setPlaceholder(placeholder) }
if let text = value as? String, !text.isEmpty { selectionCell.hidePlaceholder();
selectionCell.textLbl.text = text }
if let date = value as? Date { selectionCell.hidePlaceholder();
selectionCell.textLbl.text = DateFormatterUtil.getReadableStringFromDate(date) }
return selectionCell
case .back:
return tableView.dequeueReusableCell(withIdentifier: "BackBtnCell") as! BackBtnCell
case .confirm:
return tableView.dequeueReusableCell(withIdentifier: "ConfirmBtnCell") as! ConfirmBtnCell
case .editableNumbers(let text, let placeholder, let type):
let editableCell = tableView.dequeueReusableCell(withIdentifier: "TextEditCell") as! TextEditCell
editableCell.setup(isNumerical: true)
if let placeholder = placeholder { editableCell.setPlaceholder(placeholder) }
if let text = text {editableCell.hidePlaceholder(); editableCell.txtf.text = text }
editableCell.textChanged = {[weak self] text in
guard let text = text else { return }
if type == .sumCash {
self?.viewModel.collectedInfo[CorrectionChequeViewModel.infoKeys.sumCash.rawValue] = text
self?.setConfirmEnabledIfNeed()
}
if type == .sumElectronic {
self?.viewModel.collectedInfo[CorrectionChequeViewModel.infoKeys.sumElectronic.rawValue] = text
}
}
return editableCell
case .editableText(let text, let placeholder, let type):
let editableCell = tableView.dequeueReusableCell(withIdentifier: "TextEditCell") as! TextEditCell
editableCell.setup(isNumerical: false)
if let placeholder = placeholder { editableCell.setPlaceholder(placeholder) }
if let text = text {editableCell.hidePlaceholder(); editableCell.txtf.text = text }
editableCell.textChanged = {[weak self] text in
guard let text = text else { return }
if type == .sumCash {
self?.viewModel.collectedInfo[CorrectionChequeViewModel.infoKeys.description.rawValue] = text
}
if type == .sumElectronic {
self?.viewModel.collectedInfo[CorrectionChequeViewModel.infoKeys.number.rawValue] = text
}
}
return editableCell
default:
return UITableViewCell()
}
}
UPDATE:
I find that code work if it's not called from table view cellForRow method. I tried to launch that block of code with dispatch_after, and it works.

In your if statement, the value for tableView.cellForRow(at: ip) as? ConfirmBtnCell is nil, so you never enter the block.
In the second statement (let c = tableView.cellForRow(at: ip) as? ConfirmBtnCell), the value of c is an Optional<ConfirmBtnCell>. If you unwrap the optional, you will see that its unwrapped value is also a nil.
If your row is not visible, tableView.cellForRow will return a nil even when the cell is set. See the Apple Documentation.

Related

How to retrieve a value from Firestore to change the text of button in table cell for every listen item?

I have tableview in which there is list item and every cell possess a button, I want to fix the text and color of the button based on a boolean value retrieved from firestore, every cell's button have certain text and color based on that value only, I know the syntax to set the text and color for the button in swift, I am just not able to do on the basis of the value from the firestore, below is the code
code for retrieving the list, concerned value is checkl1value
func getComments() {
//print(postId + "received")
let commentsRef = Firestore.firestore().collection("posts").document(postId).collection("comments")
commentsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let username = data["comment_author_username"] as? String ?? ""
let comment = data["comment_author_comment"] as? String ?? ""
let spinnerC = data["comment_author_spinnerC"] as? String ?? ""
let fullname = data["comment_author_fullname"] as? String ?? ""
let email = data["comment_author_email"] as? String ?? ""
let commentUserImageUrl = data["comment_user_image"] as? String ?? ""
let commentuser_id = data["comment_author_id"] as? String ?? ""
self.checkl1value = data["l1"] as? DarwinBoolean
let newComment = Comment(_documentId: document.documentID, _commentAuthorUsername: username, _commentAuthorFullName: fullname, _commentAuthorComment: comment, _commentUserImage: commentUserImageUrl, _commentAuthorSpinnerC: spinnerC, _commentAuthorId:commentuser_id )
self.comments.append(newComment)
}
self.tableView.reloadData()
}
}
}
}
Code for cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CommentCell", for: indexPath) as! CommentCell
cell.commentLikebutton.tag = indexPath.row
cell.commentLikebutton.addTarget(self, action: #selector(likeaction1(_:)), for: .touchUpInside)
if checkl1value == true {
cell.commentLikebutton.setTitle("Text1", for: .normal)
// cell.commentLikebutton.backgroundColor = UIColor(red: 17.0/255.0, green: 119.0/255.0, blue: 151.0/255.0, alpha: 1.0)
cell.commentLikebutton.backgroundColor = UIColor.red
//cell.commentLikebutton.backgroundColor = UIColor?.red()
}
else{
cell.commentLikebutton.setTitle("Text2", for: .normal)
}
cell.set(comment: comments[indexPath.row])
return cell
}
Add something like this on the top of cellForRowAt
let thisComment = self.comments[indexPath.row]
let checkl1value = thisComment.checkl1value
I think the error is that your checkl1Value always get changed with the value of the last item in the array. You can do something like this:
var booleanValues = [DarwinBoolean]()
func getComments() {
let commentsRef = Firestore.firestore().collection("posts").document(postId).collection("comments")
commentsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let username = data["comment_author_username"] as? String ?? ""
let comment = data["comment_author_comment"] as? String ?? ""
let spinnerC = data["comment_author_spinnerC"] as? String ?? ""
let fullname = data["comment_author_fullname"] as? String ?? ""
let email = data["comment_author_email"] as? String ?? ""
let commentUserImageUrl = data["comment_user_image"] as? String ?? ""
let commentuser_id = data["comment_author_id"] as? String ?? ""
// self.checkl1value = data["l1"] as? DarwinBoolean
booleanValues.append(data["l1"] as? DarwinBoolean ?? false)
let newComment = Comment(_documentId: document.documentID, _commentAuthorUsername: username, _commentAuthorFullName: fullname, _commentAuthorComment: comment, _commentUserImage: commentUserImageUrl, _commentAuthorSpinnerC: spinnerC, _commentAuthorId:commentuser_id )
self.comments.append(newComment)
}
self.tableView.reloadData()
}
}
}
}
CellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CommentCell", for: indexPath) as! CommentCell
cell.commentLikebutton.tag = indexPath.row
cell.commentLikebutton.addTarget(self, action: #selector(likeaction1(_:)), for: .touchUpInside)
if booleanValues[indexPath.row] {
cell.commentLikebutton.setTitle("Text1", for: .normal)
// cell.commentLikebutton.backgroundColor = UIColor(red: 17.0/255.0, green: 119.0/255.0, blue: 151.0/255.0, alpha: 1.0)
cell.commentLikebutton.backgroundColor = UIColor.red
//cell.commentLikebutton.backgroundColor = UIColor?.red()
} else {
cell.commentLikebutton.setTitle("Text2", for: .normal)
}
cell.set(comment: comments[indexPath.row])
return cell
}

If there is a like button next to each user, why could scrolling trigger random users' like buttons be highlighted?

I use self.like.alpha = 0.5 to grey out the like button next to the user who was liked. Scrolling causes the highlight to sometimes disappear and appear next to other users.
I've used self.like.alpha = 0.5 last various places in the code but it changes nothing.
#IBAction func likePressed(_ sender: Any) {
self.like.alpha = 0.5
let ref = Database.database().reference()
let keyToPost = ref.child("likes").childByAutoId().key
ref.child("humans").child(self.postID).observeSingleEvent(of: .value, with: {(snapshot) in
if let humans = snapshot.value as? [String: AnyObject] {
let updateLikes: [String: Any] = ["humansWhoLike/\(keyToPost)" : Auth.auth().currentUser!.uid]
ref.child("humans").child(self.postID).updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.child("humans").child(self.postID).observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as?[String: AnyObject]{
if let likes = properties["humansWhoLike"] as? [String : AnyObject] {
let count = likes.count
let update = ["likes" : count]
ref.child("humans").child(self.postID).updateChildValues(update)
}
}
})
}
})
}
})
ref.removeAllObservers()
}
What I need is for the like button that is clicked to be greyed out. It has to stay greyed out and the greying out should not jump to another user's like button.
/Updated code after 1st answer
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let like = cell.viewWithTag(3) as! UIButton
let immy = cell.viewWithTag(1) as! UIImageView
let person: Userx = humans[indexPath.row]
cell.lblName.text = person.Education
cell.postID = self.humans[indexPath.row].postID
if let PhotoPosts = person.PhotoPosts {
let url = URL(string: PhotoPosts)
immy.sd_setImage(with: url)
}
return cell
}
Remember that tableView cells are reusable. When you dequeue one, you cannot assume anything about the existing values. If you mark a cell liked (with button formatting), when that cell is reused, the formatting is still there.
When you dequeue a cell in your cellForRowAt function, you need to reset all the values according to your data store.
I am having a little trouble understanding your database design/usage, but based on the code you added to the post:
let currUser = Auth.auth().currentUser!.uid // better to add this as a VC level variable as you will do this lookup a lot.
let likeArray = person.humansWhoLike ?? []
let likeStatus = likeArray.contains(currentUser)
//from your code, 'like' is the button to be formatted
like.alpha = likeStatus ? 0.5 : 1.0

load large data from firestore to table view Swift

firestore to store about more than 500 information and I want to display it to table view. Basically, I have successfully display all the data in my cell, but the problem is, it takes more than 1 minute to load all data. While the data loaded, I cannot scroll the table view, unless all data finish load. How to enable scrolling while the data is still loading? If not possible, how to load first 20 data first, and will continue load if user is at the end of the cell? Here is some code that I have tried to
get data from firestore:
func getData () {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
let data = document.data()
let agencyPath = data["agensi"] as? String ?? ""
let title = data["title"] as? String ?? ""
let program = data["program"] as? String ?? ""
let perniagaan = data["perniagaan"] as? String ?? ""
let newMax = data["max"] as? Int
let agencyId = document.documentID
let query = Firestore.firestore().collection("Agensi")
let newQuery = query.whereField("name", isEqualTo: "\(agencyPath)")
newQuery.getDocuments()
{
(querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)");
} else
{
for document in querySnapshot!.documents {
let data = document.data()
let logo = data["logo"] as? String ?? ""
//store to Struct
let newModel = DisplayModel(agency: title, agencyId: agencyId, programTag: program, perniagaanTag: perniagaan, max: newMax, agencyPath: agencyPath, logoUrl: logo, agencyTitle: agencyPath)
self.agencyList.append(newModel)
}
self.tableView.reloadData()
self.dismiss(animated: false, completion: nil)
}
}
}
}
}
}
display data on cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData: DisplayModel
if searchController.searchBar.text != "" {
cellData = filteredData[indexPath.row]
} else {
cellData = agencyList[indexPath.row]
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? HomeTableViewCell
cell?.agencyName.text = cellData.agency
cell?.agencyImage.sd_setImage(with: URL(string: "\(cellData.logoUrl ?? "")"), placeholderImage: UIImage(named: "no_pic_image"))
return cell!
}
Action on last row of cell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if searchController.searchBar.text != "" {
let lastElement = filteredData.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
} else {
let lastElement = agencyList.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
}
}
I really have no idea what method I should do to load 20 data first and continue load at the end of cell row, if there is no solution, at least I could scroll the table view during the load session. Thank You, for your information, i just learn swift last month. Thank you for helping me.
You should definitly adopt the UITableViewDataSourcePrefetching protocol.
Check some blogs, like:
https://www.raywenderlich.com/187041/uitableview-infinite-scrolling-tutorial
and adopt it to pagination as described here:
https://firebase.google.com/docs/firestore/query-data/query-cursors

Swift Firebase TableView cannot remove last element

I have a tableview that populates an array. I am able to add to firebase and the tableview reloads to show the newly added object. If I have 5 items in firebase then I would have 5 on the tableview. I am able to remove items from firebase through code and reload the tableview and it works great.
My issue is when I am on the last item on firebase and tableview and I delete that last item, the firebase removes it just fine, but the tableview keeps the last item but grays it out.
The app doesn't crash it just stays there until I add something back in.
Obviously if I tap on that grayed out cell my app crashes because I am tapping an index out of range.
Is there some code that I need to add to prevent this grayed out cell and just have an empty tableview?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return serviceArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "serviceCell", for: indexPath as IndexPath) as! ServiceTableViewCell
let row = indexPath.row
// cell.serviceLogoImage.image = UIImage.init(named: serviceArray[row].serviceUrl!)
cell.serviceNameLabel.text = serviceArray[row].serviceName
if serviceArray[row].serviceStatus == true {
cell.serviceStatusView.backgroundColor = .green
} else {
cell.serviceStatusView.backgroundColor = .red
}
return cell
}
I must be missing a small conditional because other than this the tableview works perfectly with firebase...
Edit 1
I have added the code used to populate the tableview
var serviceArray: [ServiceClass] = []
func pullCardData() {
serviceArray.removeAll()
let cardRef = ref.child("cards")
cardRef.observeSingleEvent(of: .value, with: { snapshot in
for cards in snapshot.children {
let allCardIDs = (cards as AnyObject).key as String
if allCardIDs == self.cardID {
let thisCardLocation = cardRef.child(self.cardID)
thisCardLocation.observeSingleEvent(of: .value, with: { snapshot in
let thisCardDetails = snapshot as FIRDataSnapshot
let cardDict = thisCardDetails.value as! [String: AnyObject]
self.selectedCard.cardID = thisCardDetails.key
self.selectedCard.nickname = cardDict["nickname"] as! String
self.selectedCard.type = cardDict["type"] as! String
self.cardNickNameLabel.text = cardDict["nickname"] as? String ?? ""
let thisCardServices = self.ref.child("cards").child(self.cardID).child("services")
let serviceRefLoc = self.ref.child("services")
thisCardServices.observeSingleEvent(of: .value, with: {serviceSnap in
if serviceSnap.hasChildren() {
for serviceChild in serviceSnap.children {
let serviceID = (serviceChild as AnyObject).key as String
serviceRefLoc.observeSingleEvent(of: .value, with: {allServiceSnap in
if allServiceSnap.hasChildren() {
for all in allServiceSnap.children {
let allServs = (all as AnyObject).key as String
let thisServiceLocationInServiceNode = self.ref.child("services").child(serviceID)
if serviceID == allServs {
thisServiceLocationInServiceNode.observeSingleEvent(of: .value, with: {thisSnap in
let serv = thisSnap as FIRDataSnapshot
let serviceDict = serv.value as! [String: AnyObject]
let aService = ServiceClass()
self.serviceCurrent = serviceDict["serviceStatus"] as? Bool
self.serviceName = serviceDict["serviceName"] as? String
self.serviceURL = serviceDict["serviceURL"] as? String
self.serviceFixedBool = serviceDict["serviceFixed"] as? Bool
self.serviceFixedAmount = serviceDict["serviceAmount"] as? String
aService.serviceUrl = serviceDict["serviceURL"] as! String
aService.serviceName = serviceDict["serviceName"] as! String
aService.serviceStatus = serviceDict["serviceStatus"] as? Bool
aService.serviceID = serviceID
self.serviceArray.append(aService)
self.tableView.reloadData()
})
}
}
}
})
}
}
})
})
}
}
})
}
Edit 2
I had the idea to check if the firebase node even exits (it shouldn't since I just deleted it. SO
func checkIfDataExits() {
ref.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.hasChild("services") {
self.pullCardData()
} else {
self.tableView.endUpdates()
print("no childen")
}
})
}
As expected since I don't have the firebase node there anymore it prints "no children" but it still shows that last tableview cell....so
Well, my last edit was one line of code off from being what I needed.
Instead of
self.tableView.endUpdates()
I replaced it with
self.tableView.reloadData()
So (without retying that long method) I simply wrote another method
func checkIfDataExits() {
serviceArray.removeAll()
ref.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.hasChild("services") {
self.pullCardData()
} else {
self.tableView.reloadData()
}
})
}
And THIS method decides wether or not to even run that long one
resolve:
serviceArray.removeAll()
Code:
ref.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.hasChild("services") {
self.pullCardData()
} else {
//code here
serviceArray.removeAll()
self.tableView.reloadData()
}
})

How to load two values from different array as a result of searchController?

I have to arrays which populates the values of searchController.
let textLabel = ["Uni", "Uni", "Faber-Castell", "Faber-Castell","Faber-Castell", "Pilot", "Pilot"]
let detailTextLabel = ["Pen", "Pencil", "Crayon", "Mechanical Pencil", "Contour Pencil", "Eraser", "Sharpener"]
These two arrays pair while reloading the data of UITableViewController. Like (Uni, Pen), (Uni, Pencil), (Faber-Castell, Crayon) ...
In pairs, first one is the cell title, second one is the subtitle. I followed iOSCreator's tutorial.
My problem is when I search text it only updates title section, not the subtitle as it was expected.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
// 3
if (self.resultSearchController.active) {
// it only updates textLabel. But when I add detailTextLabel
// subtitles comes wrong because first array contains one element
// more than twice.
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
} else {
cell.textLabel?.text = tableData[indexPath.row]
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredTableData.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text)
let array = (tableData as NSArray).filteredArrayUsingPredicate(searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
Use the single array like this:
let penList = ["Uni:Pen", "Uni:Pencil", "Faber-Castell:Crayon"]
let result = filteredTableData[indexPath.row] // example: "Uni:Pen"
let clearResult = String(result.characters.map { $0 == ":" ? " " : $0 })
// clearResult like this "Uni Pen"
cell.textLabel?.text = clearResult
Also you can get data seperated
let result = filteredTableData[indexPath.row]
let resultArray = result.componentsSeparatedByString(":")
let firstText = resultArray[0] // title text
let secondText = resultArray[1] // subtitle text