Retrieving data from Parse.com (Swift) - swift

I am attempting to fetch data from an object created on Parse.com into a custom cell that contains labels and images. The code I implemented thus far runs but my tableview remains empty and at runtime displays the following error. ERROR: Thread 1: Exc_BAD_INSTRUCTION (Code =EXC_1386_INVOP, subcode=0x0). Can someone please explain why this is occurring I am new to programming in Xcode.
#objc
protocol ViewControllerDelegate {
optional func toggleLeftPanel()
optional func toggleRightPanel()
optional func collapseSidePanels()
}
class ViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate
{
var delegate: ViewControllerDelegate?
var arrayOfParties: [Information] = [Information]()
#IBAction func menuTapped(sender: AnyObject) {
delegate?.toggleLeftPanel?()
}
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.loadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
//return self.arrayOfParties.count
return self.arrayOfParties.count
}
//Function to adjust the height of the custom cell
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return 230
}
override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomCell
let party = arrayOfParties[indexPath.row]
cell.setCell(party.partyName, promoterLabelText: party.promoterName, partyCostLabelText: party.partyCost, partyFlyerUIImage: party.flyerImage, promoterUIImage: party.promoterImage)
return cell
}
func loadData()
{
var findDataParse:PFQuery = PFQuery(className: "flyerDataFetch")
findDataParse.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]?, error: NSError?)->Void in
if (error == nil){
for object in objects!
{
var party1 = Information(partyName: (object["partyName"] as? String)!, promoterName: (object["promoterName"] as? String)!, partyCost: (object["partyCost"] as? String)!, flyerImage: "", promoterImage: "")
self.arrayOfParties.append(party1)
}
}
else {
// something went wron
}
}
}
}

The problem is that you probably have cells in the tableview already that don't have the new labels/images.
You need to tell the program to insert a type of text where there is no input of the new type.
In this case I have already since a month back worked on a project, and now I started inserting more labels into my cell, thats where the problem pops up, so the previous cells don't have this information.
So I just tell the system to check all cells, if a cell doesn't have the needed text, it should just enter some placeholder text (in this case = "").
if let content = text.objectForKey("Content") as? String {
cell.TextView.alpha = 1.0
cell.TextView.text = content
}
else{
cell.TextView.alpha = 1.0
cell.TextView.text = ""
}
I hope this helps, I have only explained how to do, I haven't really told you were in your code to do it, but from what I see you should input the code right under the part where you declare "Party1".

Related

How to properly display pictures in a TableView from Firebase Storage in SWIFT

I have a chat app where people can talk in a group and a little picture is displayed in each cell to show who is talking. I managed to display these pictures from Firebase storage but it is not always the right picture which is displayed at the right place.
It only works when I go to the previous View Controller and coming back the chat View to see the pictures displayed properly in each cell.
I tried to use DispatchQueue.main.async {} probably not in the good way cause it did not work for me.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = messageArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "customMessageCell", for: indexPath) as! CustomMessageCell
cell.selectionStyle = .none
// CHANGE TEXT ACCORDING TO SENDER
if message.sender == Auth.auth().currentUser?.email{
cell.messageBubble.backgroundColor = UIColor(red:0.30, green:0.68, blue:1.5, alpha:1.0)
// ...
} else {
cell.messageBubble.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
// ...
}
let theTimeStamp = messageArray[indexPath.row].createdAt
let doubleTime = Double(theTimeStamp)
let myDate = Date(timeIntervalSince1970: doubleTime )
let dateToShow = myDate.calenderTimeSinceNow()
cell.messageBodyTextView.text = messageArray[indexPath.row].messageBody
cell.usernameLabel.text = messageArray[indexPath.row].name
cell.timeLabel.text = dateToShow
let imagePath = self.storageRef.reference(withPath:"\(message.uid)/resizes/profilImage_150x150.jpg")
imagePath.getData(maxSize: 10 * 1024 * 1024) { (data, error) in
if let error = error {
cell.userPicture.image = UIImage(named: "emptyProfilPic")
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
print("Got an error fetching data : \(error.localizedDescription)")
return
}
if let data = data {
cell.userPicture.image = UIImage(data: data)
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
}
}
return cell
}
Thank you for your help !
You have to prepare the cell to be reusable with the proper override prepareForReuse().
For more clean code I suggest to you to implement the cells in separate cocoa Touch classes so it's easier to override and prepare for next data incoming, avoiding your problem.
What I mean it's a sort of this:
class mineCell:UITableViewCell {
#IBOutlet weak var text:UILabel!
#IBOutlet weak var img:UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
func updateCell(dataIn){
.
.
}
override func prepareForReuse() {
text.text = ""
img.image = nil
}
In your cellForRowAt table implementation just call the update function and pass your data like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "mineCell"
if let cell = mineTable.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? mineCell {
updateCell(dataIn)
return cell
}
return mineCell()
}
In this way you are always sure that your cell will be ready for every reuse and not loading wrong data from the cell above.
Just to let you know, the problem was thaT I was reloading the table View after each message loaded. Instead, the best solution was to add a row to the tableview without reloaded the tableview after each message :
self.ConvertationTableView.insertRows(at: [IndexPath(row: self.messageArray.count - 1, section: 0)], with: .automatic)

