Add a property to all view controllers in an iPhone app - iphone

View controllers in my iPhone app either extend from UIViewController or UITableViewController. I need to add a property to all of them so that I can pass user information between controllers. The solution I am aware of is to add a BaseViewController and a BaseTableViewController, add the property to both of them, then make all controllers to inherit from them instead.
Repeating the property in BaseViewController and BaseTableViewController doesn't seem to be the most elegant solution to me. Is there any better ones?
Thanks!

As extensions will not work in your case, you could write a category on UIViewController and use objc_getAssociatedObject to store the data you want and retrieve it with objc_getAssociatedObject. But you should use this option with care.
static NSString *key = #"example";
- (void)setExampleProperty:(id)value {
objc_setAssociatedObject(self, (__bridge void *)key, value, OBJC_ASSOCIATION_RETAIN);
}
- (id)exampleProperty {
return objc_getAssociatedObject(self, (__bridge void *)key);
}
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html

Related

How to save nsdictionary of a subview to a mainview based off tableviewcell selection

I am currently parsing some xml that looks like this
<Rows>
<Row MANUFACTURERID="76" MANUFACTURERNAME="Fondont" ISMANU="F" ISAUTO="F"/>
<Row MANUFACTURERID="18" MANUFACTURERNAME="Anti" ISMANU="T" ISAUTO="T"/>
</Rows>
I parse it so that there is an array of dictionaries (each dictionary has the four values of the Row in it).
I then pass ManufacturerName to my startSortingTheArray method like this
if (dataSetToParse == #"ICMfg") // ICMfg is a string passed to this view from the parent view cell selection enabling me to pass different data sets to this view
{
//Filter results (ISAUTO = T)
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K like %#",#"ISAUTO",#"T"];
NSArray *filteredArray = [myDataArray filteredArrayUsingPredicate:predicate];
//Passes Manufacturer strigs over to startSortingtheArray method
[self startSortingTheArray:[filteredArray valueForKey:#"MANUFACTURER"]];
}
So from here all of the ManufacturerNames are sent to my method as an array of strings. I then use this array to set up all of my sections / index-scroller. The method below shows how I am doing this.
//method to sort array and split for use with uitableview Index
- (IBAction)startSortingTheArray:(NSArray *)arrayData
{
//If you need to sort incoming array alphabetically use this line of code
//TODO: Check values coming in for capital letters and spaces etc
sortedArray = [arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
//If you want the standard array use this code
//sortedArray = arrayData;
self.letterDictionary = [NSMutableDictionary dictionary];
sectionLetterArray = [[NSMutableArray alloc] init];
//Index scrolling Iterate over values for future use
for (NSString *value in sortedArray)
{
// Get the first letter and its associated array from the dictionary.
// If the dictionary does not exist create one and associate it with the letter.
NSString *firstLetter = [[value substringWithRange:NSMakeRange(0, 1)] uppercaseString]; //uppercaseString puts lowercase values with uppercase
NSMutableArray *arrayForLetter = [letterDictionary objectForKey:firstLetter];
if (arrayForLetter == nil)
{
arrayForLetter = [NSMutableArray array];
[letterDictionary setObject:arrayForLetter forKey:firstLetter];
[sectionLetterArray addObject:firstLetter]; // This will be used to set index scroller and section titles
}
// Add the value to the array for this letter
[arrayForLetter addObject:value];
}
//Reload data in table
[self.tableView reloadData];
}
from here I do several things to do with setting up the tableview after [self.tableView reloadData]; is called, The main thing being is that I set the cell up with the string values of the array.
//Display cells with data
NSArray *keys = [self.letterDictionary objectForKey:[self.sectionLetterArray objectAtIndex:indexPath.section]];
NSString *key = [keys objectAtIndex:indexPath.row];
cell.textLabel.text = key;
when the cell is then selected the string value inside the cell is then sent back to the main view and used later as a search parameter... The thing being is that I am setting up several parameters that will be used as one search string.
Looking back at the XML I parsed
<Rows>
<Row MANUFACTURERID="76" MANUFACTURERNAME="Fondont" ISMANU="F" ISAUTO="F"/>
<Row MANUFACTURERID="18" MANUFACTURERNAME="Anti" ISMANU="T" ISAUTO="T"/>
</Rows>
These are the values of columns inside an SQl table that has a keyvalue MANUFACTURERID that is also found in other tables that I parse. I would like to use these key values to restrict/refine other queries but I just cannot figure out how to pass them to my parentview where I set up all of the search parameters, that is my question how can I save the dictionary of values that is related to the users tableview selection from the subview. So that I can then pass one or some of those values back to the subview of a different dataset to restrict the information that is displayed dependent on the users previous selections.
This has taken me about an hour to type up. Hopefully it makes sense, I am still fairly new to iOS development and Objective C, and this concept is really pushing my capabilities and before I move on and end up hasing some crap together that I will have to fix later on I am hoping that one or some of you will be able to lend your experience in this type of this to me so I can get this right first time :)
If you need me to clarify anything or provide you more information that will help you help me just let me know.
Thanks in advance!
The common pattern for passing information backwards in your view controller hierarchy is to use delegation. You can achieve this in your scenario by implementing the following:
1) Define a protocol in the SearchParametersViewController, which represents your the parent view controller you mentioned.
#protocol SearchParametersViewControllerDelegate <NSObject>
#optional
- (void)searchOptionsSelected:(NSArray *)selectedSearchOptions;
#end
2) Conform to that protocol in your SearchOptionsSelectionViewController, which represents the table view controller that has a list of selections to choose from. Make sure to import or forward-declare the class the protocol is defined in (e.g. SearchParametersViewController) .
#import "SearchParametersViewController.h"
#interface SearchOptionsSelectionViewController <SearchParametersViewControllerDelegate>
3) Define a delegate property in your SearchOptionsSelectionViewController (assumes you are using ARC on iOS 5.0, 4.x use unsafe_unretained instead of weak. Use assign if the project is using manual memory management). This delegate object will contain a reference to your parent view controller (e.g. SearchParametersViewController). You do not want this property to be retained as to avoid retain cycles/circular references where one object references another, which in turn has a reference back to the first and neither object is ever deallocated.
#property (nonatomic, weak) id<SearchParametersViewControllerDelegate> delegate;
4) When instantiating the SearchOptionsSelectionViewController instance inside your parent view controller (SearchParametersViewController), set the delegate property to the parent view controller instance as represented by the self keyword. This ensures you can send the message (and corresponding data) backward in your view controller hierarchy, yet the object relationships remain loosely coupled. This delegate protocol could be conformed to in any other view controller, there are no tight relationships in the selection view controller back to the parent view controller, the only thing linking them is the flexible delegate protocol adoption by the selection view controller.
SearchOptionsSelectionViewController *selectionViewController = [[SearchOptionsSelectionViewController alloc] init];
selectionViewController.delegate = self;
5) Finally, in your SearchOptionsSelectionViewController table view's -tableView:didSelectRowAtIndexPath: delegate method, pass the data corresponding to the selected row back to your parent view controller (SearchParametersViewController) via the delegate method you defined in the SearchParametersViewControllerDelegate protocol. You must use the -respondsToSelector: method to ensure that the delegate object actually implements the -searchOptionsSelected: delegate method. To force this implementation, change #optional to #required above the method prototype in the protocol definition in step #1. self.someDataArray represents a the data source you are using with the selection table view controller. The specifics of the delegate protocol method and data object(s) sent back to the parent view controller can be changed, the important thing here is the delegation pattern and not having any tightly coupled relationships between the instances of either class, but especially backwards in the view controller hierarchy.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:#selector(searchOptionsSelected:)])
{
NSArray *selectedObjs = [NSArray arrayWithObject:[self.someDataArray objectAtIndex:indexPath.row]];
[self.delegate searchOptionsSelected:selectedObjs]
}
}
6) Implement the delegate method inside SearchOptionsSelectionViewController.m
- (void)searchOptionsSelected:(NSArray *)selectedSearchOptions
{
// do what you need to with selectedSearchOptions array
}
Further reading:
Cocoa Fundamentals Guide - Delegates and Data Sources
Cocoa Core Competencies - Protocol
You could use the application delegate to achieve your goals here.
I'm going to assume your app has a structure a bit like this. Please excuse the crudity of this model.
Application delegate (A) --> Search Options View (B) --> Table where you do selections (C)
|
|
--> Some other view where you need the selection (D)
Your problem is that you need information to flow from C to D.
Your application delegate has the merit of being universally accessible via [[UIApplication sharedApplication] delegate]. So you can get a pointer to it from anywhere. From C, you can send your selection information back to A. A can either send this on automatically to D, or D can request it from A whenever it wants it.
A couple of points:
I won't expand any further on my answer at the moment because it's beer o' clock here now, plus I might have misunderstood your requirement. If you do need anything else, I will be up at baby o' clock in the morning UK time so there might be some delay.
Some people frown on using the application delegate as a "data dump" in the way I have suggested. Some of those people would rather set up a whole singleton class and treat that as a data dump instead. It seems to be one of those neverending arguments so I try not to get involved.
You have a few options, one is to use user defaults. It might be the easiest.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
Another is to post a notification with the information.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsnotificationcenter_Class/Reference/Reference.html

