Saving username information to a server instead of NSDefaults (iOS) - ios

I have a server for storing username and password data in my application. When testing the app I had everything save to the device locally using NSDefaults, but now that the app is close to being fully launched, I am trying to save them to the server instead, as it is safer that way for the user's information.
When I had it save to NSDefaults, it was easy and short work. Now however, I am trying to POST the data to the server and keep getting build errors. What do I need to change for this to work? Am I not fully understanding how POST and GET works? Thanks. Using Swift 2 as of right now, not my choice, I prefer 3, but my boss isn't letting us update it yet.
The current error is coming from the POST USER DATA TO SERVER section, where xcode claims that userNmeTxt cannot be converted into NSData. Thank you in advance.
EDIT: Error is on line 87: "Cannot convert value of type UITextField! to expected argument type NSData!"
import UIKit
class UserNameViewController: AuthorizationViewController {
#IBOutlet weak var userNameTxt: UITextField!
#IBOutlet weak var continueBtn: UIButton!
var userModel: ProfileModel!
//MARK: - SYSTEMS METHODS
override func viewDidLoad() {
super.viewDidLoad()
userNameTxt.delegate = self
userNameTxt.autocapitalizationType = .Sentences
setEnabledButton()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBarHidden = false
self.navigationItem.leftBarButtonItem = getBackButton()
self.title = ""
}
override func viewWillDisappear(animated: Bool) {
self.navigationController?.navigationBarHidden = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
continueBtn.layer.cornerRadius = 10
}
override func popToRoot(sender:UIBarButtonItem){
self.navigationController!.popViewControllerAnimated(true)
}
//MARK: - CHECK FOR AVALABILITY
func setEnabledButton(){
if userNameTxt.text == "" {
continueBtn.backgroundColor = UIColor.lightGrayColor()
} else {
continueBtn.backgroundColor = UIColor(colorLiteralRed: 63.0/255.0, green: 220.0/255.0, blue: 236.0/255.0, alpha: 1.0)
}
continueBtn.userInteractionEnabled = userNameTxt.text != ""
}
//MARK: - POST USER DATA TO SERVER
func postData(url: String, params: Dictionary<String, String>, completionHandler: (data: NSData?, response: NSURLResponse?, error: NSError?) -> ()) {
// Indicate download
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let url = NSURL(string: "myPlaceholderURLgoesHere")!
// print("URL: \(url)")
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
// Verify downloading data is allowed
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: [])
} catch let error as NSError {
print("Error in request post: \(error)")
request.HTTPBody = nil
} catch {
print("Catch all error: \(error)")
}
// Post the data
let task = session.dataTaskWithRequest(request) { data, response, error in
completionHandler(data: userNameTxt, response: userModel, error: error)
// Stop download indication
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
// Stop download indication
}
task.resume()
}
//MARK: - SEGUE
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toPassword"{
let controller = segue.destinationViewController as! PasswordViewController
controller.userModel = userModel
}
}
//MARK: - IB ACTIONS
#IBAction func continuePressed(sender: AnyObject) {
userModel.userNickName = userNameTxt.text!
performSegueWithIdentifier("toPassword", sender: self)
}
}
extension UserNameViewController: UITextFieldDelegate{
func textFieldDidEndEditing(textField: UITextField) {
self.setEnabledButton()
}
}

There are a couple of things you need to change.
userNameTxt is not the username, it's the UITextField containing the username. The text you need is userNameTxt.text?
If the function is expecting Data, you have to convert your text to Data first
let task = session.dataTaskWithRequest(request) { data, response, error in
completionHandler(data: userNameTxt.text?.data(using: .utf8), response: userModel, error: error)

I assume you have to send the data to the server.
If you don't have too you can save the data in keychain access, see: SO: Keychain Access
In order to resolve the error please edit the question with the error message and line of code (if possible).
I would suggest that you use Alamofire for POST/GET (REST). To use Alamofire you need basic knowledge of Cocoapods. It's better in the long term.
NOTE: There can be two possible error outcomes when you make the request.
1) Incorrect data format or bug from your side
2) Server error due backend bug from server side.
The data can be sent from your device with POST where the data is in the BODY or HEADER of the request. Usually it is in the body (parameters in the alamofire methods).
Here is an example:
import Alamofire
...
// MARK:- Login Feature - Universal Met for login
internal static func loginWith(serverUrl: String, parameters: [String: AnyObject]?, headers: [String: String]?, notificationName: String, serviceType: LoginService)
{
Alamofire.request(.POST, serverUrl, parameters: parameters, headers: headers).responseJSON
{ (response) in
print("\n Login feature - \n")
print(" Login url - \(serverUrl)\n")
print(" Login parameters - \(parameters)\n")
print(" Login notificationName - \(notificationName)\n")
print(" Login response - \(response)\n")
EXCDataParserH.parseResponseFrom(ServiceType.Login(type: serviceType),
operation: nil,
data: response.data,
notification: notificationName)
}
}