My TableView initializes before JSON data is received. How do you prevent this?

I am coding an app that receives air pollution data from API. The code passes JSON from aqicn.com. The data is updated too slowly and the tableview is displayed first. So there isn't any data displayed for the data view. I have tried DispatchQueue.main.async but it did not work. Delays using DispatchQueue.main.asyncAfter did not work. Been stuck on this for several days now. Please give me suggestions or solutions!
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
let locationManager = CLLocationManager()
var pm25Data:Int?
var pm10Data:Int?
override func viewDidLoad() {
super.viewDidLoad()
performRequest(with: "https://api.waqi.info/feed/Tainan/?token=\(C.APIkey)")
tableView.dataSource = self
}
func performRequest(with typeURL: String)
{
if let url = URL(string: typeURL)
{
print("pass")
let session = URLSession.shared
print("pass")
let dataTask = session.dataTask(with: url) { (data, response, error) in
if error == nil && data != nil
{
let decoder = JSONDecoder()
do
{
let info = try decoder.decode(AirData.self, from: data!)
self.pm25Data = info.data.iaqi.pm25.v
self.pm10Data = info.data.iaqi.pm10.v
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch
{
print("we have an error")
}
}
}
dataTask.resume()
}
}
override func viewWillAppear(_ animated: Bool) {
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "cellId")
}
override open var shouldAutorotate:Bool
{
return false
}
func didFailWithError(error: Error) {
print(error)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell
if(indexPath.row == 0)
{
cell.descriptionName?
.text = "PM 2.5"
cell.valueNumber?.text = "\(pm25Data)"
}
else
{
cell.descriptionName?.text = "PM 10"
cell.valueNumber?.text = "\(pm10Data)"
}
return cell
}
}
One possible way...
Maintain an array of objects containing the data for each row, I will call it dataArray so that each object in the array would contain the information required to populate each respective row. Then, in your cellForRowAt function, after dequeueing the cell like you currently have implemented, do something like this:
if indexPath.row == 0 {
cell.descriptionName?.text = "PM 2.5"
cell.valueNumber?.text = "\(pm25Data)"
// Other setup unique to the first cell
} else {
cell.descriptionName?.text = dataArray[indexPath.item - 1].description
cell.valueNumber?.text = dataArray[indexPath.item - 1].description
// Other setup unique to all following cells
}
Also, implement your numberOfRowsInSection with one line, simply return dataArray.count
Then, once your dataArray is filled with objects containing the data, call self.tableView.reloadData()
In a broader but still simplified sense:
Every time you call reloadData(), a few things happen:
The tableView will call its numberOfRowsInSection function
The tableView will call its cellForRowAt function repeatedly the number of times returned from numberOfRowsInSection and set up each row individually
The tableView will call other functions like heightForRowAt as necessary to set up each row
Some things to keep in mind:
Call reloadData() every time you want to refresh the tableView (with new data, for example)
You don't really set up each cell individually, you set up the logic to populate cells with data pulled from an itemized source (usually an array)
All you then have to do is maintain the dataArray and then call reloadData() every time you want to refresh the tableView. The cellForRowAt function handles populating each cell with data.
A useful tutorial:
https://www.youtube.com/watch?reload=9&v=VFtsSEYDNRU

Call the same view again so that another option can be selected in UITableViewController

