I have a UITableView with its contents managed by a NSFetchedResultsController fetching CoreData. I have two types of table view cells, one with an image and one without an image. The images are handled by UIDocument and are kept in the iCloud ubiquitous documents container and are referenced by client name.
As the cells are generated and reused when there are many user generated images on the screen, the memory usage of my program creeps higher and higher. Around 110 mb, I get a low memory warning.
I suspect my prepareForReuse() method in my tableView cell isn't doing its job correctly.
Here's what my UITableViewCell looks like now:
class ClientTableViewCell: UITableViewCell {
#IBOutlet weak var clientName: UILabel!
#IBOutlet weak var clientModifiedDate: UILabel!
#IBOutlet weak var profileImage: UIImageView!
let dateFormatter = NSDateFormatter()
var document: MyDocument?
var documentURL: NSURL?
var ubiquityURL: NSURL?
var metaDataQuery: NSMetadataQuery?
var myClient : Client?
{
didSet
{
updateClientInfo()
}
}
override func prepareForReuse() {
super.prepareForReuse()
clientName.text = nil
clientModifiedDate.text = nil
if let mc = myClient
{
if mc.imageurl != ""
{
if let p = profileImage
{
p.image = nil
} else
{
NSLog("nil document")
}
}
} else
{
NSLog("nil client")
}
}
func updateClientInfo()
{
if myClient != nil
{
clientName.text = myClient!.name
dateFormatter.dateStyle = .ShortStyle
let dispDate = dateFormatter.stringFromDate(NSDate(timeIntervalSinceReferenceDate: myClient!.dateModified))
clientModifiedDate.text = dispDate
if let imageName = myClient?.imageurl {
if myClient?.imageurl != "" {
var myClientName : String!
myClientName = myClient!.name
metaDataQuery = NSMetadataQuery()
metaDataQuery?.predicate = NSPredicate(format: "%K like '\(myClientName).png'", NSMetadataItemFSNameKey)
metaDataQuery?.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "metadataQueryDidFinishGathering:",
name: NSMetadataQueryDidFinishGatheringNotification,
object: metaDataQuery!)
metaDataQuery!.startQuery()
}
}
}
}
func metadataQueryDidFinishGathering(notification: NSNotification) -> Void {
let query: NSMetadataQuery = notification.object as! NSMetadataQuery
query.disableUpdates()
NSNotificationCenter.defaultCenter().removeObserver(self, name: NSMetadataQueryDidFinishGatheringNotification, object: query)
query.stopQuery()
let results = query.results
if query.resultCount == 1 {
let resultURL = results[0].valueForAttribute(NSMetadataItemURLKey) as! NSURL
document = MyDocument(fileURL: resultURL)
document?.openWithCompletionHandler({(success: Bool) -> Void in
if success {
if let pi = self.profileImage
{
pi.image = self.document?.image
}
} else {
println("iCloud file open failed")
}
})
} else {
NSLog("Could not find profile image, creating blank document")
}
}
}
If you're not familiar with iCloud and UIDocument, you might be wondering why I'm querying the metadata to get at these images/documents. If you know of a reliable way of fetching these images/documents in the ubiquitous container, I'm all ears. It causes this pop in effect during the scrolling of the table view cells.
And here's my function that populates the cells:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//set the client
let client = clients.fetchedObjects![indexPath.row] as! Client
//set the correct identifier
if let imageurl = client.imageurl as String?
{
if imageurl == ""
{
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifierNoImage, forIndexPath: indexPath) as? ClientTableViewCell
cell!.myClient = client
return cell!
} else
{
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as? ClientTableViewCell
cell!.myClient = client
return cell!
}
}
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifierNoImage, forIndexPath: indexPath) as? ClientTableViewCell
cell!.myClient = client
return cell!
}
I originally was going to use the attribute imageurl to store the name and url of the client profile image, but since the ubiquitous container url is unique for every device, I now only use it to detect if there is an image with this client or not.
I've been having this issue for weeks now and cannot seem to nail it down. Any guidance would be greatly appreciated!
Workbench: Xcode 6.3 and Swift 1.2.
Solved my own memory leak! With the help of this answer here: [UIDocument never calling dealloc
I was querying UIDocuments and never closing them. That's why the ARC wasn't cleaning up my unused UIDocuments. The link above has the Object C version, but for folks using Swift, try these lines to close your documents after using them:
document?.updateChangeCount(UIDocumentChangeKind.Done)
document?.closeWithCompletionHandler(nil)
Whew! Glad that mess was over. Hope this helps someone!
Related
I am implementing a table view in tableViewController in a Swift project. I am creating two array from a json calling in viewDidLoad and everything in viewDidLoad function works great. here is my viewDidLoad function.
First the arrays and variables are like this:
var imageList = ["usaflag","gerflag","franceflag","jpflag","gerflag"]
var titleList = ["Croatian kuna","Hungarian forint","Congolese franc","Israeli Shekel","Nigerian naira"]
var descriptionList = ["HRK","HUF","CDF","ILS","NGN"]
var myCurrency:[String] = []
var myValues:[Double] = []
var aCheckEuro:Double = 0
var resultCurrency:Double = 0
var activeCurrency:Double = 0
var zeroOriginActiveCurrency:Double = 0
var oneDestActiveCurrency:Double = 0
var currencySelected:String = ""
var zeroOriginCurrencySelected:String = ""
var oneDestCurrencySelected:String = ""
and the viewDidLoad is here
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://data.fixer.io/api/latest?access_key=....")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if (error != nil)
{
print("ERROR")
}
else
{
if let content = data
{
do
{
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(myJson)
if let rates = myJson["rates"] as? NSDictionary
{
for (key, value) in rates
{
self.myCurrency.append((key as! String))
self.myValues.append((value as? Double)!)
}
print(self.myCurrency)
print(self.myValues)
}
}
catch
{
}
}
}
}
task.resume()
}
as I said until here everything is working fine. these all are in a tableViewController . the problem is in this function
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
// Configure the cell...
cell.cellTitle.text = titleList[indexPath.row]
cell.cellDescription.text = descriptionList[indexPath.row]
cell.cellImageView.image = UIImage(named : imageList[indexPath.row])
cell.currencyCount.text = myCurrency[indexPath.row] // here has fatal erroe
return cell
}
the last line
cell.currencyCount.text = myCurrency[indexPath.row]
has fatal error and I do not know how to solve it.I should mention that currencyCount is a label.
You Will Create one Array Like This :
var myCurrency:[String] = []
your Array is Empty for First TableView Load
there for you will write this code :
if myCurrency.isEmpty == false {
cell.currencyCount.text = myCurrency[1]
}
else
{
self.tableView.reloadData()
}
}
It may happen when the tableView loads and there is no data yet in myCurrency array due to the delay of the network call. It will throw an out-of-range exception. Also may you want to put tableView.reloadData right after your network call finishes. What is returning the method numberOfItems?
Update: There are somethings that you can do.
Below self.myValues.append((value as? Double)!) put
tableView.delegate = self
tableView.dataSource = self
In your cellForRowAt add a validation to check if array is empty:
if !myCurrency.isEmpty {
cell.currencyCount.text = myCurrency[indexPath.row]
}
I've been on stack for a while now but never needed to ask a question as I've always found the answers after some searching, but now I'm stuck for real. I've been searching around and going through some trial and error for an answer and I keeping getting the same error. I'm basically making a profile page with a tableView on the bottom half of the screen. The top half is loading fine filling in the current user's information. All connections to the view controller and cell view controller seem good. The table view, however, will appear with no data and crash while loading with the fatal error:
unexpectedly found nil while unwrapping an optional value.
I also believe the cellForRowAtIndexPath is not being called at all because "test" is not printing to the logs.
I'm using the latest versions of Swift and Parse.
I'm relatively new to swift so I'll go ahead and post my entire code here and any help at all is appreciated.
import UIKit
import Parse
import ParseUI
class profileViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var profilePic: UIImageView!
#IBOutlet var userName: UILabel!
#IBOutlet var userBio: UILabel!
var image: PFFile!
var username = String()
var userbio = String()
var content = [String]()
#IBAction func logout(sender: AnyObject) {
PFUser.logOut()
let Login = storyboard?.instantiateViewControllerWithIdentifier("ViewController")
self.presentViewController(Login!, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
profilePic.layer.borderWidth = 1
profilePic.layer.masksToBounds = false
profilePic.layer.borderColor = UIColor.blackColor().CGColor
profilePic.layer.cornerRadius = profilePic.frame.height/2
profilePic.clipsToBounds = true
tableView.delegate = self
tableView.dataSource = self
self.tableView.rowHeight = 80
self.hideKeyboardWhenTappedAround()
if let nameQuery = PFUser.currentUser()!["name"] as? String {
username = nameQuery
}
if PFUser.currentUser()!["bio"] != nil {
if let bioQuery = PFUser.currentUser()!["bio"] as? String {
userbio = bioQuery
}
}
if PFUser.currentUser()!["icon"] != nil {
if let iconQuery = PFUser.currentUser()!["icon"] as? PFFile {
image = iconQuery
}
}
self.userName.text = username
self.userBio.text = userbio
if image != nil {
self.image.getDataInBackgroundWithBlock { (data, error) -> Void in
if let downIcon = UIImage(data: data!) {
self.profilePic.image = downIcon
}
}
}
// Do any additional setup after loading the view.
var postsQuery = PFQuery(className: "Posts")
postsQuery.whereKey("username", equalTo: username)
postsQuery.findObjectsInBackgroundWithBlock( { (posts, error) -> Void in
if error == nil {
if let objects = posts {
self.content.removeAll(keepCapacity: true)
for object in objects {
if object["postText"] != nil {
self.content.append(object["postText"] as! String)
}
self.tableView.reloadData()
}
}
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
print(content.count)
return content.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let profCell = self.tableView.dequeueReusableCellWithIdentifier("profCell", forIndexPath: indexPath) as! profTableViewCell
print("test")
profCell.userPic.layer.borderWidth = 1
profCell.userPic.layer.masksToBounds = false
profCell.userPic.layer.borderColor = UIColor.blackColor().CGColor
profCell.userPic.layer.cornerRadius = profCell.userPic.frame.height/2
profCell.userPic.clipsToBounds = true
profCell.userPic.image = self.profilePic.image
profCell.name.text = self.username
profCell.content.text = content[indexPath.row]
return profCell
}
}
I let it sit for a few days and I came back to realize a very dumb mistake I made. I working with around 15 view controllers right now and realized I had a duplicate of the one I posted above with the same name. I now understand why you say working with storyboards is very sticky. Though, I did not need it, I appreciate the help and I can say I learned a few things.
You probably need to register the class you are using for the custom UITableViewCell:
self.tableView.registerClass(profTableViewCell.self, forCellReuseIdentifier: "profCell")
Unless you're using prototyped cells in IB, this registration isn't done automatically for you.
As such when you call the dequeue method (with the ! forced unwrap) you're going to have issues. The dequeueReusableCellWithIdentifier:forIndexPath: asserts if you didn't register a class or nib for the identifier.
when you register a class, this always returns a cell.
The older (dequeueReusableCellWithIdentifier:) version returns nil in that case, and you can then create your own cell.
You should use a ? during the as cast to avoid the crash, although you'll get no cells!
One other reminder, you should always use capitals for a class name, ProfTableViewCell not profTableViewCell, it's just good pratice.
Much more information here in the top answer by iOS genius Rob Mayoff: Assertion failure in dequeueReusableCellWithIdentifier:forIndexPath:
You have to create a simple NSObject Class with image, username and userbio as optional values. Then you have to declare in your profileviewcontroller a var like this:
var allProfiles = [yourNSObjectClass]()
In your cellForRowAtIndexPath add:
let profile = yourNSObjectClass()
profile = allProfiles[indexPath.row]
cell.username.text = profile.username
And go on.
Use also this:
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
instead of this:
self.tableView.reloadData()
I have an issue where if a user types into the search bar too fast the program will crash with the following message
fatal error: index out of range
referring to the line var podInfo = podcastResults[row] which is part of the cellForRowAtIndexPath method. The search box is above a UITableView which is populated from the NSURLSession results.
Please see the code below.
class SearchTVC: UITableViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
var podcastResults = [[String: String]]()
var tempDict = [String: String]()
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
print("search being typed")
if searchText.characters.count >= 3 {
let searchesArray:Array = searchText.componentsSeparatedByString(" ")
//request search method to start
search(searchesArray)
}
}
func search(searchqueries: Array<String>){
let URL = iTunesSearcher().searchQuery(searchqueries) //This just complies the URL using a method in anothr class
let task = NSURLSession.sharedSession().dataTaskWithURL(URL) {
(data, response, error) in
print("URL downloaded")
//clear results and temp dict, so that new results can be displayed
self.tempDict.removeAll()
self.podcastResults.removeAll()
let data = NSData(contentsOfURL: URL) //urlString!
let json = JSON(data: data!)
for (key, subJson) in json["results"] {
if let title = subJson["collectionCensoredName"].string {
self.tempDict = ["title": title]
} else { print("JSON - no title found") }
if let feedURL = subJson["feedUrl"].string {
self.tempDict.updateValue(feedURL, forKey: "feedURL")
} else { print("JSON - no feedURL found") }
if let artworkUrl60 = subJson["artworkUrl60"].string {
self.tempDict.updateValue(artworkUrl60, forKey:"artworkURL60")
} else { print("JSON - no artwork url found") }
self.podcastResults.append(self.tempDict)
}
//Running request on main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
task.resume()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath)
let row = indexPath.row
var podInfo = podcastResults[row]
cell.textLabel?.text = podInfo["title"]
return cell
}
Any help would be much appreciated as I just can't figure it out.
Cheers.
Michael
I'm assuming that the number of rows you return in your UITableViewDataSource is self.podcastResults.count.
If so, what you need to do is turn this:
let row = indexPath.row
var podInfo = podcastResults[row]
cell.textLabel?.text = podInfo["title"]
into
let row = indexPath.row
if row < podcastResults.count {
var podInfo = podcastResults[row]
cell.textLabel?.text = podInfo["title"]
}
This will ensure that no matter when the cell is requested the index will never be out of bounds (and I think this happens after you remove all the elements from the array in the request handler).
Try reloading the table when you remove all the elements from your array ie. self.tempDict.removeAll()
self.podcastResults.removeAll() it seems that table is not refreshed and still shows the elements which are now actually removed.
I tried to reload my UITableView after adding new items. When I try with a reloadData() it's not working. Nothing is shown.
If I try to reload my getallrecords function, that reload items but they are repeated.
My source code is :
class FriendsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate {
#IBOutlet var tabeview: UITableView!
var textArray: NSMutableArray! = NSMutableArray()
var subArray: NSMutableArray! = NSMutableArray()
let defaults = NSUserDefaults.standardUserDefaults()
var valueToPass:String!
var reports_d:String!
var reports:String!
#IBOutlet var menuButton: UIBarButtonItem!
#IBOutlet var friends_icon: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
tabeview.dataSource = self
tabeview.delegate = self
tabeview.emptyDataSetSource = self
tabeview.emptyDataSetDelegate = self
tabeview.tableFooterView = UIView()
getallrecords()
self.tabeview.addPullToRefresh({ [weak self] in
// refresh code
self!.getallrecords()
self?.tabeview.stopPullToRefresh()
})
// Do any additional setup after loading the view.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.textArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "cell")
cell.textLabel?.text = self.textArray.objectAtIndex(indexPath.row) as? String
cell.detailTextLabel?.text = self.subArray.objectAtIndex(indexPath.row) as? String
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You selected cell #\(indexPath.row)!")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRowAtIndexPath(indexPath)! as UITableViewCell
valueToPass = currentCell.textLabel!.text
reports = reports_d
performSegueWithIdentifier("friends_details", sender: self)
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
// handle delete (by removing the data from your array and updating the tableview)
let currentCell = tableView.cellForRowAtIndexPath(indexPath)! as UITableViewCell
let friend2 = currentCell.textLabel!.text
let defaults = NSUserDefaults.standardUserDefaults()
let username = defaults.objectForKey("name") as! String
Alamofire.request(.GET, "http://www.example.com/app/remove_friends.php", parameters: ["key_id": "xxxxx","user_id": username,"friend_receive_id": friend2!, "action": "delete"])
.response { request, response, data, error in
print(request)
print(response)
print(error)
if(error == nil)
{
self.tabeview.beginUpdates()
self.textArray.removeObjectAtIndex(indexPath.row)
self.subArray.removeObjectAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.tabeview.endUpdates()
}
}
NSNotificationCenter.defaultCenter().postNotificationName("reloadData",object: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "friends_details") {
// initialize new view controller and cast it as your view controller
let viewController = segue.destinationViewController as! DetailsFriendsViewController
// your new view controller should have property that will store passed value
viewController.passedValue = valueToPass
viewController.reports = reports
}
}
func getallrecords(){
if(defaults.stringForKey("name") != nil ){
let username = defaults.objectForKey("name") as! String
let full = "http://www.example.com/app/danger_friend_view.php?search=true&username=" + username
let url = NSURL(string: full)
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
do {
let d = NSString(data: data!, encoding: NSUTF8StringEncoding)
var arr = d!.componentsSeparatedByString("<") // spliting the incoming string from "<" operator because before that operator is our required data and storing in array
let dataweneed:NSString = arr[0] as NSString // arr[0] is the data before "<" operator and arr[1] is actually no use for us
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
SwiftSpinner.hide()
do {
if let data = try NSJSONSerialization.JSONObjectWithData(dataweneed.dataUsingEncoding(NSUTF8StringEncoding)!, options: [NSJSONReadingOptions.MutableContainers]) as? NSArray {
for dd in data{
var name : String = dd["danger"]! as! String
self.reports_d = name
let info : String = dd["username"]! as! String
name = NSLocalizedString("SEND_ALERT_BEGIN",comment:"SEND_ALERT") + name + NSLocalizedString("ALERTS",comment:"ALERTS")
print("ID is : \(name)")
print("Username is : \(info)")
self.textArray.addObject(info)
self.subArray.addObject(name)
}
self.tabeview.reloadData()
}
} catch let error as NSError {
print(error.localizedDescription)
}
})
}
}
task.resume()
}
else
{
//Do something
}
}
#IBAction func reload_data(sender: UIButton) {
let banner = Banner(title: NSLocalizedString("RELOAD_DATA_TITLE",comment:"I'm in danger, I'm currently at "), subtitle: NSLocalizedString("RELOAD_DATA",comment:"I'm in danger, I'm currently at "), image: UIImage(named: "Icon"), backgroundColor: UIColor(red:52.00/255.0, green:152.00/255.0, blue:219.00/255.0, alpha:0.89))
banner.dismissesOnTap = true
banner.show(duration: 10.0)
dispatch_async(dispatch_get_main_queue()) {
//Not working ....
self.tabeview.reloadData()
}
}
func titleForEmptyDataSet(scrollView: UIScrollView!) -> NSAttributedString! {
let str = "Oups"
let attrs = [NSFontAttributeName: UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)]
return NSAttributedString(string: str, attributes: attrs)
}
func descriptionForEmptyDataSet(scrollView: UIScrollView!) -> NSAttributedString! {
let str = NSLocalizedString("NO_FRIENDS_TO_SHOW",comment:"No friends to show ")
let attrs = [NSFontAttributeName: UIFont.preferredFontForTextStyle(UIFontTextStyleBody)]
return NSAttributedString(string: str, attributes: attrs)
}
func imageForEmptyDataSet(scrollView: UIScrollView!) -> UIImage! {
return UIImage(named: "no-friends")
}
func buttonTitleForEmptyDataSet(scrollView: UIScrollView!, forState state: UIControlState) -> NSAttributedString! {
let str = NSLocalizedString("ADD_FRIENDS",comment:"Add a friend ")
let attrs = [NSFontAttributeName: UIFont(name: "HelveticaNeue-Light", size: 19)!]
return NSAttributedString(string: str, attributes: attrs)
}
func emptyDataSetDidTapButton(scrollView: UIScrollView!) {
let alert = SCLAlertView()
let txt = alert.addTextField("Friend's username")
alert.addButton("Add") {
if(txt.text=="")
{
let banner = Banner(title: NSLocalizedString("ERROR_NO",comment:"An error occured"), subtitle: NSLocalizedString("ERROR_NO_TEXT",comment:"I'm in danger, I'm currently at "), image: UIImage(named: "Icon"), backgroundColor: UIColor(red:152.00/255.0, green:52.00/255.0, blue:52.00/255.0, alpha:0.89))
banner.dismissesOnTap = true
banner.show(duration: 10.0)
}
else
{
let defaults = NSUserDefaults.standardUserDefaults()
let username = defaults.objectForKey("name") as! String
let remove_friend_username = txt.text! as String
Alamofire.request(.GET, "http://www.example.com/add_friends.php", parameters: ["key_id": "xxx","user_id": username,"friend_receive_id": remove_friend_username, "action": "add"])
.response { request, response, data, error in
dispatch_async(dispatch_get_main_queue()) {
self.tabeview.reloadData()
//Not working
}
}
}
}
alert.showEdit("Add friend", subTitle: "You can add a friend by enter his username")
}
}
I believe you are missing a little point in here buddy :)
Question 1
Why reloading tableView wont show new data ??
Your function reload_data is doing nothing more than reloading data buddy :) When you call reload data all the tableView delegates like number of rows in section,number of sections and cellForRowAtIndexPath gets called but all these methods return the value depending on the data source you provide isn't it buddy :)
So if you change the data source and then call reload data they will show you the new data :) but in your reload_data function you are not altering the data source at all :) simply calling reload data on the unalterred data source will re render the tableView again thats all :)
What you can do :)
You already have a method that fetches the new data using almofire :) just call it and in the success block anyway you are reloading the tableView :) So everything will be fine buddy :)
#IBAction func reload_data(sender: UIButton) {
let banner = Banner(title: NSLocalizedString("RELOAD_DATA_TITLE",comment:"I'm in danger, I'm currently at "), subtitle: NSLocalizedString("RELOAD_DATA",comment:"I'm in danger, I'm currently at "), image: UIImage(named: "Icon"), backgroundColor: UIColor(red:52.00/255.0, green:152.00/255.0, blue:219.00/255.0, alpha:0.89))
banner.dismissesOnTap = true
banner.show(duration: 10.0)
self.getallrecords() //simply call this method this will anyhow will reload data on success :)
}
Question 2
Why my tableView shows duplicate data???
Your tableView always show the data which is there in its datasource :) SO if your tableView is showing duplicate cells that means you have duplicate entry in your data source :)
You are dealing with array, in future you might migrate to coredata :)
Understand one thing, when you enter or add a entry to your data source if you dont want to show duplicates you will have to handle it explicitly.
How can I do that ???
From your code I beilieve info(username) value is unique per object. So before blindly adding response to textArray check if text array already consists that object if yes then dont add it again :)
Based on the above stated assumption and believing you are making use of swift 2.0
if let data = try NSJSONSerialization.JSONObjectWithData(dataweneed.dataUsingEncoding(NSUTF8StringEncoding)!, options: [NSJSONReadingOptions.MutableContainers]) as? NSArray {
for dd in data{
var name : String = dd["danger"]! as! String
self.reports_d = name
let info : String = dd["username"]! as! String
name = NSLocalizedString("SEND_ALERT_BEGIN",comment:"SEND_ALERT") + name + NSLocalizedString("ALERTS",comment:"ALERTS")
print("ID is : \(name)")
print("Username is : \(info)")
if !self.textArray.contains(info){
self.textArray.addObject(info)
self.subArray.addObject(name)
}
}
self.tabeview.reloadData()
}
Now that's a lot of code, I want a easier solution :)
Clear the array before adding the new response :) Thats all :)
if let data = try NSJSONSerialization.JSONObjectWithData(dataweneed.dataUsingEncoding(NSUTF8StringEncoding)!, options: [NSJSONReadingOptions.MutableContainers]) as? NSArray {
self.textArray.removeAll()
self.subArray.removeAll() //clear the arrays and then re populate them thats all no duplicate data anymore :P
for dd in data{
var name : String = dd["danger"]! as! String
self.reports_d = name
let info : String = dd["username"]! as! String
name = NSLocalizedString("SEND_ALERT_BEGIN",comment:"SEND_ALERT") + name + NSLocalizedString("ALERTS",comment:"ALERTS")
print("ID is : \(name)")
print("Username is : \(info)")
self.textArray.addObject(info)
self.subArray.addObject(name)
}
self.tabeview.reloadData()
}
Please help! I've tried everything. If anyone has any advice on how i can display my data in the table view cell, I would be eternally grateful. I'm new to iOS and am learning on a very steep pace. I grabbed data from an API that returned data in the form of JSON, parsed it, created my table view with its table view cells, but i can't seem to figure out how to print the data i parsed through in the table view cell.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var myTableView: UITableView! {
didSet {
myTableView.dataSource = self
myTableView.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.viacom.com/apiKey=someKey")!
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
var json: [String: AnyObject]!
do {
json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String : AnyObject]
} catch {
print(error)
}
//2 - Store in model, forloop through them, store into temparray,add to main array?
let episodes = json["response"] as! [String: AnyObject]
let meta = episodes["episodes"] as! [AnyObject]
let description = meta[2]["description"]! as! String?
//let title = meta[2]["title"] as! String?
let episodeNumber = meta[2]["episodeNumber"]! as! String?
dispatch_async(dispatch_get_main_queue(), {
self.myTableView.reloadData()})
data = [episodeNumber!, description!]
print("Episode Number: \(episodeNumber!)\n" + "Description: \(description!)")
} else {
print(error)
}
}
task.resume()
}
let data = [description]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel!.text = "\(self.data)"
return cell
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Your codes look very messy to me. However, I'm just assuming that you have successfully fetched the JSON data. Fetching data is asynchronous. You therefore need to add a dispatch code inside.
After your this line of code:
let episodeNumber = meta[2]["episodeNumber"]! as! String?
Add this
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()})
EDIT:
#IBOutlet weak var myTableView: UITableView! {
didSet {
myTableView.dataSource = self
myTableView.delegate = self // Add This
}
}
The reason for the failure is too much of data manipulation. There is no need to use so many variables and pass around data unnecessarily. You are getting correct output in console when printing it because you used variables "episodeNumber" and "description".
print("Episode Number: \(episodeNumber!)\n" + "Description: \(description!)")
And getting wrong data in variable "data".
So better thing would be that you should use episodeNumber and description variables to print data in Cell.
cell.textLabel!.text = "Episode Number: \(self.episodeNumber)\n" + "Description: \(description)"
But for this you have to make variable episodeNumber a global variable.
So declare it outside the function.
var episodeNumber = String()
and remove the let keyword from line
let episodeNumber = meta[2]["episodeNumber"]! as! String?
You have to add some self. keywords which the compiler will suggest you so you don't have to worry about that, just keep on double clicking the suggestions.
Now, your code looks fine to run and get desired output.
let data = [description]
is a short form of
let data = [self.description]
and self.description() is the viewController's description method used for printing debug description. That is why
cell.textLabel!.text = "\(self.data)"
gives you [(Function)], as you just created an array with a stored function in it.