The scenario is that I have more than one view that wants to invoke the Address Book. So as not to duplicate the code of the delegate in each view I have located the code in the App Delegate's header and .m file, but using an "#interface AddressBookDelegate" and "#implementation AddressBookDelegate" at the bottom of the 2 respective App Delegate fiies-
#interface AddressBookDelegate : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
AddressBookDelegate *addressBookDelegate;
}
#property (nonatomic, retain) AddressBookDelegate *addressBookDelegate;
#end
and
#implementation AddressBookDelegate
#synthesize addressBookDelegate;
- (void)peoplePickerNavigationControllerDidCancel: (ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
[super dismissModalViewControllerAnimated:YES];
...get stuff from the Address Book...
return NO;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
return NO;
}
Then in my views I have the following code:
addressBookDelegate = (AddressBookDelegate *) [[UIApplication sharedApplication] delegate];
ABPeoplePickerNavigationController *abPicker = [[ABPeoplePickerNavigationController alloc]init];
abPicker.peoplePickerDelegate = self.addressBookDelegate;
[self presentModalViewController:abPicker animated:YES];
[abPicker release];
The Address Book displays fine in all views. But when I take any user action that would invoke a delegate, like the Address Book's Cancel button, I crash-
-[MyprogAppDelegate peoplePickerNavigationControllerDidCancel:]: unrecognized selector sent to instance
It compiles clean, no Warnings.
How do I wire-up the peoplePickerDelegate to connect to the Address Delegate code when it is not physically in the same file as the view itself ? Thx.
ADDED NOTE: when I use the debugger and stop on the line
abPicker.peoplePickerDelegate = addressBookDelegate;
in the view code, I see that the address for the addressBookDelegate is stated to be the address of the MyprogAppDelegate, not AddressBookDelegate as I might have expected. That makes me think the displacement to the address book delegate code is off within the App Delegate file.
If the AddressBookDelegate Cancel Delegate code were say 1000 bytes into the AddressBookDelegate, my app is actually "entering" the code 1000 bytes into MyprogAppDelegate, and so crashes. So somehow I am not setting up the addressing of the AddressBookDelegate correctly. That's my take on it anyway...
Your code assumes that your appdelegate (MyprogAppDelegate) implements method peoplePickerNavigationControllerDidCancel.
So, your code in MyprogAppDelegate should be something like this:
#implementation MyprogAppDelegate
#synthesize ...;
#pragma mark -
#pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker{
}
EDIT Okay, the entire first answer has been tossed out. This is, with some warning, still a bit of a shot in a dark, but I think it's going to be closer to helpful. Some of the ideas do carry over though.
You very probably don't need a separate class to act as your ABPeoplePickerNavigationControllerDelegate. In all likelihood, it should be the same class that has your code at the bottom (that calls presentModalViewController:animated:. Since I don't know what controller that was, I'm going to just call it MyViewController for reference. The reason you want that view controller to be the delegate is because, in your delegate methods, you need to be able to dismiss the modal view controller that has the address book.
You definitely don't want the your program's UIApplicationDelegate to be the ABPeoplePickerNavigationControllerDelegate. As you said yourself, peoplePickerDelegate has to be a UIViewController.
So, to MyViewController. First, the interface:
/* MyViewController.h */
#interface MyViewController : UIViewController<ABPeoplePickerNavigationControllerDelegate>
...
#end
Your controller might inherit from a descendant of UIViewController (like a table view controller or something like that) - that shouldn't change, the only thing that should change is adding the ABPeoplePickerNavigationControllerDelegate to the list of implemented protocols.
Now, to implement the functionality:
/* MyViewController.m */
#implementation MyViewController
...
- (void) whateverMethodIsDisplayingTheAddressBook
{
ABPeoplePickerNavigationController *abPicker = [[ABPeoplePickerNavigationController alloc]init];
abPicker.peoplePickerDelegate = self; // This view controller is the delegate
[self presentModalViewController:abPicker animated:YES];
[abPicker release];
}
...
- (void)peoplePickerNavigationControllerDidCancel: (ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
[super dismissModalViewControllerAnimated:YES];
...get stuff from the Address Book...
return NO;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
return NO;
}
#end
In the end I was not able to make any of the above suggestions perform as hoped for. I had to cut time and move on so I duplicated the code in each view. I will revisit this another time, as I am sure it can be done in a more object based way than I ended it doing it.
Related
I am a little new to iOS development, coming from a Java / Android background. My understanding is that your custom Protocols or Delegates are like Interfaces in Java land.
If that is the case then I believe these Protocols are also Objects as well.
Case:
Assume 2 ViewControllers, Home and Profile.
1 Presenter, let's call it StuffPresenter gets instantiated individually in both ViewControllers.
StuffPresenter has an initialization method called initWithInteractor that takes in a parameter of Interactor which is a protocol.
Both Home and Profile implement a Protocol called Interactor, which has a method called initStuffInTableView(NSMutableArray *)stuff.
So I have a dilemma where if I am in Home and StuffPresenter relays information then I switch over to Profile, StuffPresenter loads stuff in Home as well as Profile.
Why is this the case?
Here is the code I have setup:
Protocol
#protocol Interactor <NSObject>
- (void)initStuffInTableView:(NSMutableArray *)stuff;
#end
Presenter
#interface Presenter : NSobject
- (id)initWithInteractor:(id<Interactor>)interactor;
- (void)loadStuff;
#end
#implementation {
#private
id<Interactor> _interactor;
}
- (id)initWithInteractor:(id<Interactor>)interactor {
_interactor = interactor;
return self;
}
- (void)loadStuff {
// Load stuff
NSMutableArray *stuff = // Init stuff in array...
[_interactor initStuffInTableView:stuff];
}
#end
Home
#interface HomeViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
[self initPullToRefresh];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
}
- (void)initPullToRefresh {
// Init pull to refresh
// ...
[self.refreshControl addTarget:self
action:#selector(reloadStuff)
forControlEvents:UIControlEventValueChanged];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - reloadStuff
- (void)reloadStuff {
[_stuffPresenter loadStuff];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Profile
#interface ProfileViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
[_stuffPresenter loadStuff];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Problem:
When I go to Profile the app crashes, because initStuffInTableView is being called in Home. Why is this the case?
A protocol is an Objective-C language feature for specifying that a Class (or another protocol) has certain features, for the benefit of the compiler/ARC/the programmer.
A delegate, or delegation, is a design pattern which makes Model View Controller easier. To make the object doing the delegation be more flexible, generally its delegate adopts a protocol.
There are a number of issues in your code:
Your Presenter has a reference cycle with its interactors
You need to call some init method that eventually calls [super init] in your Presenter's initWithInteractor: method.
As others have pointed out, your methods which begin with init violate Objective-C conventions.
It's hard to tell from what you've posted exactly what your problem is, but I'm very suspicious of how it's structured.
You have a single class (Presenter), which you make two instances of, and pass no parameters other than the Interactor.
How could each instance know to load different "stuff" based on which View controller it received as a parameter?
I still want to understand thoroughly what is going on here, but I did solve the problem by doing the following.
In both Home and Profile, I init the StuffPresenter here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animation];
[self initPresenter];
}
And when exiting a controllerview I do the following:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_stuffPresenter = nil;
}
I've spent a few hours on this trying to work it out myself but I give up!
I have a master-detail arrangement where the user input screen needs to call a function on another class to post to a web service. Upon completion of the asynchronous call, the class will then call a specified function. In this case, I'm just testing and all I want to do is go back to the main screen after the user input is accepted by the web service.
When the uses taps a button on the input screen (SetLocationViewController), the asynchronous operation is called in the class APIPostClass. After it is complete, I want SetLocationViewController to segue back to MasterViewController.
In APIPostClass.m in (called after the asynchronous op finishes)
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}
In SetLocationViewController.m
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
Calling doSegue from an action on SetLocationViewController.m does work so I know my segue is ok but the above doesn't work. I get the error reason: 'Receiver () has no segue with identifier 'SetLocationViewControllerManualUnwind''
I'm guessing the reason is because of the alloc init way of initialising of the VC, but I don't know any better. Thus, how can I call a function on another class as if it was being called by it's own class?
Create a delegate it would be much more reliable and fast than Notifications.
#protocol APIPostDelegate <NSObject>
#required
-(void)OnRequestSucess;
#end
In your APIPost add new property for delegate
#interface APIPost : NSObject
#property (weak) id<APIPostDelegate> delegate;
In SetLocationViewController implement APIPostDelegate
SetLocationViewController.h
SetLocationViewController :NSObject<APIPostDelegate>
SetLocationViewController.m
-(void)OnRequestSucess
{
[self doSegue];
}
before you make call to method on APIPost, assign self to delegate property.
APIPost *apipost=[[APIPost alloc]init];
apipost.delegate=self;
[apipost <your api method>];
APIPost.m
[self.delegate OnRequestSucess];
Hope this helps.
There are a few methods to make it happens:-
Use Delegate
Use NSNotification.
The way described by Artur above (For SplitViewController Only - iPad)
You should use delegate whenever it is possible but it might not be too straight forward. NSNotification is more straight forward but it is not a good practice and not a good programming style.
I will only share the NSNotification method as it is easier to implement.
In SetLocationViewController.m
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSegue) name:#"calldoSegue" object:nil];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"calldoSegue" object:nil];
}
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
In APIPostClass.m
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
[[NSNotificationCenter defaultCenter]postNotificationName:#"calldoSegue" object:nil];
}
The above code should work but again, this is not a good practice. You should try to learn the Delegate method.
The answer is here: Performing segue from another class
In my APIPostClass.h, I setup the view controller:
#interface APIPostClass : NSObject {
SetLocationViewController *setLocationViewController;
}
#property(nonatomic, strong) SetLocationViewController *setLocationViewController;
#end
In my APIPostClass.m, I synthesize it:
#synthesize setLocationViewController;
then, instead of this (as in my question):
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}
I have:
-(void)callWhenDone {
NSLog(#"callWhenDone loaded");
[self.setLocationViewController doSegue];
}
Over in SetLocationViewController.m, the segue method remains unchanged:
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
But when I call my API, I need to "attach" (forgive my terminology) the view controller to it. This is what I had:
- (IBAction)btnTestAPICall:(id)sender {
NSLog(#"User tapped API button");
APIPostClass *APIPostClassInstance = [[APIPostClass alloc] init];
[APIPostClassInstance APICall: ... ....
}
But this is what works after bringing all of the above:
- (IBAction)btnTestAPICall:(id)sender {
NSLog(#"User tapped API button");
APIPostClass *APIPostClassInstance= [[APIPostClass alloc] init];
UIViewController *currentVC=self;
APIPostClassInstance.setLocationViewController = currentVC;
[APIPostClassInstance APICall: ... ...
I hope this will help someone else!
I'm trying to track down the source of a bug in a cordova/phonegap plugin I wrote for creating email messages in app using a MFMailComposeViewController instance.
Everyone works fine the first time you present the the composer view. The user can dismiss the mail composer by sending the message or canceling. However, call presentViewController again renders the Cancel and Send buttons in the composer to become useless. My delegate for didFinishWithResult is never calling when pressing the inoperable buttons with the second view of the controller.
Below is simplified repro of what I'm seeing (the simple storyboard has a single view containing a single UIButton wired to my (IBAction)sendMail). What am I doing wrong in obj-c here? Shouldn't I be able to show a controller, dismiss it, and show it again?
ViewController.h:
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#interface ViewController : UIViewController
#end
ViewController.m:
#import "ViewController.h"
#interface ViewController () <MFMailComposeViewControllerDelegate>
#property (nonatomic, weak) IBOutlet UIButton *mailButton;
#property(nonatomic, strong) MFMailComposeViewController* picker;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (IBAction)sendMail
{
[self presentViewController:self.picker animated:YES completion:NULL];
}
#end
The reason for the behavior you are experiencing is the MFMailComposeViewController nils it's delegate when dismissed (maybe in -viewDidDisappear:).
- (void)viewDidLoad
{
[super viewDidLoad];
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
// Put a break point here **#breakpoint1**
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (IBAction)sendMail
{
// Put a break point here **#breakpoint2**
[self presentViewController:self.picker animated:YES completion:NULL];
}
Place breakpoints at shown in the code comment above, run, and follow me as we step through your code.
Tap the interface button that calls your IBAction; execution halts at #breakpoint2
In the console type po self.picker
You'll see the mail compose VC instance is allocated
In the console type po self and then po self.picker.delegate
You'll see these both print the same object (the instance of your view controller)
Resume running, and tap the dismiss button on the mail compose view; execution halts at #breakpoint1
If you want to, inspect local and instance variables in console and then resume running
Tap the interface button that calls your IBAction (this is the second time); execution halts at #breakpoint2
In the console typ po self.picker.delegate
nil is printed to console
This delegate nil'ing behavior isn't documented in either Apple's MFMailComposeViewController class reference or the classes header. It's probably worth filing a bug report requesting clarification and better documentation. Because it's undocumented, the behavior may change in future releases. For that reason, the suggestions to create and destroy the VC as needed certainly seem like good common sense.
This bit me once before. It's caused by the composer being deallocated after it's done being dismissed. To solve this I would place the composer's creation either in viewDidAppear:, or in sendMail as Fahim suggested.
Additionally, you may want to consider wrapping these two lines in [MFMailComposeViewController canSendMail];
I would say take below lines to sendMail... it would work.
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
You will have as below.
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)sendMail
{
self.picker = [[MFMailComposeViewController alloc] init];
self.picker.mailComposeDelegate = self;
[self presentViewController:self.picker animated:YES completion:NULL];
}
#end
This is working with me...
I am new to iOS app development. I want to create a Calculator App in iOS that has split view. The left side is the "History" Feature in Scroll View and the right side is the calculator itself. Now, regarding the History feature of this app, I am thinking that my program needs to recognize what has been pressed and display it on the Scroll View when the Equal (=) button is pressed. Do you have any idea how will this go on Objective-C? I am using XCode 4.5 and iPhone Simulator 6.0.
Thanks in Advance!
If you want to communicate/send data between views or view controllers there are several options.
If you try to communicate/send data between views and you have reference to both views you can simply call the methods from your views for example
LeftView.h
#interface LeftView : UIView {
//instance variables here
}
//properties here
//other methods here
-(NSInteger)giveMeTheValuePlease;
#end
LeftView.m
#implementation LeftView
//synthesise properties here
//other methods implementation here
-(NSInteger)giveMeTheValuePlease {
return aValueThatIsInteger; //you can do other computation here
}
RightView.h
#interface RightView : UIView {
//instance variables here
}
//properties here
//other methods here
-(NSInteger) hereIsTheValue:(NSInteger)aValue;
#end
RightView.m
#implementation LeftView
//synthesise properties here
//other methods implementation here
-(void)hereIsTheValue:(NSInteger)aValue {
//do whatever you want with the value
}
AViewController.m
#implementation AViewController.m
//these properties must be declared in AViewController.h
#synthesise leftView;
#synthesise rightView;
-(void)someMethod {
NSInteger aValue = [leftView giveMeTheValuePlease];
[rightView hereIsTheValue:rightView];
}
You can use the delegate pattern (very very common in iOS), a short and basic example of delegate you can find in one of my SO answer at this link
You can also use blocks to communicate/send data between views/view controllers but this topic I think you will use a little bit later and for you will have to google a little bit in order to get a basic idea of iOS blocks.
Here is the solution for this requirement.
In my case.. I have 2 buttons in viewcontroller. When I click on those buttons I had to display popover. For this I had to detect which button is clicked in PopoverController(AnotherViewController).
First I have taken #property BOOL isClicked; in AppDelegate.h
And in AppDelegate.m #synthesize isClicked; (synthesized it) and in
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
isClicked = FALSE;
}
Now in ViewController.m where action is implemented for buttons changed like this,
- (IBAction)citiesButtonClicked:(id)sender
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
delegate.isClicked = FALSE;
}
- (IBAction)categoryButtonClicked:(id)sender
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
delegate.isClicked = TRUE;
}
PopoverViewController (AnotherViewController) in -(void)viewDidLoad method
-(void)viewDidLoad {
{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
if (delegate.isClicked)
{
delegate.isClicked = FALSE;
NSLog(#"popover clicked");
}
else
{
delegate.isClicked = TRUE;
isClicked = YES;
}
}
I hope it helps. Let me know if you need any help.
I'm using the TapJoy SDK for a game application on iOS. The SDK has a way to display a view on top of the application: http://knowledge.tapjoy.com/integration-8-x/ios/pb/featured-app
I can give the function a UIVIewController argument, so I can manage the show/hide by myself.
I have created the following UIViewVontroller:
#interface MyViewController : UIViewController
- (void) viewDidLoad;
- (void) viewDidUnload;
- (void) viewWillLoad;
- (void) viewWillUnload;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
- (void)viewWillDisappear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated;
#end
#implementation MyViewController
- (void) viewDidLoad
{
self.view = GRAPHIC_SYSTEM::GetGlView();
NSLog(#"viewDidLoad");
}
- (void) viewDidUnload
{
NSLog(#"viewDidUnload");
}
- (void) viewWillLoad
{
NSLog(#"viewWillLoad");
}
- (void) viewWillUnload
{
NSLog(#"viewWillUnload");
}
- (void)viewWillAppear: (bool)animated
{
NSLog(#"viewWillAppear");
}
- (void)viewDidAppear:(BOOL)animated
{
NSLog(#"viewDidAppear");
}
- (void)viewWillDisappear:(BOOL)animated
{
NSLog(#"viewWillDisappear");
}
- (void)viewDidDisappear:(BOOL)animated
{
NSLog(#"viewDidDisappear");
}
#end
When I'm notified by TapJoy that a feature app is available, I show it using my view controller:
[TapjoyConnect showFeaturedAppFullScreenAdWithViewController: [[MyViewController alloc] init]];
The TapJoy view is successfully displayed on top of my game.
There are 2 problems:
Only the viewDidLoad log is printed in the console. None of the other log messages are printed
I would like to know when the user has closed the TapJoy view, so I can add some processing at that time, but none of the other functions of the view controller are called.
I've seen here on SO that some users recommend to use the Notifications. Unfortunately, as I don't have access to the source code of the TapJoy SDK, I need to find another way.
Do you have any ideas?
Thanks in advance
Mike
Well I could fix the issue by creating a custom UIView, which I set as the UIViewController view.
Next, I have then overriden the willRemoveSubview function of this custom view.
And with the viewDidLoad function of the UIViewController, I know when the view is displayed, AND when the TapJoy view is removed, so I can remove my custom view too.