Swift 3 - Declared variable giving me 'unresolved identifier error' - swift

Im trying to use the securityCode variable in an if statement but its saying that its an 'unresolved identifier', any idea why?
heres my code:
func loginAction (sender: UIButton!){
guard let url = URL(string: "myurl") else{ return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
print(data)
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let securityCode = parseJSON["security"] as? Bool
print("security code bool: \(String(describing: securityCode))")
}
} catch {
print(error)
}
}
}.resume()
if securityCode! == true {
let layout = UICollectionViewFlowLayout()
let mainScreen = MainController(collectionViewLayout: layout)
present(mainScreen, animated: true, completion: nil)
}
}

You need to read about scopes in Swift.
securityCode was declared inside this if statement:
if let parseJSON = json {
let securityCode = parseJSON["security"] as? Bool
print("security code bool: \(String(describing: securityCode))")
}
So, only the code inside the scope of this if statement will be aware of securityCode.
If you want the code after this if statement to be aware of securityCode you need to make its declaration outside of that scope, and this can be achieved like this:
var securityCode: Bool?
if let parseJSON = json {
securityCode = parseJSON["security"] as? Bool
print("security code bool: \(String(describing: securityCode))")
}

if securityCode! == true {
let layout = UICollectionViewFlowLayout()
let mainScreen = MainController(collectionViewLayout: layout)
present(mainScreen, animated: true, completion: nil)
}
This is out of scope.
To make it work, you have to embed the function into the same scope. For example,
if let parseJSON = json {
let securityCode = parseJSON["security"] as? Bool
print("security code bool: \(String(describing: securityCode))")
if let securityCode = securityCode{
if securityCode == true {
let layout = UICollectionViewFlowLayout()
let mainScreen = MainController(collectionViewLayout: layout)
self.present(mainScreen, animated: true, completion: nil)
}
}
}
Or declare the variable outside session.

Related

How to add to an NSSet using Core Data in Swift 5

So I'm practicing a little more with core data after finishing a course. So I am still a little new to it. So I Have 3 entities named Pokemon, Type & Ability. So a Pokemon can have many types like Fire,Water,Flying and so on. Type can also have multiple Pokemon that are Fire,Water,Flying and so on. Same goes for the Ability, so I made a many-to-many relationship. Here is how it looks like.
I am parsing some JSON form an api and trying to save it into core data. Now here is where I am having a bit of trouble. This is how my code looks and it just basically parse the JSON.
struct Service {
static let shared = Service()
func downloadPokemonsFromServer(completion: #escaping ()->()) {
let urlString = "https://pokeapi.co/api/v2/pokemon?limit=9"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to fetch pokemon", err)
}
guard let data = data else { return }
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let pokemonJSON = try decoder.decode(PokemonsJSON.self, from: data)
pokemonJSON.pokemons.forEach { (JSONPokemon) in
let pokemon = Pokemon(context: privateContext)
pokemon.name = JSONPokemon.name
pokemon.url = JSONPokemon.detailUrl
//Would want to set pokemon types here but
//When i call fetchMoreDetails(pokemon:,urlString:,completion:)
//The pokemon is always nil inside fetchMoreDetails
}
try privateContext.save()
try privateContext.parent?.save()
completion()
} catch let err {
print("Unable to decode PokemonJSON. Error: ",err)
completion()
}
}.resume()
}
func fetchMoreDetails(pokemon: Pokemon, urlString: String, completion: #escaping ()->()) {
guard let url = URL(string: urlString) else { return }
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print("Unable to get more details for pokemon", err)
}
guard let data = data else { return }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let pokemonDetailJSON = try decoder.decode(PokemonDetailJSON.self, from: data)
pokemonDetailJSON.types.forEach { (nestedType) in
let type = Type(context: privateContext)
type.name = nestedType.type.name
//How do I add type to pokemon.types this does work
//pokemon.types?.adding(type)
}
try privateContext.save()
try privateContext.parent?.save()
completion()
} catch let err {
print("Unable to decode pokemon more details", err)
completion()
}
}.resume()
}
}
I am able to parse everything fine and all but I just can't seem to add a new type to pokemons.types. I have look on stack overflow but most of the solutions seem to be in Objective C.
This is how my ViewController looks like and I am also using a NSFetchResultController.
class PokemonTableVC: UITableViewController {
lazy var pokemonController: NSFetchedResultsController<Pokemon> = {
let context = CoreDataManager.shared.persistentContainer.viewContext
let request: NSFetchRequest<Pokemon> = Pokemon.fetchRequest()
let nameSort = NSSortDescriptor(key: "name", ascending: true)
request.sortDescriptors = [nameSort]
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
return controller
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
tableView.refreshControl = refreshControl
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Delete", style: .done, target: self, action: #selector(handleDelete))
try? pokemonController.performFetch()
}
#objc func handleDelete() {
print("Deleting")
let context = CoreDataManager.shared.persistentContainer.viewContext
guard let pokemons = pokemonController.fetchedObjects else { return }
pokemons.forEach { (pokemon) in
context.delete(pokemon)
}
do {
try context.save()
} catch let err {
print("Unable to save data", err)
}
}
#objc func handleRefresh() {
print("DDDDD")
Service.shared.downloadPokemonsFromServer {
self.pokemonController.fetchedObjects?.forEach({ (pokemon) in
print(pokemon.name)
Service.shared.fetchMoreDetails(pokemon: pokemon, urlString: pokemon.url ?? "") {
print(pokemon.abilities?.count)
}
})
}
tableView.refreshControl?.endRefreshing()
}
}
I can provide my other structs if needed. But basically I am trying to add a type to pokemon.types would also like to add fetchMoreDetails when I fetch pokemons where I put the comment at. Would
really appreciate any feedback.
When you add a relationship to an entity Xcode creates methods for getting and setting values for that relationship using a pre-defined naming standard. So you should have some methods in your Pokemon class for setting Type instances (and code completion should be able to help here):
addToTypes(value:) // single object
addToTypes(values:) //set of objects
So in your code it should be
pokemon.addToTypes(value: type)
You also have the same methods on Type for the opposite direction