Instead of writing the whole thing every time you make a server request, try to do as follows:
import Foundation
import SystemConfiguration
class HTTPHelper{
class func httpPostDataDic(postURL:NSURL,postString:NSString,completionHandler:#escaping (NSDictionary?, NSError?) -> Void ) -> URLSessionTask{
var responseResultData: NSDictionary = NSDictionary()
let request = NSMutableURLRequest(url:postURL as URL);
request.httpMethod = "POST";// Compose a query string
request.httpBody = postString.data(using: String.Encoding.utf8.rawValue);
print(request)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil
{
print("error=\(error)")
completionHandler(nil, error as NSError?)
return
}
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
if let responseString = responseString {
print("responseString = \(responseString)")
}
do {
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
responseResultData=myJSON!
completionHandler(responseResultData, nil)
} catch {
print(error)
}
}
task.resume()
return task
}
}
Now whenever you need to make a server POST request,in your ViewController class, do as follows:
//Requesting server
func requestServer() -> Void{
let postvariable = "Value"
let url = URL(string: "your url")!
let postString = "postkey=\(postvariable)"
HTTPHelper.httpPostDataDic(postURL: url as NSURL, postString: postString) {
(responseResult, error) -> Void in
DispatchQueue.main.async {
if error != nil{
print(error ?? "unknown")
}
else{
print(responseResult ?? "unknown result")
//Parse your response
self.parseResult(result: responseResult!);
}
}
}
}
May I ask you one thing that I didn't understand in your question.
How exactly would you save the login credentials in a server? I mean, if you save the login credentials in the server, how would you authenticate user access to these saved credentials?

Related

Sending string from JSON data to variable outside of the function

I am attempting to take a string from JSON data and set it to a variable. My problem is that the variable shows as empty. I am using JSONDecoder to retrieve the JSON data and setting the string to a variable outside of the function. I then want to use that variable inside of another function
When I print the variable it still shows up as blank even after the function has loaded. Within the function the string appears correctly.
Code:
var filmTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) //Prints as an empty string
}
func loadFilms() {
let id = filmId
let apiKey = "97a0d64910120cbeae9df9cb675ad235"
let url = URL(string: "https://api.themoviedb.org/3/movie/\(id)?api_key=\(apiKey)&language=en-US")
let request = URLRequest(
url: url! as URL,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 10 )
let session = URLSession (
configuration: URLSessionConfiguration.default,
delegate: nil,
delegateQueue: OperationQueue.main
)
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try! JSONDecoder().decode(Details.self, from: data)
self.filmTitle = details.title
print(self.filmTitle) //string prints correctly
}
}
})
task.resume()
}
What am I missing to correctly set the string to the variable?
Loading data from the internet is an asynchronous method. The print statement is being called before loadFilms() has completed.
Use a callback to get the data after it has completed.
func loadFilms(completion: #escaping (Details?, Error?) -> Void) {
//...
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try JSONDecoder().decode(Details.self, from: data)
completion(details, nil)
} catch {
completion(nil, error)
}
})
}
At the call site:
override func viewDidLoad() {
loadFilms { details, error in
if error { //* Handle Error */ }
self.filmTitle = details.title
print(filmTitle)
}
}
Web request are asynchronous and from the CP's perspective, take a long time to complete. When you call this:
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) // loadFilms() hasn't finished so `filmTitle` is empty
}
It's better to set a property observer on filmTitle:
var filmTitle: String? = nil {
didSet {
print(filmTitle)
Dispatch.main.async {
// update your GUI
}
}
}
The solution to this problem was to reload the collection view that the array was being sent to within the decoder function after the data was set to the array.