Background:
Simple app that lets you select a currency from a UITableViewController, calls the same view again to make a second choice then takes user to a new view which displays the two selected currencies and exchange rate
So theoretically to me, this is only 2 views. The first being the currency list and the second is presenting chosen currencies/exchange rates. The first view is complete design wise. But I am struggling on how to make the connection between the first and second choice as it's calling the same view. How would I do this?
In my didSelectRowAt, I would typically performSegue but how do I call the same view and record the value selected from the first view? An idea I had was call a function that would record if an option is selected, and if so, call the new view else call the same view again but I'm not sure how I would implement this. Any help is appreciated!
My code thus far:
import UIKit
class SelectCurrencyTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// Get the JSON data to insert into the table
func parseJSONData()-> Array<Any> {
var finalArray = [Any]()
if let url = Bundle.main.url(forResource: "currencies", withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONSerialization.jsonObject(with: data)
if var jsonArray = jsonResult as? [String] {
while jsonArray.count > 0 {
let result: [String] = Array(jsonArray.prefix(2))
finalArray.append(result)
jsonArray.removeFirst(2)
}
}
} catch {
print(error)
}
}
return finalArray
}
func checkOptionsCount()-> Int{
// somehow check if option selected?
return 1
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return parseJSONData().count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCellController
if let array = parseJSONData()[indexPath.row] as? [String]{
cell.countryCodeLabel.text = array[0]
cell.currencyLabel.text = array[1]
cell.countryFlag.image = UIImage(named: array[0])
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// if this is 1st time, present view again
if (checkOptionsCount() == 1){
// if this is 2nd time, show new view
} else if (checkOptionsCount() == 2){
// performSegue with new view
} else {
print("How did I get here")
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
Seeing your code, I am assuming you are using storyboards. One way to accomplish what you want can be like this:
In Interface Builder select your SelectCurrencyTableViewController and add Storyboard ID to it:
Add a property where you will store your selected currency to SelectCurrencyTableViewController, something along these lines:
class SelectCurrencyTableViewController: UITableViewController {
var selectedCurrency: Currency?
//...
}
Then in didSelectRow:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// if this is 2nd time, show new view
if let selected = selectedCurrency {
// performSegue with new view
// if this is 1st time, present view again
// these is no selected currency passed from previous view controller, so this is the first time
} else {
//get view controller from storyboard using storyboard id (replace "Main" with your storyboard's name
let vc = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "SelectCurrencyTableViewController") as! SelectCurrencyTableViewController
vc.selectedCurrency = //place code for getting first currency based on indexPath.row here
show(vc, sender: self)
}
}

How to push data generated by Alamofire from tableview to webview

I am somewhat new to the Swift language. Therefore, I need some advice on how to push online data generated by Alamofire from a tableview in one view controller to a webview, located in a different view controller. More specifically, in the JSON data there are a variety of elements with the tag "url." My goal is to match those url elements with the appropriate tableview cell, so that when the cell is clicked, it takes the user to the URL in a new view controller. While I understand how this would work with an array, the JSON data I am using is that of a dictionary. In return, I have fiddled with this for a while, searching the web for any tutorials that may exist. For reference, I included my code in this post. Any information on this problem would be greatly appreciated.
class TableViewController: UITableViewController {
var responseArray = [[String:Any]]()
var mySong = 0
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request("https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/10/explicit.json").responseJSON { response in
if let json = response.result.value as? [String:Any],
let feed = json["feed"] as? [String:Any],
let results = feed["results"] as? [[String:Any]] {
print(results)
self.responseArray = results
self.tableView.reloadData()
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return responseArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "top10", for: indexPath)
let song = responseArray[indexPath.row]
cell.textLabel?.text = song["name"] as? String
cell.detailTextLabel?.text = song["artistName"] as? String
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
mySong = indexPath.row
performSegue(withIdentifier: "iTunes", sender: self)
}
}
You can use a dictionary pretty much like an array. Only difference is that accessing a dictionary value by subscript doesn't guarantee a result so the type of the returned value is optional.
Coming back to your problem, I gather that you wish to open another ViewController with a WebView in it that navigates to a URL. This URL is determined by the cell that was clicked before navigating here.
I looked into the API, since you haven't mentioned I will assume that you wish to load the webpage given as per the artistUrl key in the response.
Just add the following code to the didSelectRowAt method :
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// safely unwrap artistUrl
// init WebViewController
// set url for WebViewController
if let artistUrl = responseArray[indexPath.row]["artistUrl"], let viewController = storyboard?.instantiateViewController(withIdentifier: "WebViewController") as? WebViewController {
viewController.url = artistUrl
present(viewController, animated: true, completion: nil)
}
}
Make a WebViewController, if you already have one then just modify the code a bit.
class WebViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
var url: String?
override func viewDidLoad() {
super.viewDidLoad()
// safely unwrap url, then convert it to URL
if let url = url, let urlForRequest = URL(string: url) {
let request = URLRequest(url: urlForRequest)
webView.loadRequest(request)
}
}
}
One last thing, for this API I noticed that all the URLs are HTTPS. Sometimes if you have non-HTTPS then you need to set AllowArbitraryLoads to YES in your plist.

Query an image using Parse

I am using parse with Swift and I am trying to bring up and post an image from Parse on to my tableView but I keep receiving in the log
fatal error: unexpectedly found nil while unwrapping an Optional
value
here's my code for querying the image:
import UIKit
import Parse
class HomePage: UITableViewController {
var images = [UIImage]()
var titles = [String]()
var imageFiles = [PFFile]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
println(PFUser.currentUser())
var query = PFQuery(className:"Post")
//query.whereKey("username", equalTo:followedUser)
query.findObjectsInBackgroundWithBlock {(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
for object in objects! {
// Update - replaced as with as!
self.titles.append(object["Title"] as! String)
// Update - replaced as with as!
self.imageFiles.append(object["imageFile"] as! PFFile)
self.tableView.reloadData()
}
} else {
// Log details of the failure
println(error)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 500
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var myCell:cell = self.tableView.dequeueReusableCellWithIdentifier("myCell") as! cell
myCell.rank.text = "21"
myCell.votes.text = "4012"
myCell.postDescription.text = titles[indexPath.row]
return myCell
}
}
Also I get the one line of code highlighted in green which is this :
self.imageFiles.append(object["imageFile"] as! PFFile)
What am I doing wrong and what can I do to make the the code pull up the image from Parse?
now its no longer giving me the error unwrapped nil but it still shows
self.imageFiles.append(object["imageFile"] as! PFFile)
in green highlighting and the app keeps crashing when i open it.
this is my parse backend in case that might be an issue:
i solved the issue by adding in the ovveride func UITableviewCell
imageFile[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
myCell.postedImage.image = downloadedImage
}
}
i think this worked because i got rid of the if error == nil and now its working great for me.