Share a bool variable / NSNUmber between two view controllers

I have two view controllers and I want to share a bool variable between them.
So I create a bool variable with a #propery (nonatomic, assign) on both sides and on the one side I wrote
newVC.myBool1 = self.myBool2;
On the other view controller I can read the value of the passed bool variable, but I need to change it at the second view controller so I can read the value at the first view controller.
So I know, this is not possible, because `bool* it is a primitive type.
So I used NSNumber, but this also does not work. On the first view controller I set on viewDidLoad
self.myBool1 = [NSNumber numberWithBool:NO];
On the second view controller:
self.myBool2 = [NSNumber numberWithBool:YES];
But on the first view controller the value is 0 - NO... So it seems that creating the new NSNumber is not shared to the first view controller.
What can I do to solve this problem?
Regards Tim
You have lots of choices, but which you should use depends on whether both viewControllers need notification of when the value changes.
If you don't need notification, the easiest choice is to use a global BOOL variable, although purists will scoff at the suggestion. But really it's two lines of code and you're done. Another option would be to store the value in NSUserDefaults.
If you need change notification in each viewController, perhaps the cleanest design is to write a "set" method in one viewController that sets the value in both itself and the other viewController. Something like:
-(void) setMyBool:(BOOL)newValue
{
myBool = newValue;
otherViewController.myBool = newValue;
}
If you want to change the value from either viewController, it gets a little trickier because you have to have each viewController keep a reference to the other and make sure not to recurse when setting the value. Something like:
-(void) setMyBool:(BOOL)newValue
{
if ( self.busyFlag == YES )
return;
self.busyFlag = YES;
myBool = newValue;
otherViewController.myBool = newValue;
self.busyFlag = NO;
}
Yet another option would be to use NSNotifications to change the value and have each viewController class listen for the change notification. And TheEye's suggestion of writing a wrapper class and keeping a reference to an instance of that class in both viewControllers would work too.
If you don't need change notifications, though, I would just create a global BOOL variable and get on with the rest of the application because it's so easy, reliable and hard to mess up.
An NSNumber object is immutable, so you can't use it like that. If you write [NSNumber initWithxxx], in fact you create a new object.
If you want to share a number or boolean between several classes, you should create your own wrapper class with setters and getters for the bool value (or subclass NSNumber). This class you can share between classes.

How to obtain the container class of an Actionsheet?

I am a green hand of iPhone development, and I just get confused about a method UIActionsheet,which is the "showInView". So what is the relation between the view who called the actionsheet and the actionsheet it self.
Actually, I wannt to customize the button in an actionsheet, so I create a class for it and overide the methods, and I want really call the methods in the superview, anybody got a solution?
Thank you!
(btw, I tried the following code, but it doesn't work.)
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
{........
else if (buttonIndex==sharers.count+1)
{
AddCommentViewController *parentController=(AddCommentViewController *)[self.superview nextResponder];
}
There is no public API for accessing the container, or owner of a UIActionSheet.
You should not use the superview property to try to get to the owner, the internal view layout for the action sheet is private and can/will change between OS updates.
If you need to get hold of the owner then add a proper property to your UIActionSheet subclass to do this. For example:
#protocol MYActionSheetChoiceDelegate;
#interface MYActionSheet : UIActionSheet <UIActionSheetDelegate> {}
#property(nonatomic, assign) id<MYActionSheetChoiceDelegate> choiceDelegate;
#end
Notice that I name the property choiceDelegate since the delegate property is already taken. Now assuming your subclass is also your it's own UIActionSheetDelegate this can be done:
-(void)actionSheet:(UIActionSheet*)sheet willDismissWithButtonIndex:(NSInteger)index;
{
if (index == SOME_INDEX) {
[self.choiceDelegate actionSheet:self didChooseSomething:index];
}
}
Change and fill the gaps to your own needs.

Non-retaining array for delegates

In a Cocoa Touch project, I need a specific class to have not only a single delegate object, but many of them.
It looks like I should create an NSArray for these delegates;
the problem is that NSArray would have all these delegates retained, which it shouldn't (by convention objects should not retain their delegates).
Should I write my own array class to prevent retaining or are there simpler methods?
Thank you!
I found this bit of code awhile ago (can't remember who to attribute it to).
It's quite ingenius, using a Category to allow the creation of a mutable array that does no retain/release by backing it with a CFArray with proper callbacks.
#implementation NSMutableArray (WeakReferences)
+ (id)mutableArrayUsingWeakReferences {
return [self mutableArrayUsingWeakReferencesWithCapacity:0];
}
+ (id)mutableArrayUsingWeakReferencesWithCapacity:(NSUInteger)capacity {
CFArrayCallBacks callbacks = {0, NULL, NULL, CFCopyDescription, CFEqual};
// We create a weak reference array
return (id)(CFArrayCreateMutable(0, capacity, &callbacks));
}
#end
EDIT Found the original article: http://ofcodeandmen.poltras.com
I am presenting an important limitation of one of the earlier answers, along with an explanation and an improvement.
Johnmph suggested using [NSValue valueWithNonretainedObject:].
Note that when you do this, your reference acts not like __weak, but rather like __unsafe_unretained while inside the NSValue object. More specifically, when you try to get your reference back (using [myNSValue nonretainedObjectValue]), your application will crash with an EXC_BAD_ACCESS signal if the object has been deallocated before that time!
In other words, the weak reference is not automatically set to nil while inside the NSValue object. This took me a bunch of hours to figure out. I have worked around this by creating a simple class with only a weak ref property.
More beautifully, by using NSProxy, we can treat the wrapper object entirely as if it is the contained object itself!
// WeakRef.h
#interface WeakRef : NSProxy
#property (weak) id ref;
- (id)initWithObject:(id)object;
#end
// WeakRef.m
#implementation WeakRef
- (id)initWithObject:(id)object
{
self.ref = object;
return self;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
invocation.target = self.ref;
[invocation invoke];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.ref methodSignatureForSelector:sel];
}
#end
Check documentation of NSValue valueWithNonretainedObject method :
This method is useful for preventing an object from being retained when it’s added to a collection object (such as an instance of NSArray or NSDictionary).
I'd suggest to not-fight-the-framework and use NSPointerArray with the NSPointerFunctionsWeakMemory NSPointerFunctionOption like this:
NSPointerArray *weakReferencingArray = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory];
// NSPointerFunctionsWeakMemory - Uses weak read and write barriers
// appropriate for ARC or GC. Using NSPointerFunctionsWeakMemory
// object references will turn to NULL on last release.
Served me well in scenarios, where I had to design a delegates array, which auto-NULL's references.
You do not want to do this! Cocoa Touch have several concepts for sending events, you should use the proper concept for each case.
Target-action: For UI controls, such as button presses. One sender, zero or more receivers.
Delegates: For one sender and one receiver only.
Notification: For one sender, and zero or more receivers.
KVO: More fine grained that notifications.
What you should do is to look into how to use NSNotificationCenter class. This is the proper way to send a notification that have more than one receiver.
This one from NIMBUS would be more simple:
NSMutableArray* NICreateNonRetainingMutableArray(void) {
return (NSMutableArray *)CFArrayCreateMutable(nil, 0, nil);
}
NSMutableDictionary* NICreateNonRetainingMutableDictionary(void) {
return (NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil);
}
NSMutableSet* NICreateNonRetainingMutableSet(void) {
return (NSMutableSet *)CFSetCreateMutable(nil, 0, nil);
}
Keyword: NSHashTable, search in documentations.
I found some pieces of code from Three20 project about this topic, i hope this helps...
NSMutableArray* TTCreateNonRetainingArray() {
CFArrayCallBacks callbacks = kCFTypeArrayCallBacks;
callbacks.retain = TTRetainNoOp;
callbacks.release = TTReleaseNoOp;
return (NSMutableArray*)CFArrayCreateMutable(nil, 0, &callbacks);
}
NSMutableDictionary* TTCreateNonRetainingDictionary() {
CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks;
CFDictionaryValueCallBacks callbacks = kCFTypeDictionaryValueCallBacks;
callbacks.retain = TTRetainNoOp;
callbacks.release = TTReleaseNoOp;
return (NSMutableDictionary*)CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &callbacks);
}
I found an open source library named XMPPFramewrok
There is a multicast delegate solution in the project
https://github.com/robbiehanson/XMPPFramework/wiki/MulticastDelegate
What about storing in the array or dictionary
__weak typeof(pointer) weakPointer = pointer;

Creating the Phone App's in-call view in iPhone

I'm trying to create this sort of "pop up action sheet" view similar to the in-call view in iPhone's phone app.
I believe this is a custom view, since I can't seem to find this in any apple references. But somehow the Google app and Discover app both have this view and look awfully similar (I've attached the images below).
So is there some kind of library/tutorial/sample code out there that can help me make something like this?
Thanks.
alt text http://a1.phobos.apple.com/us/r1000/018/Purple/e1/23/02/mzl.uiueoawz.480x480-75.jpg
(source: macblogz.com)
alt text http://ployer.com/archives/2008/02/29/iPhone%20infringes%20call%20display%20patent-thumb-480x799.png
They all look suitably different to be custom views to me. If you just want a control like this for a single view (i.e. not a more flexible configurable container type control) it should be relatively quick & easy to knock it up in xcode & IB. I've done similar things in my apps. Steps I would take are as follows:
1) create an empty NIB file and design your control there by using UIView, UIImageView, UIButton controls etc.
2) Create a new ObjC class derived from UIView
3) Ensure the 'root' UIView object in the NIB has class type matching your ObjC UIView derived class
4) Attach IBOutlets & IBAction event handlers to your class and wire up all the button events ('Touch up inside') to your class event handler methods in IB.
5) Add a static factory function to your class to create itself from the NIB. e.g.
// Factory method - loads a NavBarView from NavBarView.xib
+ (MyCustomView*) myViewFromNib;
{
MyCustomView* myView = nil;
NSArray* nib = [[NSBundle mainBundle] loadNibNamed:#"MyCustomViewNib" owner:nil options:nil];
// The behavior here changed between SDK 2.0 and 2.1. In 2.1+, loadNibNamed:owner:options: does not
// include an entry in the array for File's Owner. In 2.0, it does. This means that if you're on
// 2.2 or 2.1, you have to grab the object at index 0, but if you're running against SDK 2.0, you
// have to grab the object at index:1.
#ifdef __IPHONE_2_1
myView = (MyCustomView *)[nib objectAtIndex:0];
#else
myView = (MyCustomView *)[nib objectAtIndex:1];
#endif
return myView;
}
6) Create and place onto your parent view as normal:
MyCustomView* myView = [MyCustomView myViewFromNib];
[parentView addSubview:myView];
myView.center = parentView.center;
With regard to the event handling, I tend to create just one button event handler, and use the passed id param to determine which button is pressed by comparing against IBOutlet members or UIView tags. I also often create a delegate protocol for my custom view class and call back through that delegate from the button's event handler.
e.g.
MyCustomViewDelegate.h:
#protocol MyCustomViewDelegate
- (void) doStuffForButton1;
// etc
#end
ParentView.m:
myView.delegate = self;
- (void) doStuffForButton1
{
}
MyCustomView.m:
- (IBAction) onButtonPressed:(id)button
{
if (button == self.button1 && delegate)
{
[delegate doStuffForButton1];
}
// or
UIView* view = (UIView*)button;
if (view.tag == 1 && delegate)
{
[delegate doStuffForButton1];
}
}
Hope that helps