OAuth authentication using GitHub in iOS apps - how does it work

I am trying to understand how oauth authentication with GITHUB works in case of IOS Apps.
I am developing an IOS App and want to use GITHUB for login authentication.
Here is flow of my app.
User registration - This is an offline process and happens outside of my app. When I create an account for user, I ask them to provide their GITHUB email address. I store this email address in our DB as userid for that user.
Get access token from GITHUB - when user opens our app we direct them to https://github.com/login/oauth/authorize using webview. Once user successfully login to GITHUB account I use https://github.com/login/oauth/access_token to get access token.
Get email address using access token - I am using https://api.github.com/user/emails to get email address of logged account using access token I got in step 2.
Verify email address: I verify email address I got in step3 against my database. If userid exists then user will be able to do transactions on our app.
Right now after GITHUB verification control is coming back to viewcontroller that has webview for GITHUB and a blank screen appears. How do I move flow to next viewcontroller ?
Here is my ViewController Code:
import UIKit
import WebKit
class Login: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webview: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let authURL = String(format: "%#?client_id=%#&redirect_uri=%#&scope=%#", arguments: [GITHUB.GITHUB_AUTHURL,GITHUB.GITHUB_CLIENT_ID,GITHUB.GITHUB_REDIRECT_URI,GITHUB.GITHUB_SCOPE])
let urlRequest = URLRequest.init(url: URL.init(string: authURL)!)
webview.loadRequest(urlRequest)
webview.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func webView(_ webview: UIWebView, shouldStartLoadWith request:URLRequest, navigationType: UIWebViewNavigationType) -> Bool{
return checkRequestForCallbackURL(request: request)
}
func checkRequestForCallbackURL(request: URLRequest) -> Bool {
//print("3. IN FUNCTION checkRequestForCallbackURL")
let requestURLString = (request.url?.absoluteString)! as String
//print("3. requestURLString=\(requestURLString)")
if requestURLString.hasPrefix(GITHUB.GITHUB_REDIRECT_URI) {
let range: Range<String.Index> = requestURLString.range(of: "?code=")!
handleGithubCode(code: requestURLString.substring(from: range.upperBound))
return false;
}
return true
}
func handleGithubCode(code: String) {
let urlString = "https://github.com/login/oauth/access_token"
if let tokenUrl = URL(string: urlString) {
let req = NSMutableURLRequest(url: tokenUrl)
req.httpMethod = "POST"
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.addValue("application/json", forHTTPHeaderField: "Accept")
let params = [
"client_id" : GITHUB.GITHUB_CLIENT_ID,
"client_secret" : GITHUB.GITHUB_CLIENTSECRET,
"code" : code
]
req.httpBody = try? JSONSerialization.data(withJSONObject: params, options: [])
let task = URLSession.shared.dataTask(with: req as URLRequest) { data, response, error in
if let data = data {
do {
if let content = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] {
if let accessToken = content["access_token"] as? String {
self.getComposerToken(accessToken: accessToken)
}
}
} catch {}
}
}
task.resume()
}
}
func getComposerToken(accessToken: String) {
print("5. accessToken=\(accessToken)")
let def = "NO_DATA"
let composerUrl = "http://192.168.100.112/kubher/getAccessToken.php?token=\(accessToken)"
guard let url = URL(string: composerUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let response = response {
//print(response)
}
if let data = data {
do {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let dict = json as? [String: Any],
let token = dict["accessToken"] {
print("Blockchain Token:\(token)")
}
} catch {
print(error)
}
}
}.resume()
}
}
Move to next viewcontroller depends on the architecture of your code. Try the following code based on your design.
opt 1: If you need to go back to previous ViewController, just modify your getComposerToken function:
if let dict = json as? [String: Any], {
let token = dict["accessToken"] {
print("Blockchain Token:\(token)")
dispatch_async(dispatch_get_main_queue()) {
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
}
opt 2: On the other hand, If you are using Segue for next viewController in stoaryboard, give your segue a name(Identifier) and then follow this code:
if let dict = json as? [String: Any], {
let token = dict["accessToken"] {
print("Blockchain Token:\(token)")
dispatch_async(dispatch_get_main_queue()) {
self.performSegue(withIdentifier: "YourSegueName", sender: token)
}
}
}
Additionally, you have to override prepare method to pass data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueName" {
let viewController = segue.destination as? YourViewController
if let token = sender as? String {
viewController?.token = token
}
}
}
}
OPT 3: If you use push view controller after creating from storyboard, you have to give your view controller an Identifier in storyboard and then you can instantiate it using and push it using:
if let dict = json as? [String: Any], {
let token = dict["accessToken"] {
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let abcViewController = storyboard.instantiateViewControllerWithIdentifier("YourControlleridentifier") as! YourViewController
YourViewController.token = token
navigationController?.pushViewController(YourViewController, animated: true)
}
}

