Swift custom updateUI() function does not work on viewDidLoad - ios

I'm working on an app, that should request some data from my server. I'm using Alamofire to do that, and then use SWXMLHash to parse the XML data. There are two View Controllers, on the first one I can write a shipment number, then override function prepareForSegue and send that number to the next View Controller that should display data from server and updateUI on viewDidLoad, but it does not. Where is a problem?
My Class:
class Shipment {
private var _shipmentNumber: String!
private var _shipmentStatus: String!
private var _trackURL: String!
var shipmentNumber: String {
if _shipmentNumber == nil {
_shipmentNumber = ""
}
return _shipmentNumber
}
var shipmentStatus: String {
if _shipmentStatus == nil {
_shipmentStatus = ""
}
return _shipmentStatus
}
init(spNumber: String) {
self._shipmentNumber = spNumber
_trackURL = "..."
}
func requestXmlInformation(completed: DownloadComplete) {
let url = NSURL(string: _trackURL)!
Alamofire.request(.GET, url).responseData { response in
if let xmlToParse = response.data as NSData! {
let xml = SWXMLHash.parse(xmlToParse)
do {
let xmlSpWeight = try xml["fmresultset"]["resultset"]["record"]["field"].withAttr("name", "ТotalWeight")["data"].element!.text! as String
self._shipmentStatus = xmlSpStatus
print(self._shipmentStatus)
} catch let err as NSError {
print(err.debugDescription)
}
}
}
}
}
My Second View Controller
#IBOutlet weak var numberLbl: UILabel!
#IBOutlet weak var weightLbl: UILabel!
#IBOutlet weak var statusLbl: UILabel!
#IBOutlet weak var packageQtyLbl: UILabel!
var shipment: Shipment!
override func viewDidLoad() {
super.viewDidLoad()
shipment.requestXmlInformation { () -> () in
self.updateUi()
print(self.statusLbl.text)
}
}
updateUI function:
func updateUi() {
numberLbl.text = shipment.shipmentNumber
weightLbl.text = shipment.shipmentWeight
statusLbl.text = shipment.shipmentStatus
packageQtyLbl.text = shipment.shipmentPackageQty
}
It prints data in terminal but i think updateUI function does not work.

Make sure that the code in your requestXmlInformation closure is called on the main thread. You shouldn't update the UI in background threads.
shipment.requestXmlInformation { () -> () in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.updateUi()
print(self.statusLbl.text)
})
}
Also, you don't seem to call the complete closure anywhere in your requestXmlInformation method

Related

How to assign a value to a variable in a class from JSON or pass this value to the next function?

