Objective-C: How to communicate between classes? - objective-c

after an research i discovered the sigleton that may help me but i have some problem to understand how it work.
i need that 2 class comunicate each other, here an example:
i have a tableView wich field are dinamic.
this is my code on viewController.h file:
-(void)ottieniMarche{
responseSimulate = [[NSArray alloc]initWithObjects:#"pollo",#"cane",#"gatto",#"verme",#"gallo",#"topo",#"canguro",#"elefante",#"giraffa" ,nil];
}
Now i have to send this information on my mainTableView.m ad the code that i'm actually using is this:
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
elementMainTableView = [[NSArray alloc]initWithObjects:#"aaa",#"bbb",#"ccc",#"ddd",#"eee",#"fff",#"ggg",#"hhh",#"iii" ,nil];
}
return self;
}
Now i have to change the elementMainTableView value with the responseSimulate value. I mean that the content of my maintableView are the same of my responseSimulate.

If I understand your question correctly, you want to refresh the content of the UITableView based on model changes.
UITableView requires a class that conforms to the UITableViewDataSource protocol to provide its row and section data. Often, that's a UITableViewController, but it does not have to be. The data source for your UITableView could the other class to which you refer. In that case, the key is to ask the reload the data, i.e. [tableView reloadData] when you change the data.
In your case, if your MainTableView (I'm inferring this is actually a UITableViewController subclass...) conforms to the UITableViewDataSource protocol, then you could solve the problem using Key-Value Observing for example: (Note, this example assumes you are using ARC.)
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if( !self ) return nil;
elementMainTableView = [[NSArray alloc] initWithObjects:#"aaa",#"bbb",#"ccc",nil];
[self addObserver:self forKeyPath:#"elementMainTableView" options:NSKeyValueObservingOptionNew context:NULL];
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
if( [keyPath isEqualToString:#"elementMainTableview"] )
{
[[self tableView] reloadData];
}
}
This assumes that you expose elementMainTableView as a property on MainTableView.
Now, in the other class (?ViewController) your method:
-(void)ottieniMarche {
responseStimulate = [[NSArray alloc] initWithObjects:#"pollo",#"cane",#"gato",nil];
_mainTableViewController.elementMainTableView = responseStimulate;
}
For this to work, you will need your ViewController to keep a reference to the MainTableView, e.g. _mainTableViewController above.

Related

UILabel KVO observing

I am going nuts, I looked everywhere on the web but I always found the same code for KVO observing. Yet my observeValueForKeyPath: is never called; this is the simple code I use to observe UILabel taximeterValue:
-(id) initWithCoder:(NSCoder *)aDecoder{
self=[super initWithCoder:aDecoder];
if (self){
[taximeterValue addObserver:self forKeyPath:#"text" options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld) context:NULL];
}
return self;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
if ([keyPath isEqual:#"text"]) {
if (myBookingAlert) myBookingAlert.taximeterValue=self.taximeterValue;
NSLog(#"the text changed");
// put your logic here
}
// be sure to call the super implementation
// if the superclass implements it
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
A few possible explanations:
taximeterValue is nil.
Nothing is changing taximeterValue.text.
Something is changing taximeterValue.text in a non-KVO-compliant way. Every change to taximeterValue.text must be done either by calling [taximeterValue setText:], or surrounded by appropriate willChangeValueForKey: and didChangeValueForKey: messages.

How do you access a uitableview inside a uinavigationcontroller inside a uitabbarcontroller from app delegate?

Im trying to access a refresh control method in a uitableview which is inside a navigationcontroller from a tabbarcontroller which is my root, but im having trouble getting an exact handle.
This is my code so far in the AppDelegate but it doesn't work...
UITableViewController *tableView = (UITableViewController *)[[self.tabbarController viewControllers][0] tableView];
[tableView.refreshControl beginRefreshing];
I have 5 tab bar items which I believe I can access via [0],[1],[2],[3]
And my code in the UITableView (though probably doesnt matter)...
// Add Refresh Control
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:appDelegate action:#selector(forceDownload) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
[refreshControl release];
Any help would be greatly appreciated as i cant find any online that access as deep as this.
If you need communication between objects that are unrelated, i think that the best option is to use NSNotifications. This allows you to use the singleton object[NSNotificationCenter defaultCenter], to pass notifications from one object to another (or many others).
So you can put the AppDelegate object (or other object) to observe a specific notification and then post the notification with the tableviewController when you need to refresh the control.
In app delegate class you can add the observer like:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(forceDownload)
name:#"ForceDownloadNotification"
object:nil];
And, in the tableviewController you can post de notification like:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ForceDownloadNotification"
object:self];
Here, I used the name "ForceDownloadNotification" as the name for the notification. You can use the name that you want, but in order this solution work properly, you must use the same name when you start the observation and when you post the notification.
Here you have a tutorial about this subject.
I like Luis Espinoza's approach but that doesn't answer the question per se.
If you want to call a method inside your UITableViewController nested inside a UINavigationController which is the rootViewController for your App Delegate. First we create a navigationController with the UITableViewController (or a subclass):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
CustomTableViewController *nuTableVC = [[CustomTableViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *nuNavController = [[UINavigationController alloc] initWithRootViewController:nuTableVC];
self.window.rootViewController = nuNavController;
[self.window makeKeyAndVisible];
return YES;
}
Then in your UITableViewController (or subclass) you setup the refreshcontrol just like you asked:
- (void)viewDidLoad {
[super viewDidLoad];
// Add Refresh Control
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:[[UIApplication sharedApplication] delegate]
action:#selector(forceDownload)
forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
}
Finally to access the UItableViewController you must check if the instances are really the classes that you want, here is my implementation for the method you created (forceDownload) in your App Delegate:
- (void)forceDownload {
NSLog(#"force download method in App Delegate");
UINavigationController *someNavController = (UINavigationController*)[_window rootViewController];
UIViewController *vcInNavController = [[someNavController viewControllers] objectAtIndex:0];
if ([vcInNavController isKindOfClass:[CustomTableViewController class]]) {
NSLog(#"it is my custom Table VC");
NSLog(#"here we can stop the refresh control, or whatever we want");
CustomTableViewController *customTableVC = (CustomTableViewController *)vcInNavController;
[customTableVC.refreshControl performSelector:#selector(endRefreshing)
withObject:nil
afterDelay:1.0f];
}
}
I personally prefer using NSNotificationCenter because is simpler, but that doesn't mean we can't access the objects the way you originally planed.
(if you want the example code just ask for it).
Regards.
If your goal is truely to just keep your refresh control synch'd with the networkActivityIndicator then one option is KVO.
In the viewController's viewDidAppear: add something like this
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIApplication *application = [UIApplication sharedApplication];
[application addObserver:self
forKeyPath:#"networkActivityIndicatorVisible"
options:NSKeyValueObservingOptionNew
context:myContext];
self.refreshControl.refreshing = [application isNetworkActivityIndicatorVisible];
}
Then make sure to remove this observer when the viewController doesn't need it - perhaps in viewDidDisappear:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[UIApplication sharedApplication] removeObserver:self
forKeyPath:#"networkActivityIndicatorVisible"
context:myContext];
}
Now for the actual work
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
if (myContext == context) {
self.refreshControl.refreshing = [change[NSKeyValueChangeNewKey] boolValue];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

Interface Builder: Enabled1,2 property with OR operator

I need to set the property Enabled of a control in interface builder, depending on 2 booleans int he preferences.
However the operator should be OR and not AND. If one of the two is true, than my control should be enabled.
Currently, I can only make it work with an AND operator, (See screenshot).
Thanks
Unfortunately, in IB, you're stuck with and. My suggestion would be to add a new property to an object accessible to your NIB (possibly your owner for the NIB), which is dependent on changes to the other objects in order to enable your control/view.
It looks like you're using the Shared User Defaults Controller, so I would suggest that in the owner you create a new boolean property for your combined user defaults (perhaps downloadingCastOrCrew), and then you'll need to make sure that when either of the defaults change, you change the value of downloadingCastOrCrew:
In your Interface:
#property BOOL downloadingCastOrCrew;
In the implementation as you're setting up your controller or after awakeFromNib:
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath: #"values.kSearchPreferencesDownloadCast"
options:NSKeyValueObservingOptionNew
context:NULL];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath: #"values.kSearchPreferencesDownloadCrew"
options:NSKeyValueObservingOptionNew
context:NULL];
In the implementation as you're tearing down your controller:
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver: self
forKeyPath: #"values.kSearchPreferencesDownloadCast"];
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver: self
forKeyPath: #"values.kSearchPreferencesDownloadCrew"];
Add an observer if you don't already have one:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.downloadingCastOrCrew = [defaults boolForKey: #"kSearchPreferencesDownloadCast"]
|| [defaults boolForKey: #"kSearchPreferencesDownloadCrew"];
}
By using the accessor method, you'll trigger kvo and you'll be able to use the controller's downloadingCastOrCrew as your boolean to check instead of the NSUserDefaults values directly.
Obviously, if you already have an observeValueForKeyPath, you will likely want to add a context value to the addObserver:forKeyPath:options:context call and check it in the observeValueForKeyPath:ofObject:change:context call.
You can do it in this way :
Create a third property
#property BOOL isFirst;
#property BOOL isSecond;
#property BOOL isTextFieldVisible;//this one is your third
- (IBAction)isSec:(id)sender;
- (IBAction)isFir:(id)sender;
In implementation
- (id)init
{
self = [super init];
if (self) {
self.isFirst=NO;
self.isSecond=NO;
}
return self;
}
- (IBAction)isSec:(id)sender {
self.isSecond=!self.isSecond;
[sender setTitle:[NSString stringWithFormat:#"isSecond: %d",self.isSecond]];
self.isTextFieldVisible=self.isFirst || self.isSecond;
self.isTextFieldVisible=!self.isTextFieldVisible;
NSLog(#"->%d",self.isTextFieldVisible);
}
- (IBAction)isFir:(id)sender {
self.isFirst=!self.isFirst;
[sender setTitle:[NSString stringWithFormat:#"isfirst: %d",self.isFirst]];
self.isTextFieldVisible=self.isFirst || self.isSecond;
self.isTextFieldVisible=!self.isTextFieldVisible;
NSLog(#"->%d",self.isTextFieldVisible);
}
#end
And in the binding just bind the textField to third property,
Check the running application here.
EDIT 1:
Change ValueTransformer NSNegateBoolean in IB. so that my two lines self.isTextFieldVisible=!self.isTextFieldVisible; is not required in both the IBAction.

Notification when content changes in NSOutlineView subclass

I'd like to subclass NSOutlineView to show a label in the middle of itself when there is no content yet. Much like the inspectors in XCode:
Obviously I can't use the delegate methods, because I'm implementing this as a subclass and I have to be able to set the delegate to something else when using this class.
I didn't find any notifications that I could observe, except changes in the bounds property, but that's not very reliable.
I ended up overriding several methods of NSOutlineView to inject my code. It's not a very elegant solution, but it works. If anyone has a better solution, let me know and I might accept your answer instead.
- (void)updateEmptyLabelVisibility {
int r = [[self dataSource] outlineView:self numberOfChildrenOfItem:nil];
BOOL hasRows = r > 0;
_emptyLabel.hidden = hasRows;
}
- (void)reloadItem:(id)item {
[super reloadItem:item];
[self updateEmptyLabelVisibility];
}
- (void)reloadData {
[super reloadData];
[self updateEmptyLabelVisibility];
}
- (void)awakeFromNib {
[super awakeFromNib];
[self updateEmptyLabelVisibility];
}
- (void)endUpdates {
[super endUpdates];
[self updateEmptyLabelVisibility];
}

calling presentModalViewController causes “EXC_BAD_ACCESS”

I'm creating an iPad app. In it, I have a UITabBarController set up that shows 3 views. View 1, View 2, and View 3. This all works just fine. On View 1, the user is creating an order. They make then touch a button that builds the order. This is shown in a modal view that allows the user to review it before actually sending it. They can either "submit" or "edit" the order, either way, I dismiss the modal and return to View 1. That works fine as well. But if the user touches the "make" order button again, this time the loading of the modal view causes a crash "EXC_BAD_ACCESS". I copied the code just the same as I did for another modal view in the app, that has no problem showing itself time after time after time. I'm pretty perplexed at this point and would appreciate any help. Thanks. The code calling the modal is:
-(IBAction) makeOrder {
NSMutableArray *orderItems = [[NSMutableArray alloc] init];
//code that populates orderItems array - removed for brevity
NSLog(#"order items count:%d", [orderItems count]);
// Create the modal view controller
PartsOrderViewController *modalController = [[PartsOrderViewController alloc] initWithNibName:#"PartsOrderView" bundle:nil];
//this is the only difference b/w this and the other modal view. The other
//modal presents as a formsheet
modalController.modalPresentationStyle = UIModalPresentationFullScreen;
modalController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
modalController.orderList = orderItems;
modalController.storeId = selectedCustomer.storeID;
modalController.customerInfo = customerInfo.text;
modalController.customerTamsId = selectedCustomer.customerTAMSID;
// show the Controller modally -- This is the line that cause the error after the second time
[self presentModalViewController:modalController animated:YES];
// Clean up resources
[modalController release];
}
It actually gets into the viewDidLoad of the modal, but crashes as soon as that is finished running.
Here is the code for the modal:
#import "PartsOrderViewController.h"
#implementation PartsOrderViewController
#synthesize customerTamsId;
#synthesize customerInfo;
#synthesize invoiceDate;
#synthesize invoiceTime;
#synthesize storeId;
#synthesize customerInfoLabel;
#synthesize invoiceDateLabel;
#synthesize invoiceTimeLabel;
#synthesize storeIdLabel;
#synthesize orderList;
#synthesize delegate;
#pragma mark -
#pragma mark View methods
-(IBAction) editOrder {
[self dismissModalViewControllerAnimated:YES];
}
-(IBAction) submitOrder {
//code removed for brevity
}
#pragma mark -
#pragma mark View implementation methods
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
/*
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
}
return self;
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
self.customerInfoLabel.text = self.customerInfo;
self.storeIdLabel.text = self.storeId;
self.invoiceDateLabel.text = self.invoiceDate;
self.invoiceTimeLabel.text = self.invoiceTime;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return NO;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
UPDATE: Solution found: Offending code is marked as such-
-(NSMutableArray *)buildOrderList {
NSMutableArray *orderItems = [[NSMutableArray alloc] init];
id cellObject = NULL;
int counter = 0;
NSEnumerator *theEnum = [self.partsList objectEnumerator];
while((cellObject = [theEnum nextObject]) != NULL)
{
GridTableCell *cell = (GridTableCell *)[self.partsListGrid cellForRowAtIndexPath:[NSIndexPath indexPathForRow:counter inSection:0]];
UILabel *lineAbbrev = (UILabel *)[cell.contentView.subviews objectAtIndex:0];
UILabel *partNo = (UILabel *)[cell.contentView.subviews objectAtIndex:1];
UITextView *orderQty = (UITextView *)[cell.contentView.subviews objectAtIndex:3];
//NSLog(#"OrderQty length: %d", [orderQty.text length]);
//NSLog(#"Part#:%#, OrderQty:%#", partNo.text, orderQty.text);
PartOrderIn *invItem = [[PartOrderIn alloc] init];
invItem.lineAbbrev = lineAbbrev.text;
invItem.partNumber = partNo.text;
invItem.orderQty = orderQty.text;
invItem.partMessage = #"";
if ([invItem.orderQty length] > 0) {
[orderItems addObject:invItem];
}
counter++;
[invItem release];
//The following three lines is what was killing it
//[lineAbbrev release];
//[partNo release];
//[orderQty release];
}
//NSLog(#"order items count:%d", [orderItems count]);
return orderItems;
}
At the risk of stating the obvious (sorry ;) did you step this through the debugger? Bad access is probably a memory allocation issue (again, mr obvious). How are the properties defined (is orderList retained? other properties?). Check where is crashes and note the values of your properties, either using Expressions in debugger or by memory address. My guess is something is not being retained that you assume is retained.
Nothing jumps out immediately (the problem is more than likely in the code you removed for brevity) but have you tried to enable zombies? How to enable zombies. This will usually give you some indication of the offender or at least gives you a hint of where to look...