What is the best practice for populating a tableview from a URL that takes user data from parse in the URL string. Currently the code is working, but it could result in an error if the parse user data isn't loaded before the URL request is run. This will cause the table to not load (not safe code)
The user class in parse has a key of "teamNumber" which plugs in to the URL to fetch a string of the page source. From there the string is manipulated to create an array that is displayed as the table view.
Here is the code (edited to take out the string manipulation):
override func viewDidLoad() {
super.viewDidLoad()
//To the internet to grab the "leagueNumber" of a user to input into the attemptedUrl string
let leagueNumber = PFUser.currentUser()!["leagueNumber"] as? String
//Plug in the league number from parse into URL string
let attemptedUrl = NSURL(string: "http://someURL.leagueapps.com/leagues/\(leagueNumber!)/teams")
if let url = attemptedUrl {
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
//Manipulate string to get the team information
//Ends up with array [team1, team2, team3, team4] to populate the tableview
}
} else {
print("URL could not connect")
}
//Close internet session
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
task.resume()
} else {
print("Fail")
}
}
Is there a safer way to implement this code?
I supposed you could make it "safer" by fetching the full user object from parse with a query, and then forming your URL with that result:
var query = PFUser.query
var currentUser = PFUser.currentUser()
query.whereKey("username", equalTo: currentUser.username)
query.getFirstObjectInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error != nil || object == nil {
println("The getFirstObject request failed.")
} else {
// The find succeeded.
leagueNumber = object["leagueNumber"] as! String!
println("Successfully retrieved the object.")
}
}
Related
I am trying to display some data I take from a data base into a TableView but the data is not shown in the TableView. The data I receive is formatted in JSON.
This is the data I receive form the data base and what I want to print in the TableViewis just David:
{"name":"David"}
This is the code to get the data from de data base:
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: NSURL(string: "http://localhost:8888/Patients.php")!)
let task = session.dataTaskWithRequest(request) {data, response, downloadError in
if let error = downloadError {
print("Could not complete the request \(error)")
}
else {
do {
self.json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
} catch {
fatalError()
}
dispatch_async(dispatch_get_main_queue(), {
if let parseJSON = self.json{
let name = parseJSON["name"] as! String
self.arrayData.append(name)
print("Data1:\(self.arrayData)")
}
})
}
}
task.resume()
arrayData is an array where I put the data I receive from the data base and it is declared like this:
var arrayData: [String] = []
The code
print("Data1:\(self.arrayData)")
show in the console this Data1:["David"], so I get the data correctly.
Then I implement the methods to print in the ´TableView´, the numberOfSectionsInTableViewmethod, the numberOfRowsInSection method and the cellForRowAtIndexPath method but the data is not printed in the TableView.
I think the problem is that the TableViewis drawn before I put the data in the array so it prints nothing because the array is empty, and I don´t know how to fix it.
Anyone knows what is my problem?
yes, you're right.
session.dataTaskWithRequest
is async. Data is not returned immediately, it have delay.
You must to reload tableview after recieved data:
self.arrayData.append(name)
self.tableview.reloadData()
Usually i will use block:
func getData(handleComplete:((arr:NSMutableArray)->())){
let aray = NSMutableArray()
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: NSURL(string: "http://localhost:8888/Patients.php")!)
let task = session.dataTaskWithRequest(request) {data, response, downloadError in
if let error = downloadError {
print("Could not complete the request \(error)")
}
else {
do {
self.json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
} catch {
fatalError()
}
dispatch_async(dispatch_get_main_queue(), {
if let parseJSON = self.json{
let name = parseJSON["name"] as! String
aray.append(name)
print("Data1:\(self.arrayData)")
}
handleComplete(aray)
})
}
}
task.resume()
arrayData
}
and in viewdidload i will call:
override func viewDidLoad() {
super.viewDidLoad()
self.getData { (arr) -> () in
self.tableview.reloadData()
}
}
it 's better
I am stuck in a problem. I think it is all due to my weak basics. I am sure someone can help me easily and put me in the right direction.
I have different segues and all get the data from JSON via remote URL.
So in-short all segues need to open URL and parse JSON and make them into an ARRAY
I have made the first segue and it is working fine.
Now i plan to use the functions where it download JSON and turns it into ARRAY as a common function
I read in another page on stackoverflow that I can declare all common functions outside the class in ViewController
I hope everyone is with me this far.
now in ViewController i declare a function
getDataFromJson(url: String)
This function code looks like following
func getJsonFromURL(url: String)
{
// some class specific tasks
// call the common function with URL
// get an array
let arrJSON = getJsonArrFromURL(url)
for element in arrJSON
{
// assign each element in json to ur table
print("Element: \(element)")
}
// some class specific tasks
}
and this will call the common function declared outside the score of class
getArrFromJson(url: String) -> NSArray
This common function is just very generic.
Take a URL, call it, open it, parse its data into ARRAY and return it back.
The problem i am stuck is where to put the return
It returns empty array as the task is not finished and i am clueless
func getJsonArrFromURL(var url: String) -> NSArray
{
var parseJSON : NSArray?
if ( url == "" )
{
url = self.baseURLHomepage
}
print("Opening a JSON URL \(url)")
let myUrl = NSURL(string: url);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let postString = "";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if ( error != nil )
{
print("Error open JSON url \n\(error)")
return
}
do
{
parseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
}
catch
{
self.showAlert("Error", msg: "Error occurred while trying to process the product information data")
print("Error occured in JSON = \(error)")
}
}
task.resume()
return parseJSON!
}
You can probably add a method like below in any of your class
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
print("response String is nil")
completionHandler(nil, error)
return
}
if let dataNew = data {
completionHandler(NSString(data: (NSData(base64EncodedData: dataNew, options: NSDataBase64DecodingOptions([])))!, encoding: NSASCIIStringEncoding), nil)
}
}
}
task.resume()
return task
}
and access it anywhere like
let url = "your URL String"
let info = "The data you would like to pass"
yourClassName.post(url, info: info) { responseString, error in
guard responseString != nil else {
print("response String is nil")
print(error)
return
}
do {
if !(responseString as? String)!.isEmpty {
let json = try NSJSONSerialization.JSONObjectWithData((responseString as! String).data, options: NSJSONReadingOptions.init(rawValue: 0))
//process your json here
}
} catch {
print("Error\n \(error)")
return
}
}
Extend your string like follows
extension String {
var data:NSData! {
return dataUsingEncoding(NSUTF8StringEncoding)
}
}
Ok so I am scraping some basic data of a web page. I wanted to refactor out my code to another class and return a string from what I retrieved but it is difficult with the asynchronous function and I'm new with swift.
I now realize that this function is incapable of returning a string but I can't quite figure out how to configure the completion handler and how to call the function after from the main class using the completion handler.
Any help would be greatly appreciated, thanks.
func getNameFromProfileUrl(profileUrl: NSURL) -> String {
var playerName = ""
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl, completionHandler: { (data, response, error) -> Void in
if error == nil {
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
var urlContentArray = urlContent.componentsSeparatedByString("<title>")
var statusArray = urlContentArray[1].componentsSeparatedByString("</title>")
playerName = statusArray[0] as! String
}
})
task.resume()
return playerName
}
Essentially, you'll want to provide a completion handler to this function from the main class that can handle just the return of the player name (or not). You'd change the function to not have a return value, but to accept a second parameter that is a completion handler:
func getNameFromProfileUrl(profileUrl: NSURL, completionHandler: (String?) -> Void) {
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl, completionHandler: { (data, response, error) -> Void in
if error == nil {
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
var urlContentArray = urlContent.componentsSeparatedByString("<title>")
var statusArray = urlContentArray[1].componentsSeparatedByString("</title>")
let playerName = statusArray[0] as? String
completionHandler(playerName)
} else {
completionHandler(nil)
}
})
task.resume()
}
From your main class, you'd then call it with something like this:
myWebScraper.getNameFromProfileUrl(profileURL) { playerName in
// always update UI from the main thread
NSOperationQueue.mainQueue().addOperationWithBlock {
if let playerName = playerName {
playerNameField.text = playerName
} else {
playerNameField.text = "Player Not Found"
}
}
}
I am developing an iPad application using Swift. For http requests I use Alamofire library. So far I have managed to pull some data from an API. But the problem is since it is an asynchronous call I don't know how to check whether the request has completed. Any help would be appreciated.
This is the code I have implemented so far
Client class
func getUsers(completionHandler: ([User]?, NSError?) -> ()) -> (){
var users: [User] = []
let parameters = [
"ID": "123",
"apikey": "1234",
]
Alamofire.request(.GET, "API_URL", parameters: parameters).responseJSON() {
(_, _, JSON, error) in
let items = (JSON!.valueForKey("Users") as! [NSDictionary])
for item in items {
var user: User = User()
user.userId = item.valueForKey("ID")! as? String
user.userName = item.valueForKey("Name")! as? String
user.group = item.valueForKey("Group")! as? String
users.append(user)
}
completionHandler(users, error)
}
}
Main class
func getUsers(){
FacilityClient().getUsers() { (users, error) -> Void in
if users != nil {
self.users = users!
} else{
println("error - \(error)")
}
}
tableUsers.reloadData()
}
Thank you.
The closure part of the Alamofire request is called, when the request has completed. I've used your code and commented the line where the request hast finished:
Alamofire.request(.GET, "API_URL", parameters: parameters).responseJSON() {
(_, _, JSON, error) in
//
// The request has finished here. Check if error != nil before doing anything else here.
//
let items = (JSON!.valueForKey("Users") as! [NSDictionary])
for item in items {
var user: User = User()
user.userId = item.valueForKey("ID")! as? String
user.userName = item.valueForKey("Name")! as? String
user.group = item.valueForKey("Group")! as? String
users.append(user)
}
//
// After creating the user array we will call the completionHandler, which probably reports back to a ViewController instance
//
completionHandler(users, error)
}
If you want the tableView to reload data after successfully fetching data over network, you will need to call tableUsers.reloadData() inside that completion handler, like this:
func getUsers(){
FacilityClient().getUsers() { (users, error) -> Void in
if users != nil {
self.users = users!
tableUsers.reloadData() // SHOULD be here
} else{
println("error - \(error)")
}
}
// tableUsers.reloadData() // WAS here
}
And Alamofire make sure the completion handler is called on main queue if you don't specify a queue. You don't need to add code to make it be called on mainThread again.
And to your originally question: The only way Alamofire let you know a request has completed is by calling that completion handler which you give to the Alamofire.request method.
I am making a GET request using alamofire and attempting to convert to JSON using SwiftyJSON. I can successfully make the request, but I'm unable to convert the response into a usable JSON object.
I am trying to grab data from a foursquare venue detail response, and update a UITableView (the detail page) with appropriate information about the venue. The UITableView has 3 different sections.
Here is my makeRequest code for VenueDetailViewController (UITableViewController).
func makeRequest() {
Alamofire.request(.GET, self.foursquareEndpointURL, parameters: [
"client_id" : self.foursquareClientID,
"client_secret" : self.foursquareClientSecret,
"v" : "20140806"
])
.responseJSON { (request, response, data, error) in
println(request)
println(response)
println(error)
println(data)
if data != nil {
var jsonObj = JSON(data!)
// response is not in the form of an array? I think... thats why data is not setting .. I think "arrayValue" below is incorrect. should be something like dictionaryValue or stringValue
if let obj = jsonObj["response"]["venue"].arrayValue as [JSON]? {
self.responseitems = obj
self.tableView.reloadData()
println("Objis: \(obj)")
}
}
}
}
When I println("Obis: \(obj)") I get a console output of Objis: [] telling me that I'm grabbing the wrong data from the JSON response? Here is the url for the Foursquare Venue Detail API request
Oh and self.responseitems is var responseitems:[JSON] = []
Please help! Thanks
Check out this code:
let url = "Foursquare Venue Detail API request goes here"
Alamofire .request(.GET, url) .responseJSON(options: nil) { (_, _, data, error) -> Void in
if error != nil {
println(error?.localizedDescription)
} else if let data: AnyObject = data {
let jObj = JSON(data)
if let venue = jObj["response"]["venue"].dictionary {
println(venue)
}
}
}
So the main difference here is that the data is a dict, not an array.
By the way, consider reloading the table view in the main queue as this affects the UI:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData() // Update UI
}