Hi everyone!
At the moment, I am taking a course at the Harvard computer science CS50.
My homework is almost ready, but has some incompleteness.
I cannot assign a value from a function to a variable in the class or pass
this value to the next function.
import UIKit
class PokemonViewController: UIViewController {
var url: String!
var name: String!
#IBOutlet var pokemonImage: UIImageView!
#IBOutlet var nameLabel: UILabel!
#IBOutlet var numberLabel: UILabel!
#IBOutlet var type1Label: UILabel!
#IBOutlet var type2Label: UILabel!
#IBOutlet var catchButton: UIButton!
#IBOutlet var descriptionLabel: UILabel!
// MARK: - additional properties
var currentDescURL: String!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadPokemon()
showPokemonDescription()
}
//MARK: - pokemon loading
func loadPokemon() {
guard let pokemonURL = URL(string: url) else { return }
URLSession.shared.dataTask(with: pokemonURL) { (data, _, error) in
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(PokemonResult.self, from: data)
DispatchQueue.main.async {
self.navigationItem.title = self.capitalize(text: result.name)
self.nameLabel.text = self.capitalize(text: result.name)
self.numberLabel.text = String(format: "#%03d", result.id)
for typeEntry in result.types {
if typeEntry.slot == 1 {
self.type1Label.text = typeEntry.type.name
}
else if typeEntry.slot == 2 {
self.type2Label.text = typeEntry.type.name
}
}
// Create Image and Update ImageView
guard let imageURL = URL(string: result.sprites.front_default) else { return }
if let data = try? Data(contentsOf: imageURL) {
self.pokemonImage.image = UIImage(data: data)
}
self.currentDescURL = result.species.url
print(self.currentDescURL)
}
} catch let error { print(error) }
}.resume()
}
// MARK: - Get the URL of a specific Pokémon
func showPokemonDescription() {
guard let pokemonDescriptionURL = URL(string: currentDescURL) else { return }
URLSession.shared.dataTask(with: pokemonDescriptionURL) { (data, _, error) in
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(PokemonDescription.self, from: data)
DispatchQueue.main.async {
// Check and get first pokemon description in English
for index in 0..<result.flavor_text_entries.count {
if result.flavor_text_entries[index].language.name == "en" {
self.descriptionLabel.text = result.flavor_text_entries[index].flavor_text
}
}
}
} catch let error { print(error) }
}.resume()
}
}
The first function loadPokemon() inside itself gets value from JSON and prints the value to the console -> print(self.currentDescURL). Moreover, if you display this value in viewWillAppear, then "nil" will be displayed in the console. I understand that the loadPokemon() function processes the values in the stream that occur at the very end. Perhaps because of this, the variable currentDescURL cannot get the value from the loadPokemon() function and the showPokemonDescription() function cannot use this value since currentDescURL is nil.
I ask you to explain to me what my mistake is and to help finish the assignment.
Move the call for method showPokemonDescription from viewWillAppear to loadPokemon after the currentDescURL property is set.
class PokemonViewController: UIViewController {
//...
var currentDescURL: String!
//...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadPokemon()
getPreferences()
// <- remove the call from here
}
//...
func loadPokemon() {
//...
self.currentDescURL = result.species.url
self.showPokemonDescription() // <- move the call here
}
//...
func showPokemonDescription() {
//...
}
}

TableViewCell pass data to ViewController (Parse.com)?

Tell me what can be a mistake. I have 6 columns, two columns belong to TableViewCell. The other 4 belong to the ViewController. How do I tie the rows correctly so that the match information matches its row in TableViewCell.
import UIKit
import Parse
class DetailTennisViewController: UIViewController {
#IBOutlet weak var imageTennis: UIImageView!
#IBOutlet weak var outClose: UIButton!
#IBOutlet weak var titleTennis: UILabel!
#IBOutlet weak var textTennis: UITextView!
#IBOutlet weak var progTennis: UILabel!
#IBAction func closeOut(_ sender: Any) {
dismiss(animated: false, completion: nil)
}
var tenises : Tennis?
func configureButton() {
outClose.layer.cornerRadius = 0.5 * outClose.bounds.size.width
outClose.clipsToBounds = true
}
override func viewDidLoad() {
super.viewDidLoad()
loadTennis()
configureButton()
}
func loadTennis () {
let qwery = PFQuery(className: "tennis")
qwery.getFirstObjectInBackground() { (object,error) ->Void in
if error == nil {
self.textTennis.text = object!["textTen"] as? String
self.progTennis.text = object!["progTen"] as? String
self.titleTennis.text = object!["titleTen"] as? String
let imageFile = object!["tenImage"] as? PFFile
imageFile?.getDataInBackground() { (data:Data?, error:Error?)->Void in
if error == nil {
if let imageData = data {
self.imageTennis.image = UIImage(data: imageData)
}
}
}
}
}
}
}
How to make the data string Parse.com How to make the data TableViewCell cells corresponded to the description ViewController. In my code, when the cell is opened, the same data is displayed. I uploaded 4 events and information about them. So the information about them opens up the same. How to make the one that is indicated by their "objectId"

Parse Subclass Instance Method EXC_BAD_INSTRUCTION

