I have largely populated tableView running in my project. I am trying to shuffle the tableView array at once only.I found the similar code from this link How do I shuffle an array in Swift? code works ok..But the code shuffles TableView array every time when i swipe(up/down). I just want to restrict the code to happen once only...
I am using the below code.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var indexArray = Array(MY ARRAY NAME.indices)
var index = indexArray.endIndex
let indexGenerator: AnyGenerator<Int> = anyGenerator {
if index == indexArray.startIndex { return nil }
index = index.advancedBy(-1, limit: indexArray.startIndex)
let randomIndex = Int(arc4random_uniform(UInt32(index)))
if randomIndex != index {
swap(&indexArray[randomIndex], &indexArray[index])
}
return indexArray[index]
}
let permutationGenerator = PermutationGenerator(elements: MY ARRAY NAME, indices: AnySequence(indexGenerator))
let newArray = Array(permutationGenerator)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
////////////////////////////////////
cell.textLabel?.text = newArray[indexPath.row] //use of unsolved identifier "newArray"
////////////////////////////////////
return cell
}
Thanks in Advance.
Just call this inside of your viewDidLoad and it should shuffle your table view cells only once. There's no need to call it in every cell.
Related
I have a chat app where people can talk in a group and a little picture is displayed in each cell to show who is talking. I managed to display these pictures from Firebase storage but it is not always the right picture which is displayed at the right place.
It only works when I go to the previous View Controller and coming back the chat View to see the pictures displayed properly in each cell.
I tried to use DispatchQueue.main.async {} probably not in the good way cause it did not work for me.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = messageArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "customMessageCell", for: indexPath) as! CustomMessageCell
cell.selectionStyle = .none
// CHANGE TEXT ACCORDING TO SENDER
if message.sender == Auth.auth().currentUser?.email{
cell.messageBubble.backgroundColor = UIColor(red:0.30, green:0.68, blue:1.5, alpha:1.0)
// ...
} else {
cell.messageBubble.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
// ...
}
let theTimeStamp = messageArray[indexPath.row].createdAt
let doubleTime = Double(theTimeStamp)
let myDate = Date(timeIntervalSince1970: doubleTime )
let dateToShow = myDate.calenderTimeSinceNow()
cell.messageBodyTextView.text = messageArray[indexPath.row].messageBody
cell.usernameLabel.text = messageArray[indexPath.row].name
cell.timeLabel.text = dateToShow
let imagePath = self.storageRef.reference(withPath:"\(message.uid)/resizes/profilImage_150x150.jpg")
imagePath.getData(maxSize: 10 * 1024 * 1024) { (data, error) in
if let error = error {
cell.userPicture.image = UIImage(named: "emptyProfilPic")
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
print("Got an error fetching data : \(error.localizedDescription)")
return
}
if let data = data {
cell.userPicture.image = UIImage(data: data)
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
}
}
return cell
}
Thank you for your help !
You have to prepare the cell to be reusable with the proper override prepareForReuse().
For more clean code I suggest to you to implement the cells in separate cocoa Touch classes so it's easier to override and prepare for next data incoming, avoiding your problem.
What I mean it's a sort of this:
class mineCell:UITableViewCell {
#IBOutlet weak var text:UILabel!
#IBOutlet weak var img:UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
func updateCell(dataIn){
.
.
}
override func prepareForReuse() {
text.text = ""
img.image = nil
}
In your cellForRowAt table implementation just call the update function and pass your data like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "mineCell"
if let cell = mineTable.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? mineCell {
updateCell(dataIn)
return cell
}
return mineCell()
}
In this way you are always sure that your cell will be ready for every reuse and not loading wrong data from the cell above.
Just to let you know, the problem was thaT I was reloading the table View after each message loaded. Instead, the best solution was to add a row to the tableview without reloaded the tableview after each message :
self.ConvertationTableView.insertRows(at: [IndexPath(row: self.messageArray.count - 1, section: 0)], with: .automatic)
I have used a tableview with 16 cells on a view controller. Each cell has a textfield and a picker view as a inputview for textfield. The odd thing is that When I choose the value for the first cell, it's fine. When I scrolled down to the last cell, the value is same as the first one. But I have never touched the last cell. Why would this happened?
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
{
// selected value in Uipickerview in Swift
answerText.text = pickerDataSource[row]
answerText.tag = row
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell", forIndexPath: indexPath) as! AddFollowTableViewCell
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
cell.answerText.addTarget(self, action: #selector(AddFollowUpViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingDidEnd)
return cell
}
func textFieldDidChange(sender: UITextField){
let rowIndex: Int!
let selectValue = sender.tag
if let txtf = sender as? UITextField {
if let superview = txtf.superview {
if let cell = superview.superview as? AddFollowTableViewCell {
rowIndex = myTable.indexPathForCell(cell)?.row
dictAnswer[rowIndex] = selectValue - 1
}
}
}
}
After two days, it solved by thousands of trials:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell") as! AddFollowTableViewCell
if(cell.identifier == true){
cell.answerText.text = selectedAnswerForRow[indexPath.row]
}
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
dictAnswer[indexPath.row] = cell.pickerValue
cell.answerText.addTarget(self, action: #selector(AddFollowUpViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingDidEnd)
cell.identifier = true
return cell
}
func textFieldDidChange(sender: UITextField){
let rowIndex: Int!
let cell = sender.superview?.superview as! AddFollowTableViewCell
rowIndex = myTable.indexPathForCell(cell)?.row
selectedAnswerForRow[rowIndex] = cell.answerValue
print(selectedAnswerForRow[rowIndex])
cell.answerText.text = sender.text
cell.identifier = true
}
It might have some performance issue need to be optimised , but it shows exactly what i want. LOL
You're basically recycling your views and not clearing them. That's the whole point of -dequeueReusableCellWithIdentifier:indexPath:.
Allocating and deallocating memory is very power consuming, so the system recycles every cell that goes out of viewport bounds.
You don't set the text inside answerText (I assume it's the text field that causes trouble) so its content will be kept when recycled.
Assuming you'll store user selection inside a dictionary var selectedAnswerForRow: [IndexPath:String]:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell", forIndexPath: indexPath) as! AddFollowTableViewCell
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
cell.answerText.addTarget(self, action: "textFieldDidChange:"), forControlEvents: UIControlEvents.EditingDidEnd)
cell.answerText.text = self.selectedAnswerForRow[indexPath] ?? "" // add this
return cell
}
self.selectedAnswerForRow[indexPath] ?? "" returns the result or an empty string if it's not present in the dictionary.
Also, you're adding several times the action for edition control event. You have to check first if it isn't already bound.
Because the cell is reused. So you have to implement prepareForReuse() in your custom cell class and reset all the changing variables
UPDATE
See :
class MyCell : UITableViewCell {
#IBOutlet weak var myTextField : UITextField!
//Add the following
override func prepareForReuse() {
myTextField.text = nil
myTextField.inputView = myPickerView
super.prepareForReuse()
}
}
Hi Guys I have created a function which is below which saves items to a tweetData : [PFObject] array
func loadData() {
//all current data to be displayed, adding data 1 by 1 remove whats there on reloading
tweetData.removeAll()
//query database for all tweets, very important that whats in the classname here, is the
//same name as the class with Parse where these are being stored
let findTweets:PFQuery = PFQuery(className: "Tweets")
findTweets.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error: NSError?) -> Void in
if error == nil {
for object:PFObject in objects! {
self.tweetData.append(object)
}
//--very important!!! dont forget to reload table data otherwise you will return no results
self.tableView.reloadData()
} else {
print("nothing to load")
}
}
}
how can I get this into my tableview to display the data? been struggling and getting a variety of errors. This is my table view cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
//use your custom table view cell here which is defined in another class
let cell:CellTableViewCellController = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CellTableViewCellController
//NEED TO LOAD MY DATA
return cell
//-----add load data function to viewDidAppear method
}
could anyone help me on this at all? thanks
all sorted just used query.objectforkey
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//use your custom table view cell here which is defined in another class
let cell:CellTableViewCellController = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellTableViewCellController
let query = tweetData[indexPath.row]
cell.textLabel!.text = query.objectForKey("TweetContent") as? String
return cell
//-----add load data function to viewDidAppear method
}
I have a UITableView created with 2 prototype cells, each of which have separate identifiers and subclasses.
My problem is when I display the cells the second prototype's first row gets absorbed under the first prototype cell.
For example, I have the first prototype cell displaying only 1 item. The second prototype cell should display 4 items. But, the first item from the second prototype cell is not displaying and, instead, there are only 3 of the four items visible.
Here is the code I have implemented:
override func viewDidLoad() {
super.viewDidLoad()
self.staticObjects.addObject("Please...")
self.objects.addObject("Help")
self.objects.addObject("Me")
self.objects.addObject("Thank")
self.objects.addObject("You")
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.objects.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(indexPath.row==0){
let staticCell = self.tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! StaticTableViewCell
staticCell.staticTitleLabel.text = self.staticObjects.objectAtIndex(indexPath.row) as? String
return staticCell
}
else{
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PillarTableViewCell
cell.titleLabel.text = self.objects.objectAtIndex(indexPath.row) as? String
return cell
}
Thanks for all the help.
You have logic issues with how you are counting the number of rows in your table for both tableView:numberOfRowsInSection and tableView:cellForRowAtIndexPath.
Your code is producing a display output as shown below where:
The blue cells represent your staticCell prototype table cell view; these are the values from the staticsObjects array.
The yellow cells represent your cell prototype table cell view; these are the values from the objects array.
1. Look at tableView:cellForRowAtIndexPath:
In the cellForRowAtIndexPath method, you are only returning the count of the objects array.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.objects.count
}
That means that the number of rows you will have in your table will be 4 instead of 5. Instead, you want to return the sum of the two arrays you are using in your table: objects.count + staticObjects.count. For example:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count + staticObjects.count
}
2. Look at tableView:cellForRowAtIndexPath:
Here's your original code with my comments..
// You are assuming that `staticObjects` will always have
// exactly one row. It's better practice to make this
// calculation more dynamic in case the array size changes.
if (indexPath.row==0){
let staticCell = self.tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! StaticTableViewCell
staticCell.staticTitleLabel.text = self.staticObjects.objectAtIndex(indexPath.row) as? String
return staticCell
} else {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PillarTableViewCell
// Here's your problem! You need to calculate the row
// because you want to put the objects from your other
// array first. As a result, you need to account for them.
cell.titleLabel.text = self.objects.objectAtIndex(indexPath.row) as? String
return cell
}
Now, here's one way to fix your errors stated in the above discussion:
// If the indexPath.row is within the size range of staticObjects
// then display the cell as a "staticCell".
// Notice the use of "staticObjects.count" in the calculation.
if indexPath.row < staticObjects.count {
let staticCell = self.tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! StaticTableViewCell
staticCell.staticTitleLabel.text = self.staticObjects.objectAtIndex(indexPath.row) as? String
return staticCell
} else {
// If you get here then you know that your indexPath.row is
// greater than the size of staticObjects. Now you want to
// display your objects values.
// You must calculate your row value. You CANNOT use the
// indexPath.row value because it does not directly translate
// to the objects array since you put the staticObjects ahead
// of them. As a result, subtract the size of the staticObjects
// from the indexPath.row.
let row = indexPath.row - staticObjects.count
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PillarTableViewCell
cell.titleLabel.text = self.objects.objectAtIndex(row) as? String
return cell
}
Now you should see this:
The tableView doesn't show the cells with results from my search. The search is working fine, I verified all the data, but when I assign the cell.localName?.text to the value, it's always nil. Not sure what is wrong. I've found another post with same issue in Objective-C and the answer there was that I need to use the cell from the real tableView, by adding self.tableView when dequeueing the cell, but it didn't work for me. Any suggestions?
Here is my code:
class SearchResultsController: UITableViewController, UISearchResultsUpdating {
var localNames: [String] = []
var filteredNames: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(localCell.self, forCellReuseIdentifier: "localCell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchedString = searchController.searchBar.text
filteredNames.removeAll(keepCapacity: true)
if !searchedString.isEmpty {
let filter: String -> Bool = {name in
let range = name.rangeOfString(searchedString, options: NSStringCompareOptions.CaseInsensitiveSearch)
return range != nil
}
let matches = localNames.filter(filter)
filteredNames += matches
}
tableView.reloadData()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return filteredNames.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: localCell = self.tableView.dequeueReusableCellWithIdentifier("localCell") as localCell
cell.localName?.text = filteredNames[indexPath.row]
println(cell.localName?.text)
cell.localAddress?.text = "text"
cell.scheduleData?.text = "text"
cell.salaData?.text = "text"
cell.wifiData?.text = "text"
return cell
}
If your cell is returning nil then create one yourself using the following after your dequeueReusableCellWithIdentifier call:
if(cell == nil){
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "localCell")
}
Also, if you are using a UISearchController you should use 2 different arrays for populating your UITableView rather than manipulating the same array.
You can do this by checking whether the search controller is active inside your tableView delegate methods and acting accordingly.
Ok, so I found the solution and posting in case someone else will have the same issue as I had. I was using the custom cell from a prototype cell from storyboard. The resultController creates a new tableView that knows nothing about the custom cell, that's why it returns always nil. To fix it, I made a reference to the firstly used tableView and not a new one, or get the cell from a real table, this way my custom cell was recognised.