Alamofire using value outside completion handler - swift

I made a request to server with Alamofire and get data from it with completion handler, but I want to use some value outside of it, is it possible to do that?
let retur = Json().login(userName: param1, password: param2) { (json) in
print(json)
let jsonDic = JSON(json)
for item in jsonDic["result"].arrayValue {
let token = item["ubus_rpc_session"].stringValue
}
print(jsonDic["result"][1]["ubus_rpc_session"].stringValue)
}

If you want to access result outside the Alamofire then follow below code.
override func viewDidLoad()
{
super.viewDidLoad()
let retur = Json().login(userName: param1, password: param2) { (json) in
print(json)
let jsonDic = JSON(json)
for item in jsonDic["result"].arrayValue
{
let token = item["ubus_rpc_session"].stringValue
self.myFunction(str: token)
}
print(jsonDic["result"][1]["ubus_rpc_session"].stringValue)
}
}
func myFunction(str: String)
{
//Here it will print token value.
print("Token value ====%#",str)
}
Solution 2:
Here I'll use NSUserDefault, to know more about it you can search about it, there are multiple examples are available on google.
Now here, we will use NSUserDefault.
//Use below code in alamofire block to save token value.
let defaults = UserDefaults.standard
defaults.set(token, forKey: "tokenValue")
//Use below code to print anywhere.
let defaults = UserDefaults.standard
if let myToken = defaults.value(forKey: "tokenValue") as? String
{
print("defaults savedToken: \(myToken)")
}
Hope it works for you.
For more details you can refer this answer https://stackoverflow.com/a/41899777/5886755

As I mention in comment that you want to access your token everywhere in app you can do it this way:
Create a separate class for that like
import UIKit
class UserSettings: NSObject {
class func setAuthToken(authToken: String?) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(authToken, forKey: "authToken");
}
class func getAuthToken() -> String? {
let defaults = NSUserDefaults.standardUserDefaults()
return defaults.stringForKey("authToken")
}
}
Now you can set token this way:
let token = item["ubus_rpc_session"].stringValue
UserSettings.setAuthToken(token)
and you get token anywhere in app this way:
if let token = UserSettings.getAuthToken() {
//do your stuff here with token
}

Related

```webAuthSession.presentationContextProvider = self``` ERROR: cannot find self in scope

