I'm doing a practice, and find my class property(adNames) can't be assigned. But it can be assigned outside(the place where I comment out). Here is my code:
import UIKit
import Alamofire
import SwiftyJSON
class AdTableViewController: UITableViewController {
var adNames: [JSON]?
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "http://codewithchris.com/code/afsample.json").responseJSON { (Response) -> Void in
if let value = Response.result.value{
let json = JSON(value)
print(json["secondkey"].arrayValue) //["item1","item2","item3"]
self.adNames = json["secondkey"].arrayValue
}
}
// self.adNames = ["item1", "item2", "item3"]
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let names = adNames {
return names.count
}
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
}
}
Can anybody tell me the reason? Thanks!
Your Alamofire request runs in the background. Meanwhile, your tableView wants to display and calls numberOfRowsInSection. By this time, Alamofire has not finished its work, so adnames hasn't been set.
You need to refresh the tableView with reloadData() after adNames is set. i.e.
Alamofire.request(.GET, "http://codewithchris.com/code/afsample.json").responseJSON { (Response) -> Void in
if let value = Response.result.value{
let json = JSON(value)
self.adNames = json["secondkey"].arrayValue
self.tableView.reloadData()
}
}
Then the tableView will refresh and re-call numberOfRowsInSection, and at this time, adNames will not be nil.
You are accessing self inside a closure which is passed to the request call. Use capture lists inside the closure to hold the references to the self object.
[weak self] (Response) -> Void in (in this case you have to use self? to access the self instance)
...
or [unowned self] (Response) -> Void in
If you are sure that the object will be present after the request call completes.
Related
I'm learning how to get data using Github API and making a simple app in swift. Tutorials I've been watching make an empty array and geting response function in the same controller file. However, I want my view controller to keep as small as possible, and I decided to create getting data function in another file. My question is how can I access the empty array in a different file after I fetch data. Maybe this explanation make you confused, so I put my code below.
This is my view controller file, and I want to populate the repositories array after fetch the data using an API call.
import UIKit
class RepositoryListVC: UIViewController {
var tableView = UITableView()
var repositories: [Repository] = []
override func viewDidLoad() {
super.viewDidLoad()
title = "AAAAAA"
configureTableView()
Service.fetchData()
}
func configureTableView() {
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 100
tableView.register(RepositoryCell.self, forCellReuseIdentifier: Cells.repositoryCell)
tableView.pin(to: view)
}
}
extension RepositoryListVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.repositoryCell) as! RepositoryCell
cell.set()
return cell
}
}
This is the file for fetching data using an API call. I want to populate the repository valuable in the view controller file above. I have successfully get data and parse JSON data, so I want to push Repository(...) to repository array, but I was not able to figure out how...
import UIKit
import SwiftyJSON
import Alamofire
struct Service {
static func fetchData() {
AF.request(API.gitHubEndpoint).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
let repositoryItemArr = json["items"].arrayValue
for item in repositoryItemArr {
Repository(userImageUrl: item["owner"]["avatar_url"].stringValue, userName: item["owner"]["user_name"].stringValue, repositoryName: item["owner"]["repository_name"].stringValue, starNum: item["owner"]["star_number"].intValue)
}
case .failure(let error):
print(error)
}
}
}
After parsing JSON I got my data in arraOfData of ViewController, but collectionView not display this. If I use static array all working.
collectionview.reloadData() is not working. In my opinion, need somehow waiting data from parsing, then try load collectionView. I tried use refresherControler for waiting data, but I don't know where put method "endRefreshing" (i guess startRefreshing need in ViewDidLoad)
If I use reloadData like didSet in arraOfData
I got error cause collectionView is nil. Use reloadData right after got data from parsing same didn't work for me
And a big request to tell a couple of resources (articles) on this topic, I feel that I have big problems with displaying data and in what sequence functions start to load. Thanks a lot.
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var arraOfData = [GettingMoney]() {
didSet {
print("Changes: \(arraOfData.count)")
}
}
// let arr = ["1", "2", "3"] - test, if i use this array, collectionView display data
override func viewDidLoad() {
super.viewDidLoad()
GettingMoney.fetchData()
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegate,
UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arraOfData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellId", for: indexPath) as? CollectionViewCell
cell?.currentCurrency.text = "\(arraOfData[indexPath.row])"
return cell!
}
}
As this
GettingMoney.fetchData()
is a sign of asynchronous process that doesn't reload the collection , so do
func fetchData(completion:#escaping ([String]) -> ()) {
Api.load {
completion(arr)
}
}
Then use like
GettingMoney.fetchData { arr in
self.arraOfData = arr
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
A not direct case like yours is here Returning data from async call in Swift function
I am fairly new to Swift and I am having a few issues with getting understanding how to do what I want to do.
I am currently testing some stuff with json.
What I am trying to do is to append the data I get from my json data into an array. And when my array contains the data I wish to present it to my UICollectionView. I am assuming that I should be using an array.
import UIKit
import Foundation
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getData() {
let path = "http://myurl/test.json"
let url = URL(string: path)
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data: Data?, response: URLResponse?, error: Error?) in
let json = JSON(data: data!)
for result in json["dokumentstatus"]["dokutskott"]["utskott"].array! {
let punkter = result["punkt"].string!
print("punkt: \(punkt)")
let rubrik = result["rubrik"].string
print("rubrik: \(rubrik)")
let forslag = result["forslag"].string
print("förslag: \(forslag)")
}
}
task.resume()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return //someArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
cell.customLabel.text = //Put the data form rubrik variable
cell.customTW.text = //Put the data from foreleg variable
return cell
}
}
the function getData() gets the correct json data I just need help understanding how to put this data to an array.
(Also, I know I probably shouldn't be getting the data in the ViewController, but this is only a test.)
import Foundation
class Information: NSObject {
var punkter: String?
var rubrik: String?
var forslag: String?
}
I'm thinking that maybe I should be using an array that looks something like this:var someArray = [Information]()
But I do not know how to then use my getData()
Or maybe I should be using three different arrays, one for each of my json variables.
Since you're still using a custom class (well done!, three different arrays are horrible) it's correct to declare a data source array like you suggested
var someArray = [Information]()
Most likely a struct is sufficient, I recommend to use non-optional strings
struct Information {
var punkter : String
var rubrik : String
var forslag : String
}
If the properties won't change you could even use let to make the properties constants.
To populate the array use the nil coalescing operator to check for nil,create the Information instance with the memberwise initializer and append the instance to the datasource array. Then reload the collection view on the main thread.
...
for result in json["dokumentstatus"]["dokutskott"]["utskott"].array! {
let punkter = result["punkt"].string ?? ""
let rubrik = result["rubrik"].string ?? ""
let forslag = result["forslag"].string ?? ""
let information = Information(punkter:punkter, rubrik:rubrik, forslag:forslag)
self.someArray.append(information)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
...
Edit:
To display the data in cellForItemAtIndexPath use
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
let information = someArray[indexPath.row]
cell.customLabel.text = information.rubrik
cell.customTW.text = information.foreleg
return cell
}
I have two ViewControllers. For the first ViewController, it displays my Array data on the table. I want to get the indexPath of the selected cell, and pass this data to another ViewController.
In my First ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var nameList = [NameManager]()
#IBOutlet weak var NameTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
NameTable.dataSource = self
GetData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func GetData(){
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.json-generator.com/api/json/get/bPfifKWNaq?indent=2")!)
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
do{
let resultJSON = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let resultArray = resultJSON as? NSArray
for jsonObjectString in resultArray!{
let code = jsonObjectString["code"] as! String
let name = jsonObjectString["name"] as! String
let description = jsonObjectString["description"] as! String
self.nameList.append(NameManager(code: code, name: name, description: description))
}
self.nameList.count
dispatch_async(dispatch_get_main_queue(), {
self.NameTable.reloadData()
})
}catch _{
print("Received not-well-formatted JSON")
}
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
let count = nameList.count
return count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let myCell = NameTable.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as UITableViewCell
myCell.textLabel?.text = nameList[indexPath.row].name
myCell.detailTextLabel?.text = nameList[indexPath.row].description
return myCell
}
In my Second ViewController, which is also another class, I want to capture the indexPath of what was selected. Using the index value, I search my Array and pass that particular object to the next class. I don't have codes for this as I don't know how it works.
You could create a property outside the scope of your class and then set that within the cellForRowAtIndexPath method. That property could be accessed in your second view controller. You’ll want to look at the tableview method didSelectRowAtIndexPath as that will be where you set your property to the cell that’s been selected.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
appDelegate().index = indexPath.row
}
I’ve made a super simple project on github showing two ways of creating a property outside the scope of your view controller. One way is creating a variable within AppDelegate to be accessed via a singleton, the other is a Globals.swift file.
Hope this helps!
If you want to pass values to the controller which pops after tapping on the cell, using a singleton wouldn't be an elegant way. If you are using storyboards, then you have to use 'prepare for segue'. You implement the method in the class that handles the transfer and set all the properties in another view controller.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "SecondVC") {
// set properties
}
}
I have a tablecell to work with and I can populate it when I use a written array (like values = [""]) so I know it is working.
But I am using json with swiftyjson to get my info in my table, which is part of a right slideout page I made with mmdrawer. When I println the json output I get all the info I need, but it is not being taken to the table or other variables/arrays.
How do I make this code work?
import UIKit
class RightSideViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var songname = [String]()
var menuImage = [String]()
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBarHidden = true
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
getmusiclist()
}
func getmusiclist(){
let search:NSString = "music" as NSString
let url = NSURL(string:"http://xxxxxx/music-manager.php")
let cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
var request = NSMutableURLRequest(URL: url!, cachePolicy: cachePolicy, timeoutInterval: 2.0)
request.HTTPMethod = "POST"
// set Content-Type in HTTP header
let boundaryConstant = "----------V2ymHFg03esomerandomstuffhbqgZCaKO6jy";
let contentType = "multipart/form-data; boundary=" + boundaryConstant
NSURLProtocol.setProperty(contentType, forKey: "Content-Type", inRequest: request)
// set data
var dataString = "search=\(search)"
let requestBodyData = (dataString as NSString).dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPBody = requestBodyData
// set content length
//NSURLProtocol.setProperty(requestBodyData.length, forKey: "Content-Length", inRequest: request)
var response: NSURLResponse? = nil
var error: NSError? = nil
let dataReply = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&error)
var results = NSJSONSerialization.JSONObjectWithData(dataReply!, options: nil, error: &error) as! NSDictionary
var jsonOutput = JSON(data: dataReply!)
println(jsonOutput)
let musicListArray = jsonOutput.arrayValue
dispatch_async(dispatch_get_main_queue(), {
for playlist in musicListArray
{
let trackname = playlist["track_name"].stringValue
println("trackName: \(trackname)")
self.songname.append(trackname)
}
/* dispatch_async(dispatch_get_main_queue(),{
self.tableView.reloadData()
})*/
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return songname.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var mycell = tableView.dequeueReusableCellWithIdentifier("playerCell", forIndexPath: indexPath) as! MusicTableViewCell
mycell.artistLabel?.text = songname[indexPath.row]
return mycell
}
}
Eventually I would also like to take the name, genre and streaming url and have avplayer play it - will that be something I can add to this code?
the problem with in the part that i forgot to add the uitableview outlet in the uiviewcontroller. I have added it and now the error is gone. I still have to figure out why i am not getting anything but it seems that i am nu getting the data from the jsonOutput.arrayvalue.