When subclassing from a parse user object PFUser, I get bad-instruction errors when using my own methods. Do you guys have any thoughts on why this is happening
User.swift
import UIKit
import Parse
class User: PFUser {
// Instance Variables
#NSManaged var photo: PFFile
// Parse Setup
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
// Instance Methods
func fetchPhoto(callback: (image: UIImage) -> Void) {
guard let url = self.photo.url else {
return
}
let request = NSURLRequest(URL: NSURL(string: url)!)
if let image = Globals.imageCache.imageForRequest(request) {
callback(image: image)
return
}
Globals.imageDownloader.downloadImage(URLRequest: request) { response in
if let image: UIImage = response.result.value {
callback(image: image)
Globals.imageCache.addImage(image, forRequest: request)
} else {
print(response)
}
}
}
}
ViewController.swift
import UIKit
class ProfileHeaderController: UIViewController {
#IBOutlet weak var avatarButton: UIButton!
#IBOutlet weak var userLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
private var user = User.current()
func updateHeader() {
self.userLabel.text = self.user.username
self.nameLabel.text = self.user.fullName
self.user.fetchPhoto { (image) -> Void in
self.avatarButton.setImage(image, forState: .Normal)
}
}
}
Yes you need to register the subclass in your Appdelegate or before you call Parse.setApplicationId
its stated in this block of text: https://parse.com/docs/ios/guide#objects-subclasses
example:
Appdelegate.swift
....
User.registerSubclass()
Parse.setApplicationId("appid", clientId:"clientid")

Swift link Image data from Parse array using segues to secondViewController [duplicate]

This question already has answers here:
Swift link Image from Parse array using segues to secondViewController
(2 answers)
Closed 7 years ago.
Here is my code, i am trying to use the "prepareForSegue" function to send an image from tableViewController (firstViewController) to my detailedViewController (secondViewController). I have managed to populate my firstViewController from the parse cloud successfully and I have managed to get my secondViewController Labels to update, but i can not get the imageView to update. I have posted my code below
firstViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let eventDetailVC: EventDetailsVC = segue.destinationViewController as! EventDetailsVC
if let selectedArrayIndex = tableView.indexPathForSelectedRow?.row {
eventDetailVC.detailNameLabel = postedEvents[selectedArrayIndex]
eventDetailVC.detailAddressLabel = postedAddress[selectedArrayIndex]
eventDetailVC.detailCityLabel = postedCity[selectedArrayIndex]
eventDetailVC.detailStateLabel = postedState[selectedArrayIndex]
eventDetailVC.detailStartLabel = postedStart[selectedArrayIndex]
eventDetailVC.detailEndLabel = postedEnd[selectedArrayIndex]
eventDetailVC.detailPriceLabel = postedPrices[selectedArrayIndex]
eventDetailVC.detailDescriptionLabel = postedDescription[selectedArrayIndex]
// The error is here....i think
postedImages[selectedArrayIndex].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
eventDetailVC.detailImageView.image = downloadedImage
}
}
}
}
secondViewController
var detailNameLabel = String()
var detailDescriptionLabel = String()
var detailPriceLabel = String()
var detailStartLabel = String()
var detailEndLabel = String()
var detailAddressLabel = String()
var detailCityLabel = String()
var detailStateLabel = String()
var detailImageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
detailName.text = detailNameLabel
detailDescription.text = detailDescriptionLabel
detailPrice.text = detailPriceLabel
detailStart.text = detailStartLabel
detailEnd.text = detailEndLabel
detailAddress.text = detailAddressLabel
detailCity.text = detailCityLabel
detailState.text = detailStateLabel
// its this line below....i think
detailImage.image = detailImageView.image
}
Please can someone help me figure this out, Im kinda new to this whole thing
I don't see where "detailImage" is defined, so this is assuming it is a property on .
Set a breakpoint inside the "getDataInBackgroundWithBlock" completion block to see if and when it is getting called. If it is getting called add "eventDetailVC.detailImage.image = downloadedImage" to the completion block.
Your problem is that the download of the image completes asynchronously, so by the time it has completed, your view controller has already executed the line detailImage.image = detailImageView.image with a nil image.
Rather than putting all of the code in your viewDidLoad, define didSet observers for your properties that set the appropriate UI elements. This way any time the property is updated it will automatically update the UI element;
Finally, your properties have confusing names, detailAddressLabel is actually a string and the UILabel instance seems to be named detailAddress - this has caused you to define detailImageView as UIImageView instead of a UIImage.
My suggested implementation is (after changing the names of IBOutlets and other properties to make more sense):
class someViewController : UIViewController {
#IBOutlet var detailNameLabel : UILabel! {
didSet {
self.detailNameLabel?.text=self.detailName
}
}
#IBOutlet var detailDescriptionLabel : UILabel! {
didSet {
self.detailDescriptionLabel?.text=self.detailDescription
}
}
#IBOutlet var detailPriceLabel : UILabel! {
didSet {
self.detailPriceLabel?.text=self.detailPrice
}
}
#IBOutlet var detailStartLabel : UILabel! {
didSet {
self.detailStartLabel?.text=self.detailStart
}
}
#IBOutlet var detailEndLabel : UILabel! {
didSet {
self.detailEndLabel?.text=self.detailEnd
}
}
#IBOutlet var detailAddressLabel : UILabel! {
didSet {
self.detailAddressLabel?.text=self.detailAddress
}
}
#IBOutlet var detailCityLabel : UILabel! {
didSet {
self.detailCityLabel?.text=self.detailCity
}
}
#IBOutlet var detailImageView : UIImageView! {
didSet {
self.detailImageView?.image=self.detailImage
}
}
var detailName : String = "" {
didSet {
self.detailNameLabel?.text=self.detailName
}
}
var detailDescription : String = "" {
didSet {
self.detailDescriptionLabel?.text=self.detailDescription
}
}
var detailPrice : String = "" {
didSet {
self.detailPriceLabel?.text=self.detailPrice
}
}
var detailStart : String = "" {
didSet {
self.detailStartLabel?.text=self.detailStart
}
}
var detailEnd : String = "" {
didSet {
self.detailEndLabel?.text=self.detailEnd
}
}
var detailAddress: String = "" {
didSet {
self.detailAddressLabel?.text=self.detailAddress
}
}
var detailCity : String = "" {
didSet {
self.detailCityLabel?.text=self.detailCity
}
}
var detailImage : UIImage? {
didSet {
self.detailImageView?.image=self.detailImage
}
}
}