Alamofire best way to recall api on failure

I need to recall the api when receiving an specific error say error code -1005.
I want to handle this in Alamofire files so that it can work with all api in project.
I was handling this in ObjC's AFNetworking in dataTaskWithHTTPMethod with below code:-
if (failure)
{
if (error.code == -1005)
{
[self POST:URLString parameters:parameters progress:nil success:success failure:failure];
}
}
Can anyone help me to do this in Alamofire?
lets understand with One Example.
let me guess as you have created one class for managing all the webservice realted stuff there. (if not yet then it will be good to create one for best practice).
Okay now create two typealias for managing response.
here it is :-
here i assume again that you want whole dictionary for success response and error for failure response.
typealias successCompletion = (([String:Any]) -> ())
typealias failureCompletion = ((Error) -> ())
now here its one WSManager Class for handling all the API Related stuff there.
class AlamofireManager {
static func sampleFunctionOfWebService(successCompletion:successCompletion , failureCompletion:failureCompletion) {
if success {
successCompletion(["Key":"success"])
} else {
failureCompletion(-1005 as! Error)
}
}
}
you need to pass the both typealias in function for getting CallBack
in desired class.
Here for only understanding purpose we are going to pass static
dictionary :-> ["Key":"success"] and static -1005 as Error.
now how to use this function in our desired class?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func wsCalling() {
AlamofireManager.sampleFunctionOfWebService(successCompletion: { (dict) in
print(dict)
}) { (error) in
if error.code == -1005 {
self.wsCalling() // recall API Again
} else {
// your other logic
}
}
}
}
I have not mentioned here for URLSeeionTask and All , its good thing
to manage URLSeeionTask. If you have a instance of URLSeeionTask of
Previous API then cancel it first and then try to recall it again.
Happy Coding.
//MARK: Your method
func hitYourService(){
showActivityIndicator(decision: true, inViewConroller: self, animated: true)
let deviceToken = HelpingClass.userDefaultForKey(key: "deviceToken")
let paramDict = ["email":txtFEmail.text ?? "" ,"password":txtFPassword.text ?? "" ,"device_token":deviceToken]
NetworkManager.instance().hitPostServiceJsonForm(paramDict, myReqURL: ServiceMethods.serviceBaseURL+ServiceMethods.login, unReachable: {
// Here you can handle the case if there is no internet connection
}) { (response) in
showActivityIndicator(decision: false, inViewConroller: self, animated: true)
if response != nil{
if response?["error_code"] as! Int == 0{
//Handle your response
}else{
//Show error
showHudWithMessage(message: response?["error_msg"] as! String, inViewConroller: self, animated: true, hideAfter: 2)
}
}else{
//You can hit back your service
self.hitYourService()
}
}
}
//MARK: Alamofire method to hit service, written in NetworkManager class
func hitPostServiceJsonForm(_ params:Dictionary<String,Any>, myReqURL:String, unReachable:(() -> Void),handler:#escaping ((Dictionary<String,Any>?) -> Void)) {
if networkReachable() == false {
unReachable()
}
var request = URLRequest(url: URL(string: myReqURL)!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
print_debug(object: "ReqParameter: \(String(describing: params))") // http url response
//JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
request.httpBody = try! JSONSerialization.data(withJSONObject: params, options: [])
Alamofire.request(request).responseJSON { response in
// SwiftLoader.hide()
print_debug(object: "Request: \(String(describing: response.request))") // original url request
print_debug(object: "Response: \(String(describing: response.response))") // http url response
print_debug(object: "Result: \(response.result)") // response serialization result
print_debug(object: "Result.value \(String(describing: response.result.value))")
switch response.result {
case .success:
if let jsonDict = response.result.value as? Dictionary<String,Any> {
print_debug(object: "Json Response: \(jsonDict)") // serialized json response
handler(jsonDict)
}
else{
handler(nil)
}
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Server Response: \(utf8Text)") // original server data as UTF8 string
}
break
case .failure(let error):
handler(nil)
print_debug(object: error)
break
}
}
}

