Realm large query blocking the UI - ios

I think this has been talked about quite a bit here and people are working towards passing items from background to main threads.
My question is simple. I have a large list in a UITableView when loading the list the UI for 1-2 seconds. What is the best workaround to prevent this at this point (Ideally without loading the whole fetched data in the memory)?
func bindTableView(term: String? = nil, segment: String? = nil) {
resultsBag = DisposeBag()
if let p = searchPredicateOrganisations(term) , segmentControl.selectedSegmentIndex == 1 {
organisationsResults = realm.objects(Organisation.self).filter(p)
sectionTitles = Set(organisationsResults.value(forKeyPath: "sectionKey") as! [String]).sorted()
Observable.changesetFrom(organisationsResults)
.subscribe(onNext: {organisationResults, changes in
self.processChanges(changes: changes)
}).addDisposableTo(resultsBag)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
if let i = individualsResults {
cell.individual = i.filter("sectionKey == %#", sectionTitles[indexPath.section])[indexPath.row]
}
return cell
}
Thank you in advance.

Related

Getting IndexPath Item in TableView Embedded in CollectionView

I have a TableView that is embedded into a CollectionView, and I am trying to show relevant data in the TableView that corresponds to the correct CollectionViewCell or IndexPath Item. I tried assigning tag as such: cell.tableView.tag = indexPath.item but it seems to be problematic.
I tried print(tableView.tag) in my collectionViewCell and it printed
2 1 0 3 4 5
but I have 7 collectionViewCells in total so the last tag isn't printing for some reason.
My collectionView is embedded in another TableView already, below is the code in the MasterTableViewCell.swift:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if diningIndexPath.section == 0 {
let cell: FoodCourtCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "foodCourtCell", for: indexPath) as! FoodCourtCollectionViewCell
cell.tableView?.register(UINib(nibName: "RestaurantTableViewCell", bundle: nil), forCellReuseIdentifier: "restaurantCell")
cell.tableView.tag = indexPath.item
//...
return cell
}
}
In the customCollectionViewCell.swift, I have this code for my tableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: RestaurantTableViewCell = tableView.dequeueReusableCell(withIdentifier: "restaurantCell", for: indexPath) as! RestaurantTableViewCell
print(tableView.tag)
let currentRestaurant = foodCourts[tableView.tag].childLocations[indexPath.row]
cell.textLabel.text = currentRestaurant.name
//...
return cell
}
Is there any way to fix this, or are there other ways to achieve what I want to do? Any help is appreciated, thanks!
Nesting these entities is always a pain especially when you need to access indexPaths of items later. If if get your problem correctly. One of the solutions I suggest is to store a map (dictionary) of your paths. For a fast access to them. Here's an example of how I managed this in a similar situation:
typealias CollectionIndexPath = IndexPath
typealias TableIndexPath = IndexPath
var indexMap: [CollectionIndexPath: TableIndexPath] = [:]
Now when you need to access some of the items or configure it.
func cellForItemAtIndexPath { ... } {
let cell = { ... }
let cellPath = indexPath
let tablePath = indexMap[cellPath]
let foodCourtForCell = foodCourts[cellPath.item]
let childLocationsForTableView = foodCourtForCell.childLocations
cell.configureWith(court: foodCourtForCell, locations: childLocations)
Now you can manage all the data related to this nested monster from the outside.

JSON response not being shown inside the cell

I’m creating a Tableview and trying to include in the cell one information that I receive through a JSON from an API.
The information (JSON) is being received very well and recorded correctly inside the variable.
However, what I could find is that as the information is received with a small “delay” is not being set as the cell’s label text at the cell creation moment, which is being set with the default variable content.
I guess the solution would be to update the label at the moment I parse the JSON content, right? How do I do this? (Update a cell’s label after it is already created)
Any other insight/ solution is greatly appreciated.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AddStock {
let operacoesAcoes = ListaOperacoes()
var todasAsOperacoes : [Operacao] = []
#IBOutlet weak var acoesTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
acoesTableView.delegate = self
acoesTableView.dataSource = self
acoesTableView.register(UINib(nibName: "StandardStockListCell", bundle: nil), forCellReuseIdentifier: "standardCell")
operacoesAcoes.consolidaAcoes()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return operacoesAcoes.carteiraAcoes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "standardCell", for: indexPath) as! StandardStockListCell
let informacaoCompletaAcao = operacoesAcoes.carteiraAcoes[indexPath.row]
cell.codigoTextField.text = informacaoCompletaAcao.codigoAcao
cell.nomeTextField.text = informacaoCompletaAcao.nomeAcao
cell.quantidadeTotal.text = String(informacaoCompletaAcao.quantidadeTotal)
cell.precoMedioLabel.text = String(format: "%.2f", informacaoCompletaAcao.precoMedio)
//
// This is the part of the code that should set one label with a value returned from "buscaCotacao" but it does not work
// because at the time the cell is displayed it is still not updated from JSON information:
// Note: the buscaCotacao func is working fine
cell.precoAtualLabel.text = buscaCotacao(ativo: informacaoCompletaAcao.codigoAcao)
return cell
}
You need to reload the table view on the main thread after receiving and parsing the JSON.
self.acoesTableView.reloadData()
I did some research and tryouts, and could figure out a very simple (and now obvious) solution to update my Label after the result of the request is received:
- I call the function which retrieves the information from an API to update the cell ("buscaCotacao"), including the [cell row] information
- I update the cell's label from inside the function, which will happen only after the reply is received:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "standardCell", for: indexPath) as! StandardStockListCell
let informacaoCompletaAcao = operacoesAcoes.carteiraAcoes[indexPath.row]
cell.codigoTextField.text = informacaoCompletaAcao.codigoAcao
cell.nomeTextField.text = informacaoCompletaAcao.nomeAcao
cell.quantidadeTotal.text = String(informacaoCompletaAcao.quantidadeTotal)
cell.precoMedioLabel.text = "R$ "+String(format: "%.2f", informacaoCompletaAcao.precoMedio)
buscaCotacao(ativo: informacaoCompletaAcao.codigoAcao, for: cell, indexPath: indexPath)
return cell
}
And in the function:
func buscaCotacao (ativo: String, for cell: StandardStockListCell, indexPath: IndexPath) {
let finalURL = "https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&interval=1min&apikey=XXXXXXXXXXXXXX&outputsize=compact&symbol=" + ativo
Alamofire.request(finalURL, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let resultadoJSON : JSON = JSON(response.result.value!)
let resultado = Float (self.parseResultado(json: resultadoJSON))!
cell.precoAtualLabel.text = "R$ "+String(format: "%.2f", resultado)
self.cotacoes[ativo] = resultado
} else {
cell.precoAtualLabel.text = "N/D"
print("Error: \(response.result.error!)")
}
}
}

