I have an application with fairly simple authorization/authentication and need a bit of help on how to handle the authorization life cycle. Here's the jist of it:
The user enters their credentials to log in and the server responds with a token and stores in the keychain
The token is used in the "Authorization" header for every subsequent URL request (REST calls)
The token eventually expires and the server will respond with 401 status code
At this point the user needs to re-authenticate via the login screen so we can get a new token and retry the request
Here's some code that represents the best I could come up with right now.
static NSString * _authorizationToken;
#implementation MyRestfulModel
+ (id) sharedRestfulModel
{
// singleton model shared across view controllers
static MyRestfulModel * _sharedModel = nil;
#synchronized(self) {
if (_sharedModel == nil)
_sharedModel = [[self init] alloc];
}
return _sharedModel;
}
+ (NSString *) authorizationToken
{
if (!_authorizationToken)
_authorizationToken = #"";
return _authorizationToken;
}
+ (void) setAuthorizationToken: (NSString *token)
{
_authorizationToken = token;
}
-(void) doSomeRestfulCall: (NSString *) restURL
completionHandler: (void (^)(NSData * data)) callback
{
// construct url path and request
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:restURL];
[request setValue:MyRestfulModel.authorizationToken forHTTPHeaderField:#"Authorization"];
[[[NSURLSession sharedSession] dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse * response, NSError * error) {
NSHTTPResponse * httpResponse = (NSHTTPResponse *) response;
if([httpResponse statusCode] == 401) { // WHAT TO DO HERE ?
dispatch_sync(dispatch_get_main_queue(), ^{
MyAppDelegate * delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[delegate showLoginViewController callback:^(NSString * username, NSString * newToken) {
// recreate the NSURLSession and NSURLConfiguration with the new token and retry
MyRestfulModel.authenticationToken = token;
[self doSomeRestfulCall:callback];
}];
}
return;
} else {
callback(data);
}
}] resume];
}
I hope to do it like this so that the view controllers never need to worry about retrying a call due to a token expiration, all of the scary session handling and authentication can be done by one object.
I was considering trying to use NSURLSessionDelegate but I couldn't figure out the didReceiveChallenge part w.r.t. popping up the login view controller. The other option I was considering is adding a "retryHandler" similar to the completion handler which would be called after the user re-authenticates.
I think I found a nice solution. Take the "doSomeRestfulCall" method for example.
-(void) doSomeRestfullCall: (NSString *) restURL
completionHandler: (void (^)(NSData * data)) callback
{
// construct url path and request
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:restURL];
[request setValue:MyRestfulModel.authorizationToken forHTTPHeaderField:#"Authorization"];
[[[NSURLSession sharedSession] dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse * response, NSError * error) {
NSHTTPResponse * httpResponse = (NSHTTPResponse *) response;
if([httpResponse statusCode] == 401) {
[LoginViewController showWithCompletion:^(NSString *username, NSString *token) {
NSLog(#"Retrying request after user reauthenticated");
MyRestfulModel.authorizationToken = token;
[self doSomeRestfulCall:restURL completionHandler:callback];
}];
return;
} else {
callback(data);
}
}] resume];
}
Then the login view controller is where a lot of the magic happens. (You can also use an alert view if you don't want a custom login controller, check this post out: http://nscookbook.com/2013/04/ios-programming-recipe-22-simplify-uialertview-with-blocks/)
#interface LoginViewController
#property (copy) void(^completionBlock)(NSString * username, NSString * tokenCredentials);
#end
#implementation LoginViewController
+ (void) showWithCompletion: (void (^)(NSString * username, NSString * tokenCredentials))completion
{
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
UIViewController * rootController = appDelegate.window.rootViewController;
UIStoryboard * storyboard = rootController.storyboard;
LoginViewController * controller = [storyboard instantiateViewControllerWithIdentifier:#"Login"];
controller.completionBlock = completion;
controller.delegate = wrapper;
controller.modalPresentationStyle = UIModalPresentationFormSheet;
[rootController presentViewController:controller animated:YES completion:nil];
}
//
// <code for contacting your authentication service>
//
-(void) loginSuccessful: (NSString *) username withTokenCredentials:(NSString *)token
{
// <code to store username and token in the keychain>
if (self.completionBlock)
self.completionBlock(username, token);
}
Related
While trying to integrate Azure AD B2C, I am stuck with an error "oauthConnection Error: Bad Request". Following their given sample app it all works fine. But after integrating the same copy paste code from the working sample app, and trying to log in with Facebook or Google Plus, it throws an error! I am pretty sure that every credential that I used in the sample app is the same for my app. Any idea about this will be highly appreciated. Here is my code, AppDelegate.m
#import "AppData.h"
#import "NXOAuth2.h"
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setupOAuth2AccountStore];
// Override point for customization after application launch.
return YES;
}
- (void)setupOAuth2AccountStore {
AppData *data = [AppData getInstance]; // The singleton we use to get the settings
NSDictionary *customHeaders =
[NSDictionary dictionaryWithObject:#"application/x-www-form-urlencoded"
forKey:#"Content-Type"];
// Azure B2C needs
// kNXOAuth2AccountStoreConfigurationAdditionalAuthenticationParameters for
// sending policy to the server,
// therefore we use -setConfiguration:forAccountType:
NSDictionary *B2cConfigDict = #{
kNXOAuth2AccountStoreConfigurationClientID : data.clientID,
kNXOAuth2AccountStoreConfigurationSecret : data.clientSecret,
kNXOAuth2AccountStoreConfigurationScope :
[NSSet setWithObjects:#"openid", data.clientID, nil],
kNXOAuth2AccountStoreConfigurationAuthorizeURL :
[NSURL URLWithString:data.authURL],
kNXOAuth2AccountStoreConfigurationTokenURL :
[NSURL URLWithString:data.tokenURL],
kNXOAuth2AccountStoreConfigurationRedirectURL :
[NSURL URLWithString:data.bhh],
kNXOAuth2AccountStoreConfigurationCustomHeaderFields : customHeaders,
// kNXOAuth2AccountStoreConfigurationAdditionalAuthenticationParameters:customAuthenticationParameters
};
[[NXOAuth2AccountStore sharedStore] setConfiguration:B2cConfigDict
forAccountType:data.accountIdentifier];
}
LoginViewController.m
#import "AppData.h"
#import "LoginViewController.h"
#import "NXOAuth2.h"
#interface LoginViewController ()
#end
#implementation LoginViewController {
NSURL *myLoadedUrl;
bool isRequestBusy;
}
// Put variables here
- (void)viewDidLoad {
[super viewDidLoad];
// OAuth2 Code
self.loginView.delegate = self;
[self requestOAuth2Access];
[self setupOAuth2AccountStore];
NSURLCache *URLCache =
[[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
}
- (void)resolveUsingUIWebView:(NSURL *)URL {
// We get the auth token from a redirect so we need to handle that in the
// webview.
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(resolveUsingUIWebView:)
withObject:URL
waitUntilDone:YES];
return;
}
NSURLRequest *hostnameURLRequest =
[NSURLRequest requestWithURL:URL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0f];
isRequestBusy = YES;
[self.loginView loadRequest:hostnameURLRequest];
NSLog(#"resolveUsingUIWebView ready (status: UNKNOWN, URL: %#)",
self.loginView.request.URL);
}
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
AppData *data = [AppData getInstance];
NSLog(#"webView:shouldStartLoadWithRequest: %# (%li)", request.URL,
(long)navigationType);
// The webview is where all the communication happens. Slightly complicated.
myLoadedUrl = [webView.request mainDocumentURL];
NSLog(#"***Loaded url: %#", myLoadedUrl);
// if the UIWebView is showing our authorization URL or consent URL, show the
// UIWebView control
if ([request.URL.absoluteString rangeOfString:data.authURL
options:NSCaseInsensitiveSearch]
.location != NSNotFound) {
self.loginView.hidden = NO;
} else if ([request.URL.absoluteString rangeOfString:data.loginURL
options:NSCaseInsensitiveSearch]
.location != NSNotFound) {
// otherwise hide the UIWebView, we've left the authorization flow
self.loginView.hidden = NO;
} else if ([request.URL.absoluteString rangeOfString:data.bhh
options:NSCaseInsensitiveSearch]
.location != NSNotFound) {
// otherwise hide the UIWebView, we've left the authorization flow
self.loginView.hidden = YES;
[[NXOAuth2AccountStore sharedStore] handleRedirectURL:request.URL];
} else {
self.loginView.hidden = NO;
}
return YES;
}
#pragma mark - UIWebViewDelegate methods
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// The webview is where all the communication happens. Slightly complicated.
}
- (void)handleOAuth2AccessResult:(NSURL *)accessResult {
// parse the response for success or failure
if (accessResult)
// if success, complete the OAuth2 flow by handling the redirect URL and
// obtaining a token
{
[[NXOAuth2AccountStore sharedStore] handleRedirectURL:accessResult];
} else {
// start over
[self requestOAuth2Access];
}
}
- (void)setupOAuth2AccountStore {
[[NSNotificationCenter defaultCenter]
addObserverForName:NXOAuth2AccountStoreAccountsDidChangeNotification
object:[NXOAuth2AccountStore sharedStore]
queue:nil
usingBlock:^(NSNotification *aNotification) {
if (aNotification.userInfo) {
// account added, we have access
// we can now request protected data
NSLog(#"Success!! We have an access token.");
} else {
// account removed, we lost access
}
}];
[[NSNotificationCenter defaultCenter]
addObserverForName:NXOAuth2AccountStoreDidFailToRequestAccessNotification
object:[NXOAuth2AccountStore sharedStore]
queue:nil
usingBlock:^(NSNotification *aNotification) {
NSError *error = [aNotification.userInfo
objectForKey:NXOAuth2AccountStoreErrorKey];
// Always got stuck here while trying to login with any credentials
NSLog(#"Error!! %#", error.localizedDescription);
}];
}
- (void)requestOAuth2Access {
AppData *data = [AppData getInstance];
[[NXOAuth2AccountStore sharedStore]
requestAccessToAccountWithType:data.accountIdentifier
withPreparedAuthorizationURLHandler:^(NSURL *preparedURL) {
NSURLRequest *r = [NSURLRequest requestWithURL:preparedURL];
[self.loginView loadRequest:r];
}];
}
ViewController.m
#import "ViewController.h"
#import "AppData.h"
#import "LoginViewController.h"
#import "NXOAuth2.h"
// Login Action
- (IBAction)login:(id)sender {
LoginViewController *userSelectController =
[self.storyboard instantiateViewControllerWithIdentifier:#"login"];
[self.navigationController pushViewController:userSelectController
animated:YES];
}
In case if anybody stumbles in this, Here is the solution
Go to pod, NXOAuth2Client.m and replace the method
- (void)requestTokenWithAuthGrant:(NSString *)authGrant redirectURL:(NSURL *)redirectURL; with the below code
- (void)requestTokenWithAuthGrant:(NSString *)authGrant redirectURL:(NSURL *)redirectURL;
{
NSAssert1(!authConnection, #"authConnection already running with: %#", authConnection);
NSMutableURLRequest *tokenRequest = [NSMutableURLRequest requestWithURL:tokenURL];
[tokenRequest setHTTPMethod:self.tokenRequestHTTPMethod];
[authConnection cancel]; // just to be sure
self.authenticating = YES;
NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"authorization_code", #"grant_type",
clientId, #"client_id",
// clientSecret, #"client_secret",
[redirectURL absoluteString], #"redirect_uri",
authGrant, #"code",
nil];
if (self.desiredScope) {
[parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:#" "] forKey:#"scope"];
}
if (self.customHeaderFields) {
[self.customHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
[tokenRequest addValue:obj forHTTPHeaderField:key];
}];
}
if (self.additionalAuthenticationParameters) {
[parameters addEntriesFromDictionary:self.additionalAuthenticationParameters];
}
authConnection = [[NXOAuth2Connection alloc] initWithRequest:tokenRequest
requestParameters:parameters
oauthClient:self
delegate:self];
authConnection.context = NXOAuth2ClientConnectionContextTokenRequest;
}
Commenting clientSecret solved the issue
Quickblox webrtc video call receive method is not called .I am call to someone he accept the call and we can communicate but while he is calling me i am not geting that call.
`
- (void)didReceiveNewSession:(QBRTCSession *)session userInfo:(NSDictionary *)userInfo {
if (self.session ) {
[session rejectCall:#{#"reject" : #"busy"}];
return;
}
self.session = session;
[QBRTCSoundRouter.instance initialize];
NSParameterAssert(!self.nav);
IncomingCallViewController *incomingViewController =
[self.storyboard instantiateViewControllerWithIdentifier:#"IncomingCallViewController"];
incomingViewController.delegate = self;
incomingViewController.session = session;
incomingViewController.usersDatasource = self.dataSource;
self.nav = [[UINavigationController alloc] initWithRootViewController:incomingViewController];
[self presentViewController:self.nav animated:NO completion:nil];
}
The Quickblox webrtc video call receive method only called when the user is online so make sure you add in Your -
(Void)ViewDidLoad{
[QBRequest logInWithUserLogin:#"xxxxxx"
password:#"xxxxx"
successBlock:^(QBResponse * _Nonnull response, QBUUser * _Nullable user)
{
}];
[[QBChat instance] connectWithUser:self.user completion:^(NSError * _Nullable error) {
NSLog(#"User%#",self.user);
}];
}
It will be called.
I need to know the MIMEType of the file or web page, available at particular URL and according to MIMEType I’ll take the decision whether to load that page/file to the UIWebView or not.
I know, I can get MIMEType with NSURLResponse object, but problem is, when I get this response that page would have already downloaded.
So, is there any way to ask only header of the file/webpage at particular URL, so that I can check the MIMEType and request for that page/file, only when it is required?
You can get the content type by using the "Content-Type" property of the response headers:
func getContentType(urlPath: String, completion: (type: String)->()) {
if let url = NSURL(string: urlPath) {
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "HEAD"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (_, response, error) in
if let httpResponse = response as? NSHTTPURLResponse where error == nil {
if let ct = httpResponse.allHeaderFields["Content-Type"] as? String {
completion(type: ct)
}
}
}
task.resume()
}
}
getContentType("http://example.com/pic.jpg") { (type) in
print(type) // prints "image/jpeg"
}
I see you don't want the page to be loaded in UIWebView until you determine the Content-Type of the response. For doing this you can implement a custom NSURLProtocol. This is what I did, but you are always welcome to enhance or suggest a different solution. Please go through inline comments.
***** CustomURLProtocol.h *****
#interface CustomURLProtocol : NSURLProtocol
#end
***** CustomURLProtocol.m *****
#interface CustomURLProtocol ()
#property(nonatomic, strong) NSURLConnection * connection;
#property(nonatomic, strong) NSMutableData * reponseData;
#property(nonatomic, strong) NSURLRequest * originalRequest;
#end
#implementation CustomURLProtocol
+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSString * urlScheme = [request.URL.scheme lowercaseString];
//only handling HTTP or HTTPS requests which are not being handled
return (![[NSURLProtocol propertyForKey:#"isBeingHandled" inRequest:request] boolValue] &&
([urlScheme isEqualToString:#"http"] || [urlScheme isEqualToString:#"https"]));
}
+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
-(void)startLoading
{
NSMutableURLRequest * requestCopy = [self.request mutableCopy];
[NSURLProtocol setProperty:[NSNumber numberWithBool:YES] forKey:#"isBeingHandled" inRequest:requestCopy];
self.originalRequest = requestCopy;
self.connection = [NSURLConnection connectionWithRequest:requestCopy delegate:self];
}
-(void)stopLoading
{
[self.connection cancel];
}
#pragma mark - NSURLConnectionDelegate methods
-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
[self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if ([response isKindOfClass:[NSHTTPURLResponse class]])
{
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
NSString * contentType = [httpResponse.allHeaderFields valueForKey:#"Content-Type"];
/*
Check any header here and make the descision.
*/
if([contentType containsString:#"application/pdf"])
{
/*
Let's say you don't want to load the PDF in current UIWebView instead you want to open another view controller having a webview for that.
For doing that we will not inform the client about the response we received from NSURLConnection that we created.
*/
[self.connection cancel];
//post a notification carrying the PDF request and load this request anywhere else.
[[NSNotificationCenter defaultCenter] postNotificationName:#"PDFRequest" object:self.originalRequest];
}
else
{
/*
For any other request having Content-Type other than application/pdf,
we are informing the client (UIWebView or NSURLConnection) and allowing it to proceed.
*/
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
}
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connectionLocal didFailWithError:(NSError *)error
{
[self.client URLProtocol:self didFailWithError:error];
}
#end
One more thing which I forgot to mention is that you have to register the CustomURLProtocol in your app delegate like below:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[NSURLProtocol registerClass:[CustomURLProtocol class]];
return YES;
}
You want to get the headers only. Use HTTP HEAD method for that. Example:
let request = NSMutableURLRequest(URL: NSURL(string: "your_url")!)
request.HTTPMethod = "HEAD"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
// Analyze response here
}
task.resume()
Hi I am attempting to add in Braintrees drop In UI. I am following their guide on their website and have successfully added their cocoa pods to my project. I am now attempting to test out their Drop in UI. However when I attempt to execute the example code I get the following error.
Warning: Attempt to present <UINavigationController: 0x7dc09a00>
on <CheckOutViewController: 0x7c26ca20>
whose view is not in the window hierarchy!
Here is my source code I'm using
#import "CheckOutViewController.h"
#import "BraintreeCore.h"
#import "BraintreeUI.h"
#interface CheckOutViewController ()<BTDropInViewControllerDelegate>
#property (nonatomic, strong) BTAPIClient *braintreeClient;
#end
#implementation CheckOutViewController
- (void)viewDidLoad {
[super viewDidLoad];
// TODO: Switch this URL to your own authenticated API
/* NSURL *clientTokenURL = [NSURL URLWithString:#"https://braintree-sample-merchant.herokuapp.com/client_token"];
NSMutableURLRequest *clientTokenRequest = [NSMutableURLRequest requestWithURL:clientTokenURL];
[clientTokenRequest setValue:#"text/plain" forHTTPHeaderField:#"Accept"];
[[[NSURLSession sharedSession] dataTaskWithRequest:clientTokenRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// TODO: Handle errors
*/
NSString *clientToken = [[NSString alloc] init];
clientToken = #"eyJ2ZXJzaW9uIjoyLCJhdXRob3JpemF0aW9uRmluZ2VycHJpbnQiOiJiMzJlMDJmNmJkNjNkM2M5MzY2ZDg0YzEyNmI3ZDIyNmE0YTJjZDliYTQ1ZWNlYjk5ZGE5ZTY3NTlkOTAzYTgyfGNyZWF0ZWRfYXQ9MjAxNi0wMi0xOFQwNToxNDowOS44NzM0MDUzNTUrMDAwMFx1MDAyNm1lcmNoYW50X2lkPTM0OHBrOWNnZjNiZ3l3MmJcdTAwMjZwdWJsaWNfa2V5PTJuMjQ3ZHY4OWJxOXZtcHIiLCJjb25maWdVcmwiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tOjQ0My9tZXJjaGFudHMvMzQ4cGs5Y2dmM2JneXcyYi9jbGllbnRfYXBpL3YxL2NvbmZpZ3VyYXRpb24iLCJjaGFsbGVuZ2VzIjpbXSwiZW52aXJvbm1lbnQiOiJzYW5kYm94IiwiY2xpZW50QXBpVXJsIjoiaHR0cHM6Ly9hcGkuc2FuZGJveC5icmFpbnRyZWVnYXRld2F5LmNvbTo0NDMvbWVyY2hhbnRzLzM0OHBrOWNnZjNiZ3l3MmIvY2xpZW50X2FwaSIsImFzc2V0c1VybCI6Imh0dHBzOi8vYXNzZXRzLmJyYWludHJlZWdhdGV3YXkuY29tIiwiYXV0aFVybCI6Imh0dHBzOi8vYXV0aC52ZW5tby5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIiwiYW5hbHl0aWNzIjp7InVybCI6Imh0dHBzOi8vY2xpZW50LWFuYWx5dGljcy5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tIn0sInRocmVlRFNlY3VyZUVuYWJsZWQiOnRydWUsInRocmVlRFNlY3VyZSI6eyJsb29rdXBVcmwiOiJodHRwczovL2FwaS5zYW5kYm94LmJyYWludHJlZWdhdGV3YXkuY29tOjQ0My9tZXJjaGFudHMvMzQ4cGs5Y2dmM2JneXcyYi90aHJlZV9kX3NlY3VyZS9sb29rdXAifSwicGF5cGFsRW5hYmxlZCI6dHJ1ZSwicGF5cGFsIjp7ImRpc3BsYXlOYW1lIjoiQWNtZSBXaWRnZXRzLCBMdGQuIChTYW5kYm94KSIsImNsaWVudElkIjpudWxsLCJwcml2YWN5VXJsIjoiaHR0cDovL2V4YW1wbGUuY29tL3BwIiwidXNlckFncmVlbWVudFVybCI6Imh0dHA6Ly9leGFtcGxlLmNvbS90b3MiLCJiYXNlVXJsIjoiaHR0cHM6Ly9hc3NldHMuYnJhaW50cmVlZ2F0ZXdheS5jb20iLCJhc3NldHNVcmwiOiJodHRwczovL2NoZWNrb3V0LnBheXBhbC5jb20iLCJkaXJlY3RCYXNlVXJsIjpudWxsLCJhbGxvd0h0dHAiOnRydWUsImVudmlyb25tZW50Tm9OZXR3b3JrIjp0cnVlLCJlbnZpcm9ubWVudCI6Im9mZmxpbmUiLCJ1bnZldHRlZE1lcmNoYW50IjpmYWxzZSwiYnJhaW50cmVlQ2xpZW50SWQiOiJtYXN0ZXJjbGllbnQzIiwiYmlsbGluZ0FncmVlbWVudHNFbmFibGVkIjp0cnVlLCJtZXJjaGFudEFjY291bnRJZCI6ImFjbWV3aWRnZXRzbHRkc2FuZGJveCIsImN1cnJlbmN5SXNvQ29kZSI6IlVTRCJ9LCJjb2luYmFzZUVuYWJsZWQiOmZhbHNlLCJtZXJjaGFudElkIjoiMzQ4cGs5Y2dmM2JneXcyYiIsInZlbm1vIjoib2ZmIn0=";
NSLog(#"here we go again");
self.braintreeClient = [[BTAPIClient alloc] initWithAuthorization:clientToken];
// As an example, you may wish to present our Drop-in UI at this point.
// Continue to the next section to learn more...
// }] resume];
NSLog(#"HMM");
[self here];
}
-(void)here{
BTDropInViewController *dropInViewController = [[BTDropInViewController alloc]
initWithAPIClient:self.braintreeClient];
dropInViewController.delegate = self;
// This is where you might want to customize your view controller (see below)
// The way you present your BTDropInViewController instance is up to you.
// In this example, we wrap it in a new, modally-presented navigation controller:
UIBarButtonItem *item = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(userDidCancelPayment)];
dropInViewController.navigationItem.leftBarButtonItem = item;
UINavigationController *navigationController = [[UINavigationController alloc]
initWithRootViewController:dropInViewController];
[self presentViewController:navigationController animated:YES completion:nil];
}
- (void)userDidCancelPayment {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dropInViewController:(BTDropInViewController *)viewController
didSucceedWithTokenization:(BTPaymentMethodNonce *)paymentMethodNonce {
// Send payment method nonce to your server for processing
[self postNonceToServer:paymentMethodNonce.nonce];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)postNonceToServer:(NSString *)paymentMethodNonce {
// Update URL with your server
NSURL *paymentURL = [NSURL URLWithString:#"https://your-server.example.com/checkout"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:paymentURL];
request.HTTPBody = [[NSString stringWithFormat:#"payment_method_nonce=%#", paymentMethodNonce] dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPMethod = #"POST";
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// TODO: Handle success and failure
}] resume];
}
#end
Can anyone help explain how I fix this problem?
Here is Braintrees documentation I am following
https://developers.braintreepayments.com/start/hello-client/ios/v4#present-drop-in-ui
The problem can be rectified by presenting the view controller outside the super viewDidLoad method
Specifically viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showDropIn(clientTokenOrTokenizationKey: clientToken)
}
I have a method (requestData) that can be called several times in my ViewController but the first time the ViewController is loaded (in ViewDidLoad method) I need to call it two times BUT the second request should be sent only after the first request has completed:
- (void)viewDidLoad {
[super viewDidLoad];
dataForPlot = 1;
[self requestData: dataForPlot];
dataForPlot = 2;
[self requestData: dataForPlot];
}
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
}
else if (forPlot == 2) {
...
}
}
}
I know I probably need to use blocks but, even if I've tried to read some tutorials, I don't know how.
Anyone could help me ?
Thanks, Corrado
Here is what I've implemented following Duncan suggestion:
typedef void(^myCompletion)(BOOL);
- (void)viewDidLoad {
[super viewDidLoad];
[self requestData:^(BOOL finished) { // first request
if(finished) {
NSLog(#"send second request");
[self requestData: ^(BOOL finished) {}]; // second request
}
}];
- (void) requestData: (myCompletion) compblock {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
...
NSLog(#"request completed");
compblock(YES);
}
}
Don't call the second request until the first completes:
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
dataForPlot = 2;
[self requestData: dataForPlot];
}
else if (forPlot == 2) {
...
}
}
}
Refactor your requestData method to take a completion block.
In your requestData method, When the url request completes, invoke the completion block.
In viewDidLoad, use a completion block that calls requestData a second time, this time with an empty completion block.
In your other calls to the requestData method, pass in a nil completion block (or whatever other action you need to trigger when the request finishes)