Presenting MFMailComposeViewController from NSObject - ios

I am trying to present MFMailComposeViewController from NSObject subclass. Scenario is:
I have UIViewController subclass at what user tap a button to start some process
All logic for this process is taken out of this ViewController to the NSObject subclass
If something wrong with the process I am showing UIAlertView
One of the buttons in this alertView should open mailComposer for user to send feedback to me.
If I am trying present mailComposer from NSObject subclass I am getting "trying to present modal view controller what is not in class hierarchy". So I am setting my viewController as delegate for MFMailComposeViewController. But by touching a button "Send feedback" on alertView it disappears and I am getting the same "trying to present modal view controller what is not in class hierarchy" error. I am trying to log what is the rootViewController this way:
UIWindow *window = [UIApplication sharedApplication].keyWindow;
UIViewController *rootViewController = window.rootViewController;
NSLog(#"rvc - %#", [rootViewController description]);
but log shows to me that rvc is equal to (null). Can I present mailController from NSObject directly, or how it can be done in right way?

The object which presents the MFMailComposeViewController must be part of the view hierarchy, as the error message indicates. In other words, it must be a view controller that has a view currently visible. Your NSObject-derived class that has all your logic should have a delegate back to the UIViewController which it manages that it can call to tell it to display the MFMailComposeViewController. This pattern is integral to iOS development and is covered in the View Controller Programming guide. Here's an example:
#protocol ACFeedback <NSObject>
- (void)showFeedbackMailMessage;
#end
#interface ACDataThing : NSObject
#property (nonatomic, weak) id<ACFeedback> feedbacker;
#end
#interface ACEmptyViewController : UIViewController<ACFeedback>
...
When you instantiate ACDataThing, set the feedbacker property to the ACEmptyViewController reference, then in ACEmptyViewController implement the method showFeedbackMailMessage to create and show the MFMailComposeViewController.

Related

How to segue back from one view controller to another within a custom UIView?

I want to segue back from ViewControllerTwo to ViewControllerOne. I created a button that is responsible for doing that, but my problem is that the button is part of custom UIView class that is added to ViewControllerTwo, the button is not a part of the main view of ViewControllerTwo.
So in the custom UIView class I have the method that reacts if the button is clicked...
-(void)buttonClicked{
[SecondViewController performSegueWithIdentifier: "ShowFirstViewController" sender:nil];
}
When I do this I get an error: "performSegueWithIdentifier not a method of class" which makes sense.
So how can I segue between two viewcontrollers where the button responsible for the segue is not actually part of either view controller and is in a different class.
I think you can have a delegate call back to your SecondViewController and implement the performSegueWithIdentifier in the delegate callback method in SecondViewController.
It goes like this:
Above your custom UIView class interface create a protocol like this
#protocol CustomViewDelegate <NSObject>
- (void)buttonDidTap;
#end
Then create a property in your interface
#property (nonatomic, weak) id <CustomViewDelegate> delegate;
In your custom UIView *.m add this
-(void)buttonClicked{
[self.delegate buttonDidTap];
}
Conform the protocol to your SecondViewController like this
#interface SecondViewController: UIViewController <CustomViewDelegate>
set the delegate in your viewDidLoadMethod like this
-(void)viewDidLoad{
[super viewDidLoad];
self.yourCustomView.delegate = self;
}
implement this method inside the view controller .m file
- (void)buttonDidTap{
[self.performSegueWithIdentifier: "ShowFirstViewController" sender:self];
}
I'm more of a swift guy i think this should work fine.
iOS 9.3, Xcode 7.3, ARC enabled
This is what I'd do to troubleshoot:
Step 1: Make sure that you have a proper storyboard identifier for the view controllers you wish to segue between. The views simply attach to the view controllers, custom or not.
To do this, go to "*.storyboard" show the Utilities (right pane) and navigate to the Identity Inspector. Make sure you have "ShowFirstViewController" entered in the Storyboard ID field.

Understanding the mechanism when passing data back from a second view controller to main view controller

I'm currently trying to have a better understanding on how the mechanisms of passing data between controllers work and I'm a little confused especially when passing data back from a second view controller to the main view controller.
This is what I have that works but don't fully understand. I have two view controllers, in the first one I have a button that when clicked it basically goes to the second view controller and a label which shows a message sent from the second view controller. In the second view controller I have a button and a textField, the button basically sends whatever is in the textfield to the label in main view controller.
Here is the code...
// FirstVC.h
#import <UIKit/UIKit.h>
#import "SecondVC.h"
#interface FirstVC : UIViewController <passNames>
#property (nonatomic, strong) NSString* firstNameString;
#property (weak, nonatomic) IBOutlet UILabel *firstNameLabel;
#end
//FirstVC.m
#import "FirstVC.h"
#implementation FirstVC
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier]isEqualToString:#"secondController"])
{
UINavigationController *navController = segue.destinationViewController;
SecondVC *vc2 = (SecondVC*)navController.topViewController;
[vc2 setDelegate:self];
}
}
-(void)viewWillAppear:(BOOL)animated
{
self.firstNameLabel.text = _firstNameString;
}
-(void)setFirstName:(NSString *)firstName
{
_firstNameString = firstName;
}
#end
//SecondVC.h
#import <UIKit/UIKit.h>
#protocol passNames <NSObject>
-(void)setFirstName:(NSString*)firstName;
#end
#interface SecondVC : UIViewController
#property (retain)id <passNames> delegate;
- (IBAction)send:(UIBarButtonItem *)sender;
#property (nonatomic, strong) NSString *firstNameString;
#property (weak, nonatomic) IBOutlet UITextField *firstNameText;
#end
//SecondVC.m
#import "SecondVC.h"
#import "FirstVC.h"
#interface SecondVC ()
#end
#implementation SecondVC
- (IBAction)send:(UIBarButtonItem *)sender
{
_firstNameString = _firstNameText.text;
[[self delegate]setFirstName:_firstNameString];
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Can someone explain how the prepareForSegue method works in the above code? The reason for this question is because I added an NSLog and it looks like this method is only called in the transition from main view controller to the second controller. Why is this method needed if it is not called when transitioning from second view controller to main view controller which in my case is what I'm doing? It makes sense to use it when passing data from main view controller to a second controller not on the case shown above.
Can some explain the whole mechanism when passing data back to the main view controller?
FYI, I do understand about protocols and delegation.
Thanks a lot.
In your case, you are setting your delegate method of the second view controller to self in mainViewController in you prepareForSegue. This means that apart from navigating to the SecondViewController, you are implementing the callback mechanism in your main view controller, so that your delegate method gets called when the value is passed from the second view controller and this delegate method collects the value as a parameter to handle it in the main View Controller. You might have set the delegate of VC2 as self inn your prepareForSegue because you are creating the instance of VC2 in this method to navigate to the second controller.
Your goal is to hand back the data, like this:
[[self delegate] setFirstName:_firstNameString];
But you can't do that unless you know who to send setFirstName: to, and the compiler won't let you do it unless you guarantee that whoever you are sending setFirstName: to can accept that message.
That is what prepareForSegue prepares. FirstVC has declared that it adopts the passNames protocol, which means that it implements setFirstName:. And now you are saying:
[vc2 setDelegate:self];
...where self is the FirstVC instance. This solves both problems at once. The SecondVC instance (vc2) now has a delegate (the FirstVC instance), it is the right object to send the info back to, and because its delegate is declared as adopting passNames, we know that SecondVC can actually send setFirstName: to that delegate.
Now to the heart of your actual question: The reason for doing this in prepareForSegue is merely that this is the only moment when the FirstVC instance and the SecondVC instance "meet" one another! There is no other moment when the FirstVC instance has a reference to the SecondVC instance so as to be able to call setDelegate on it in the first place. If you weren't using segues and storyboards, the FirstVC would simply create the SecondVC instance directly - and would set itself as its delegate, just as you do:
SecondVC *vc2 = [SecondVC new];
UINavigationController *nav = [
[UINavigationController alloc] initWithRootViewController: vc2];
[vc2 setDelegate:self];
[self presentViewController: nav animated: YES completion: nil];
This is one reason I don't like storyboards: they muddy the story. It's all so simple and obvious when you don't use them and just do everything directly like this.

initWithNibName help: returning blank screen when trying to pass data

Ok so I am trying to pass a string from one view controller to another via the AppDelegate. I want to stay on the current view while this happens.
This is the main body of the code I am currently using to do this:
AppDelegate *dataCenter = (AppDelegate *)[[UIApplication sharedApplication] delegate];
MyMealViewController *vc = [[MyMealViewController alloc] initWithNibName:nil bundle:nil];
dataCenter.selectedMenuItem = recipeLabel.text;
[self presentViewController:vc animated:YES completion:NULL];
When I run the program I am able to confirm that the string is correctly passed. However, then the view on the simulator just turns black. I assume that this is because initWithNibName is set to nil.
So my question is: how should I change my code so that the string will still be passed, but the current view will continue to be displayed on the iphone. Is there a line of code that I could write that would just reload the current view?
Thanks for your help with this issue. I am new to xcode so I may be making a very basic error. Please let me know if any additional information would be helpful in answering this question.
Edit: It looks like you want to show a list of food items in the first view. Tapping an items opens a detail view. From that detail view, the user can press a button to add it to the meal. Eventually, they can tap a button on the first view to open the meal view, which should contain all of the items that they selected.
If this is the case, keep an array on the first view controller, and make sure the detail (second) view controller has a reference to the first view controller when it is presented. This will let us use that array. Note that there are better ways to architect this, but this will work for now:
#interface FoodListViewController : UIViewController
#property (strong, nonatomic) NSMutableArray *foodItems
#end
#implementation FoodListViewController
- (void)showFoodItem
{
FoodItemDetailViewController *detailViewController = [[FoodItemDetailViewController alloc] initWithNibName:nil bundle:nil];
detailViewController.foodListController = self;
[self presentModalViewController:detailViewController animated:YES];
}
#end
Once the detail view is presented, tapping the 'add to meal' button should add the current 'mealItem' to the array. In your example, you were using strings - if you would rather keep an array of strings for some reason, I'll leave that to you.
#interface FoodItemDetailViewController : UIViewController
#property (nonatomic, weak) FoodItemsViewController *foodListController;
#end
#implementation FoodItemDetailViewController
- (IBAction)buttonTapped:(id)sender
{
[self.foodListController.foodItems addObject:self.mealItem];
// Update the UI to let the user know that the item was added to the meal
}
#end
Finally, when it comes time to present the MealDetailsViewController, just pass it the array that you have been building:
#interface MealDetailsViewController : UIViewController
#property (nonatomic, strong) NSArray *foodItems;
#end
#implementation MealDetailsViewController
// Set foodItems before this view controller is presented, then use it to drive the
// UITableView data source, or find some other way of displaying it.
#end
As you can see, both the second and third view controllers are presented by the first. View controllers (nearly) always form a hierarchy - so keeping your data at the top of that hierarchy (by storing it in FoodListViewController) lets you neatly pass it down the hierarchy as you present other view controllers.

iPhone simple question: How to call a method from the main-class?

I have following code:
LoginViewController *lvc = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self presentModalViewController:lvc animated:false];
[lvc release];
That is called from my MainViewController.
Now, when the LoginViewController will be dismissed (of course this only happens when the login is correct) I must call a method in my MainViewController to load the initial data for my app.
I read a lot about delegate and tried it, but don't get it to work.
Could someone help me please?
(if possible, please with a few lines of code)
Any help is welcome!
I read a lot about delegate and tried it, but don't get it to work.
What have you tried really? Your LoginViewController must define a simple delegate protocol, and your MainViewController must conform to it.
All you need to do is add something like this in LoginViewController.h above #interface:
#protocol LoginViewControllerDelegate
- (void)loginViewControllerDidFinish;
#end
Which declares a protocol with one method. Then add this between #interface and #end:
#property (nonatomic, assign) id <LoginViewControllerDelegate> delegate;
Which means your login view controller will have a property called delegate which will point to an instance of any class (that's what id means) that conforms to it's delegate protocol (the thing between < and >). Don't forget to #synthesize delegate; inside .m file.
Now what you need to do is inside MainViewController.h add to #interface line like this:
#interface MainViewController : UIViewController <LoginViewControllerDelegate>
Which tells the compiler your MainViewController class conforms to this LoginViewControllerDelegate delegate protocol. Now implement the - (void)loginViewControllerDidFinish; method inside MainViewController.m and before presenting the login view controller modally set it's delegate to self (login.delegate = self;). When you are done inside your login view controller, before you dismiss it, call the delegate method on your delegate:
[self.delegate loginViewControllerDidFinish];
And that's it. Any more questions?
Try this:
1) when pushing login view, set some flag in MainViewController
2) in method viewWillAppear in MainViewController check that flag from 1). If it is set then load the initial data and unset flag. Otherwise push LoginView.
You've got an UIApplicationDelegate, and it should have an instance variable that points to the MainViewController. Expose this instance variable via a property, say mainViewController (on your UIApplicationDelegate), and then you can access it like this:
[(MyUIApplicationDelegate *)[[UIApplication sharedApplication] delegate] mainViewController]