Iterating through TableViewCells skips some

I have a tableview and in each cell there is a checkbox. I also have a "select all" button.
My problem is that when I click select all I want to update all the checkboxes to checked state. So from a list of 100 cells, all get checked but every 13th cell does not. To make it clearer, on my simulators screen are 12 cells visible that all get checked. When I start scrolling, the first cell that comes up is unchecked, and is then followed by 12 checked ones :S
When I scroll a little and click "select all" again, the skipped ones become also checked..
Anyone have a clue what am I missing?
This is the cell code:
class ListTableViewCell: UITableViewCell {
#IBOutlet weak var checkbox: UIButton!
var buttonState = false{
didSet{
if buttonState{
checkbox.setImage(#imageLiteral(resourceName: "checked"), for: .normal)
}else{
checkbox.setImage(#imageLiteral(resourceName: "unchecked"), for: .normal)
}
}
}
#IBAction func checkboxAction(_ sender: UIButton) {
if buttonState {
buttonState = false
}else{
buttonState = true
}
}
func simulateCheck(){
buttonState = true
}
And here are some snipets from my controller:
private var articleValues: [ArticleValue] = []{
didSet{
tableView.reloadData()
}
}
func selectAll(){
for i in 0..<articleValues.count{
let cell = tableView.cellForRow(at: IndexPath(item: i, section: 0)) as? ListTableViewCell
cell?.simulateCheck()
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
}
return cell
}
Your UITableView is backed by a data source. This means that you shouldn't change cells directly like you do here:
cell?.simulateCheck()
tableView.reloadData()
Instead you should keep a list of all the checked positions, maybe another array that has bools for each corresponding articleValue (this is not the best design).
var checkedValues = Bool
In your
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method you would then set the state of the cell:
articleValueCell.buttonState = checkedValues[indexPath.row]
In your selectAll method fill this array with true values and then call tableView.reloadData()
private var checkedValues = [Bool]()
private var articleValues: [ArticleValue] = []{
didSet{
checkedValues = Array(repeating: false, count: articleValues.count)
tableView.reloadData()
}
}
func selectAll(){
checkedValues = Array(repeating: true, count: articleValues.count)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
articleValueCell.buttonState = checkedValues[indexPath.row]
}
return cell
}
Another mistake is that you should never iterate on all the cells in the table because they are reused, no point in going through your data source and getting a cell for each. It only makes sense to iterate through tableView.visibleCells. But like in your case, most of the time you don't need that either, you should just update your data source accordingly and reload the table or just the modified cell.
It's not recommended that you refer to cells directly within a table view. The reason is that UITableViews have an efficient method of only loading the cells as they are needed (and deallocating them when they are no longer needed, e.g. the cell scrolls off screen). Because of this the cell you are try to refer to may not be loaded.
Instead you should interact with it via the cellForRowAt method. If you want to "select all" cells, you should create a property that stores the value of checked or not checked via a Bool and then set all of the ArticleValue elements to true for that property and reload the data inside selectAll().
It could work something like this:
func selectAll() {
articleValues.forEach {
$0.checked = true
}
tableView.reloadData()
}
// ...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
if articleValue.checked {
articleValueCell.simulateCheck()
}
}
return cell
}

