I have two Objective-C classes that inherit from the UIViewController and am trying a different approach at learning how to interact with the iPhone's address book. The example Apple provides assumes that everything is in one class, but this isn't the way I need it done. My objective would be to have the address book view close after a person is selected. Please have a look and let me know how i can accomplish this without having CallerClass implement ABPeoplePickerNavigationControllerDelegate. Thanks!
-- edit --
What it seems to be boiling down to is the [self dismissModalViewControllerAnimated:YES]; does not have any effect in CalleeClass.m. I still can't seem to get a reaction to close the address book from this command.
CallerClass.m
#import "CallerClass.h"
#implementation CallerClass
- (IBAction)openAddressBook {
CalleeClass *cc = [[CalleeClass alloc] init];
[self presentModalViewController:[cc doIt] animated:YES];
}
CalleeClass.h
#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#interface CalleeClass : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
NSString *name;
}
-(ABPeoplePickerNavigationController *)doIt;
#property (nontoxic, retain) NSString *name;
#end
CalleeClass.m
#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#import "CalleeClass.h"
#implementation CalleeClass
#synthesize name;
… (default ABPeoplePickerNaviationControllerDelegate implementation outside of what's listed)
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {}
return self;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {
self.name = (NSString *)ABRecordCopyValue(person,kABPersonAddressProperty);
[self dismissModalViewControllerAnimated:YES];
return NO;
}
-(ABPeoplePickerNavigationController *)doIt {
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
return picker;
}
#end
If the problem is, as you say, that [self dismissModalViewControllerAnimated:YES] has no effect if called from CalleeClass, this is because dismissModalViewControllerAnimated: must be called on the presenting view controller (i.e., the one on which you called presentModalViewController:Animated:. Since you don't have a reference to your CallerClass instance in CalleeClass, this doesn't work.
Fortunately, as the documentation for dismissModalViewControllerAnimated: notes:
If you call this method on the modal view controller itself, however, the modal view
controller automatically forwards the message to its parent view controller.
So this should work:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {
self.name = (NSString *)ABRecordCopyValue(person,kABPersonAddressProperty);
[peoplePicker dismissModalViewControllerAnimated:YES];
return NO;
}
Once you have identified the contact you want to act on you can just pass around the int32 recordID, although as mentioned in the API docs you should probably also use the composite name since as you will notice the recordID is a simple value that starts with "1" and you could run into trouble if your database was restored to a phone with new contacts in old recordID values. Every time you need to access something from the Address Book you do need to create the phone book but you can close it right after, so with recordID you can open, get what you want and then close it. My suggestion is just use the picker like a normal view controller up to the point where you get the recordID, dismiss it, then keep that unique identifier. Use picker again when you need to find a new recordID.
Related
I've currently got an iOS app where an item is randomly chosen from an array of items and is displayed on the screen. I've already built the "randomness" and display functionality into the app, but am now trying to set it up so that you can email the item from the array that is currently on the screen from within the app. For example, you hit the button and it randomly displays a number from 1-10. I'd like to be able for the user to email whatever number is randomly shown on the screen, with the email body pre-populated with the number on the screen. So, the user gets the number "3", hit's the email button and when the email compose comes up "3" is already pre-populated in the body.
I am having two issues, first is figuring out how to implement the email function code within my current code. I've already built a tester app that has a button that triggers the email compose to appear, and populate the body with some static text, so I've got a basic understanding of how the code works, but I do not know how to integrate it with the code I've already written.
My second issue is getting the body of the mail message to be pre-populated with the random number from the screen.
For the first problem here is what my ViewController.h looks like (I've already add the MessageUI framework)
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#interface ViewController : UIViewController {
NSArray *testArray;
}
- (IBAction)buttonGo:(UIButton *)sender;
#property (strong, nonatomic) IBOutlet UILabel *testLabel;
#property (strong, nonatomic) NSArray *testArray;
- (void) makePrediction;
#end
My ViewController.m looks like
#import "ViewController.h"
#import <MessageUI/MessageUI.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize testArray;
#synthesize testLabel;
- (void)viewDidLoad
{
[super viewDidLoad];
self.testArray = [[NSArray alloc] initWithObjects:#"number one",#"number `two",#'numberthree", nil];`
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)buttonGo:(id)sender {
NSUInteger index = arc4random_uniform(self.testArray.count);
self.testLabel.text = [self.testArray objectAtIndex:index];
}
- (void) makePrediction {
NSUInteger index = arc4random_uniform(self.
testArray.count);
self.testLabel.text = [self.testArray objectAtIndex:index];
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
- (void) motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
self.testLabel.text = #"";
}
- (void) motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if ( motion == UIEventSubtypeMotionShake ){
[self makePrediction];
}
}
- (void) motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {
NSLog(#"motion cancelled");
}
#end
The app runs fine, but I'm unsure where to implement my email code. I'm also unsure how to populate my email body with the random choice from my array. I am assuming it will have something to do with this bit of MessageUI
NSString * sentFrom = #"text in email body";
[myMail setMessageBody:sentFrom isHTML:YES];
This is the place to do your job as you will be holding text
- (IBAction)buttonGo:(id)sender
and for second issue
Note isHTML has to be set NO in case your text doesnt use it.
MFMailComposeViewController *mailController = [[MFMailComposeViewController alloc] init];
[mailController setSubject:#"randomNumber"];
[mailController setMessageBody:self.ideaLabel.text isHTML:NO];
I know this should be a simple thing to fix, but I can't see what's going wrong. May be extra pair will help. Here is what I am trying to do.
In my table view controller, there is an (+) button on the navigation controller to add new item.
I have a modal segue that takes it to the next view controller. User fills in a form and hit saves the table view controller reloads with the newly added record.
To do this, I implemented protocol with a delegate.
MyFormViewController.h
protocol MyCustomDelegate <NSObject>
#required
- (void)addNewRecord:(myFormViewController *)formViewController itemToAdd:(Item *)item;
#end
#property (nonatomic,weak) id<MyCustomDelegate> delegate;
MyFormViewController.m
#synthesize delegate;
- (IBAction)addItem:(id)sender {
Item *item = [[Item alloc]init];
item.name = itemName.text;
item.desc = itemDescription.text;
// I am having problem here, self.delegate is being null even though, it's being set in prepareForSegue.
if ([self.delegate respondsToSelector:#selector(addNewRecord:)]) {
[self.delegate addNewRecord:self itemToAdd:item];
}
else{
// delegate is getting set to null for some reason.
NSLog(#"Delegate method not getting called...%#",delegate);
}
}
in MyTableViewController.h
#interface MyTableViewController : UITableViewController
MyTableViewController.m
-(void)addItem:(myFormViewController *)formViewController itemToAdd:(Item *)item{
if(item)
{
MyClass *_itemClass = [[MyClass alloc]initWithPath:#"items/"];
[_itemClass addItemForUser:item];
}
[formViewController dismissViewControllerAnimated:YES completion:nil];
}
in my prepareForSegue method I am setting my tableviewcontroller as delegate.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"addItemSegue"]){
myFormViewController *_showaddTopic = [[myFormViewController alloc]init];
_showaddTopic.delegate = self;
}
After all this, my delegate in myFormViewController is being set to "null". I am not sure why it's not working. It's pretty basic stuff but giving me hard time.
Thank you
myFormViewController *_showaddTopic = [[myFormViewController alloc]init];
_showaddTopic.delegate = self;
There's your problem. You are creating a new MyFormViewController. But that's the wrong MyFormViewController; you want to use the one that is already the segue's destination controller. So you are setting the wrong object's delegate.
(PS Notice my use of a capital letter to start the name of a class? Always do that.)
maybe _showaddTopic.delegate = self; can not written here and shuold this object alloc after at once
I am working on a splitView application for my iPad. I have implemented a UIButton called as Upload. On clicking on it, a UITableView appears inside a UIPoverController. On clicking on any of the contents, I want to display some respective site in my UIwebView in UIDetailView. For this I have implemented a delegate method protocol. I have used the following lines of code in UploadTableViewController.h file::
#protocol UploadTableViewDelegate <NSObject>
#required
- (void)selected:(NSString *)his;
#end
#interface UploadSpaceTableViewController : UITableViewController{
id<UploadSpaceTableViewDelegate> delegate;
}
#property (retain) id delegate;
#end
In the corresponding .m file I have used the following lines of code ::
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (delegate != nil) {
NSString *hisSelected = [keys objectAtIndex:indexPath.row];
NSLog(#"%# lolwa", hisSelected);
[delegate selected:hisSelected];
}
}
in the .m file of class where I have implemented the function Selected, the code is ::
- (void)selected:(NSString *)Key {
NSLog(#"hello");
[self.UploadSpaceTableViewPopover dismissPopoverAnimated:YES];
}
-(IBAction)uploadpressed:(id)sender{
Upload.delegate = self;
self.Upload = [[UploadSpaceTableViewController alloc]
initWithStyle:UITableViewStylePlain];
self.UploadTableViewPopover = [[UIPopoverController alloc]
initWithContentViewController:UploadSpace];
[self.UploadTableViewPopover presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
However, I am unable to get hello (written in the function Selected) NSLogged in gdb. This is the first time that I am using this delegate method protocol. I am unable to sort this out. Can someone help me out ? Thanks and regards.
[delegate keySelected:hisKeySelected]; is your first problem. You don't declare a delegate method named -keySelected:, you declare a delegate method named -Selected:.
Your second problem is the fact that you are most definitely not the delegate of your table view. In order for a delegate method like -didSelectRowAtIndexPath: to be called, you must be the table's delegate.
PS, don't begin instances, or method names, with an uppercase letter. In ObjC, uppercase letters indicate a class.
EDIT: this is what your UploadSpaceTableViewController header should look like:
#protocol UploadTableViewDelegate <NSObject>
#required
- (void)selected:(NSString *)his;
#end
#interface UploadSpaceTableViewController : UITableViewController<UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, assign) id <UploadSpaceTableViewDelegate>delegate; //delegates are weak!!!
#end
And the .m, I will skip a lot of the unnecessary stuff:
-(void)viewDidLoad {
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
}
//other code
Furthermore, your delegate is declared retain, which is an absolutel No-No in ObjC. Declare is weak if using ARC, or assign if not.
You are also producing a nil delegate in in your -uploadPressed: method by setting it before you explicitly own or initialize the object. Here's how it should look:
self.Upload = [[UploadSpaceTableViewController alloc]initWithStyle:UITableViewStylePlain];
Upload.delegate = self;
Delegation works like this
declare a protocol - you have done this
declare a delegate property - you have done this
In the class which you want to be the delegate say it conforms to the protocoll
#interface MyClass : MySuperClass <UploadTableViewDelegate>
set the delegate property so the delegate class can get the delegate messages
uploadSpaceTVC.delegate = self;
call the delegate methods in your non delegate class (UploadSpaceTableViewController)
[self.delegate selected:#"test"];
im new to IOS and Objective-C and the whole MVC paradigm and i'm stuck with the following.
I am working on (replica) Contact app, also available in iphone as build in app. i want to pass data through another view controller and the data is pass (null) :(.
My Question is, How do I transfer the data from one view to another?
As most the answers you got, passing data between one controller and another just means to assign a variable from one controller to the other one.
If you have one controller to list your contacts and another one to show a contact details and the flow is starting from the list and going to detail after selecting a contact, you may assign the contact variable (may be an object from the array that is displayed in your list) and assign it to the detail view controller just before showing this one.
- (void)goToDetailViewControllerForContact:(Contact *)c
{
ContactDetailViewController *detailVC = [[[ContactDetailViewController alloc] init] autorelease];
detailVC.contact = c;
[self.navigationController pushViewController:c animated:YES];
//[self presentModalViewController:detailVC animated:YES]; //in case you don't have a navigation controller
}
On the other hand, if you want to insert a new contact from the detail controller to the list controller, I guess the best approach would be to assign the list controller as a delegate to the detail one, so when a contact is added the delegate is notified and act as expected (insert the contact to the array and reload the table view?).
#protocol ContactDelegate <NSObject>
- (void)contactWasCreated:(Contact *)c;
// - (void)contactWasDeleted:(Contact *)c; //may be useful too...
#end
#interface ContactListViewController : UIViewController <ContactDelegate>
#property (nonatomic, retain) NSArray *contacts;
...
#end
#implementation ContactListViewController
#synthesize contacts;
...
- (void)goToDetailViewControllerForContact:(Contact *)c
{
ContactDetailViewController *detailVC = [[[ContactDetailViewController alloc] init] autorelease];
detailVC.contact = c;
detailVC.delegate = self;
[self.navigationController pushViewController:c animated:YES];
//[self presentModalViewController:detailVC animated:YES]; //in case you don't have a navigation controller
}
- (void)contactWasCreated:(Contact *)c
{
self.contacts = [self.contacts arrayByAddingObject:c]; //I'm not sure this is the correct method signature...
[self reloadContacts]; //may be [self.tableView reloadData];
}
...
#end
#interface ContactDetailViewController : UIViewController
#property (nonatomic, assign) id<ContactDelegate> delegate;
...
#end
#implementation ContactDetailViewController
#synthesize delegate; //remember to don't release it on dealloc as it is an assigned property
...
- (void)createContactAction
{
Contact *c = [[[Contact alloc] init] autorelease];
[c configure];
[self.delegate contactWasCreated:c];
}
...
#end
Technically, you shouldn't!
The whole idea is not for "views" to control what happens to the data.
What you want to do is to pass data between controllers (which I imagine is exactly what you are planning to do anyway).
You can have shared model (an instance of an object that both view controllers would access) keeping the data you want to share,
You can use notifications to pass data (it is best suited for certain cases).
You can write something to disk and read it again later.
You can use NSUserDefaults.
You can use KeyChain.
...
The best way is:
declare the appropriate #property in the second view controller
when you create it, simply set the property with
viewController.property = valueYouWantToPass;
I'm a big fan of delegates and protocols.
And in some occasions use a Singleton pattern.
two ways to pass/share data between view controller
create an object and sent the data like this
QGraduteYr *tableverify=[[QGraduteYr alloc]initWithStyle:UITableViewStyleGrouped];
tableverify.mystring=myString
[self.navigationController pushViewController:tableverify animated:YES];
another method is stor it in the delegates and use it via shared delegates
MedicalAppDelegate *appdelegate=(MedicalAppDelegate *)[[UIApplication sharedApplication]delegate];
appdelegate.collnameStr=collStr;
and ust this appdelegates value whereever you need
I have too much code to know which i need to quote here, but in my app delegate I have an NSMutableArray. Then in another class, it creates a new entry to the NSMutableArray but upon passing back to another class which should use that to display something on screen, it doesn't display anything. Putting an NSLog for the NSMutableArray count at the end of the class creating it displays the number 1, and then putting the same NSLog code at the start of the class which is meant to use that returns 0.
Any ideas why this is?
EDIT: Ok, i'll try and include all related code..
app delegate.h:
#interface palettesAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *colourPalettesContainer;
}
#property (assign, readwrite) NSMutableArray *colourPalettesContainer;
#end
app delegate.m:
#import "palettesAppDelegate.h"
#implementation palettesAppDelegate
#synthesize colourPalettesContainer;
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
Homeview.h:
#import <UIKit/UIKit.h>
#import "HandlingPalettes.h"
#interface HomeView : UIViewController {
HandlingPalettes *handlingPalettes;
}
#end
Homeview.m:
#import "HomeView.h"
#import <QuartzCore/QuartzCore.h>
#implementation HomeView
- (void)viewDidLoad {
[super viewDidLoad];
handlingPalettes = [[HandlingPalettes alloc] init];
[handlingPalettes newPalette];
}
-(void)viewWillAppear:(BOOL)animated {
NSLog(#"view will appear: %i", [dataCenter.colourPalettesContainer count]);
int numberOfExisting = [dataCenter.colourPalettesContainer count];
}
- (void)dealloc {
[handlingPalettes release];
[super dealloc];
}
#end
HandlingPalettes.h:
#import <UIKit/UIKit.h>
#interface HandlingPalettes : UIViewController {
}
-(void)newPalette;
#end
HandlingPalettes.m:
#import "HandlingPalettes.h"
#import "HomeView.h"
#import "palettesAppDelegate.h"
#implementation HandlingPalettes
-(void)newPalette {
palettesAppDelegate *dataCenter = (palettesAppDelegate *)[[UIApplication sharedApplication] delegate];
//If this is the first palette
if (dataCenter.colourPalettesContainer == nil) {
dataCenter.colourPalettesContainer = [[NSMutableArray alloc] init];
}
//Add a new palette
[dataCenter.colourPalettesContainer addObject:#"Test1", #"Test2", nil];
NSLog(#"Handling: %i", [dataCenter.colourPalettesContainer count]);
}- (void)dealloc {
[super dealloc];
}
#end
Your main mutablearray is in your app delegate. So, see what happens if in EVERY METHOD that you want to access the array you have the line to set up the app delegate relationship
palettesAppDelegate *dataCenter = (palettesAppDelegate *)[[UIApplication sharedApplication] delegate];
Now, when you call the dataCenter object you will be referencing the App Delegate and your program will find the array.
You may also find that you will need to have an #import "palettesAppDelegate.h" in each object that is going to reference the App Delegate.
Note, just adding the app delegate code is not necessarily the proper way to deal with this issue from an architectural standpoint. But if it works you at least know the answer to your original question.
I suspect the problem is ultimately related to confused memory management of the colourPalettesContainer member. You release it in the app delegate's dealloc method, but that class never retains it! It would be much cleaner if you'd follow Apple's memory management guidelines: your classes should only release objects that they own (i.e., that they themselves retained earlier). For example, you can do this by declaring the array's property retain:
#property (retain) NSMutableArray *colourPalettesContainer;
(To prevent leaking the array, you'll also need to release or autorelease it in the newPalette method. Retain and release should always come in close pairs.)
But even better, why not simply create the array in the app delegate's init method, or in its accessor (if for some reason you want to continue creating it only on its first use)? Unless you want to replace all palettes at once, there is no reason to let the array be assigned to from outside the app delegate.
#interface PalettesAppDelegate : NSObject <UIApplicationDelegate> {
#private
NSMutableArray *colourPalettesContainer;
}
#property (readonly) NSMutableArray *colourPalettesContainer;
#end
#implementation PalettesAppDelegate
- (NSMutableArray *)colourPalettesContainer {
if (colourPalettesContainer == nil) {
colourPalettesContainer = [[NSMutableArray alloc] init];
return colourPalettesContainer;
}
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
To make the design even cleaner, change the type of the colourPalettesContainer property to NSArray * and add an -addPalette: method to the app delegate. (It is rarely a good idea to publicly expose a mutable array inside a class.) You can then simply get rid of -newPalette in HandlingPalettes. (If you want to have all your palette-handling methods in HandlingPalettes, then simply move the array there. If you need to access the palettes from random places in your app, then you can simply put a retained reference to your HandlingPalettes object in the app delegate.)
Once you clean up the object ownership mess, the count mismatch will either resolve itself "by magic" or the cause will likely become much more obvious. In the latter case, check that the HomeView's dataCenter is actually the same object as the one in HandlingPalettes. (You omitted how HomeView gets its reference — are you sure you aren't creating another instance of the app delegate by accident?)
(By the way, you probably meant to use -addObjects:, not -addObject: in newPalette. Note also that all class names should be capitalized, with no exceptions: i.e., always use PalettesAppDelegate, never palettesAppDelegate. If for some reason Xcode's project template created it like that, simply rename the class. Lowercase class names are much too easy to confuse with variable names. Also, try to find better names in general: e.g., instead of HandlingPalettes, I'd use PalettesViewController (to reflect the fact that it is a subclass of UIViewController); and instead of dataCenter, I'd rather just choose appDelegate.)
I would be inclined to get rid of the newPalette method, and instead create a getter method for colourPalettesContainer in your app delegate.
ie:
appdelegate.h
#interface PalettesAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *colourPalettesContainer;
}
#property (non-atomic, retain) NSMutableArray *colourPalettesContainer;
#end
#implementation palettesAppDelegate
appdelegate.m
#import "appdelegate.h"
#synthesize colourPalettesContainer;
- (NSMutableArray *) colourPalettesContainer{
if(colourPalettesContainer==nil){
colourPalettesContainer=[[NSMutableArray alloc] init];
}
return colourPalettesContainer;
}
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
you should then be able to add items by calling
[appDelegate.colourPalettesContainer addObject:object];