Ok, need a little help here. I'm new to Swift. Here's my issue.
When getting data for my UITableView, I'm calling image data from a url, so there is a slight delay when grabbing reused cells, resulting in the cell showing old data for half a second. I've tried to call func prepareForReuse to reset properties, but it doesn't seem to be working. Any help is appreciated!
Here's my code when calling cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.alpha = 0
let book = books[indexPath.row]
cell.textLabel?.text = book.bookTitle
cell.detailTextLabel?.text = book.postURL
let url = URL(string: book.postPicture)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
cell.alpha = 0
cell.backgroundView = UIImageView(image: UIImage(data: data!))
UIView.animate(withDuration: 0.5, animations: {
cell.alpha = 1
})
}
}
cell.contentView.backgroundColor = UIColor.clear
cell.textLabel?.backgroundColor = cell.contentView.backgroundColor;
cell.detailTextLabel?.backgroundColor = cell.contentView.backgroundColor;
func prepareForReuse(){
cell.alpha = 0
cell.backgroundView = UIImageView(image: UIImage(named: "book.jpg"))
}
return cell
}
You should subclass UITableView cell and inside your custom class override:
import UIKit
class CustomTableViewCell: UITableViewCell {
override func prepareForReuse() {
// your cleanup code
}
}
then in UITableViewDataSource method reuse the custom cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomTableViewCell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! CustomTableViewCell
return cell
}
Related
I have 2 tableView with 2 tableView first to display data & sec for predictive texts and I'm using textField as searchBar so when I set the cell for the sec tableView is give me that error when I try to return the cell at cellForRowAt method
Cannot convert return expression of type UITableViewCell.Type to return type UITableViewCell
and that's my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == tableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchCell", for: indexPath) as! searchCell
//cell.pics = receiveData[indexPath.item]
cell.titleCell.text = receiveData[indexPath.row].adeTitle
cell.cityCell.text = receiveData[indexPath.row].city
cell.dateCell.text = receiveData[indexPath.row].date
cell.distanceCell.text = receiveData[indexPath.row].distance
cell.priceCell.text = receiveData[indexPath.row].adePrice
if receiveData[indexPath.row].typ == "2" {
cell.kindCell.text = "used"
cell.kindCell.backgroundColor = UIColor.darkGray
cell.kindCell.textColor = UIColor.white
} else if receiveData[indexPath.row].typ == "1" {
cell.kindCell.text = "New"
} else {
cell.kindCell.text = "none"
cell.kindCell.backgroundColor = UIColor.darkGray
cell.kindCell.textColor = UIColor.white
}
} else {
var cell = searchTableView.dequeueReusableCell(withIdentifier: "Cell" )
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
}
cell?.textLabel?.text = inputs[indexPath.row]
}
return UITableViewCell
}
Looks like UITableViewCell's instance is not returned in your cellForRowAt method,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == tableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchCell", for: indexPath) as! searchCell
...
// do your stuffs with cell
...
return cell
}
return UITableViewCell()
}
I'm trying to add two prototype cell on my UITableView. However, I don't know how I could verify to be able to "return" the correct cells for each prototype. Can you guys give me a hand?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if ??? {
let cell = itensTableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! tableviewCell
cell.nameCell.text = "Oculos"
return cell
}else{
let cellAdicionar = itensTableView.dequeueReusableCell(withIdentifier: "cellIdAdc", for: indexPath) as! tableviewBotaoAdicionar
cellAdicionar.botaoAdicionar.text = "Adicionar"
return cellAdicionar
}
}
Storyboard Picture
You need to set your model to answer that inside cellForRowAt
var arr = ["first","second","first"]
//
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = arr[indexPath.row]
if item == "first" {
let cell = itensTableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! tableviewCell
cell.nameCell.text = "Oculos"
return cell
} else {
let cellAdicionar = itensTableView.dequeueReusableCell(withIdentifier: "cellIdAdc", for: indexPath) as! tableviewBotaoAdicionar
cellAdicionar.botaoAdicionar.text = "Adicionar"
return cellAdicionar
}
}
I do know how to input background colours for my row, but I don't really know how I can filter it by only the bottom 5 rows are "cell.backgroundColor = UIColor.red;" whereas the rest stays the same. Appreciate those who can help me this thanks!
P.S: Sorry as my swift is quite rusty.
UITableView Controller
import UIKit
import FirebaseDatabase
var postData2 = [String]()
var postData3 = [String]()
var tableDataArray = [tableData]()
class ResultsController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference() //set the firebase reference
// Retrieve the post and listen for changes
databaseHandle = ref?.child("Posts3").observe(.value, with: { (snapshot) in
postData2.removeAll()
postData3.removeAll()
tableDataArray.removeAll()
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
let value = String(describing: snap.value!)
let rating = (value as NSString).integerValue
postData2.append(key)
postData3.append(value)
tableDataArray.append(tableData(boothName: key, boothRating: rating))
}
postData2.removeAll()
postData3.removeAll()
let sortedTableData = tableDataArray.sorted(by: { $0.boothRating > $1.boothRating })
for data in sortedTableData {
postData2.append(data.boothName)
let value = String(describing: data.boothRating)
postData3.append(value)
}
self.tableView.reloadData()
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postData2.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.font = UIFont.init(name: "Helvetica", size: 23)
cell.textLabel?.text = postData2[indexPath.row]
cell.detailTextLabel?.text = postData3[indexPath.row] + " ♥"
cell.detailTextLabel?.textColor = UIColor.red;
cell.detailTextLabel?.font = UIFont.init(name: "Helvetica", size: 23)
// cell.backgroundColor = UIColor.red;
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 80
}
}
class tableData {
var boothName: String
var boothRating: Int
init(boothName: String, boothRating: Int) {
self.boothName = boothName
self.boothRating = boothRating
}
}
A simple way is to have an conditional check to see if the indexPath.row value is within the last five.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(indexPath.row >= postData2.count-5){
cell.backgroundColor = UIColor.red
}else{
cell.backgroundColor = UIColor.white /* Remaining cells */
}
return cell
}
Some of the other answers will work - but it is nicer to use cells that have a known configuration when they are dequeued by cellForRowAt, not deal with a bunch of possible starting conditions each time you dequeue a cell. To do this subclass the UITableViewCell and override prepareForReuse(). This function will be called just before a cell is returned by dequeueReusableCell. Then cells can be set to a known starting point before you configure them. If cells could be received configured any possible way in cellForRowAt, you soon wind up with a very long function with a lot of if/else conditions.
The condition
if indexPath.row >= postData2.count - 5 {
cell.backgroundColor = UIColor.red
}
can be used as it is, and prepareForReuse takes care of the cells not keeping any settings when they are recycled. Here's an example:
override func prepareForReuse() {
super.prepareForReuse()
backgroundColor = UIColor.white
}
With this one simple setting it's a wash whether you do the if/else approach or use subclassing to make the most of prepareForReuse. But as soon as you have more than one thing to set in a cell you will find it is far less complex to use this function and results in far fewer mistakes with the appearance of cells - consider what would happen if there were more than one possible color a cell could be, or there were multiple elements in the cell to be configured with multiple possible values...
You can add simple logic
if indexPath.row >=(postData2.count-5) {
cell.backgroundColor = UIColor.red
}else {
cell.backgroundColor = UIColor.white
}
Just check a condition for setting the red colour for last five rows.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(indexPath.row >= postData2.count-5){
cell.backgroundColor = UIColor.red;
}else {
cell.backgroundColor = UIColor.white; //white colour for other rows
}
return cell
}
This method is recommended by the system, this method is more circumventing reuse in some cases (like when you modify the contents of a control in the cell surface)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "cell")
// Not the type of cell, if the queue will return nil, at this time requires create ⼀ cell
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
}
}
If it involves data processing, you can create a new NSMutableSet(), Used to store your operations (ordinary data is lost, stored in the didSelecetRow inside indexPath like) save anyway, a unique tag.
These are just solve the problem of multiplexing, to deal with discoloration, refer to the above solution.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(indexPath.row >= postData2.count-5){
cell.backgroundColor = UIColor.red
}else{
cell.backgroundColor = UIColor.white /* Remaining cells */
}
return cell
}
hey i have a tableview with images and some text labels the whole cell is 400 in height and i want to extend it to 700 when i click the cell to make the user see more information of the cell so i wrote this code
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpanded && self.selectedIndex == indexPath{
return 700
}
return 400.0
}
var selectedIndex:IndexPath?
var isExpanded = false
func didExpandCell() {
self.isExpanded = !isExpanded
self.postsTableView.reloadRows(at: [selectedIndex!], with: .automatic)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndex = indexPath
self.didExpandCell()
}
but this code requires a .xib file off the Cell, i have tryed to run the app without the code below but it doesnt work and i have tryed it with it and it doesnt work because i need a .xib file for the cell so i was wondering if it is possible that someone could help me with this and just explain how i can this cells epandable without this .xib file of the cell
func unnesseryNibFile(){
//this code goes in the viewDidLoad func and it is required to have a .xib file of the Cell protoype
let nib = UINib.init(nibName: "postTableViewCell", bundle: nil)
self.postsTableView.register(nib, forCellReuseIdentifier: "Cell")
}
here is my 'cellForRowAt'
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PostTableViewCell
// Configure the cell...
let post = self.posts[indexPath.row] as! [String: AnyObject]
cell.locationLabel.text = post["location"] as? String
cell.usernameLabel.text = post["username"] as? String
cell.titleLabel.text = post["title"] as? String
cell.contentTextView.text = post["content"] as? String
if let imageName = post["image"] as? String {
let imageRef = Storage.storage().reference().child("images/\(String(describing: imageName))")
imageRef.getData(maxSize: 25 * 1024 * 1024, completion: { (data, error) -> Void in
if error == nil {
//succeful
let image = UIImage(data: data!)
cell.postImageView.image = image
}else{
//Not Succesful
print("error Downloading The Image\(String(describing: error?.localizedDescription))")
}
})
}
return cell
}
thanks for your time. :)
I keep getting checkmarks being marked in other sections of my table view when I click a row. Im not certain if I need to set my accessoryType. I tried mytableView.reloadData() however that didn't help either.
var selected = [String]()
var userList = [Users]()
#IBOutlet weak var myTableView: UITableView!
#IBAction func createGroup(_ sender: Any) {
for username in self.selected{
ref?.child("Group").childByAutoId().setValue(username)
print(username)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
myCell.selectionStyle = UITableViewCellSelectionStyle.none
myCell.nameLabel.text = userList[indexPath.row].name
return myCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if myTableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCellAccessoryType.checkmark{
myTableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
let currentUser = userList[indexPath.row]
selected = selected.filter { $0 != currentUser.name}
}
else{
myTableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
let currentUser = userList[indexPath.row]
selected.append(currentUser.name!)
}
}
Your problem is not inside this method but in the one that "loads" the cells. (cell for row)
Since Table Views use reusable cells, more often than not they will be loading a cell that was already presented somewhere else.
Because of this, on the cell loading method you should "Reset the state" the loaded cell, this includes the accessory type, and any other properties you might have changed.
So just change this in your code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
myCell.selectionStyle = UITableViewCellSelectionStyle.none
myCell.nameLabel.text = userList[indexPath.row].name
// ADD THIS
if userList[indexPath.row].isSelected {
myCell.accessoryType = UITableViewCellAccessoryType.checkmark
} else {
myCell.accessoryType = UITableViewCellAccessoryType.none
}
return myCell
}
EDIT:
"userList[indexPath.row].isSelected" is a property that YOU have to create and manage. (So you must also modify it in the didSelectRowAt method.
The issue is you are not maintaining the selected User info properly, which will be used while you scroll the table and when the cell has to reload data.
As you have already created var selected = [String]() , I suggest using the same.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
let dataFoundInselectedArr = selected.filter { $0 == userList[indexPath.row].name}
if(dataFoundInselectedArr.count > 0){
myCell.accessoryType = UITableViewCellSelectionStyle.checkmark
}else{
myCell.accessoryType = UITableViewCellSelectionStyle.none
}
myCell.nameLabel.text = userList[indexPath.row].name
return myCell
}
The table selection delegate method remains the same.