Invalid value around character 0 swift

I hope you are having a great day. I encountered an error that I did some research about. The error I think means that my JSON object is not a proper one to be serialized by the JSONSerialization class on swift 3.0. I verified that my json object is not valid by using the method .isValidJSONObject of the JSONSerialization class.
I checked that my json object is valid. The error occure at the line where "JSONSerialization.jsonObject" method execute. I would love if you can help me figure out how to solve this problem. Feel free to ask for more code parts or project settings. Thanks in advance.
Here is the code used:
import UIKit
class ViewController: UIViewController, NSURLConnectionDataDelegate {
lazy var receivedData = NSMutableData()
override func viewDidLoad() {
super.viewDidLoad()
var url = NSURL(string:"http://localhost:8080/OurServer/webresources/server")!
var request = NSURLRequest(url: url as URL)
var connection = NSURLConnection(request: request as URLRequest, delegate: self, startImmediately: false)!
connection.start()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func connection(_ connection: NSURLConnection, didReceive data: Data)
{
receivedData.append(data)
var temp1 = receivedData as NSMutableData
do
{
var temp3 = JSONSerialization.isValidJSONObject(receivedData)
var jsonResult = try JSONSerialization.jsonObject(with: receivedData as Data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSDictionary
print("\n")
print(jsonResult)
}
catch let error as NSError
{
print("\n" + "\(error)")
}
}
}
As mentioned in the comment NSURLConnection is outdated.
This is a modern, Swift 3 compliant version of your code using URLSession
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string:"http://localhost:8080/OurServer/webresources/server")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
do {
let jsonResult = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
print("\n", jsonResult)
} catch {
print("\n", error)
}
}
task.resume()
}
}
After checking, it appears i made a not very smart mistake. The mistake was with my url. excuse me for that guys.

Why does this iOS app take very long time to pop up string to UILabel?

I created a UILabel in storyboard in order to show the response from the server. However, it takes very long time to show the string from the server. It did work, just take almost 1 minute.
I use print to check if there is any delay in the server, but the application will print the response in debug area immediately once opening the application. I guess it should be app's problem.
I check the code and looks fine, works well. Only has delay issue. Can anyone have any idea? (This is a tvOS app using Swift 3.0 BTW)
import UIKit
import Foundation
class LoginViewController: UIViewController, UITextFieldDelegate, URLSessionDelegate, URLSessionDataDelegate {
#IBOutlet weak var RandomString: UILabel!
let UUIDValue = UIDevice.current.identifierForVendor!.uuidString
func sendRequest(url: String, parameters: String, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask {
let parameterString = parameters
let requestURL = URL(string:"\(url)\(parameterString)")!
print("requestURL = \(requestURL)")
var request = URLRequest(url: requestURL)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: completionHandler)
task.resume()
return task
}
override func viewDidLoad() {
super.viewDidLoad()
self.sendRequest(url: "http://www.google.com/api", parameters: UUIDValue, completionHandler:{data, response, error in
print (self.UUIDValue)
guard error == nil && data != nil else {
print("error=\(error)")
return
}
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("responseString = \(responseString)")
//If app receives "true" response from server, go to Home screen.
if responseString == "true" {
let viewController:UIViewController = UIStoryboard(name: "Main", bundle:nil).instantiateViewController(withIdentifier: "Tab") as UIViewController
self.present(viewController, animated: false, completion: nil)
}
//If app receives "false" response from server, display the response from server.
else {
self.RandomString.text = responseString as String?
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
You're setting the label's text inside of the server call, which is not guaranteed to be on the main thread. All UI updates need to happen on the main thread. Try this:
DispatchQueue.main.async {
self.RandomString.text = responseString as String?
}

Resources