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()
Related
The problem: I cannot get data downloaded into arrays in a singleton class to populate table views in two view controllers.
I am writing a bank book iOS app with a Parse backend. I have a login viewController and four other view controllers in a Tab Bar Controller. I have a singleton class that gets data from the Parse server and loads four arrays. I want that data to populate table views in two other view controllers. I want to make as few data calls as possible. The initial view controller is where user enters debits and credits. So my plan was to call GetData class from the viewDidLoad to populate tables in case user visits them without entering a debit or a credit.
When a debit or credit is entered, there is one function where after the debit or credit is saved to Parse server, the GetData class is called again to update the arrays in the GetData class.
The two view controllers access the arrays in the GetData class to fill the tables, and there is a tableView.reloadData() call in the viewDidAppear in each view controller when the view is accessed via the tab controller.
It works intermittently at best. sometimes I get five successful updates and then it keeps displaying old data, then it will suddenly display all the data.
Looking at my cloud DB, all the entries are there when made, and I have verified the viewWillAppear is firing in each view controller who accessed.
What I need is a reliable method to get the data to update in the other view controllers every. time. I will gladly scrap this app and rewrite if needed.
Here is the code of my singleton class:
class GetData {
static let sharedInstance = GetData()
var transactionArray = [String]()
var dateArray = [String]()
var toFromArray = [String]()
var isDebitArray = [String]()
func getdata() {
let query = PFQuery(className:"Transaction")
query.findObjectsInBackground { (objects, error) in
self.transactionArray.removeAll()
self.dateArray.removeAll()
self.toFromArray.removeAll()
self.isDebitArray.removeAll()
print("query fired")
if objects != nil {
for object in objects! {
if let amount = object.object(forKey: "amount") as? String {
if let date = object.object(forKey: "date") as? String {
if let toFrom = object.object(forKey: "toFrom") as? String {
if let isDebit = object.object(forKey: "isDebit") as? String {
self.transactionArray.append(amount)
self.dateArray.append(date)
self.toFromArray.append(toFrom)
self.isDebitArray.append(isDebit)
}
}
}
}
}
}
self.transactionArray.reverse()
self.dateArray.reverse()
self.toFromArray.reverse()
self.isDebitArray.reverse()
dump(self.toFromArray)
}
}
}
Here is a sample of one of the view controllers:
class RecordVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var recordTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
recordTableView.delegate = self
recordTableView.dataSource = self
recordTableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
recordTableView.reloadData()
print("recordVC viewWillAppear fired")
}
#IBAction func resetFoundButton(_ sender: Any) {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = recordTableView.dequeueReusableCell(withIdentifier: "RecordCell", for: indexPath) as! RecordCell
cell.amountLabel?.text = "$\(GetData.sharedInstance.transactionArray[indexPath.row])"
cell.dateLabel?.text = "\(GetData.sharedInstance.dateArray[indexPath.row])"
cell.toFromLabel?.text = "\(GetData.sharedInstance.toFromArray[indexPath.row])"
let cellColor = backGroundColor(isDebit: GetData.sharedInstance.isDebitArray[indexPath.row])
cell.backgroundColor = cellColor
cell.backgroundColor = cellColor
return cell
}
func backGroundColor(isDebit:String) -> UIColor{
if isDebit == "false" {
return UIColor.green
} else {
return UIColor.blue
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return GetData.sharedInstance.transactionArray.count
}
}
Thank you
I would say that instead of reloading the tables by calling tableView.reloadData() in viewWillAppear() , after your query execution and data updates in GetData Class , then you should fire a notification or use a delegate to reloadData() in tableview.
Whats happening is that sometimes when the tableView.reloadData() gets called the Data in the singleton class (GetData class) has not yet updated.
func getdata() {
let query = PFQuery(className:"Transaction")
query.findObjectsInBackground { (objects, error) in
self.transactionArray.removeAll()
self.dateArray.removeAll()
self.toFromArray.removeAll()
self.isDebitArray.removeAll()
print("query fired")
if objects != nil {
for object in objects! {
if let amount = object.object(forKey: "amount") as? String {
if let date = object.object(forKey: "date") as? String {
if let toFrom = object.object(forKey: "toFrom") as? String {
if let isDebit = object.object(forKey: "isDebit") as? String {
self.transactionArray.append(amount)
self.dateArray.append(date)
self.toFromArray.append(toFrom)
self.isDebitArray.append(isDebit)
// Here you should fire up a notification to let the 2 ViewControllers know that data has to be reloaded.
}
}
}
}
}
}
self.transactionArray.reverse()
self.dateArray.reverse()
self.toFromArray.reverse()
self.isDebitArray.reverse()
dump(self.toFromArray)
}
}
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.
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 trying to get search results to display on a tableView. I believe I have correctly parsed the JSON, the only problem is that the results won't display on my tableView.
Here is the code:
var searchText : String! {
didSet {
getSearchResults(searchText)
}
}
var itemsArray = [[String:AnyObject]]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}
// MARK: - Get data
func getSearchResults(text: String) {
if let excapedText = text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
Alamofire.request(.GET, "https://api.duckduckgo.com/?q=\(excapedText)&format=json")
.responseJSON { response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error \(response.result.error!)")
return
}
let items = JSON(response.result.value!)
if let relatedTopics = items["RelatedTopics"].arrayObject {
self.itemsArray = relatedTopics as! [[String:AnyObject]]
}
if self.itemsArray.count > 0 {
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 6 // itemsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SearchResultCell", forIndexPath: indexPath) as! SearchResultCell
if itemsArray.count > 0 {
var dict = itemsArray[indexPath.row]
cell.resultLabel?.text = dict["Text"] as? String
} else {
print("Results not loaded yet")
}
return cell
}
If I had a static API request I think this code would work because I could fetch in the viewDidLoad and avoid a lot of the .isEmpty checks.
When I run the program I get 6 Results not loaded yet (from my print in cellForRowAtIndexPath).
When the completion handler is called response in, it goes down to self.items.count > 3 (which passes) then hits self.tableView.reloadData() which does nothing (I checked by putting a breakpoint on it).
What is the problem with my code?
Edit
if self.itemsArray.count > 0 {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
Tried this but the tableView still did not reload even though its reloading 6 times before the alamofire hander is called...
Here is the strange thing, obviously before the hander is called my itemsArray.count is going to be 0 so that's why I get Results not loaded yet. I figured out why it repeats 6 times though; I set it in numberOfRowsInSection... So #Rob, I can't check dict["Text"] or cell.resultLabel?.text because they're never getting called. "Text" is correct though, here is the link to the JSON: http://api.duckduckgo.com/?q=DuckDuckGo&format=json&pretty=1
Also, I do have the label linked up to a custom cell class SearchResultCell
Lastly, I am getting visible results.
Two problems.
One issue is prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let searchResultTVC = SearchResultsTVC()
searchResultTVC.searchText = searchField.text
}
That's not using the "destination" view controller that was already instantiated, but rather creating a second SearchResultsTVC, setting its searchText and then letting it fall out of scope and be deallocated, losing the search text in the process.
Instead, you want:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let searchResultTVC = segue.destination as? SearchResultsTVC {
searchResultTVC.searchText = searchField.text
}
}
You shouldn't rely on didSet in the destination view controller to trigger the search, because that property is getting set by source view controller before the table view has even been instantiated. You do not want to initiate the search until view has loaded (viewDidLoad).
I would advise replacing the didSet logic and just perform search in viewDidLoad of that SearchResultsTVC.
My original answer, discussing the code provided in the original question is below.
--
I used the code originally provided in the question and it worked fine. Personally, I might streamline it further:
eliminate the rid of the hard coded "6" in numberOfRowsInSection, because that's going to give you false positive errors in the console;
the percent escaping not quite right (certain characters are going to slip past, unescaped); rather than dwelling on the correct way to do this yourself, it's better to just let Alamofire do that for you, using parameters;
I'd personally eliminate SwiftyJSON as it's not offering any value ... Alamofire already did the JSON parsing for us.
Anyway, my simplified rendition looks like:
class ViewController: UITableViewController {
var searchText : String!
override func viewDidLoad() {
super.viewDidLoad()
getSearchResults("DuckDuckGo")
}
var itemsArray: [[String:AnyObject]]?
func getSearchResults(text: String) {
let parameters = ["q": text, "format" : "json"]
Alamofire.request("https://api.duckduckgo.com/", parameters: parameters)
.responseJSON { response in
guard response.result.error == nil else {
print("error \(response.result.error!)")
return
}
self.itemsArray = response.result.value?["RelatedTopics"] as? [[String:AnyObject]]
self.tableView.reloadData()
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemsArray?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath) as! SearchResultCell
let dict = itemsArray?[indexPath.row]
cell.resultLabel?.text = dict?["Text"] as? String
return cell
}
}
When I did that, I got the following:
The problem must rest elsewhere. Perhaps it's in the storyboard. Perhaps it's in the code in which searchText is updated that you didn't share with us (which triggers the query via didSet). It's hard to say. But it doesn't appear to be a problem in the code snippet you provided.
But when doing your debugging, make sure you don't conflate the first time the table view delegate methods are called and the second time they are, as triggered by the responseJSON block. By eliminating the hardcoded "6" in numberOfRowsInSection, that will reduce some of those false positives.
I think you should edit :
func getSearchResults(text: String) {
if let excapedText = text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
Alamofire.request(.GET, "https://api.duckduckgo.com/?q=\(excapedText)&format=json")
.responseJSON { response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error \(response.result.error!)")
return
}
let items = JSON(response.result.value!)
if let relatedTopics = items["RelatedTopics"].arrayObject {
self.itemsArray = relatedTopics as! [[String:AnyObject]]
// if have result data -> reload , & no if no
if self.itemsArray.count > 0 {
self.tableView.reloadData()
}
}else{
print("Results not loaded yet")
}
}
}
}
And
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SearchResultCell", forIndexPath: indexPath) as! SearchResultCell
// i 'm sure: itemsArray.count > 0 in here if in numberOfRowsInSection return itemsArray.count
var dict = itemsArray[indexPath.row]
cell.resultLabel?.text = dict["Text"] as? String
return cell
}
And you should share json result(format) ,print dict in cellForRowAtIndexPath, so it s easy for help
In the app I'm working on, it allows users to post on the main timeline and other users can comment on that user's post, so I've been trying to display the comments in the tableView, but it's not showing. I have already confirmed that the data is being posted to parse, so on that end it's working as expected, but when it comes to display the comments, I cannot seem to get it to work. I'm using this function to display the comments:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("commentCell", forIndexPath: indexPath) as! CommentTableViewCell
cell.commentLabel?.text = comments![indexPath.row]
return cell
}
is anything wrong with my code? or is there another way to display the comments?
where is the code to retrieve the comments? make sure you are calling "self.tableView.reloadData()" after the for loop.
the way I generally retrieve information from parse is like so:
func query() {
var query = PFQuery(className: "comments")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (caption2: [AnyObject]?, erreur: NSError?) -> Void in
if erreur == nil {
// good
for caption in caption2! {
self.comments.append(caption["<YOUR COLUMN NAME WHERE COMMENT IS STORED IN PARSE HERE>"] as! String)
}
self.tableView.reloadData()
}
else {
// not good
}
}
}
Add this function to your class. Then change this:
func reply() {
post?.addObject(commentView!.text, forKey: "comments")
post?.saveInBackground()
if let tmpText = commentView?.text {
comments?.append(tmpText)
}
commentView?.text = ""
println(comments?.count)
self.commentView?.resignFirstResponder()
self.commentTableView.reloadData()
}
to this:
func reply() {
post?.addObject(commentView!.text, forKey: "comments")
post?.saveInBackground()
if let tmpText = commentView?.text {
comments?.append(tmpText)
}
commentView?.text = ""
println(comments?.count)
self.commentView?.resignFirstResponder()
self.query
}
It turned out that I was missing:
UITableViewDataSource
in my class, so this fixed it:
class DetailViewContoller: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {
...
...
...
override func viewDidLoad() {
super.viewDidLoad()
commentTableView.delegate = self
commentTableView.dataSource = self