Swift : Delegate is nil - ios

I've tried to build a delegate design pattern. I have a simple delegate in WeatherManager , but it's always nil.
I've tried to add weatherManager.delegate = self in override func viewDidLoad() of WeatherViewController.
However, I have another Protocol and delegate, which works fine.
I'm using the API to obtain geographic coordinates from city names, and the URL of the API to get information about the weather is created and executed.
WeatherManager
import Foundation
// delegate design parttern
protocol WeatherManagerDelegate:AnyObject {
func didUpdateWeather(inputWeatherModel: WeatherModel)
}
class WeatherManager {
// delegate
weak var delegate: WeatherManagerDelegate?
// fetchCoordinate
func fetchWeather(urlString: String) {
performRequest(inputURLString: urlString)
}
// performRequest
func performRequest(inputURLString: String) {
// 1. Create URL
if let url = URL(string: inputURLString) {
// 2. Create URLSession
let session = URLSession(configuration: .default)
// 3. Give the URLSession a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
if let weather = self.parseJSON(JSONobject: safeData) {
// ---------------- problem here -------------------
self.delegate?.didUpdateWeather(inputWeatherModel: weather)
}
}
}
// 4. Start the task
task.resume()
}
}
// parse JSON object
func parseJSON(JSONobject: Data) -> WeatherModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(WeatherData.self, from: JSONobject)
let id = decodedData.weather[0].id
let temp = decodedData.main.temp
let name = decodedData.name
let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
return weather
} catch {
print(error)
return nil
}
}
}
WeatherModel
import Foundation
struct WeatherModel {
let conditionId: Int
let cityName: String
let temperature: Double
var temperatureString: String {
return String(format: "%.1f", temperature)
}
var conditionName: String {
switch conditionId {
case 200...232:
return "cloud.bolt"
case 300...321:
return "cloud.drizzle"
case 500...531:
return "cloud.rain"
case 600...622:
return "cloud.snow"
case 701...781:
return "cloud.fog"
case 800:
return "sun.max"
case 801...804:
return "cloud.bolt"
default:
return "cloud"
}
}
}
WeatherViewController
import UIKit
class WeatherViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {
#IBOutlet weak var conditionImageView: UIImageView!
#IBOutlet weak var temperatureLabel: UILabel!
#IBOutlet weak var searchTextField: UITextField!
#IBOutlet weak var cityLabel: UILabel!
let weatherManager = WeatherManager()
var coordinateManager = CoordinateManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
searchTextField.delegate = self
searchTextField.keyboardType = .asciiCapable
weatherManager.delegate = self
}
#IBAction func searchPressed(_ sender: UIButton) {
searchTextField.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
searchTextField.endEditing(true)
return true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if textField.text != "" {
return true
} else {
textField.placeholder = "Type Something"
return false
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if let city = searchTextField.text {
coordinateManager.fetchCoordinate(cityName: city)
}
searchTextField.text = ""
}
func didUpdateWeather(inputWeatherModel: WeatherModel) {
print(inputWeatherModel.temperature)
}
}
CoordinateManager
import Foundation
struct CoordinateManager {
// WeatherManager
let weathetManager = WeatherManager()
// geographical coordinates(lat, lon) 地理座標
let coordinateURL = "https://api.openweathermap.org/geo/1.0/direct?limit=1&appid=c8e60c6317c653a1789294c00f54ae19"
// fetchCoordinate
func fetchCoordinate(cityName: String) {
let urlString = "\(coordinateURL)&q=\(cityName)"
performRequest(inputURLString: urlString)
}
// transformURLString
func transformURLString(_ string: String) -> URLComponents? {
guard let urlPath = string.components(separatedBy: "?").first else {
return nil
}
var components = URLComponents(string: urlPath)
if let queryString = string.components(separatedBy: "?").last {
components?.queryItems = []
let queryItems = queryString.components(separatedBy: "&")
for queryItem in queryItems {
guard let itemName = queryItem.components(separatedBy: "=").first,
let itemValue = queryItem.components(separatedBy: "=").last else {
continue
}
components?.queryItems?.append(URLQueryItem(name: itemName, value: itemValue))
}
}
return components!
}
// performRequest
func performRequest(inputURLString: String) {
// 1. Create URL
let components = transformURLString(inputURLString)
if let url = components?.url {
// 2. Create URLSession
let session = URLSession(configuration: .default)
// 3. Give the URLSession a task
let task = session.dataTask(with: url) {(data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(JSONobject: safeData)
}
}
// 4. Start the task
task.resume()
}
}
// parseJSON
func parseJSON(JSONobject: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([CoordinateData].self, from: JSONobject)
let name = decodedData[0].name
let lat = decodedData[0].lat
let lon = decodedData[0].lon
let coordinateModel = CoordinateModel(name: name, lat: lat, lon: lon)
let weatherURL = coordinateModel.urlString
// ------- WeatherManager fetchWeather ---------
weathetManager.fetchWeather(urlString: weatherURL)
} catch {
print(error)
}
}
}
CoordinateModel
import Foundation
struct CoordinateModel {
let name: String
let lat: Double
let lon: Double
// wheather 地理座標を元に検索した天気の情報
let weatherstr = "https://api.openweathermap.org/data/2.5/weather?"
let str = "units=metric&appid=c8e60c6317c653a1789294c00f54ae19#"
// computedproperty
var urlString: String {
return "\(weatherstr)&lat=\(lat)&lon=\(lon)&\(str)"
}
}