I am trying to implement a web authentication service in my app however, when I try to put webAuthSession.presentationContextProvider = self , I get an error saying Cannot find 'self' in scope. All the turorials I have followed did the same thing without any errors. I am truly desperate so any help would be appreciated.
Thank you very much in advance!
import Foundation
import AuthenticationServices
import CryptoKit
typealias ASPresentationAnchor = UIWindow
class SignInViewModel: NSObject, ObservableObject, ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return ASPresentationAnchor()
}
}
public func UrlGenerator() -> URL {
func randomString(length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
let code_verifier = randomString(length: 86)
let hashdata = Data(code_verifier.utf8)
let code_challenge_data = SHA256.hash(data: hashdata)
let code_challenge_generated = code_challenge_data.compactMap { String(format: "%02x", $0) }.joined()
let url = "auth.tesla.com/oauth2/v3/authorize"
let client_id = "ownerapi"
let code_challenge = "\(code_challenge_generated)"
let code_challenge_method = "S256"
let redirect_uri = "https://auth.tesla.com/void/callback"
let response_type = "code"
let scope = "openid+email+offline_access"
let state = "\(code_verifier)"
let TeslaLink = "\(url)?client_id=\(client_id)&code_challenge=\(code_challenge)&code_challenge_method=\(code_challenge_method)&redirect_uri=\(redirect_uri)&response_type=\(response_type)&scope=\(scope)&state=\(state)"
let TeslaLinkURL = URL(string: TeslaLink)!
return TeslaLinkURL
}
let webAuthSession = ASWebAuthenticationSession.init(
url: UrlGenerator(),
callbackURLScheme: "TESRIUM",
completionHandler: { (callback:URL?, error:Error?) in
})
public func TeslaSignIn() {
webAuthSession.presentationContextProvider = self
webAuthSession.prefersEphemeralWebBrowserSession = false
webAuthSession.start()
}
To answer your question, the reason self is not available is because you're referring to it in the global context and self has no meaning in the global context. self must refer to an object and self in the global scope is an environment, not an object.
The reason your specific code doesn't work is because you've separated the instantiation of webAuthSession from the work you perform on it. In the tutorial, webAuthSession = ASWebAuthenticationSession.init... and webAuthSession?.presentationContextProvider = self are in the same scope. Therefore, simply merge them together into the same function. Now, where that function is also matters because if it's a global function you'll have the same problem. You need to place this function into the right object so self is referring to what it's supposed to refer to.
public func TeslaSignIn() {
let webAuthSession = ASWebAuthenticationSession.init(
url: UrlGenerator(),
callbackURLScheme: "TESRIUM",
completionHandler: { (callback:URL?, error:Error?) in
})
webAuthSession.presentationContextProvider = self
webAuthSession.prefersEphemeralWebBrowserSession = false
webAuthSession.start()
}

Access array outside of Firebase call

From the documentation I see that I can get some user data (which I'm already getting correctly), however, the way it's structured, it doesn't allow me to access the array outside of it, this is what I mean, I have a function:
func observe() {
let postsRef = Database.database().reference(withPath: "post")
struct test {
static var tempPosts = [Post]()
}
postsRef.observe(.value, with: { snapshot in
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
// let timestamp = data["timestamp"] as? Double,
let first_name = data["Author"] as? String,
let postTitle = data["title"] as? String,
let postDescription = data["description"] as? String,
let postUrl = data["postUrl"] as? String,
let postAddress = data["Address"] as? String,
let url = URL(string:postUrl)
{
// Convert timestamp to date
// let newDate = self.getDateFromTimeStamp(timestamp:timestamp)
// Store variables from DB into post
let post = Post(author: first_name, postTitle: postTitle, postDescription: postDescription, postUrl: url, postAddress: postAddress)
test.tempPosts.append(post)
}
}
self.posts = test.tempPosts
// HERE IT WORKS
print(test.tempPosts[0].postTitle , " 0")
self.tableView.reloadData()
})
// HERE IT DOESN'T WORK
print(test.tempPosts[0].postTitle , " 0")
}
and I'm trying to access the data where it says: // HERE IT DOESN'T WORK, how can I access that array outside of it? I need to call it later
The observe() method is asynchronous, so after you call postsRef.observe the code executed within that closure is run ONLY AFTER the application receives a response from Firebase, so there's a delay. All code after this call that's NOT stored within the closure will be executed immediately though.
So the .observe asynchronous function call is executed, and then the next line under // HERE IT DOESN'T WORK is executed immediately after. This is why this doesn't work because test.tempPosts doesn't contain any values until after the server response is received, and by that time, your print statement outside the closure has already run.
Check out this StackOverflow answer to get some more information about async vs sync.
Asynchronous vs synchronous execution, what does it really mean?
Also too, you may want to look into closures on Swift here.
If you want to access the value outside of the closure, you'll need to look into using a completion handler or a class property.
Edit:
Here's an example
func observe (finished: #escaping ([Post]) -> Void) {
// ALL YOUR CODE...
finished(test.tempPosts)
}
func getTempPosts () {
observe( (tempPosts) in
print(tempPosts)
}
}

Swift execute functions sequentially

I have the below code which obtains the access token from a facebook login and returns it as 'accessToken'. Once I have the access token I wish to pass this to my server in a request and return the response as an array.
The issue I have is that the request to the server executes before the accessToken is obtained. I have looked into closure statements but I cannot see a way where I can order the execution of the functions without ending up nesting. I don't mind nesting in this instance but in future if I have say 5 functions this will begin to look messy.
Am I approaching this in the best way by using classes and functions? Usually when I code in swift all the code relevant to the viewController would be contained in 1 file, but as the project gets larger I am looking to implement a more OOP approach to make the project more manageable. How would I best achieve this?
import Foundation
import UIKit
class registrationPage: UIViewController {
#IBAction func facebookButton(_ sender: Any) {
// Get the access token from facebook
let accessToken = facebookLogin().login()
// Get the users currency, langage and locale.
let currency = Locale.current.currencyCode ?? "GBP"
let language = Locale.current.languageCode ?? "GB"
let region = Locale.current.regionCode ?? "GB"
let params = "accessToken=\(accessToken)&currency=\(currency)&language=\(language)&region=\(region)"
let resultArray = database().connect(endPoint: "loginfb.php?", params: "\(params)")
print(resultArray)
}
}
class facebookLogin {
var response = ""
func login(completion: (_ result: String) -> Void) {
let loginManager = LoginManager()
loginManager.logIn(readPermissions:[ReadPermission.publicProfile, ReadPermission.email], viewController: registrationPage() as UIViewController) {
loginResult in switch loginResult {
case .failed:
self.response = "ERROR"
case .cancelled:
self.response = "ERROR"
case .success:
self.response = "\(String(describing: FBSDKAccessToken.current().tokenString!))"
print(self.response)
}
}
completion(self.response)
}
}
loginManager.logIn is asynchronous, thats why it takes a closure. You can synchronize the call or as you said use nested closures where one calls the next.
To make let accessToken = facebookLogin().login() synchronous with DispatchGroup:
class facebookLogin {
func login() -> String {
let loginManager = LoginManager()
var response = ""
let group = DispatchGroup()
group.enter() // loginManager.logIn
loginManager.logIn(readPermissions:[ReadPermission.publicProfile, ReadPermission.email], viewController: registrationPage() as UIViewController) {
loginResult in switch loginResult {
case .failed:
self.response = "ERROR"
case .cancelled:
self.response = "ERROR"
case .success:
self.response = "\(String(describing: FBSDKAccessToken.current().tokenString!))"
print(self.response)
}
group.leave() // loginManager.logIn
}
group.wait()
return response
}
}
If you don't like the facebookLogin().login() { accessToken in ... } syntax, you could put the { accessToken in ... } part into its own function
func callServer(accessToken: String) {
// Get the users currency, langage and locale.
let currency = Locale.current.currencyCode ?? "GBP"
let language = Locale.current.languageCode ?? "GB"
let region = Locale.current.regionCode ?? "GB"
let params = "accessToken=\(accessToken)&currency=\(currency)&language=\(language)&region=\(region)"
let resultArray = database().connect(endPoint: "loginfb.php?", params: "\(params)")
print(resultArray)
}
and call it with
#IBAction func facebookButton(_ sender: Any) {
// Get the access token from facebook
facebookLogin().login(completion: callServer(accessToken:))
}