Return response as object in swift

I have a function that connects to an API to retrieve data. The API takes two parameters accessCode (provided by user in a text box) and then UDID (UDID of their device). I can parse the data from within the function, but only locally. I need to store the values that are returned but am unsure on how to return them properly. Essentially I need this to return the json object as a dictionary (I think...) so it can be parsed outside of the async task. I've read through the swift documentation and that's where I found out how to do the requests, but I can't find a way to store the returned values in memory for access outside of the function.
func getResponse(accessCode:String, UDID:String, _ completion: #escaping (NSDictionary) -> ()) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult as? NSDictionary
print(results)
completion(results!)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
First of all don't use NSDictionary in Swift, use native [String:Any] and declare the type as optional to return nil if an error occurs.
And never use .mutableContainers in Swift, the option is useless.
func getResponse(accessCode:String, UDID:String, completion: #escaping ([String:Any]?) -> Void)) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error else {
print(error)
completion(nil)
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
print(jsonResult)
completion(jsonResult)
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
task.resume()
}
Your mistake is that you don't consider the closure, you have to execute the entire code inside the completion handler
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { [weak self] result in
if let accessCodeFound = result?["Found"] as? Bool {
print("Value of Found during function:")
//If access code is valid, go to License view
print(accessCodeFound)
if accessCodeFound {
//Load License View
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self?.show(licenseController, sender: self)
}
}
}
}
}
Your completion closure should handle the obtained data. You would call the function like this:
getResponse(accessCode: "code", UDID: "udid", completion: { result in
// Do whatever you need to do with the dictionary result
}
Also, I'd recommend you to change your NSDictionary with a swift Dictionary.
This is what the API returns as a response
{
AccessCode = 00000000;
Client = "0000 - My Company Name";
EmailAddress = "brandon#brandonthomas.me";
FirstName = Brandon;
Found = 1;
LastName = Thomas;
Status = A;
UDIDregistered = 1;
}
And this is what calls the function. I am calling at after clicking a button after an access code is being entered in a text field.
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
var accessCodeFound: Bool? = nil
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { result in
accessCodeFound = result["Found"] as! Bool
print("Value of Found during function:")
print(accessCodeFound)
//accessCodeFound = true
}
//If access code is valid, go to License view
print("Value of Found after function:")
print(accessCodeFound)
//accessCodeFound = nil ???
//it seems the value is getting reset after the function completes
if accessCodeFound == true{
//Load License View
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self.show(licenseController, sender: Any?.self)
}
}