You create a weatherManager in the WeatherViewController , and in the viewDidLoad method you set the WeatherViewController as a delegate to the weatherManager instance you created here. In the CoordinateManager class, you create a new weatherManager, and don't set a delegate for it.
2 solutions, use what is most convenient for you:
Create an initializer for the CoordinateManager, to pass it the WeatherManager that you have created and that you are listening to
// CoordinateManager class
let weatherManager: WeatherManager
init(weatherManager: WeatherManager) {
self.weatherManager = weatherManager
}
// WeatherViewController class
let weatherManager = WeatherManager()
var coordinateManager = CoordinateManager(weatherManager: weatherManager)
You can directly set WeatherViewController as a delegate to the desired weatherManager instance.
// WeatherViewController class
override func viewDidLoad() {
super.viewDidLoad()
searchTextField.delegate = self
searchTextField.keyboardType = .asciiCapable
// Instead weatherManager.delegate = self
coordinateManager.weatherManager.delegate = self
}

Related

Swift I can't move data with delegate

I'm trying to make a quiz application using the API, I can import the
data into the quizManager file, but I can't transfer the data to the
viewController, When I try to print in quizManger, I can print, but in viewController does not.
how do I move data to viewController?
QuizManager
import Foundation
protocol quizManagerDelegate {
func didUpdateQuiz(_ Quizmanager: QuizManager ,quiz: QuizModel)
}
struct QuizManager {
var delegate: quizManagerDelegate?
func performRequest(){
let urlString = "https://opentdb.com/api.php?amount=1&type=multiple"
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
return
}
if let safeData = data{
if let quiz = self.parseJSON(quizdata: safeData){
delegate?.didUpdateQuiz(self, quiz: quiz)
}
}
}
task.resume()
}
}
func handle(data: Data?, response: URLResponse?, error: Error?) -> Void {
}
func parseJSON(quizdata: Data) -> QuizModel? {
let decoder = JSONDecoder()
do{
let decodedData = try decoder.decode(Welcome.self, from: quizdata)
let correct = decodedData.results?[0].correct_answer ?? "error"
let quest = decodedData.results?[0].question ?? "error"
let incorrect = decodedData.results?[0].incorrect_answers ?? ["error"]
let question = QuizModel(correctAnswer: correct, question: quest, falseAnswer: incorrect)
// print(question.correctAnswer)
// print(question.question)
// print(question.falseAnswer)
return question
} catch {
print(error)
return nil
}
}
}
QuizData
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let results: [Result]?
}
// MARK: - Result
struct Result: Codable {
let category: String?
let question, correct_answer: String?
let incorrect_answers: [String]?
}
QuizModel
import Foundation
struct QuizModel {
let correctAnswer : String
let question : String
let falseAnswer : [String]
}
ViewController
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var ChoiceButton4: UIButton!
#IBOutlet weak var ChoiceButton3: UIButton!
#IBOutlet weak var ChoiceButton2: UIButton!
#IBOutlet weak var ChoiceButton1: UIButton!
#IBOutlet weak var QuestionTextView: UITextView!
var quizMangager = QuizManager()
override func viewDidLoad() {
super.viewDidLoad()
QuestionTextView.layer.cornerRadius = 15
quizMangager.performRequest()
self.quizMangager.delegate = self
}
}
extension ViewController : quizManagerDelegate{
func didUpdateQuiz(_ Quizmanager: QuizManager, quiz: QuizModel) {
DispatchQueue.main.async {
print("***")
print(quiz.correctAnswer)
}
}
}
The problem is in viewDidLoad that lines
quizMangager.performRequest()
self.quizMangager.delegate = self
You call performRequest before to delegate it so when performRequest is called var delegate: quizManagerDelegate? is nil . Just call delegate before call function
override func viewDidLoad() {
super.viewDidLoad()
...
quizMangager.delegate = self
quizMangager.performRequest()
...
}
You have a slight mistake in your code. You just need to confirm the delegate first and then call its function .

