I have a tabbarcontroller with four tableviewcontrollers that are connected by navigation controllers. The tableviews are popualted by images and text download from the internet by a XMLParser. When the app loads, after the splash screen, the screen goes black for a few seconds, then the first table view appears. Tab clicks on the other tableviews also lag. How can I display something in place of a black screen or unresponsive interface while the tableview controller's data is downlaoded?
The code of one of the tableviews:
import UIKit
class TopicsTableViewController: UITableViewController, XMLParserDelegate {
var xmlParser : XMLParser!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "http://sharontalon.com/feed")
xmlParser = XMLParser()
xmlParser.delegate = self
xmlParser.startParsingWithContentsOfURL(url!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: XMLParserDelegate method implementation
func parsingWasFinished() {
self.tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return xmlParser.arrParsedData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCell", forIndexPath: indexPath)
let currentDictionary = xmlParser.arrParsedData[indexPath.row] as Dictionary<String, String>
let url = currentDictionary["enclosure"]
let data = NSData(contentsOfURL: url!.asNSURL) //make sure your image in this url does exist, otherwise unwrap in a if let check
let description = currentDictionary["description"]
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.textLabel?.text = currentDictionary["title"]
cell.detailTextLabel?.text = String(htmlEncodedString: description!)
cell.detailTextLabel?.numberOfLines = 3;
cell.textLabel?.numberOfLines = 2;
cell.imageView?.image = UIImage(data: data!)
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let dictionary = xmlParser.arrParsedData[indexPath.row] as Dictionary<String, String>
let tutorialLink = dictionary["link"]
let publishDate = dictionary["pubDate"]
let tutorialViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("idTutorialViewController") as! TutorialViewController
tutorialViewController.tutorialURL = NSURL(string: tutorialLink!)
tutorialViewController.publishDate = publishDate
showDetailViewController(tutorialViewController, sender: self)
}
This may be caused by a simple threading issue, give this a shot and if it doesn't work I'll try to help you further:
First move your resource heavy operation to a background thread:
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "http://sharontalon.com/feed")
xmlParser = XMLParser()
xmlParser.delegate = self
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {
self.xmlParser.startParsingWithContentsOfURL(url!)
})
}
Next, move any code that will update the user interface to the foreground thread:
func parsingWasFinished() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
If this doesn't resolve your issue, let me know any I'll remove this answer and rethink your problem.
Reloading table data has to be on the main thread of the table to be renewed immediately.
func parsingWasFinished() {
self.tableView.reloadData()
}
Would be:
func parsingWasFinished() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
Related
Currently using Alamofire synchronously within cellForRowAtIndexPath that queries a JSON array from Heroku, and within a for loop, creates a struct from each JSON object within the JSON array with image and text properties and then appends each struct in an array property within the table view controller. Not surprising that this is really slow. On app launch, the initial VC is a container VC that either shows a navigation controller or page VC based on if the user is "logged in." The initial VC in the page VC is a container VC that holds the table VC in question.
I'm totally new to GCD and the concept of concurrency. Was wondering how I can populate my array that serves as the foundational data for each of the table view cells.
Here's my current code - changing some variable names because I signed an NDA for this project:
import UIKit
import Alamofire
import Alamofire_Synchronous
final class PopularPearsTableViewController: UITableViewController {
let screenSize: CGRect = UIScreen.main.bounds
var pears: [Pear] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(PopularPearTableViewCell.self, forCellReuseIdentifier: "popularPear")
tableView.rowHeight = (screenSize.height) * 0.3
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - Table View Data Source
extension PopularShopsTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// just a stub, will be replaced with dynamic code later on
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(#function)
let cell = tableView.dequeueReusableCell(withIdentifier: "popularPear", for: indexPath) as! PopularPearTableViewCell
let userDefaults = UserDefaults.standard
guard let pearUUID = userDefaults.string(forKey: "pearUUID"),
let pearToken = userDefaults.string(forKey: "pearToken")
else {
return cell
}
if indexPath.row == 0 {
let header = createAuthenticatedHeader(user: pearUUID, password: pearToken)
let pearResponse = Alamofire.request("url", headers: header).responseJSON()
if let pearsFromResponse = (pearResponse.result.value! as! JSON)["data"] as? [JSON] {
for pear in pearsFromResponse {
let name = pear["name"] as! String
let pictureURL = pear["picture_url"] as! String
let imageURL = URL(string: pictureURL)
let imageData = NSData(contentsOf: imageURL!)
let image = UIImage(data: imageData as! Data)!
let newPear = Pear(name: name, image: image)
self.pears.append(newPear)
}
}
}
cell.giveCell(pearImage: pears[indexPath.row].image, pearName: pears[indexPath.row].name)
return cell
}
}
I have a problem with my UITableView. I have 3 views with navigation controller. The second one have a table, the third is a search with some values. I want to click on button search (third view) and open second view and update the table. But not update. To work I have to return to first view and open again second view.
Second view code:
class Empresa: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate{
let textCellIdentifier = "cell"
var menu:[[String]] = [[]]
var buscaEmp:BuscadorEmpresa = BuscadorEmpresa()
#IBOutlet weak var tablaVista: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
recuperaEmpresas()
tablaVista.delegate = self
tablaVista.dataSource = self
}
func recuperaEmpresas(){
menu = buscaEmp.getEmpresas()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menu.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
let row = indexPath.row
cell.nombreEmp.text = menu[row][0]
return cell
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
recuperaEmpresas()
self.tablaVista.reloadData()
}
As you can see I reload my tableView (tablaVista) on viewWillAppear but don't update.
RecuperaEmpresa() call this method:
func getEmpresas() -> [[String]]{
NSUserDefaults.standardUserDefaults().synchronize()
var array = [[""]]
if((NSUserDefaults.standardUserDefaults().arrayForKey("ARRAYEMPRESA")) != nil){
array = NSUserDefaults.standardUserDefaults().arrayForKey("ARRAYEMPRESA")! as! [[String]]
return array
}
return array }
I was debugging and it's like getEmpresa don't update first time
EDIT:
GetEmpresa not return the last value the first time, but if I print return correct value. The problem must be NSUSERDEFAULT
EDIT WITH IMAGES
EDITED FOR Alessandro Ornano
Finally my tableView reload but I see an instant my last values and change it with news. I use that:
Second view:
let textCellIdentifier = "cell"
var menu:[[String]] = [[]]
//var buscaEmp:BuscadorEmpresa = BuscadorEmpresa()
#IBOutlet weak var tablaVista: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
recuperaEmpresas()
tablaVista.delegate = self
tablaVista.dataSource = self
}
func recuperaEmpresas(){
self.menu = NSUserDefaults.standardUserDefaults().arrayForKey("ARRAYEMPRESA")! as! [[String]]
//self.menu = buscaEmp.getEmpresas()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menu.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
let row = indexPath.row
cell.nombreEmp.text = menu[row][0]
return cell
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
recuperaEmpresas()
viewDidLoad()
self.tablaVista.reloadData()
}
Third view button call this methods:
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil
{
print("error=\(error)")
return
}
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
var err: NSError?
do{
let myJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
let jsonEmpresas = myJSON["TablaEmp"]!!["item"]
let empresas: [[String: AnyObject]]
if let multipleEmpresas = jsonEmpresas as? [[String: AnyObject]] {
empresas = multipleEmpresas
} else if let singleEmpresa = jsonEmpresas as? [String: AnyObject] {
empresas = [singleEmpresa]
} else {
empresas = []
}
for empresa in empresas{
let zcif = empresa["Zcif"] as? String
let zcccp = empresa["Zcccp"] as? String
let zfax = empresa["Zfax"] as? String
let zdocu = empresa["Zdocu"] as? String
self.arrayEmpresas.append([zcif!, zcccp!, zfax!, zdocu!])
}
NSUserDefaults.standardUserDefaults().setObject(self.arrayEmpresas, forKey:"ARRAYEMPRESA")
NSUserDefaults.standardUserDefaults().synchronize()
}catch { print(error)}
}
task.resume()
And
func back(){
navigationController?.popViewControllerAnimated(true)
}
getMethod:
func getEmpresas() -> [[String]]{
NSUserDefaults.standardUserDefaults().synchronize()
var array = [[""]]
if((NSUserDefaults.standardUserDefaults().arrayForKey("ARRAYEMPRESA")) != nil){
array = NSUserDefaults.standardUserDefaults().arrayForKey("ARRAYEMPRESA")! as! [[String]]
return array
}
return array
}
When you have this kind of issue many times there are involved synchronism between sampling data and UI update. In your code I dont know where you get this data, how long it takes to arrive and how you set this array on NSUserDefaults. So, if you can't see immediatly your UI updates probably you launch reloadData (main thread) too early because there are still operations to the background thread (http server queries, data fetching...)
You can also write a more elegant:
func getEmpresas() -> [String] {
NSUserDefaults.standardUserDefaults().synchronize()
if let array = NSUserDefaults.standardUserDefaults().arrayForKey("ARRAYEMPRESA")! as! [[String]] {
return array
}
return [String]()
}
UPDATE:
I've seen your update code. I want to focus your attention in this method:
func recuperaEmpresas(){
// Are my data ready to read??? who can say to me this in main thread??
menu = buscaEmp.getEmpresas()
}
So you cant call self.tablaVista.reloadData() until you are pretty sure your data can be exists. To do it you can use a completion handler (click me if you want to know how to build it)
func recuperaEmpresas(url: NSURL,completionHandler: CompletionHandler){
callMyNetwork() { //do call and after finish do...
menu = buscaEmp.getEmpresas()
self.tablaVista.reloadData()
}
}
Try to put tableVista.reloadData() in your prepareForSegue or unwindToHome function (whichever you are using to go back to second view). If this is not possible, you can always set a bool var which determines if the view is loaded from the first view or the second view and then reload data accordingly.
Hope this helps. :)
I am creating an app that is retrieving data from Firebase. In my 'MealViewController' I have a TableView that has the view controller as it's delegate and data source. I am getting the issue "Type 'MealViewController" does not conform to protocol 'UITableViewDataSource' because it requires both :numberOfRowsInSection: and :cellForRowAtIndexPath: . However, when I add both, another issue appears - 'Definition conflict with previous value'. I've looked through all the Stack Overflow issues related to this, and no luck has been had. Here's my View Controller:
class MealViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var bgImage: UIImageView?
var image : UIImage = UIImage(named: "pizza")!
#IBOutlet weak var blurEffect: UIVisualEffectView!
#IBOutlet weak var mealTableView: UITableView!
var items = [MealItem]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
bgImage = UIImageView(image: image)
bgImage?.contentMode = .ScaleAspectFill
bgImage!.frame = view.layer.bounds
self.view.addSubview(bgImage!)
//self.bgImage?.addSubview(blurEffect)
//bgImage!.bringSubviewToFront(blurEffect)
view.bringSubviewToFront(blurEffect)
mealTableView.layer.cornerRadius = 5.0
mealTableView.layer.borderColor = UIColor.whiteColor().CGColor
mealTableView.layer.borderWidth = 0.5
let ref = Firebase(url: "https://order-template.firebaseio.com/grocery-items")
mealTableView.delegate = self
mealTableView.dataSource = self
// MARK: UIViewController Lifecycle
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(items.count)
return items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> MealsCellTableViewCell { //issue occurs here
let groceryItem = items[indexPath.row]
if let cell = mealTableView.dequeueReusableCellWithIdentifier("ItemCell") as? MealsCellTableViewCell {
cell.configureCell(groceryItem)
// Determine whether the cell is checked
self.mealTableView.reloadData()
return cell
}
}
func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// [1] Call the queryOrderedByChild function to return a reference that queries by the "completed" property
ref.observeEventType(.Value, withBlock: { snapshot in
var newItems = [MealItem]()
for item in snapshot.children {
let mealItem = MealItem(snapshot: item as! FDataSnapshot)
newItems.append(mealItem)
}
self.items = newItems
self.mealTableView.reloadData()
})
}
func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
}
func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
}
}
override func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
bgImage = UIImageView(image: image)
bgImage?.contentMode = .ScaleAspectFill
bgImage!.frame = view.layer.bounds
self.view.addSubview(bgImage!)
view.bringSubviewToFront(blurEffect)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UITableView Delegate methods
}
The cellForRowAtIndexPath should look like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "ItemCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MealsCellTableViewCell
let groceryItem = self.items[indexPath.row]
cell.configureCell(groceryItem)
return cell
}
Note that the returned cell is a MealsCellTableViewCell which is a subclass of UITableViewCell so it conforms to that class.
Don't change the function definition as that will make it not conform to what the delegate protocol specifies.
Here's a link to the Apple documentation for the specific implementation of custom tableView cells for reference.
Create a Table View
The problem is that your view controller's conformance to UITableViewDatasource cellForRowAtIndexPath method is not right. You should refactor your implementation of cellForRowAtIndexPath method like so:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let groceryItem = items[indexPath.row]
guard let cell = tableView.dequeueReusableCellWithIdentifier("ItemCell") as? MealsCellTableViewCell else {
fatalError("No cell with identifier: ItemCell")
}
cell.configureCell(groceryItem)
return cell
}
You also need to move the datasource methods out of viewDidLoad method.
You return MealsCellTableViewCell instead of UITableViewCell in cellForRowAtIndexPath method, that's the reason.
Developing a swift app at the moment with a table view that is populated using cloud kit.
For some reason when the app opens the tableview appears blank, but once the screen is touched my list of entries suddenly appear.
I've provided my whole Tableview Master Class below, My thinking is that it's something to do with the viewDidLoad() function but I can't seem to figure it out no matter what I try.
import UIKit
import CloudKit
import MobileCoreServices
class MasterViewController: UITableViewController {
//// DB setup (outside DidLoad)
let container = CKContainer.defaultContainer()
var Database: CKDatabase?
var currentRecord: CKRecord?
var detailViewController: DetailViewController? = nil
///Array to save dbrecords
var objects = [CKRecord]()
/// Initialise object of ClipManager class
let MyClipManager = ClipManager()
override func viewDidLoad() {
super.viewDidLoad()
// Database loading on runtime
Database = container.privateCloudDatabase
///Build Query
let query = CKQuery(recordType: "CloudNote", predicate: NSPredicate(format: "TRUEPREDICATE"))
///Perform query on DB
Database!.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
if (error != nil) {
NSLog("Error performing query. \(error.debugDescription)")
return
}
self.objects = records!
self.tableView.reloadData()
}
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem()
let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func viewWillAppear(animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Tableview stuff --- Done
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
/////// Get number of rows
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
//// FIll the table
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let object = objects[indexPath.row]
cell.textLabel!.text = object.objectForKey("Notes") as? String
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
//// Deleting the table
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
//DB call working
MyClipManager.DeleteMethod(Database!, MyRecord:objects[indexPath.row])
objects.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
Considering the reload is happening in a completion handler, I assume it's on a background thread. Try dispatching your UI update back to the main thread.
(Typing a sample without Xcode here, so check my syntax.)
Example:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
If you try these, it works
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
Swift 5.2:
DispatchQueue.main.async{
self.tableView.reloadData()
}
I have made a separate class whose subclass is tableViewController and I am inheriting it in my MapViewController but Addresses are not getting loaded in table View . TableView is blank on simulator. Please tell me some way through which it would be able to pass values(names of different places through google API ) in tableView
override func viewDidLoad()
{
super.viewDidLoad()
self.searchResults = Array()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cellIdentifier")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.searchResults.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath)
cell.textLabel?.text = self.searchResults[indexPath.row]
return cell
}
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath){
// 1
self.dismissViewControllerAnimated(true, completion: nil)
// 2
let correctedAddress:String! = self.searchResults[indexPath.row].stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.symbolCharacterSet())
let url = NSURL(string: "https://maps.googleapis.com/maps/api/geocode/json?address=\(correctedAddress)&sensor=false")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
// 3
do {
if data != nil{
let dic = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableLeaves) as! NSDictionary
let lat = dic["results"]?.valueForKey("geometry")?.valueForKey("location")?.valueForKey("lat")?.objectAtIndex(0) as! Double
let lon = dic["results"]?.valueForKey("geometry")?.valueForKey("location")?.valueForKey("lng")?.objectAtIndex(0) as! Double
// 4
self.delegate.locateWithLongitude(lon, andLatitude: lat, andTitle: self.searchResults[indexPath.row] )
}
}
catch
{
print("Error")
}
}
// 5
task.resume()
}
func reloadDataWithArray(array:[String])
{
self.searchResults = array
self.tableView.reloadData()
}
}