Converting Swift ios Networking to use Alamofire

I got a source code from a github page written in swift and implementing GoogleMaps. I now want to refactor the codes to use Alamofire and SwiftyJSON so that I can improve the code but I got confused because through my learning of swift I used Alamofire and swiftyJSON for every networking process so I am confused currently. the code below
typealias PlacesCompletion = ([GooglePlace]) -> Void
typealias PhotoCompletion = (UIImage?) -> Void
class GoogleDataProvider {
private var photoCache: [String: UIImage] = [:]
private var placesTask: URLSessionDataTask?
private var session: URLSession {
return URLSession.shared
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
func fetchPlacesNearCoordinate(_ coordinate: CLLocationCoordinate2D, radius: Double, types:[String], completion: #escaping PlacesCompletion) -> Void {
var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(coordinate.latitude),\(coordinate.longitude)&radius=\(radius)&rankby=prominence&sensor=true&key=\(appDelegate.APP_ID)"
let typesString = types.count > 0 ? types.joined(separator: "|") : "food"
urlString += "&types=\(typesString)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? urlString
guard let url = URL(string: urlString) else {
completion([])
return
}
if let task = placesTask, task.taskIdentifier > 0 && task.state == .running {
task.cancel()
}
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
placesTask = session.dataTask(with: url) { data, response, error in
var placesArray: [GooglePlace] = []
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(placesArray)
}
}
guard let data = data,
let json = try? JSON(data: data, options: .mutableContainers),
let results = json["results"].arrayObject as? [[String: Any]] else {
return
}
results.forEach {
let place = GooglePlace(dictionary: $0, acceptedTypes: types)
placesArray.append(place)
if let reference = place.photoReference {
self.fetchPhotoFromReference(reference) { image in
place.photo = image
}
}
}
}
placesTask?.resume()
}
func fetchPhotoFromReference(_ reference: String, completion: #escaping PhotoCompletion) -> Void {
if let photo = photoCache[reference] {
completion(photo)
} else {
let urlString = "https://maps.googleapis.com/maps/api/place/photo?maxwidth=200&photoreference=\(reference)&key=\(appDelegate.APP_ID)"
guard let url = URL(string: urlString) else {
completion(nil)
return
}
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
session.downloadTask(with: url) { url, response, error in
var downloadedPhoto: UIImage? = nil
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(downloadedPhoto)
}
}
guard let url = url else {
return
}
guard let imageData = try? Data(contentsOf: url) else {
return
}
downloadedPhoto = UIImage(data: imageData)
self.photoCache[reference] = downloadedPhoto
}
.resume()
}
}
}
any help to refactor the codes to use Alamofire and swiftyJSON would be appreciated.
Both Alamofire and SwiftyJSON have pretty decent instructions, and there are plenty of examples online to look for. However, this would be a decent starting point - you need to replace your session.dataTask and session.downloadTask with Alamofire methods. For example, instead of:
session.downloadTask(with: url) { url, response, error in
var downloadedPhoto: UIImage? = nil
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(downloadedPhoto)
}
}
guard let url = url else {
return
}
guard let imageData = try? Data(contentsOf: url) else {
return
}
downloadedPhoto = UIImage(data: imageData)
self.photoCache[reference] = downloadedPhoto
}
.resume()
use this skeleton and implement your models and logic:
Alamofire
.request(url)
.responseJSON { dataResponse in
switch dataResponse.result {
case .success:
guard let json = JSON(dataResponse.data) else {
return
}
// Continue parsing
case .failure(let error):
// Handle error
print("\(error)")
}
}

Index Out of range Swift