How to get the registered user Notification API data come for guest user too?

Current Scenario?:
Th registered user of the book app gets the notifications such as 'books available for download', q&A portal answers, live streaming link.
What is the goal?:
To get the notifications for guest where it can show the available books for download, and other notification that comes for registered user.
What is the issue/errors?:
When user clicks on the notification button , nothing comes on the screen. Everything blank and no notification is shown. It show the below error::
What i tried?:
When we call API, the token is generated from the backend. This token is then used to get the access. I tried to copy the API method to the home-screen that is used from 'signUpVC'(sign up view controller) to get the token bit showing above error.
Admin user notification looks as below:
Guest user (notification from our android app)
Code: for SignUp
import UIKit
import PKHUD
import SDWebImage
class SignupVC: ThemeController {
// MARK: - Outlets
#IBOutlet weak var imgProfile: TappableImageView!
#IBOutlet weak var passwordView: UIStackView!
#IBOutlet weak var lblRegister: UILabel!
var isFromUpdateProfile = Bool()
// -----------------------------------------------------------------------------------------------
// MARK: - Class Properties
#IBOutlet weak var txtFirstName: UITextField!
#IBOutlet weak var txtLastName: UITextField!
#IBOutlet weak var txtEmail: UITextField!
#IBOutlet weak var txtCity: UITextField!
#IBOutlet weak var txtPassword: UITextField!
// -----------------------------------------------------------------------------------------------
// MARK: - Memory Management Functions
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
deinit {
}
// -----------------------------------------------------------------------------------------------
// MARK: - Class Functions
private func errorsInTextFields() -> String? {
self.view.endEditing(true)
guard !txtFirstName.isEmpty else { return UserMessages.kBlankFirstName }
guard txtFirstName.hasValid(.alphabetWithSpace) else { return UserMessages.kValidFirstName }
guard !txtLastName.isEmpty else { return UserMessages.kBlankLastName }
guard txtLastName.hasValid(.alphabetWithSpace) else { return UserMessages.kValidLastName }
guard !txtEmail.isEmpty else { return UserMessages.kBlankEmail }
guard txtEmail.hasValid(.email) else { return UserMessages.kValidEmail }
guard !txtCity.isEmpty else { return UserMessages.kBlankCity }
guard txtCity.hasValid(.alphabetWithSpace) else { return UserMessages.kValidCity }
guard !txtPassword.isEmpty else { return UserMessages.kBlankPassword }
//guard txtPassword.hasValid(.password) else { return UserMessages.kValidPassword }
// No Errors
return nil
}
private func errorsInEditProfileTextFields() -> String? {
self.view.endEditing(true)
guard !txtFirstName.isEmpty else { return UserMessages.kBlankFirstName }
guard txtFirstName.hasValid(.alphabetWithSpace) else { return UserMessages.kValidFirstName }
guard !txtLastName.isEmpty else { return UserMessages.kBlankLastName }
guard txtLastName.hasValid(.alphabetWithSpace) else { return UserMessages.kValidLastName }
guard !txtEmail.isEmpty else { return UserMessages.kBlankEmail }
guard txtEmail.hasValid(.email) else { return UserMessages.kValidEmail }
guard !txtCity.isEmpty else { return UserMessages.kBlankCity }
guard txtCity.hasValid(.alphabetWithSpace) else { return UserMessages.kValidCity }
// No Errors
return nil
}
// -----------------------------------------------------------------------------------------------
// MARK: - Action Functions
#IBAction func btnRegisterAction(_ sender: RoundButton) {
if let _ = User.current?.accessToken{
//TextField Verification
if let error = errorsInEditProfileTextFields() {
SnackBar.show(error)
return
}
//API Calling
self.apiEditProfileCall()
}else{
//TextField Verification
if let error = errorsInTextFields() {
SnackBar.show(error)
return
}
//API Calling
apiRegisterDeviceCall()
}
}
// -----------------------------------------------------------------------------------------------
// MARK: - Web Service Functions
private func apiRegisterDeviceCall() {
HUD.show(.progress)
var deviceToken:String = UserDefaults.standard.string(forKey: "DeviceToken") ?? "empty"
let parameters: [String: Any] = [
"vDeviceUniqueId" : DeviceManager.deviceUniqueId,
"txDeviceToken" : deviceToken,
"tDeviceOs" : DeviceManager.deviceOS,
"vDeviceName" : DeviceManager.modelName,
"vResolution" : DeviceManager.resolution,
"vOsVersion" : DeviceManager.osVersion,
]
print(parameters)
APIManager.shared.makeRequest(method: .registerDevice, parameters: parameters, withLoader: false) { (response, error) in
print(response)
if let accessToken = response["data"]["access_token"].string {
UserDefaults.standard.setValue(accessToken, forKey: "AccessToken")
self.apiRegisterCall()
} else {
HUD.hide()
SnackBar.show("Something went wrong")
}
}
}
private func apiRegisterCall() {
let parameters: [String: Any] = [
"vFirstName" : txtFirstName.trimmedText,
"vLastName" : txtLastName.trimmedText,
"vEmail" : txtEmail.trimmedText,
"vPassword" : txtPassword.trimmedText,
"vCityName" : txtCity.trimmedText,
]
var images: [String: UIImage] = [:]
if let image = imgProfile.image {
images["txProfileImageUrl"] = image
}
APIManager.shared.makeRequest(method: .registerUser, parameters: parameters, imageParameters: images, withLoader: false) { (response, error) in
HUD.hide()
if response["data"].exists() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.popViewController()
}
// Alert.showWith("User Registered", message: "Please check your email inbox for varification email", positiveTitle: "Ok", shouldResignOnTouchOutside: false) { isOk in
// if isOk {
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
// self.popViewController()
// }
// }
// }
} else {
SnackBar.show(response["message"].stringValue)
}
}
}
private func apiEditProfileCall() {
HUD.show(.progress)
let parameters: [String: Any] = [
"vFirstName" : txtFirstName.trimmedText,
"vLastName" : txtLastName.trimmedText,
"vEmail" : txtEmail.trimmedText,
"vCityName" : txtCity.trimmedText,
]
var images: [String: UIImage] = [:]
if let image = imgProfile.image {
images["txProfileImageUrl"] = image
}
APIManager.shared.makeRequest(method: .editProfile, parameters: parameters, imageParameters: images, withLoader: false) { (response, error) in
HUD.hide()
if response["data"].exists(){
if let accessToken = User.current?.accessToken{
var updateUser = User(withJSON: response["data"])
updateUser.accessToken = accessToken
User.current = updateUser
SnackBar.show("Profile successfully updated.")
self.navigationController?.popViewControllers(viewsToPop: 2)
}
}else{
SnackBar.show(response["message"].stringValue)
}
}
}
// -----------------------------------------------------------------------------------------------
// MARK: - Life Cycle Functions
override func viewDidLoad() {
super.viewDidLoad()
if let _ = User.current?.accessToken{
self.passwordView.isHidden = true
self.lblRegister.text = "Update"
self.title = "Edit Profile"
self.imgProfile.sd_imageIndicator = SDWebImageActivityIndicator.gray
self.imgProfile.sd_setImage(with: URL(string: User.current!.profileImage), placeholderImage: nil)
self.txtFirstName.text = User.current!.firstName
self.txtLastName.text = User.current!.lastName
self.txtEmail.text = User.current!.email
self.txtCity.text = User.current!.cityName
}
}
// -----------------------------------------------------------------------------------------------
}

