I have next simple code, for downloading info from service.
UIViewController *nextController;
[request authWithBlock:^(NSDictionary *result, NSError *error)
{
// Result of auth request
if (!nextController) nextController = [UIViewController alloc] init];
[navigationController pushViewController: nextController];
}];
And I need show next view when request return success.
I want to know - this code are correct, or I must create and show viewcontroller in another place, not in block?
Thanks
Your code format is correct. But you can improve your code by below
__weak typeof(self) weakSelf = self;
[request authWithBlock:^(NSDictionary *result, NSError *error)
{
// Result of auth request
if (!error)
{
UIViewController *nextController = [UIViewController alloc] init];
// pass result to nexview controller if you want..
[weakSelf.navigationController pushViewController: nextController];
}
}
Related
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'm trying to disable the navigation bar button when I start the app and after I finish the process(fetching data), I enable it back but unfortunately it won't enable.
Please where would be my issue? While I'm putting enable to YES and when I debug it I can see that it enabling it to YES.
- (void)viewDidLoad {
UIImage *searchBtn = [UIImage imageNamed:#"search_icon.png"];
barButtonSearch = [[UIBarButtonItem alloc] initWithImage:[searchBtn imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:self action:#selector(searchButton)];
UIImage *menuBtn = [UIImage imageNamed:#"menu_icon.png"];
barButtonMenu = [[UIBarButtonItem alloc] initWithImage:[menuBtn imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:self action:#selector(menuButton)];
self.navigationItem.rightBarButtonItem = barButtonMenu;
self.navigationItem.leftBarButtonItem = barButtonSearch;
barButtonMenu.enabled = NO;
barButtonSearch.enabled = NO;
}
- (void)unhide{
if (!(barButtonSearch.enabled && barButtonMenu.enabled)) {
barButtonMenu.enabled = YES;
barButtonSearch.enabled = YES;
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ViewController *theInstance = [[ViewController alloc] init];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
_dic = (NSDictionary *)responseObject;
[theInstance unhide];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Err");
}];
[operation start];
return YES;
}
}
Well there it is!
You are initializing your ViewController but that doesn't call your viewDidLoad: method. The viewDidLoad: method gets called when your ViewController is either Pushed or Presented! That is the time when the view gets loaded into the memory.
Therefore, the barButtons are never created and you are unable to see them.
So either make your network call inside the viewDidLoad: method of your ViewController
OR
Push the instance of your ViewController and then call the method unhide.
Edit
Since you are using Storyboards and not pushing any ViewController from AppDelegate, you need to use reference of your ViewController.
replace this in your - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method
ViewController *theInstance = (ViewController *)[(UINavigationController*)self.window.rootViewController topViewController];
You are calling [theInstance unhide] from the completion block of the AFHTTPOperation - this will almost certainly be executed on a background queue.
All UI operations must be performed on the main queue.
You should use -
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
_dic = (NSDictionary *)responseObject;
dispatch_async(dispatch_get_main_queue(),^{
[theInstance unhide];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Err");
}];
Update
Your main problem is that theInstance points to an instance of your view controller that isn't on the screen - It is just an instance you have allocated but not actually presented.
Assuming this view controller is the initial view controller loaded by your application you can get a reference to the correct instance using [UIApplication sharedApplication].keyWindow.rootViewController
Remove this method from the completion block of app delegate.
[theInstance unhide];
And add some delegate function which will be activated after the asynchronous call completes its task. And add that unhide method there (In your view controller may be).
I want to send a bool value, didAddNewItem, from my SearchViewController to MatchCenterViewController, and then run a function depending on the state of the bool value. I attempt to send a didAddNewItem value of YES to my destination, MatchCenterViewController, but it doesn't seem to send correctly, as the function below never runs.
Here's how I'm sending it from SearchViewController (edited to reflect Rob's answer):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowMatchCenterSegue"]) {
_didAddNewItem = YES;
MatchCenterViewController *controller = (MatchCenterViewController *) segue.destinationViewController;
NSLog(#"we're about to set controller values before segueing to MC");
// Send over the matching item criteria
controller.itemSearch = self.itemSearch.text;
controller.matchingCategoryId = self.matchingCategoryId1;
controller.matchingCategoryMinPrice = self.matchingCategoryMinPrice1;
controller.matchingCategoryMaxPrice = self.matchingCategoryMaxPrice1;
controller.matchingCategoryCondition = self.matchingCategoryCondition1;
controller.matchingCategoryLocation = self.matchingCategoryLocation1;
controller.itemPriority = self.itemPriority;
[self.tabBarController setSelectedIndex:1];
}
}
And here's where I try to make use of it in the destination, MatchViewController:
- (void)viewDidAppear:(BOOL)animated
{
if (_didAddNewItem == YES) {
NSLog(#"well then lets refresh the MC");
// Start loading indicator
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview: activityIndicator];
[activityIndicator startAnimating];
// Disable ability to scroll until table is MatchCenter table is done loading
self.matchCenter.scrollEnabled = NO;
_matchCenterDone = NO;
// Add new item to MatchCenter Array with the criteria from the matching userCategory instance, plus the search term
[PFCloud callFunctionInBackground:#"addToMatchCenter"
withParameters:#{
#"searchTerm": self.itemSearch,
#"categoryId": self.matchingCategoryId,
#"minPrice": self.matchingCategoryMinPrice,
#"maxPrice": self.matchingCategoryMaxPrice,
#"itemCondition": self.matchingCategoryCondition,
#"itemLocation": self.matchingCategoryLocation,
#"itemPriority": self.itemPriority,
}
block:^(NSString *result, NSError *error) {
if (!error) {
NSLog(#"'%#'", result);
self.matchCenterArray = [[NSArray alloc] init];
[PFCloud callFunctionInBackground:#"MatchCenter3"
withParameters:#{}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[_matchCenter reloadData];
[activityIndicator stopAnimating];
// Reenable scrolling/reset didAddNewItem bool
_matchCenterDone = YES;
self.matchCenter.scrollEnabled = YES;
//_didAddNewItem = NO;
NSLog(#"Result: '%#'", result);
}
}];
}
}];
}
}
I made sure it was properly setup as a property in the headers of both ViewControllers, so I'm not sure why it's not setting the value in the destination VC correctly. I know for a fact that addToMatchCenter function is running correctly without error, so it should be working.
#property (assign) BOOL didAddNewItem;
In your prepareForSegue, you are calling callFunctionInBackground asynchronously, meaning that it is quite likely that the segue will finish and the new view controller will be presented well before you set didAddNewItem in the block of callFunctionInBackground.
I'd be inclined to change that destination controller to initiate this asynchronous request itself, but have it show a UIActivityIndicatorView (or something) to suggest that the dependent request has not yet been finished, and then in the block you can remove the activity indicator view and update the UI accordingly.
So I'm trying to log-in/signup users using ACAccountStore. This happens using a view controller that presented modally. It works just fine that way, however, when I dismiss the view controller, the underlying/presenting view controller is still a black window. I assume this happens, because I do not wait for the completion block to finish.
So my question: How do I wait for the completion block to finish before calling [self dismissViewControllerAnimated:YES completion:nil];?
-(void)loginWithTwitter{
ACAccountStore *account = [[ACAccountStore alloc] init];
ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:
ACAccountTypeIdentifierTwitter];
[account requestAccessToAccountsWithType:accountType options:nil
completion:^(BOOL granted, NSError *error)
{
if (granted) {
//do something -> call function to handle the data and dismiss the modal controller.
}
else{
//fail and put our error message.
}
}];
}
Completion block is that thing that will be executed after the main process (access to accounts request in this case) is finished. So you can put [self dismissViewControllerAnimated:YES completion:nil] in it.
Another thing: it is bad to have reference to self in block because of retain cycles. You would modify your code to look like this:
ACAccountStore *account = [[ACAccountStore alloc] init];
ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:
ACAccountTypeIdentifierTwitter];
__weak UIViewController *weakSelf = self;
[account requestAccessToAccountsWithType:accountType options:nil
completion:^(BOOL granted, NSError *error) {
[weakSelf dismissViewControllerAnimated:YES completion:nil];
if (granted) {
//do something -> call function to handle the data and dismiss the modal controller.
}
else {
//fail and put our error message.
}
}];
I've been pulling my hair out a bit over this. I'm creating a very simple app, it simply downloads an rss feed and displays it in a UITableview, which is inside a UINavigationController. Whilst it's downloading the feed I'm presenting a Modal View.
In my modal view I'm displaying a UIImageView and a UIActivityIndicatorView that is set to spin. I'm using ASIHTTRequest to asynchronously grab the feed and then using the either the completion block to get the response string and stop the spinner or the failure block to get the NSError and display a alert View. This all works perfectly.
I've then created a protocol to dismiss the modal view from the tableview which is called inside the completion block. But the modal view is never dismissed! I've tried pushing it into the navigation controller but exactly the same problem occurs. I even have tried setting the modal view delegate to nil but still no luck.
I've checked it without blocks using the ASIHTTPRequest delegate methods and it's the same, and if I don't present the modal view the table view is displayed normally.
Any Ideas? I've skipped out all the tableview delegate and datasource methods as well as the dealloc and any unused functions.
#interface MainTableViewController ()
-(void)loadModalView;
#end
#implementation MainTableViewController
#synthesize tableView;
#synthesize modalView;
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
[super loadView];
tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) style:UITableViewStylePlain];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
[self loadModalView];
}
-(void)loadModalView
{
modalView = [[ModalViewController alloc]init];
modalView.delegate = self;
[self presentModalViewController:modalView animated:NO];
}
//Modal View Delegate
-(void)downloadComplete
{
modalView.delegate = nil;
[self dismissModalViewControllerAnimated:NO];
}
#end
#interface ModalViewController ()
- (void)loadView
{
[super loadView];
backgroundImage = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
[self.view addSubview:backgroundImage];
spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.frame = CGRectMake(160, 240, spinner.bounds.size.width, spinner.bounds.size.height);
spinner.hidesWhenStopped = YES;
[self.view addSubview:spinner];
[spinner startAnimating];
NSString* urlString = FEED_URL;
NSURL* url = [NSURL URLWithString:urlString];
ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
[spinner stopAnimating];
[delegate downloadComplete];
// Use when fetching binary data
}];
[request setFailedBlock:^{
NSError *error = [request error];
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:#"Error" message:error.description delegate:self cancelButtonTitle:#"Continute" otherButtonTitles: nil];
[alert show];
[alert release];
}];
[request startAsynchronous];
}
Matt
In my understanding.. you solution is quite complicated..
wouldn't it be better if the class MainTableViewController is the
one who downloads the Feeds.. for the ModalView it will just act as an ActivityIndicator and dismiss after downloading..
so inside your MainTableViewController loadview:
- (void)loadView
{
NSString* urlString = FEED_URL;
NSURL* url = [NSURL URLWithString:urlString];
ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:url];
[request startAsynchronous];
//after starting the request show immediately the modalview
modalView = [[ModalViewController alloc]init];
[self presentModalViewController:modalView animated:NO];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
//then when it is complete dissmiss the modal
[modalView dismissModalViewControllerAnimated:NO];
// Use when fetching binary data
}];
[request setFailedBlock:^{
NSError *error = [request error];
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:#"Error" message:error.description delegate:self cancelButtonTitle:#"Continute" otherButtonTitles: nil];
[alert show];
[alert release];
}];
}
i didnt use blocks in my projects, but i think it will work the same..
also I use a plain UIActivityIndicatorView (large) as subviews not modalViews.. sadly i cant test the code here now.. but i can check it later though
The only way I solved this error was to synchronously download the data and push and pop the download view onto the navigation stack. Not ideal but it works.