.Type does not have a member named "x" when trying to get variable value in Struct(Swift)

I am currently working on an app and I am having an issue. When the user login the webservice, if the login is successful the server responds with JSON, where we use the "firstName" and "SecondName" to then create our "User" which is a struct defined in another file called User.swift . Then, what I want to do is user the "firstName" that has been given to the "User struct" as a UILabel in my homepageview that comes after a successful login. when I try to give my label User.prenom(which is firstName in french) I get the error: User.type does not have a member called...
Here is my code:
the client file where the Login Method is defined:
import Foundation
import Alamofire
import SwiftyJSON
private let _instance = Client()
class Client {
// Router is used to do a request to the server.
private enum Router: URLRequestConvertible {
private static let baseURL = "https://mobile.uqam.ca/portail_etudiant/"
// stores the authentication token.
static var code_perm: String?
static var nip:String?
// Login request.
case Login(String, String)
// URLRequestConvertible protocol.
var URLRequest: NSURLRequest {
// Returns the path, http method and parameters for the request.
var (path: String, method: Alamofire.Method, parameters: [String: AnyObject]) = {
switch self {
case .Login (let code_perm, let nip):
let params: [String: AnyObject] = [
"code_perm": code_perm,
"nip": nip,
]
return ("proxy_dossier_etud.php", .POST, params)
}
}()
// Setup the URLRequest.
let url = NSURL(string: Router.baseURL)
let urlRequest = NSMutableURLRequest(URL: url!.URLByAppendingPathComponent(path))
urlRequest.HTTPMethod = method.rawValue
if let code_perm = Router.code_perm {
if let nip = Router.nip{
parameters["nip"] = nip
parameters["code_perm"] = code_perm
}
}
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(urlRequest, parameters: parameters).0
}
}
// Singleton
class var sharedInstance: Client {
return _instance
}
private init() {}
// Login logs in the user with his email and password.
func login(code_perm:String, nip:String, callback:(LoginResponse?) -> Void) {
Alamofire.request(Router.Login(code_perm, nip)).responseJSON { (_, _, data, error) in
if(error != nil) {
callback(nil)
return
}
var json = JSON(data!)
let prenom = json["socio"]["prenom"].stringValue
let nom = json["socio"]["nom"].stringValue
Router.code_perm = code_perm
Router.nip = nip
callback(LoginResponse(
user: User(prenom: prenom,nom: nom)
))
}
}
}
the loginViewController where the login function is called
import UIKit
class LoginViewController: UIViewController {
#IBOutlet weak var LoginScreenImage: UIImageView!
#IBOutlet weak var codeTextField: UITextField!
#IBOutlet weak var nipTextField: UITextField!
#IBOutlet weak var loadingLogin: UIActivityIndicatorView!
let client = Client.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
LoginScreenImage.image = UIImage(named: "UQAMLOGO")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func connect() {
let code_perm = codeTextField.text
let nip = nipTextField.text
self.loadingLogin.startAnimating()
if code_perm != "" && nip != "" {
client.login(code_perm, nip: nip, callback: { (response) in
if let response = response {
self.loadingLogin.stopAnimating()
let homeViewController = self.storyboard!.instantiateViewControllerWithIdentifier("HomeViewController") as HomeViewController
self.showViewController(homeViewController, sender: self)
} else {
self.loadingLogin.stopAnimating()
let badLogin = UIAlertController(title: "Échec de connexion", message: "La combinaison du code permanent et du nip n'est pas bonne", preferredStyle: .Alert)
let reessayer = UIAlertAction(title: "Réessayer", style: .Default, handler: { (reessayer) -> Void in
self.dismissViewControllerAnimated(true , completion: nil)
})
badLogin.addAction(reessayer)
self.presentViewController(badLogin, animated: true, completion: nil)
}
})
}
}
}
the User.swift while where the user struct is
import Foundation
struct User {
var prenom :String
var nom: String
}
struct LoginResponse {
var user: User
}
and finally the HomePageViewController where I try to give the value to my label:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var schedule: UIImageView!
#IBOutlet weak var courses: UIImageView!
#IBOutlet weak var email: UIImageView!
#IBOutlet weak var grades: UIImageView!
#IBOutlet weak var bienvenueLabel: UILabel!
let client = Client.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
schedule.image = UIImage(named:"schedule")
courses.image = UIImage(named: "courses")
email.image = UIImage(named:"mail")
grades.image = UIImage(named:"grades")
bienvenueLabel.text = User.prenom
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Thanks everyone for the help and have a great day/night
Charles
You are accessing the class instead of an instance. Instead, you should pass the response instance to your HomeViewController:
class HomeViewController : .. {
// ...
var loginResponse : LoginResponse
// ...
override func viewDidLoad() {
// ...
bienvenueLabel.text = loginResponse.user.prenom
}
}
// ...
client.login(code_perm, nip: nip, callback: { (response) in
if let loginResponse = response as LoginResponse {
self.loadingLogin.stopAnimating()
let homeViewController = self.storyboard!.instantiateViewControllerWithIdentifier("HomeViewController") as HomeViewController
homeViewController.loginResponse = loginResponse
// assign your instance ^^^^^^^^^^^^^^^^^^^^^^^^
self.showViewController(homeViewController, sender: self)
}
You are accessing the class instead of an instance. Instead, you should pass the response instance to your HomeViewController:
class HomeViewController : .. {
// ...
var loginResponse : LoginResponse
// ...
override func viewDidLoad() {
// ...
bienvenueLabel.text = loginResponse.user.prenom
}
}
// ...
client.login(code_perm, nip: nip, callback: { (response) in
if let loginResponse = response as LoginResponse {
self.loadingLogin.stopAnimating()
let homeViewController = self.storyboard!.instantiateViewControllerWithIdentifier("HomeViewController") as HomeViewController
homeViewController.loginResponse = loginResponse
// assign your instance ^^^^^^^^^^^^^^^^^^^^^^^^
self.showViewController(homeViewController, sender: self)
}
This really isn't very good structure, but it should at least answer your question.

Resources