Adding a local variable to downloaded MySQL Data with Models

I am using MySQL and PHP to download a restaurants menu but the user of the app should be able to add a certain amount to which item from the menu they want. Currently I am using a stepper to indicate the amount and adding that amount to a UserDefaults key which gets called when the menu is downloaded again.
This makes me have to download the menu again when I go to another viewController which sums up the order but I can't seem to filter out only them items which do have an amount.
What is a better way to add that amount to the downloaded data and how can I filter these items in my cart ViewController to only show and use the items which have an amount.
My current downloadModel, MenuModel, cellViewController (for the menu tableview) look like this:
MenuDownload.swift:
import UIKit
protocol MenuDownloadProtocol: class {
func productsDownloaded(products: NSArray)
}
class MenuDownload: NSObject {
//properties
weak var delegate: MenuDownloadProtocol!
func downloadProducts() {
let urlPath = "http://server.com/download.php" // Fake URL obviously
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Menu downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let products = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let restomenu = MenuModel()
//the following insures none of the JsonElement values are nil through optional binding
if let product = jsonElement["product"] as? String,
let price = jsonElement["price"] as? String,
let info = jsonElement["info"] as? String,
let imageurl = jsonElement["imageurl"] as? String
{
let productandprice = product + " " + "€" + price
let quantityy = UserDefaults.standard.object(forKey: productandprice) as? String
restomenu.product = product
restomenu.price = price
restomenu.info = info
restomenu.imageurl = imageurl
restomenu.quantity = quantityy
}
products.add(restomenu)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.productsDownloaded(products: products)
})
}
}
extension String {
func chopPrefix(_ count: Int = 1) -> String {
return substring(from: index(startIndex, offsetBy: count))
}
func chopSuffix(_ count: Int = 1) -> String {
return substring(to: index(endIndex, offsetBy: -count))
}
}
MenuModel.swift:
import UIKit
class MenuModel: NSObject {
//properties
var product: String?
var price: String?
var info: String?
var imageurl: String?
var quantity: String?
//empty constructor
override init()
{
}
init(product: String, price: String, info: String, imageurl: String, quantity: String) {
self.product = product
self.price = price
self.info = info
self.imageurl = imageurl
self.quantity = quantity
}
//prints object's current state
override var description: String {
return "product: \(String(describing: product)), price: \(String(describing: price)), info: \(String(describing: info)), imageurl: \(String(describing: imageurl)), quantity: \(String(describing: quantity))"
}
}
tableViewCell.swift:
import UIKit
class productTableViewCell: UITableViewCell {
#IBOutlet weak var productLabel: UILabel!
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var cellView: UIView!
#IBOutlet weak var orderCount: UILabel!
#IBOutlet weak var stepper: UIStepper!
var amount: String?
#IBAction func stepperValueChanged(_ sender: UIStepper) {
amount = Int(sender.value).description
orderCount.text = amount
// let defaultkey = String(productLabel.text!)
UserDefaults.standard.setValue(amount, forKey: productLabel.text!)
if amount == "0"
{
orderCount.isHidden = true
UserDefaults.standard.removeObject(forKey: productLabel.text!)
}
else
{
orderCount.isHidden = false
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
EDIT: after trying filtering options and many different ways I still haven't found how to fix this. I think I'm overthinking it too much.

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() {
//...
}
}

How to store my first textfield to not ask for the second time in swift

I created a UITextField where, when the user writes a link and enters, my textfield disappears and my webView appears.
What I am trying to do is, when the user writes the first time their link, the textfield saves that link and when the user opens again the app, the web view opens directly from the last link that the user wrote in the textfield. Basically the stored link should run the second time.
Here is all my code:
import UIKit
import Foundation
let urlKey = "User URL"
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.webView.hidden = true
self.textField.addTarget(self, action: #selector(ViewController.textFieldDidUpdate(_:)), forControlEvents: UIControlEvents.EditingChanged)
if doesURLExist() {
self.textField.text = getURL()
}
}
// Text Field Delegate
func textFieldDidUpdate(textField: UITextField)
{
// Remove Spaces
textField.text = textField.text!.stringByReplacingOccurrencesOfString(" ", withString: "", options: [], range: nil)
// Validate URL
NSURL.validateUrl(textField.text, completion: { (success, urlString, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if (success)
{
self.saveURL(urlString!)
self.webView.hidden = false
self.textField.hidden = true
let request = NSURLRequest(URL: NSURL(string: urlString!)!)
self.webView.loadRequest(request)
}
else
{
self.webView.stopLoading()
self.webView.hidden = true
}
})
})
}
#IBAction func dismissKeyboard(sender: AnyObject) {
self.resignFirstResponder()
self.view.endEditing(true)
}
func saveURL(urlString: String) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(urlString, forKey: urlKey)
}
func getURL() -> String {
let defaults = NSUserDefaults.standardUserDefaults()
let urlString = defaults.objectForKey(urlKey) as! String
return urlString
}
func doesURLExist() -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
guard let _ = defaults.objectForKey(urlKey) where defaults.objectForKey(urlKey) is String else {
return false
}
return true
}
}
Here is my project in GitHub: https://github.com/anappleapp/NSURLvalidation
You'll want to check if the url exists first by calling doesURLExist, if it does, you opt out of presenting that textfield. If it does not exist, call saveURL. NSUserDefaults provides a simple means to store lightweight data.
let urlKey = "User URL"
func saveURL(urlString: String) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(urlString, forKey: urlKey)
}
func getURL() -> String {
let defaults = NSUserDefaults.standardUserDefaults()
let urlString = defaults.objectForKey(urlKey) as! String
return urlString
}
func doesURLExist() -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
guard let _ = defaults.objectForKey(urlKey) where defaults.objectForKey(urlKey) is String else {
return false
}
return true
}
So your class should look something like:
import UIKit
import Foundation
let urlKey = "User URL"
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.webView.hidden = true
self.textField.addTarget(self, action: #selector(ViewController.textFieldDidUpdate(_:)), forControlEvents: UIControlEvents.EditingChanged)
if(doesURLExist) {
self.textField.text = getURL()
}
// Demo UI Settings
}
}
// Text Field Delegate
func textFieldDidUpdate(textField: UITextField)
{
// Remove Spaces
textField.text = textField.text!.stringByReplacingOccurrencesOfString(" ", withString: "", options: [], range: nil)
// Validate URL
NSURL.validateUrl(textField.text, completion: { (success, urlString, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if (success) {
self.saveURL(urlString)
self.webView.hidden = false
self.textField.hidden = true
let request = NSURLRequest(URL: NSURL(string: urlString!)!)
self.webView.loadRequest(request)
} else {
self.webView.stopLoading()
self.webView.hidden = true
}
})
})
}
Don't forget to add the original functions to your class.
You should save the entered string to user defaults. When your app opens you should check user defaults to see if there's already a saved string.
Swift 2 code to save your URL to user defaults:
NSUserDefaults.standardUserDefaults().setObject(urlString!, forKey: "EnteredURLString")
Swift 2 code to check whether there's a saved URL string:
if let urlString = NSUserDefaults.standardUserDefaults().stringForKey("EnteredURLString") {
}

Resources