Error when creating tableview with multiple custom cells

I have encountered an error in swift when attempting to create a tableview made up of custom cells dependent upon a set of conditions.
Here is my code:
var tableData: [String] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let phonenocell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier("phonecell", forIndexPath: indexPath) as! MyCustomCell
let pincell:SocialCell = self.tableView.dequeueReusableCellWithIdentifier("socialcell", forIndexPath: indexPath) as! SocialCell
let fbcell:FacebookCell = self.tableView.dequeueReusableCellWithIdentifier("facebookcell", forIndexPath: indexPath) as! FacebookCell
let snapcell:SnapchatCell = self.tableView.dequeueReusableCellWithIdentifier("snapchatcell", forIndexPath: indexPath) as! SnapchatCell
let twitcell:TwitterCell = self.tableView.dequeueReusableCellWithIdentifier("twittercell", forIndexPath: indexPath) as! TwitterCell
let instacell:InstagramCell = self.tableView.dequeueReusableCellWithIdentifier("instagramcell", forIndexPath: indexPath) as! InstagramCell
if tableData.contains("Number") {
return phonenocell
}
if tableData.contains("Social") {
return pincell
}
if tableData.contains("Facebook") {
return fbcell
}
if tableData.contains("Snapchat") {
return snapcell
}
if tableData.contains("Twitter") {
return twitcell
}
if tableData.contains("Instagram") {
return instacell
}
}
When attempting to build and run I get a build failed with the following fault:
"Missing Return in a function expected to return 'UITableViewCell'
I have been over and over my code but I honestly cannot see where I am going wrong...
Any help would be greatly appreciated!
You need to return cell for sure.
You already do in conditions, but in case none of your condition statements would success, your return call wouldn't be fired.
Appending, for example:
return phonenocell
to the end of the function, should be quick fix for your code. It ensures, that the function will return a cell (that is mandatory).
My data source is the array tableData. This is constructed on the previous view as: #IBAction func switch1Toggled(sender: UISwitch) { if mySwitch1.on { fbTextBox.text = "Selected" dataArray.append("Facebook")
And this may be the main issue:
Assuming, that you choose 'facebook' and that you reload your tableView, every row will pass the first condition as it IS contained.
You should put this in your method:
//assuming your data source contains multiple members, and your numberOfRowsInSections... method return tableData.count, you need to get each item for each row:
let currentTag = tableData[indexPath.row]
if (currentTag == "something") { //e.g. Facebook
let somethingcell:MySomethingCell = ...
self.tableView.dequeueReusableCellWithIdentifier("somethingcell", forIndexPath: indexPath) as! MySomethingCell
return somethingcell
} else if {
...
}
return emptycell //this line is just for the case, when no of your conditions will pass and you don't catch all the situations...
maybe your array elements doesn't match the condition, it's better to return default value instead of ur conditions failed

Fatal error. Array index out of range in indexpath - swift

My following code is part of a search controller. It works without a problem but after 8-10 searches, I encounter fatal error (Thread 1 : EXC_BAD_INSTRUCTION) on following line :
let movie = filteredMovies[indexPath.item]
Could you please advise the way of solving this kind of problems.
extension searchResults: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("searchCell", forIndexPath: indexPath) as! searchResultCell
let movie = filteredMovies[indexPath.item]
cell.searchLabel.text = movie.title
let fileUrl = NSURL(string: movie.thumb)!
if let data = NSData(contentsOfURL: fileUrl)
{
cell.searchImage.contentMode = UIViewContentMode.ScaleAspectFit
cell.searchImage.image = UIImage(data: data)
// to make images rounded
cell.searchImage.backgroundColor = UIColor.clearColor()
cell.searchImage.layer.cornerRadius = 15.0
cell.searchImage.clipsToBounds = true
cell.backgroundColor = UIColor.clearColor()
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return filteredMovies.count
}
}
You can check if indexPath.item is within the bounds of the array filteredMovies:
if (indexPath.item >= 0 || indexPath.item < movie.count) {
// now you can safely use filteredMovies[indexPath.item]
} else {
// print something so you can investigate
}
And if you wonder why indexPath.item might be out of the bounds of filteredMovies, it's other programming logic you have to investigate (maybe you remove some elements in filteredMovies after the tableView is loaded).
After all, always checking the bounds is a good thing when you have doubt or want to make sure it will never cause crash (fault tolerance concept).

Resources