Property declared in my class isn't recognized when attempting to use it inside a function?

I've checked for the misspelling of the property, that's definitely not the case. I'm trying to use the property mySong that I declared in my class inside the parseSongs() function.
That function isn't inside the class but it's in the same file. And the target membership of that class is set to the project name as are the other files as well.
I'm very confused why the compiler isn't recognizing the name of my property in the parseSongs()?
I can declare the property outside of the class but I should be able to use it even if it's declared inside the class.
import UIKit
class SongsTableViewController: UITableViewController {
//A property that is an array of type 'Song'
var mySong = [Song]()
private let cache = NSCache()
private func fetchMyData(){
let myUrl = NSURL(string: "http://itunes.apple.com/search?term=beatles&country=us")!
let mySession = NSURLSession.sharedSession()
//The work to be queued initiates
let myTask = mySession.dataTaskWithURL(myUrl){
//This closure right here is the Completion Handler
data, response, error in
if error != nil{
//Handle error
}else{
let myHttpResponse = response as! NSHTTPURLResponse
switch myHttpResponse.statusCode {
case 200..<300:
print("OK")
print("data: \(data)")
default: print("request failed: \(myHttpResponse.statusCode)")
}
}
}
myTask.resume()
}
}
func parseJson(myData data: NSData){
do{
let json: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data, options: [])
if let unwrappedJson: AnyObject = json{
parseSongs(unwrappedJson)
}
}catch{
}
}
func parseSongs(json1: AnyObject){
mySong = []
//Optional Binding
if let array = json1["results"] as? [[String:AnyObject]]{
//For-In loop
for songDictionary in array{
if let title = songDictionary["trackName"] as? NSString{
if let artist = songDictionary["artistName"] as? NSString{
if let albumName = songDictionary ["collectionName"] as? NSString{
if let artWorkUrl = songDictionary["artWorkUrl100"] as? NSString {
let song = Song(artist: (artist as String), title: (title as String), albumName: (albumName as String), artWorkUrl: (artWorkUrl as String))
mySong.append(song)
}
}
}
}
}
dispatch_async(dispatch_get_main_queue()){
self.tableView.reloadData()
}
}
}
To use the property outside which declared inside a class you have to follow this
SongsTableViewController().theProperty
If you declare it outside class then you can access it in function of outside class

Sending data to 3D Touch shortcut

I am building a weather application and I want to be able to have weather data (e.g. Temperature) be seen when a user activates a shortcut menu (via 3D Touch on the home screen). I would like the weather data to show up in the shortcut so the user does not have to enter the application to check the temperature. Here is the code used to retrieve the weather data, I will post more code if need be:
struct ForecastService {
let forecastAPIKey: String
let forecastBaseURL: NSURL?
init(apiKey: String) {
forecastAPIKey = apiKey
forecastBaseURL = NSURL(string: "https://api.forecast.io/forecast/\(forecastAPIKey)/")
}
func getForecast(lat: Double, long: Double, completion: (CurrentWeather? -> Void)) {
if let forecastURL = NSURL(string: "\(lat),\(long)", relativeToURL: forecastBaseURL) {
let networkOperation = NetworkOperation(url: forecastURL)
networkOperation.downloadJSONFromURL {
(let JSONDictionary) in
let currentWeather = self.currentWeatherFromJSON(JSONDictionary)
completion(currentWeather)
}
} else {
print("Could not construct a valid URL")
}
}
func currentWeatherFromJSON(jsonDictionary: [String: AnyObject]?) -> CurrentWeather? {
if let currentWeatherDictionary = jsonDictionary?["currently"] as? [String: AnyObject] {
return CurrentWeather(weatherDictionary: currentWeatherDictionary)
} else {
print("JSON Dictionary returned nil for 'currently' key")
return nil
}
}
}//end struct
You should create a UIApplicationShortcutItem with its title set to the weather conditions you want to show, then set your application’s shortcutItems to an array containing that item. For example:
let item = UIApplicationShortcutItem(type:"showCurrentConditions", localizedTitle:"64° Sunny")
UIApplication.sharedApplication().shortcutItems = [item]
Note that the “type” parameter is an arbitrary string—your app delegate just needs to be able to recognize it when the user selects the shortcut.