Programmatically add UINavigationController in UIViewController

I currently have a view set up as the following:
#interface BlogViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
UITableView *mainTableView;
}
#property (nonatomic, retain) UITableView *mainTableView;
As you can see, it has a UITableView inside of it that I load all of my data to. However, when I call the following function:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SingleBlogViewController *viewController = [[SingleBlogViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
//[self presentModalViewController:viewController animated:YES];
[viewController release];
}
nothing happens. For some reason my UITableView inside of my UIViewController isn't pushing the view. Is this because it isn't a UITableViewController? I tried changing it to that, and it still didn't work.
Am I missing something here?
I found the first parts of this blog post useful for showing how to create and use a UINavigationController programmatically without Interface Builder.
Some of the things I wish the docs and tutorials would have stressed to me:
When you create the UINavigationController you get a UIView and UINavigationBar for free (i.e. you don't need to separately add them and figure out how to hook them together).
You add the myUINavigationController.view property as the "main" view and then push/pop UIViewControllers onto the UINavigationController and they will automatically show up as visible in the myUINavigationController.view UIView.
When you push a UIViewController onto a UINavigationController, the UIViewController.navigationController is filled in. If you haven't pushed the view onto the navigation controller, I'm guessing that property is empty.
The time/place to programmatically add buttons to the UINavigationBar is not when and where you construct the UINavigationController, but rather by the individual UIViewControllers when they are loaded as you push onto the UINavigationController.
I only had to add a "Done" button to the UINavigationController's UINavigationBar in the viewDidLoad method of the first UIViewController pushed onto the UINavigationController. Any UIViewController I pushed on top of that first view automatically had a "back" button in the to navigate to the covered up UIViewController.
If you set the title property of your UIViewController before you put it on the UINavigationController's stack. The UINavigationBars title will be the title of your view. As well any "back" buttons will automatically name the view you are returning to instead of generically saying "back".
Please visit "How to add UINavigationController Programmatically"
Please visit above link & get all information regarding UINavigationController.
UINavigationController is a subclass of UIViewController, but unlike UIViewController it’s not usually meant for you to subclass. This is because navigation controller itself is rarely customized beyond the visuals of the nav bar. An instance of UINavigationController can be created either in code or in an XIB file with relative ease.

Resources