I occasionally get an error 'Index out of Range' from the following code:
I suspect its to do with the way the table data is reloaded and have tried moving it in and out of the loop but no avail.
any help resolving this would be much appreciated!
override func viewDidAppear(_ animated: Bool) {
self.tableView.rowHeight = 150
self.tableData.removeAll(keepingCapacity: true)
self.tableImages.removeAll(keepingCapacity: true)
self.tableDesc.removeAll(keepingCapacity: true)
// Register custom cell
let nib = UINib(nibName: "vwTblCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "cell")
let url = NSURL(string: "https://www.checkmyweave.co.uk/backend/offerstest.php")!
let task = URLSession.shared.dataTask(with: url as URL) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: []) as? [[String:AnyObject]] {
var i = 0
while i < jsonResult.count {
self.tableData.append(jsonResult[i]["offertitle"]! as! String)
self.tableImages.append(jsonResult[i]["imagename"]! as! String)
self.tableDesc.append(jsonResult[i]["offerdesc"]! as! String)
i = i + 1
}
}
} catch {
print("JSON serialization failed")
}
} else {
print("ERROR FOUND HERE")
}
DispatchQueue.main.async(execute: { () -> Void in
self.tableView.reloadData()
})
}
task.resume()
}

Extra argument 'error' in call in swift

I am new to swift so please treat me as beginner.
I am following tutorial, this is pretty old tutorial and it has used GoogleMap framework whereas I am doing it with pod. In func geocodeAddress in MapTasks.swift file I am getting error called
Extra argument 'error' in call
func geocodeAddress(address: String!, withCompletionHandler completionHandler: ((status: String, success: Bool) -> Void)) {
if let lookupAddress = address {
var geocodeURLString = baseURLGeocode + "address=" + lookupAddress
geocodeURLString = geocodeURLString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let geocodeURL = NSURL(string: geocodeURLString)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let geocodingResultsData = NSData(contentsOfURL: geocodeURL!)
let request = NSMutableURLRequest(URL: geocodingResultsData)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
(let data, let response, let error) in
if let _ = response as? NSHTTPURLResponse {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if error != nil {
print("error=\(error!)")
return
}
if let parseJSON = json {
}
} catch {
print(error)
}
}
}
task.resume()
else {
// Get the response status.
let status = dictionary["status"] as! String
if status == "OK" {
let allResults = dictionary["results"] as! Array<Dictionary<NSObject, AnyObject>>
self.lookupAddressResults = allResults[0]
// Keep the most important values.
self.fetchedFormattedAddress = self.lookupAddressResults["formatted_address"] as! String
let geometry = self.lookupAddressResults["geometry"] as! Dictionary<NSObject, AnyObject>
self.fetchedAddressLongitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lng"] as! NSNumber).doubleValue
self.fetchedAddressLatitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lat"] as! NSNumber).doubleValue
completionHandler(status: status, success: true)
}
else {
completionHandler(status: status, success: false)
}
}
})
}
else {
completionHandler(status: "No valid address.", success: false)
}
}
So far I know is I am getting this error because of the diffrent version of swift. Tutorial I am following is written in old version of swift and I am doing it in new
In Swift 2.0, you cannot add 'error' argument in NSJSONSerialization method, you need to use try-catch statement as follows:
func geocodeAddress(address: String!, withCompletionHandler completionHandler: ((status: String, success: Bool) -> Void)) {
if let lookupAddress = address {
var geocodeURLString = baseURLGeocode + "address=" + lookupAddress
geocodeURLString = geocodeURLString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let geocodeURL = NSURL(string: geocodeURLString)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let geocodingResultsData = NSData(contentsOfURL: geocodeURL!)
let request = NSMutableURLRequest(URL: geocodeURL!)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
(let data, let response, let error) in
if let _ = response as? NSHTTPURLResponse {
do {
let dictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if error != nil {
print("error=\(error!)")
return
}
if let parseJSON = dictionary {
let status = dictionary["status"] as! String
if status == "OK" {
let allResults = dictionary["results"] as! Array<Dictionary<NSObject, AnyObject>>
self.lookupAddressResults = allResults[0]
// Keep the most important values.
self.fetchedFormattedAddress = self.lookupAddressResults["formatted_address"] as! String
let geometry = self.lookupAddressResults["geometry"] as! Dictionary<NSObject, AnyObject>
self.fetchedAddressLongitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lng"] as! NSNumber).doubleValue
self.fetchedAddressLatitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lat"] as! NSNumber).doubleValue
completionHandler(status: status, success: true)
}
else {
completionHandler(status: status, success: false)
}
}
} catch {
print(error)
}
}
}
task.resume()
})
}
}