I´m building a widget for iOS with Swift. The main app´s purpose is to connect to a URL news feed and get the latest news, while the widget only get the title to display in a tableView in the Today view.
I´ve written this method for the widget in order to get the data to populate the table, but for some reason nothing is showing. I´ve tried to debug it, but being a widget it seems to be practically imposible.
This is the cellForRowAt, where I connect to the feed and try to extract data. The funny part is, the main app uses basically the same code and it works perfectly.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let urlRequest = URLRequest(url: URL(string: "https://newsapi.org/v1/articles?source=techcrunch&sortBy=top&apiKey=c64849bc30eb484fb820b80a136c9b0a")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let articlesFromJson = json["articles"] as? [[String: AnyObject]] {
if !(error != nil) {
var resultArray: NSMutableArray = NSMutableArray()
for articlesFromJson in articlesFromJson {
if let title = articlesFromJson["title"] as? String{
resultArray.add(title)
}
let array:NSArray = resultArray.reverseObjectEnumerator().allObjects as NSArray
resultArray = array as! NSMutableArray
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
}
}
}
//reload on main thread to speed it up
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
return cell
}
If someone can help me figure out where is the mistake it would be a huge help, i´ve been stuck on this issue for days now. Thanks
You want to make your network request outside of cellForRow and then reloadData once it's complete to have the tableView reload the cells which calls cellForRow.
store the array of data outside of request so you can reference it from outside the function.
var resultArray: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
func getData() {
let urlRequest = URLRequest(url: URL(string: "https://newsapi.org/v1/articles?source=techcrunch&sortBy=top&apiKey=c64849bc30eb484fb820b80a136c9b0a")!)
let task = URLSession.shared.dataTask(with: urlRequest) {[weak self] (data,response,error) in
guard let strongSelf = self else { return }
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let articlesFromJson = json["articles"] as? [[String: AnyObject]] {
if error == nil {
for articlesFromJson in articlesFromJson {
if let title = articlesFromJson["title"] as? String{
strongSelf.resultArray.add(title)
}
let array:NSArray = strongSelf.resultArray.reverseObjectEnumerator().allObjects as NSArray
strongSelf.resultArray = array as! NSMutableArray
DispatchQueue.main.async {
strongSelf.tableView.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
return cell
}
checks proper if TableView delegate or datasource proper connected. and check array count before load data in cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if (resultArray.count > 0){
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
}
else
{
print("Error: resultArray contain nil value ")
}
return cell
}
Related
Im receiving a JSON array from php and trying to passing data to table view. However, my tableview does not display the data.
class test1ViewController: UIViewController , UITableViewDataSource, UITableViewDelegate {
var TableData:Array< String > = Array < String >()
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return TableData.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! testTableViewCell
cell.mylabel1.text = TableData[indexPath.row]
return cell
}
func get_data_from_url(_ link:String)
{
let url:URL = URL(string: link)!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.httpMethod = "GET"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(
data, response, error) in
guard let _:Data = data, let _:URLResponse = response , error == nil else {
return
}
print(data!)
self.extract_json(data!)
})
task.resume()
}
func extract_json(_ data: Data)
{
let json: Any?
do
{
json = try JSONSerialization.jsonObject(with: data, options: [])
// print(json!)
}
catch
{
return
}
guard let data_list = json as? NSArray else
{
return
}
if let countries_list = json as? NSArray
{
for i in 0 ..< data_list.count
{
if let country_obj = countries_list[i] as? NSDictionary
{
if let name = country_obj["name"] as? String
{
if let age = country_obj["age"] as? String
{
TableData.append(name + age)
}
}
}
}
}
}
When the array gives initial values like
animals = ["hinno", "ali", "khalil"],
the values appear to custom cell, but when i take the data from a server and do the json conversion, nothing appears.
tableview.reloadData() any time you make changes to the array.
If reload Data of the collection or tableView doesn't work use that in Dispatch code like this
DispatchQueue.main.async {
yourTableViewName.reloadData()
}
In my application, I download a JSON file off of the internet and fill up a UITableView with items from the file. It does work well, and there are no problems or errors, but the scrolling performance is very laggy, and the UI glitches out a tiny bit.
I assume this is because of the images that I'm downloading from the JSON file, so I've looked into multi-threading, but I don't think I am doing it right because it does load much faster, but scrolling performance is still the same as before.
Can somebody please tell me how to fix this? This UITableView is the most important thing in the app, and I have been spending much time on trying to fix it. Thank you!
Here is my code-
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var nameArray = [String]()
var idArray = [String]()
var ageArray = [String]()
var genderArray = [String]()
var descriptionArray = [String]()
var imgURLArray = [String]()
let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
final let urlString = "https://pbsocfilestorage.000webhostapp.com/jsonDogs.json"
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithURL()
// Activity Indicator
myActivityIndicator.center = view.center
myActivityIndicator.hidesWhenStopped = true
myActivityIndicator.startAnimating()
view.addSubview(myActivityIndicator)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func downloadJsonWithURL() {
let url = NSURL(string:urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) ->
Void in
print("Good so far...")
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
print(jsonObj!.value(forKey: "dogs"))
if let dogArray = jsonObj!.value(forKey: "dogs") as? NSArray {
print("Why u no work!")
for dog in dogArray {
if let dogDict = dog as? NSDictionary {
if let name = dogDict.value(forKey: "name") {
self.nameArray.append(name as! String)
}
if let name = dogDict.value(forKey: "id") {
self.idArray.append(name as! String)
}
if let name = dogDict.value(forKey: "age") {
self.ageArray.append(name as! String)
}
if let name = dogDict.value(forKey: "gender") {
self.genderArray.append(name as! String)
}
if let name = dogDict.value(forKey: "image") {
self.imgURLArray.append(name as! String)
}
if let name = dogDict.value(forKey: "description") {
self.descriptionArray.append(name as! String)
}
OperationQueue.main.addOperation ({
self.myActivityIndicator.stopAnimating()
self.tableView.reloadData()
})
}
}
}
}
}).resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let imgURL = NSURL(string: imgURLArray[indexPath.row])
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell") as! TableViewCell
URLSession.shared.dataTask(with: (imgURL as! URL), completionHandler: {(data, resp, error) -> Void in
if (error == nil && data != nil) {
OperationQueue.main.addOperation({
cell.dogNameLabel.text = self.nameArray[indexPath.row]
cell.idLabel.text = self.idArray[indexPath.row]
cell.ageLabel.text = self.ageArray[indexPath.row]
cell.genderLabel.text = self.genderArray[indexPath.row]
print("Cell info was filled in!")
if imgURL != nil {
let data = NSData(contentsOf: (imgURL as? URL)!)
cell.dogImage.image = UIImage(data: data as! Data)
}
})
}
}).resume()
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDog" {
if let indexPath = self.tableView.indexPathForSelectedRow{
let detailViewController = segue.destination as! DetailViewController
detailViewController.imageString = imgURLArray[indexPath.row]
detailViewController.nameString = nameArray[indexPath.row]
detailViewController.idString = idArray[indexPath.row]
detailViewController.ageString = ageArray[indexPath.row]
detailViewController.descriptionString = descriptionArray[indexPath.row]
detailViewController.genderString = genderArray[indexPath.row]
}
}
}
}
There is a big mistake. You are loading data with dataTask but you aren't using that returned data at all. Rather than you are loading the data a second time with synchronous contentsOf. Don't do that.
And don't update the labels in the asynchronous completion block. The strings are not related to the image data.
This is more efficient:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let imgURL = URL(string: imgURLArray[indexPath.row])
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! TableViewCell
cell.dogNameLabel.text = self.nameArray[indexPath.row]
cell.idLabel.text = self.idArray[indexPath.row]
cell.ageLabel.text = self.ageArray[indexPath.row]
cell.genderLabel.text = self.genderArray[indexPath.row]
print("Cell info was filled in!")
URLSession.shared.dataTask(with: imgURL!) { (data, resp, error) in
if let data = data {
OperationQueue.main.addOperation({
cell.dogImage.image = UIImage(data: data)
})
}
}.resume()
return cell
}
Note: You are strongly discouraged from using multiple arrays as data source. It's very error-prone. Use a custom struct or class. And create imgURLArray with URL instances rather than strings. This is also much more efficient.
Nevertheless, you should use a download manager which caches the images and cancels downloads if a cell goes off-screen. At the moment each image is downloaded again when the user scrolls and cellForRow is called again for this particular cell.
I'm trying to find a way to parse through some Json data on reddit and display the information in a table view. (https://api.reddit.com).
So far this is what my code looks like:
var names: [String] = []
var comment: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://api.reddit.com")
do{
let reddit = try Data(contentsOf: url!)
let redditAll = try JSONSerialization.jsonObject(with: reddit, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String : AnyObject]
if let theJSON = redditAll["children"] as? [AnyObject]{
for child in 0...theJSON.count-1 {
let redditObject = theJSON[child] as! [String : AnyObject]
names.append(redditObject["name"] as! String)
}
}
print(names)
}
catch{
print(error)
}
}
//Table View
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
//Configure cells...
cell.textLabel?.text = names[indexPath.row]
cell.detailTextLabel?.text = comments[indexPath.row]
return cell
}
I know for a fact, the information is actually coming through the "redditALL" constant but i'm not sure what i'm doing incorrect after the JSONSerialization.
Also, i would really appreciate it if there was some kind of link to help me understand JSON Parsing in swift better, Thanks.
First of don't use Data(contentsOf:) to get JSON from URL because it will block your Main thread instead of that use URLSession.
Now to retrieve your children array you need to first access data dictionary because children is inside it. So try like this way.
let url = URL(string: "https://api.reddit.com")
let task = Session.dataTask(with: url!) { data, response, error in
if error != nil{
print(error.)
}
else
{
if let redditAll = (try? JSONSerialization.jsonObject(with: reddit, options: []) as? [String : Any],
let dataDic = redditAll["data"] as? [String:Any],
let children = dataDic["children"] as? [[String:Any]] {
for child in children {
if let name = child["name"] as? String {
names.append(name)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
task.resume()
JSON parsing in Swift (Foundation) is dirt-simple. You call JSONSerialization.jsonObject(with:) and you get back an "object graph". Usually it's a dictionary or array containing other objects. You have to know about the format of the data you're getting in order to cast the results to the proper types and walk the object graph. If you cast wrong your code will fail to run as expected. You should show us your JSON data. It's likely there is a mismatch between your JASON and your code.
I'm trying to connect my swift ios app to mysql with php... and the upon receiving the JSON from the php.. i converted it into nsarray and tried to populate my tableview with it.. however it doesnt seem to show anything in the tableview when i run it.... the data is successful in passing into the NSArray as i see my result when i print(values).. it just cant seem to show up on my tableview and i dont know why
#IBOutlet weak var tableView: UITableView!
var values:NSArray = []
#IBAction func php(_ sender: Any) {
let url = NSURL(string: "http://localhost/try.php")
let data = NSData(contentsOf: url! as URL)
values = try! JSONSerialization.jsonObject(with: data! as Data, options:JSONSerialization.ReadingOptions.mutableContainers) as! NSArray
print (values)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.descriptionView.text = (values[indexPath.row] as AnyObject) as? String
return cell
}
That's the recommended way to load data over the network.
As mentioned in the comment do not use NSArray, NSData and NSURL in Swift 3. URLSession loads the data asynchronously and in the background. The table view is reloaded on the main thread.
var values = [[String:String]]()
#IBAction func php(_ sender: AnyObject) {
let url = URL(string: "http://localhost/try.php")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
do {
self.values = try JSONSerialization.jsonObject(with: data!, options:[]) as! [[String:String]]
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
task.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
let item = values[indexPath.row]
cell.descriptionView.text = item["title"]
return cell
}
I have just started working with Swift and am able to do some basic things. Right now I am trying to populate my UITableView with Json Data that I am successfully retrieving. Right now I have this simple Table that looks like this
That is a basic TableView that I was able to create with this code
#IBOutlet var StreamsTableView: UITableView!
let groceries = ["Fish","lobster","Rice","Beans"]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let mycell:UITableViewCell = StreamsTableView.dequeueReusableCell(withIdentifier: "prototype1", for: indexPath)
mycell.textLabel?.text = groceries[indexPath.row]
return mycell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return groceries.count
}
override func viewDidLoad() {
super.viewDidLoad()
StreamsTableView.dataSource = self
}
I now have a JsonRequest that I am completing successfully using this code below
override func viewDidLoad() {
super.viewDidLoad()
StreamsTableView.dataSource = self
// Do any additional setup after loading the view.
var names = [String]()
let urlString = "http://localhost:8000/streams"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let Streams = parsedData["Streams"] as! [AnyObject]? {
for Stream in Streams {
if let post = Stream["post"] as? String {
names.append(post)
}
}
}
} catch let error as NSError {
print(error)
}
print(names)
}
}).resume()
}
What I essentially like to do is put the value of
let post = Stream["post"] as? String
inside the TableView instead of the Groceries array . As I stated before the value is coming back from the Json, I just have not found any way that I could put that value inside the TableView any help would be great . I am using swift 3.0 .
Add reloading data code
DispatchQueue.main.async {
StreamsTableView.reloadData()
}
just after your for loop
for Stream in Streams { ...
if let Streams = parsedData["Streams"] as! [AnyObject]? {
for Stream in Streams {
if let post = Stream["post"] as? String {
names.append(post)
}
}
}
StreamsTableView.reloadData()
After loop done
StreamsTableView.reloadData()
update:
mycell.textLabel?.text = groceries[indexPath.row]
to
mycell.textLabel?.text = names[indexPath.row]