I am taking an iOS programming class through udemy and am trying to build use the UITableViewDelegate functions to define and populate a table.
I am getting a "Definition conflicts with previous value" error, when calling cellForRowAtIndexPath, and it is referring to numberOfRowsInSection.
I am using Version 6.1.1 (6A2008a)
Any ideas?
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//let's go ahead and define the URL that we are going to use. this literrally is saying, create a URL that Xcode can use using the string below.
let url0 = NSURL(string: "http://www.weather-forecast.com")
//let's create a task. the task will go to weather-forecast.com and get the contents.
//first, we define the task, and then specify the method that is associated with that task.
//we need to create an array of string values for the cities we are looking for that will be used in the task, as well as outside of the task.
var arrayCities: [String] = []
let task0 = NSURLSession.sharedSession().dataTaskWithURL(url0!) {
//NSURLSession opens an http session inside of the app.
//.dataTaskWithURL gets the data from the URL
//.dataTaskWithURL responds with three variables: data, response, error
(data, response, error) in
if error == nil {
//if error is nil, create a variable urlContent with type NSSTring, and encode data with NSUTF8StringEncoding
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding)
//let's then set up a sting that we can parse correctly
var stringParse = NSString(string: urlContent!)
//let's start parsing urlContent and putting it into an array for us to put into a table.
//let's see if the desired list of cities are indeed inside of the urlContent
if urlContent?.rangeOfString(">Paris Weather Forecast<") != nil{
arrayCities.append("Paris")
}
else{println("paris has not been added")}
if urlContent?.rangeOfString(">New York Weather Forecast<") != nil{
arrayCities.append("New York")
}
else {println("New York has not been added")}
if urlContent?.rangeOfString(">Dubai Weather Forecast<") != nil{
arrayCities.append("Dubai")
}
else {println("Dubai has not been added")}
if urlContent?.rangeOfString(">Rome Weather Forecast<") != nil{
arrayCities.append("Rome")
}
else {println("Rome has not been added")}
if urlContent?.rangeOfString(">Mumbai Weather Forecast<") != nil{
arrayCities.append("Mumbai")
}
else {println("Mumbai has not been added")}
if urlContent?.rangeOfString(">London Weather Forecast<") != nil{
arrayCities.append("London")
}
else {println("London has not been added")}
if urlContent?.rangeOfString(">Berlin Weather Forecast<") != nil{
arrayCities.append("Berlin")
}
else {println("Berlin has not been added")}
//println(stringParse)
//println(urlContent)
println(arrayCities)
println(arrayCities.count)}
}
//make sure we kick off task0.
//we do this by calling the resume() method
task0.resume()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return arrayCities.count
}
//the below code will be run eveytime for every amount of rows in the table.
//the mehtod defines the contents of each individual cell. you can put images and looks of the cells. it returns a UITable view cell, and all the content that goes with it.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//the variable indexPath tells you what row you are in everytime.
//let's create a cell to return. the reuseIdentifier is used to create a reference name for the cell.
//when we create and edit a prototype cell, we are referencing all the cell styles.
//if we had a situation where we had active users that appeared red in the table and active users that appeared green, we would need to set up two seperate Prototpye cells and two reuseIdentifiers.
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
cell.textLabel?.text = arrayCities[indexPath.row]
return cell
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Please, paste your log error if none of the two propositions below didn't help.
Cell Identifier
In your ViewController view (storyboard), make sure you have a prototype cell. Select it and access its attributes. Make sure the cell identifier is the same that you use here :
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
Reload data
After you retrieved you data, you might want to reload the tableview :
self.tableView.reloadData()
Related
I'm new to this so have just been learning about completion blocks, but I am unsure of how to do so in such a way that I get the data to then be apart of a tableview. I have seen other questions related, but regarding older versions of Swift.
I want the table view to contain all the fruit names collected from my database.
I have initialised an empty array list like so:
var fruitNames : [String] = []
Then fetch the data from my firestore database
func getNames(){
let db = Firestore.firestore()
db.collection("fruits").getDocuments() {(snapshot, error) in
if let error = error {
print("There was an error!")
} else {
for document in snapshot!.documents {
let name = document.get("name") as! String
self.fruitNames.append(name)
//completion needed
}
}
}
}
}
I have an extension added on for my tableView
extension FruitsViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
fruitNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = self.fruitNames[indexPath.row]
return cell!
}
}
Inside your completion block, you need to tell your table view to update by calling the reloadData() method. In your class, you should have a variable holding your tableView. So, your getName should look like
func getNames(){
let db = Firestore.firestore()
db.collection("fruits").getDocuments() {(snapshot, error) in
if let error = error {
print("There was an error!")
} else {
for document in snapshot!.documents {
let name = document.get("name") as! String
self.fruitNames.append(name)
//completion needed
}
self.tableView.reloadData()
}
}
}
First of all, use [weak self] in closure. Otherwise it can lead to memory leak or crashes. You should read about
automatic reference counting and memory management
closures (https://docs.swift.org/swift-book/LanguageGuide/Closures.html)
If you want to display fruit names, you should call .reloadData() on your tableView object. Then, all delegate methods like numberOfRowsInSection or cellForRowAt will be called again.
You can do something like this :
You have to take an escaping closure as a parameter to the getName() method, which would return Void :
func getName(onComplition: #escaping (_ isSuccess: Bool, _ dataList: [String]) -> Void) {
let db = Firestore.firestore()
db.collection("fruits").getDocuments() {(snapshot, error) in
if let error = error {
print("There was an error!")
onComplition(true, [])
} else {
var data = [String]()
for document in snapshot!.documents {
let name = document.get("name") as! String
data.append(name) // Here data is local variable.
}
onComplition(true, data)
}
}
}
in ViewDidLoad()
override func viewDidLoad() {
self.getName { [weak self] (isSuccess, dataList) in
guard let weakSelf = self else { return }
weakSelf.fruitNames = dataList // fruitNames is your TableViewController's instance variable
weakSelf.tableView.reloadData()
}
}
I have written it directly in IDE, please ignore if there's any syntax error
If you have written perfect code to fetch fruit names.
But your table view is already initialized and loaded with default/empty items in the table view.
You have fetched data after the table view loaded.
So solution is you have to reload your table view again.
So in the closure (After fetching and appending your data to an array) just reload the table view like below and it reloads fresh data.
tableView.reloadData()
User [weak self] or [unowned self] for closure to avoid retain cycles and it causes memory issues.
I have a tableView controller where each line will represent the value of a specific cryptocurrency. I have a class for each different cryptocurrency the user wants to track called CryptoCurrency. Under the TableView CellforRowAt function I'm calling another function called getCryptoData, that will use AlamoFire to send api request to get the price of each cryptocurrency.
The problem is that the cellForRowAt function returns a cell with the default price of 0.00 before the getCryptoData function can finish and update the model.
I'm assuming this is because that function is run asynchronously?
How do I make it either wait for the function to finish before returning the cell or reload the cell after it is finished?
I tried adding tableView.reloaddata() at the end of the updateCryptoData function but that resulted in an endless loop.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CryptoCurrencyCell", for: indexPath)
let cryptoCurrencyItem = cryptoCurrencyContainer.listOfCryptoCurrencies[indexPath.row]
getCryptoData(url: getApiString(for: cryptoCurrencyItem.id), currencyItem: cryptoCurrencyItem)
cell.textLabel?.text = cryptoCurrencyContainer.listOfCryptoCurrencies[indexPath.row].name + "\(cryptoCurrencyItem.currentPrice)"
print(cryptoCurrencyItem.currentPrice)
return cell
}
func getCryptoData(url: String, currencyItem: CryptoCurrency) {
Alamofire.request(url, method: .get).responseJSON {
response in
if response.result.isSuccess {
print("Sucess! bitcoin data")
let cryptoDataJSON : JSON = JSON(response.result.value!)
print(cryptoDataJSON)
self.updateCryptoData(json: cryptoDataJSON, currencyItem: currencyItem)
} else {
print("oops")
print("Error: \(String(describing: response.result.error))")
//self.bitcoinPriceLabel.text = "Connection Issues"
}
}
}
func updateCryptoData(json : JSON, currencyItem: CryptoCurrency) {
if let cryptoPriceResult = json["ask"].double {
//bitcoinPriceLabel.text = String(bitcoinResult)
currencyItem.updatePrice(price: cryptoPriceResult)
print(currencyItem.currentPrice)
} else {
//bitcoinPriceLabel.text = "Price Unavailable"
print("something aint right")
}
}
Under the cellForRowAt function there is a print statement:
print(cryptoCurrencyItem.currentPrice)
capturing the curent price before the cell is return. The console shows that it is still 0.00 implying that the getCryptoData function hasn't finished running.
Probably it is not the best solution but you could pass the cell to your method
func getCryptoData(url: String, currencyItem: CryptoCurrency, for cell: UITableViewCell) {
Alamofire.request(url, method: .get).responseJSON {
response in
if response.result.isSuccess {
print("Sucess! bitcoin data")
let cryptoDataJSON : JSON = JSON(response.result.value!)
print(cryptoDataJSON)
self.updateCryptoData(json: cryptoDataJSON, currencyItem: currencyItem, for: cell)
} else {
print("oops")
print("Error: \(String(describing: response.result.error))")
//self.bitcoinPriceLabel.text = "Connection Issues"
}
}
}
func updateCryptoData(json : JSON, currencyItem: CryptoCurrency, for cell: UITableViewCell) {
if let cryptoPriceResult = json["ask"].double {
//bitcoinPriceLabel.text = String(bitcoinResult)
currencyItem.updatePrice(price: cryptoPriceResult)
print(currencyItem.currentPrice)
cell.textLabel?.text = "\(cryptoPriceResult)" // your cell
} else {
//bitcoinPriceLabel.text = "Price Unavailable"
cell.textLabel?.text = "\(cryptoPriceResult)" // your cell
print("something aint right")
}
}
Since UITableViewCell is a reference type, once the async call is finished, it will update the cell text label.
Your request is running in a background thread. It means your tableView runs before you get data from request. So when you get success in updateCryptoData method you should call tableView.reloadData() in main thread.
DispatchQueue.main.async {
tableView.reloadData()
}
This code will run tableView cellForRow method
Yes tableView.reloaddata() at the end of the updateCryptoData function will rsult in infinite loop because in every reload after api call you again calling api and cycle continues.
From one of many approach, You can have a data structure of key-value based.
In your cellForRowAt function, you will be assigning value from data sctructure if available to your Label. if not value is available in your data structure then show some default value like "loading" or tiny loading wheel UI.
cell.priceLabel?.text = dataStructre["key"]
Now this key could be your some unique id that you identify your currency items.
Before calling your api from cell, check beforehand if value for "key" is available in your data structure. If value is unavailable then only you will call the api else simple fetch from data structure and set it to your Label.
if let value = dataStructre["key"]{
cell.priceLabel?.text = value
}
else{
callApi()
}
Finally In success method of your api call simple store the value that you got from server in you data structure with the "key identifier". After saving to your data structure, simply call "reloadData".
function callApi(){
if(success){
dataStructure["key"] = value from server
table.reloadData()
}
}
Currently I have the following code which saves an object however I am wanting to update/reload the tableview. The button isn't attached to a cell/row it's top right within my navigation controller (plus icon)
Note: Everything is happening within the same scene therefore any events attached to segue where I could reload table data is out of the question.
#IBAction func addWeek (sender: UIButton){
let newnumber:Int = routineWeeks.count + 1
// save data using cor data managed object context
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
week = NSEntityDescription.insertNewObjectForEntityForName("Weeks", inManagedObjectContext: managedObjectContext) as! Weeks
week.weekNumber = newnumber
do {
try managedObjectContext.save()
} catch {
print(error)
return
}
}
//currently not reloading table with new data
tableView.reloadData()
//print information in console
print("end of save function, dismiss controller")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
// Configure the cell...
cell.textLabel?.text = "Week \(routineWeeks[indexPath.row].weekNumber)"
return cell
}
UPDATE
Thanks Stackoverflow community for pointing me in the right direction.
routineWeeks.append(week)
print("this is a new week:\(week)")
tableView.reloadData()
I do not know if it will help you or not but I had the same problems and I added Refresher (to add "Pull To Refresh" function)
In my class :
override func viewDidLoad() {
super.viewDidLoad()
// Pull to refresh - DEBUT
tableFiches.addPullToRefreshWithAction {
NSOperationQueue().addOperationWithBlock {
//sleep(1)
NSOperationQueue.mainQueue().addOperationWithBlock {
self.loadMyData() // My func here to load data
self.tableFiches.stopPullToRefresh()
}
}
}
// Pull to refresh - FIN
}
func loadMyData(){
// request here
let recupJSON = JSON(value) // my data
if let resData = recupJSON["Data"].arrayObject {
//print("OK2")
self.ArrayFiches = resData as! [[String:AnyObject]] // Attribute json ==> ArrayFiches
}
if self.ArrayFiches.count > 0 {
self.tableFiches.reloadData()
}
}
And when I want reload my data, I use :
tableFiches.startPullToRefresh()
And it works :)
You are not updating routineWeeks array. Update it with your new data before reloading the tableView.
You seem to never add "week" to routineWeeks.
EDIT :
You should reload the datas in routineWeeks (from CoreData) right before your tableView.reloadData.
routineWeeks.append(week)
print("this is a new week:\(week)")
tableView.reloadData()
I was thinking about PFQuery.
I'm developing an App that shows a Feed to the Users and it also displays a Like counter for each Post (like a Facebook App or Instagram App).
So in my PFQueryTableViewController I have my main query, that basically show all the Posts:
override func queryForTable() -> PFQuery {
let query = PFQuery(className: "Noticias")
query.orderByDescending("createdAt")
return query
}
And I use another query to count the number of Likes on another Class in Parse that contais all the Likes.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("FeedCellIdentifier") as! FeedCell!
if cell == nil {
cell = FeedCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FeedCellIdentifier")
}
let query2 = PFQuery(className:"commentsTable")
query2.whereKey("newsColumn", equalTo: object!)
query2.findObjectsInBackgroundWithBlock {
(objectus: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let quantidade = objectus!.count
let commentQuantidade = String(quantidade)
cell.comentariosLabel.text = commentQuantidade
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
This way to code works, and I achieve what I want, but! I know that I'm reusing cells, I know that this block of code is called everytime a cell appear.
And I know those facts:
A lot of query requests is sent to Parse Cloud, everytime I scroll the tableview
It's possible to see the values changing, when I'm scrolling the tableview, for example, because I'm reusing the cells a post has a value of my previous cell and then with the new query it's refreshed, this works but not look good for user experience.
So, my main doubt is, is it the right way to code? I think not, and I just want another point of view or an idea.
Thanks.
EDIT 1
As I said I've updated my count method to countObjectsInBackgroundWithBlock instead of findObjectsInBackgroundWithBlock but I'm not able to move the query to the ViewDidLoad, because I use the object to check exactly how many comments each Post have.
EDIT 2
I've embed the query to count the number of comments for each post and printing the results, now I'm think my code is better than the previous version, but I'm not able to pass the result to a label because I'm receiving a error:
Use of unresolved identifier 'commentCount'
I'm reading some documentations about Struct
Follows my updated code bellow:
import UIKit
import Social
class Functions: PFQueryTableViewController, UISearchBarDelegate {
override func shouldAutorotate() -> Bool {
return false
}
var passaValor = Int()
let swiftColor = UIColor(red: 13, green: 153, blue: 252)
struct PostObject{
let post : PFObject
let commentCount : Int
}
var posts : [PostObject] = []
// Initialise the PFQueryTable tableview
override init(style: UITableViewStyle, className: String!) {
super.init(style: style, className: className)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// The className to query on
self.parseClassName = "Noticias"
// The key of the PFObject to display in the label of the default cell style
self.textKey = "text"
// Uncomment the following line to specify the key of a PFFile on the PFObject to display in the imageView of the default cell style
self.imageKey = "image"
// Whether the built-in pull-to-refresh is enabled
self.pullToRefreshEnabled = true
// Whether the built-in pagination is enabled
self.paginationEnabled = true
// The number of objects to show per page
self.objectsPerPage = 25
}
// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery {
let query = super.queryForTable()
return query
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
loadObjects()
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func viewDidLoad() {
super.viewDidLoad()
// navigationBarItems()
let query = PFQuery(className:"Noticias")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
// The find succeeded.
print("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects {
for object in objects {
let queryCount = PFQuery(className:"commentsTable")
queryCount.whereKey("newsColumn", equalTo: object)
queryCount.countObjectsInBackgroundWithBlock {
(contagem: Int32, error: NSError?) -> Void in
let post = PostObject(object, commentCount:commentCount)
posts.append(post)
print("Post \(object.objectId!) has \(contagem) comments")
}
self.tableView.reloadData()
}
}
}
//Self Sizing Cells
tableView.estimatedRowHeight = 350.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// Define the query that will provide the data for the table view
//override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("FeedCellIdentifier") as! FeedCell!
if cell == nil {
cell = FeedCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FeedCellIdentifier")
}
cell?.parseObject = object
if let assuntoNoticia = object?["assunto"] as? String {
cell?.assuntoNoticia?.text = assuntoNoticia
}
if let pontos = object?["pontos"] as? Int {
let pontosPosts = String(pontos)
cell?.pontosLabel?.text = String(pontosPosts)
}
if let zonaLabel = object?["zona"] as? String {
cell?.zonaLabel?.text = zonaLabel
}
if let criticidade = object?["criticidade"] as? String {
if criticidade == "Problema"{
cell.criticidadeNoticia.backgroundColor = UIColor.redColor()
} else {
cell.criticidadeNoticia.backgroundColor = UIColor.greenColor()
}
}
return cell
}
}
And the result of print:
Successfully retrieved 5 scores.
Post wSCsTv8OnH has 4 comments
Post LbwBfjWPod has 0 comments
Post fN4ISVwqpz has 0 comments
Post 1rXdQr2A1F has 1 comments
Post eXogPeTfNu has 0 comments
Better practice would be to query all data on view load saving it into model and then read data from it on table view scroll. When processing query you can show downloading indicator or placeholder data. When query is complete you'll call tableView.reloadData()
You can accomplish this by creating a new variable like this:
var cellModels : [PFObject] = []
In your query2.findObjectsInBackgroundWithBlock:
for object in objectus{
self.cellModels.append(object)
}
self.tableView.reloadData()
And in cellForRowAtIndexPath:
let model = cellModels[indexPath.row]
// configure cell according to model
// something like cell.textLabel.text = model.text
P.S You should take a look at method countObjectsInBackgroundWithBlock if you only need to get count of objects. Because if there're a lot of e.g comments findObjectsInBackgroundWithBlock will return maximum of 1000 objects and still you won't be downloading whole objects, only one number this will speed up query and spare user's cellular plan.
Update: Also if you need to store numbers of comments you can create simple struct like this:
struct PostObject{
let post : PFObject
let commentCount : Int
}
var posts : [PostObject] = []
And when you query for you posts you loop through received objects and populate posts array.
for object in objects{
// create countObjectsInBackgroundWithBlock query to get comments count for object
// and in result block create
let post = PostObject(object, commentCount:commentCount)
posts.append(post)
}
tableView.reloadData()
And in cellForRowAtIndexPath:
let post = posts[indexPath.row]
cell.postCountLabel.text = String(post.commentCount)
// configure cell accordingly
You should do your queries before you present the information in your tableview.
I'm just getting to grips with iOS development and Xcode altogether, and I'm learning it with Swift 2. I'm trying to get some JSON data from a URL, split it up into a swift Array, and display it in a TableView. I have managed to split the JSON data into an Array, but I'm having trouble reloading the table's data to get it to display this. Here's the code I have:
//
// ViewController.swift
// Table JSON
//
// Created by James Allison on 06/11/2015.
// Copyright © 2015 James Allison. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
var cellContent = ["this should be replaced","something","something else"]
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// construct url
let url = NSURL(string: "http://127.0.0.1:8888/json.php")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
// the following will happen when the task is complete
if let urlContent = data {
var webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
// remove []
webContent = webContent?.stringByReplacingOccurrencesOfString("[", withString: "")
webContent = webContent?.stringByReplacingOccurrencesOfString("]", withString: "")
// split by commas
var webContentArr = webContent?.componentsSeparatedByString(",")
var temp = ""
// remove quote marks
for var i = 0; i < webContentArr!.count; i++ {
temp = webContentArr![i]
temp = temp.stringByReplacingOccurrencesOfString("\"", withString: "")
webContentArr![i] = temp
}
print(webContentArr!)
self.cellContent = webContentArr! as! Array
self.table.reloadData()
}
else {
// something failed
print("Error: invalid URL or something.")
}
}
task.resume()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellContent.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel?.text = cellContent[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
At the moment when I run it the table displays the original cellContent variable, but not the new one. No errors are produced, and the array is printed okay.
Edit: Thanks Joshua for your answer. I ended up using the following code to solve my issue:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.cellContent = webContentArr! as! Array
self.table.reloadData()
})
At a guess, your "will happen when the task is complete" code is being run on some thread/queue other than main, which does not play well with UI updates. Anything that touches the UI must be done on the main queue.
You should adjust your code so that both the replacement of cellContent and the call to your table view to reloadData() are scheduled on the main queue after you're finished processing everything. To do so, wrap both the above-mentioned calls in an async dispatch sent to the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
self.cellContent = webContentArr! as! Array
self.table.reloadData()
});
This will ensure the cellContent array isn't being modified "behind the table view's back" while it's updating the UI on the main queue (bad!) and that the table view doesn't try updating again until it's done with any ongoing updates.
I hope this helps.