How to push data generated by Alamofire from tableview to webview - swift

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.

Related

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

Variable not updating inside the view

For some reason, the variable Tags isnt being updated. When SelectedSauce is ran, it should run the function and then update the variable. But it's not being updated once it leaves the function. Im not sure what is wrong with this. When I change views, I pass a variable to selectedsauce from the previous view to here. im not sure if it helps or changes anything but I am using the Realm Database
class InfoUITableViewController: UITableViewController {
var SelectedSauce: Sauce? {
didSet {
print("1st")
AcquireData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
print("3rd")
print("Loaded")
tableView.register(UINib(nibName: "DetailsTableViewCell", bundle: nil), forCellReuseIdentifier: "Super")
tableView.dataSource = self
//Returns nill even though i changed the variable in acquiredata
print(Tags)
}
let realm = try! Realm()
var Tags: List<NiceTags>?
//MARK: - Data Manipulation
func AcquireData() {
print("2nd")
if let Sauce = SelectedSauce {
Tags = Sauce.tags
// print(Tags)
}
self.Tags = self.SelectedSauce?.tags
print(self.Tags)
tableView.reloadData()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("4th")
let cell = tableView.dequeueReusableCell(withIdentifier: "Super", for: indexPath) as! DetailsTableViewCell
//This isn't running, and just uses the default text inside the label
if let TheTags = Tags?[indexPath.row] {
cell.Inflabel.text = TheTags.tags
}
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of row
print("5th")
//Returns 7 because tags is still nill
return Tags?.count ?? 7
}
}
Set SelectedSauce from tabbar like this
override func viewDidLoad() {
super.viewDidLoad()
swipeAnimatedTransitioning?.animationType = SwipeAnimationType.sideBySide
// isCyclingEnabled = true
// Do any additional setup after loading the view.
print("order")
let first_vc = self.viewControllers?.first as! DetailViewController
let last_vc = self.viewControllers?.last as! InfoUITableViewController
first_vc.SelectedSauce = SelectedSauce
last_vc.SelectedSauce = SelectedSauce
}

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)
}
}

Retrieve value from specific core data table row - then store value to UserDefaults

What I am trying to do in general:
1. Generate a table with all registered users (stored in core data).
2. Then allow the user to select a registered-user by pressing on a row.
3. When user selects a row, a new page opens with detailed user info.
I am trying to solve this by:
1. First storing the UserName from the given cell into "UserDefaults.standard".
2. Then on the second page, accessing the stored UserName from "UserDefaults.standard" and using that dynamic value to retrieve Core Data for more detailed user information.
Problems:
I am certain that the data that is passed into "UserDefaults.standard" is the full table and not data from a specific cell. I have seen this by using "Print()", is actually show all of the rows instead of only the one row that is selected.
So the UserName that is then generated into the second page is a random row that is further down (see picture).
public class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var userNameobject : [NSManagedObject] = []
#IBAction func pushTwo(_ sender: Any) {
}
#IBOutlet weak var usersList: UITableView!
override public func viewDidLoad() {
super.viewDidLoad()
usersList.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return }
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Users")
do {
userNameobject = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
public func tableView(_ usersList: UITableView, numberOfRowsInSection section: Int) -> Int {
return userNameobject.count
}
public func tableView(_ usersList: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let person = userNameobject[indexPath.row]
let cell = usersList.dequeueReusableCell(withIdentifier: "CellTwo", for: indexPath) as UITableViewCell
cell.textLabel?.text = person.value(forKeyPath: "userName") as? String
// STORE USERNAME
let defaults = UserDefaults.standard
defaults.set(person.value(forKeyPath: "userName"), forKey: "userNameDefault")
return cell
}
}
public func tableView(_ userList: UITableView, didSelectRowAt indexPath: IndexPath)
{
}
1- Connect segue source to VC not to cell
func tableView(_ userList: UITableView, didSelectRowAt indexPath: IndexPath)
{
self.performSegue(withIdentifier: "toSecond", sender:userNameobject[indexPath.row])
}
2- Implement
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let des = segue.destination as? SecondVC {
des.person = sender as! NSManagedObject
}
}
3-
class SecondVC:UIViewController {
var person: NSManagedObject?
}
I solved this by making giving "Sender:" the exact same line that generates each given cell the User Name. It works perfectly now!
public func tableView(_ userList: UITableView, didSelectRowAt indexPath: IndexPath)
{
let person = userNameobject[indexPath.row]
let two = person.value(forKeyPath: "userName") as? String
self.performSegue(withIdentifier: "userLinkSegue", sender: two!)
print(two!)
// STORE USERNAME
let defaults = UserDefaults.standard
defaults.set(two!, forKey: "userNameDefault")
}

Retrieving data from Parse.com (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".