Call AppDelegate Method From Subclass? - objective-c

I'm probably not explaining this logically, as I'm new to Objective-C, but here I go...
I am writing an application in Objective-C that interacts with a WebView. Part of the app involves sharing an image via NSSharingService that is currently displayed in the WebView. Consequently, I have a method like this defined in my AppDelegate.m file:
#import "CCAppDelegate.h"
#import <WebKit/WebKit.h>
#import <AppKit/AppKit.h>
#implementation CCAppDelegate
-(void)shareFromMenu:(id)sender shareType:(NSString *)type{
NSString *string = [NSString stringWithFormat: #"window.function('%#')", type];
[self.webView stringByEvaluatingJavaScriptFromString: string];
}
#end
I then have a subclass of NSMenu, defined in CCShareMenu.m, which creates a menu of available sharing options:
#import "CCShareMenu.h"
#implementation CCShareMenu
- (void)awakeFromNib{
[self setDelegate:self];
}
- (IBAction)shareFromService:(id)sender {
NSLog(#"%#", [sender title]);
// [CCAppDelegate shareFromMenu];
}
- (void)menuWillOpen:(NSMenu *)menu{
[self removeAllItems];
NSArray *shareServicesForItems = #[
[NSSharingService sharingServiceNamed:NSSharingServiceNameComposeMessage],
[NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail],
[NSSharingService sharingServiceNamed:NSSharingServiceNamePostOnFacebook],
[NSSharingService sharingServiceNamed:NSSharingServiceNamePostOnTwitter]
];
for (NSSharingService *service in shareServicesForItems) {
NSMenuItem *item = [[NSMenuItem alloc] init];
[item setRepresentedObject:service];
[item setImage:[service image]];
[item setTitle:[service title]];
[item setTarget:self];
[item setAction:#selector(shareFromService:)];
[self addItem:item];
}
}
#end
These methods both work fine on their own, except I need to call the shareFromMenu method from within the shareFromService IBAction.
I attempted moving the IBAction method to AppDelegate.m, then realized that made zero sense as the menuWillOpen-created selectors would never find the correct methods. Similarly, I tried following the instructions posted here, but:
[CCAppDelegate shareFromMenu];
Also responded with an error saying that the method was not found.
I realize I'm doing something fundamentally wrong here, so guidance would be appreciated.

-[CCAppDelegate shareFromMenu]
is different from
-[CCAppDelegate shareFromMenu:shareType:]
I would try adding the following to CCAppDelegate.h between #interface and #end:
-(void)shareFromMenu:(id)sender shareType:(NSString *)type
Then change your shareFromService: method to something like:
- (IBAction)shareFromService:(id)sender
{
NSString *shareType = #"Set your share type string here.";
CCAppDelegate *appDelegate = (CCAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate shareFromMenu:sender shareType:shareType];
}

-(void)shareFromMenu is a member method, but
[CCAppDelegate shareFromMenu]
is calling a class function which is not the correct way to call a member function.
You may try to get the CCAppDelegate instance and then call the function like this
CCAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate shareFromMenu];

Related

Passing CLGeocoder city name to new class UILabel

I´m currently trying to pass the city name retrieved by CLGeocoder successfully to a UILabel of another class. First the CLGecoder-class
FindLocation.h
#property (nonatomic, strong) NSString *cityName;
FindLocation.m - inside method (void)locationManager:(CLLocationManager *)manager...
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
if (self.isFirstUpdate) {
self.isFirstUpdate = NO;
return;
}
CLLocation *location = [locations lastObject];
if (location.horizontalAccuracy > 0) {
self.currentLocation = location;}
CLGeocoder *fgeo = [[CLGeocoder alloc] init];
// Reverse Geocode a CLLocation to a CLPlacemark
[fgeo reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError
*error){
NSLog(#"%#", location);
// Make sure the geocoder did not produce an error
// before continuing
if(!error){
// Iterate through all of the placemarks returned
// and output them to the console
for(CLPlacemark *placemark in placemarks){
NSLog(#"%#",[placemark description]);
self.cityName = [placemark locality];
NSLog(#"city is %#",cityName); }
[self.locationManager stopUpdatingLocation];
[self.delegate findLocationDidGeocodeCityName:self.cityName];
} else {
// Our geocoder had an error, output a message
// to the console
NSLog(#"There was a reverse geocoding error\n%#",
[error localizedDescription]);
Further in my FirstViewController it looks like this:
FindLocation *cityname = [[FindLocation alloc] init];
[cityname cityName];
[self.cityLabel setText:(cityname.cityName)];
NSLog(#"CityName is...%#", cityname.cityName); //Log shows CityName is...(null)
I don´t know whats wrong here. Since the city is found there must be something wrong with the latter part of code, but I don´t know what. Is the passing of NSString - cityName implemented wrong?
I think the problem is that the geocode process is not instantaneous. You are instantiating FindLocation and then immediately trying to access cityName. However, even assuming that your init method triggers the location update, the geocoder will take a little bit of time to go to the server and get a response. Additionally, the location manager won't return a location immediately, and since it looks like that is what kicks off the geocoder, you have to wait a little bit longer for that as well.
One solution here would be to implement a delegate protocol on FindLocation so that it can notify FirstViewController when the geocoding has completed. Something like this:
FindLocation.h
#import <UIKit/UIKit.h>
#class FindLocation;
#protocol FindLocationDelegate <NSObject>
- (void)findLocationDidGeocodeCityName:(NSString *)cityName;
#end
#interface FindLocation : NSObject
#property (weak, nonatomic) id<FindLocationDelegate> delegate;
#end
and then in your CLGeocoder completionHandler after retrieving cityName:
[self.delegate findLocationDidGeocodeCityName:self.cityName];
Now, in FirstviewController.h, indicate that you conform to the FindLocationDelegate protocol:
#interface FirstViewController : UIViewController <FindLocationDelegate>
In your FirstViewController.m, set the delegate like so:
FindLocation *cityName = [[FindLocation alloc] init];
cityName.delegate = self;
and then also in FirstViewController, implement the delegate method:
#pragma mark - FindLocation Delegate
- (void)findLocationDidGeocodeCityName:(NSString *)cityName
{
[self.cityLabel setText:cityName];
}
There are other ways to do this as well, but the key is you have to wait until the completionHandler runs, and then notify any other interested objects about what got returned.

UIViewController maintains state after being nilled

In my app, I made a BookViewController class that displays and animates the pages of a book and a MainMenuViewController class that displays a set of books the user can read.
In the latter class, when the user taps on one of the books, a function is called that should create a completely new instance of BookViewController, but for some reason the instance maintains its state (i.e. it resumes from the page the user left off).
How can this be if I set it to nil? What am I missing here? (Note that I'm using ARC).
MainMenuViewController.m
#interface MainMenuViewController ()
#property (strong) BookViewController *bookViewController;
#end
#implementation MainMenuViewController
#synthesize bookViewController;
-(void)bookTapped:(UIButton *)sender{
NSString *bookTitle;
if(sender == book1button) bookTitle = #"book1";
else if(sender == book2button) bookTitle = #"book2";
bookViewController = nil;
bookViewController = [[BookViewController alloc] initWithBookTitle:bookTitle];
[self presentViewController:bookViewController animated:YES completion:nil];
}
BookViewController.h
#interface BookViewController : UIViewController
-(id)initWithBookTitle:(NSString *)bookTitle;
#end
BookViewController.m
#implementation BookViewController
-(id)initWithBookTitle:(NSString *)theBookTitle{
self = [super init];
if(self){
bookTitle = [NSString stringWithFormat:#"%#", theBookTitle];
[self setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
NSLog(#"init a BookViewController with bookTitle: %#", bookTitle);
}
return self;
}
edit 1:
Every time a book is tapped, bookTapped: is called, and thee console always prints:
2012-08-31 16:29:51.750 AppName[25713:c07] init a BookViewController with bookTitle: book1
So if a new instance of BookViewController is being created, how come it seems to be returning the old one?
edit 2:
I inserted NSLog(#"bookViewController %#",bookViewController); just before the line [self presentViewController:bookViewController. The console output is:
2012-08-31 16:37:41.426 Henry[25784:c07] bookViewController <BookViewController: 0x6a21540>
2012-08-31 16:38:23.321 Henry[25784:c07] bookViewController <BookViewController: 0xe425540>
2012-08-31 16:38:53.393 Henry[25784:c07] bookViewController <BookViewController: 0x6839330>
Your variables are declared outside of the #implementation of the class (you are declaring global variables).
I suspect that you are using the ivars instead of the properties. Please replace bookViewController with self.bookViewController.
Try:
if(self){
self.bookTitle
The variables that were maintaing their state in the new instance were declared thus:
#import "BookViewController.h"
int currentPage = 0;
#implementation BookViewController
-(id)initWithBookTitle:(NSString *)theBookTitle{
...
So I managed to fix the issue by initialising the variables in the init method:
-(id)initWithBookTitle:(NSString *)theBookTitle{
self = [super init];
if(self){
currentPage = 0; //added this line
bookTitle = [NSString stringWithFormat:#"%#", theBookTitle];
[self setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
NSLog(#"init a BookViewController with bookTitle: %#", bookTitle);
}
return self;
}
But this doesn't solve the underlying problem, which is that doing this:
bookViewController = [[BookViewController alloc] initWithBookTitle:bookTitle];
[self presentViewController:bookViewController animated:YES completion:nil];
still presents a bookViewController with the old value for currentPage! This might be because I'm not declaring it as a property, nor initialising it in the init method... Any thoughts?

How to get Class object without instance?

Is there any way to get the result of:
NSObject *a = [[NSObject alloc] init];
[a class];
Without instantiating?
I wish to pass Class object to function as argument, and check it then. Like this:
- (void) pushControllerOfClass: (Class) aClass
{
if([aClass isSubclassOfClass: *what should I write here?*]) {
...
}
}
Intuitively, I've tried to write
if([aClass isSubclassOfClass: UIViewController]) {
But it doesn't work.
Thx for future response.
Update:
Is such a function considered bad in ObjectiveC?
I've refactored code from Nahavandipoor's book iOS 5 Programming Cookbook.
It was like that:
- (void) pushSecondController{
SecondViewController *secondController = [[SecondViewController alloc]
initWithNibName:nil
bundle:NULL];
[self.navigationController pushViewController:secondController animated:YES];
}
As for me, this is kind of bad function:
It doesn't parametrized when it should be.
Try with:
if([aClass isSubclassOfClass:[UIViewController class]])
In answer to your question, you don't need an instance of an object to get its class. You can get it directly from the class like:
[NSString class];
[YourClass class];
However, if all you are wanting is a method that can push any view controller you give it, then you can just use UIViewController as the parameter type:
- (void)pushAnyViewController:(UIViewController *)viewController
{
[self.navigationController pushViewController:viewController animated:YES];
}
This will take any view controller that is a subclass of UIViewController and push it onto the navigation stack.
If you really want the method to handle the allocations as well you can do:
- (void)pushViewControllerClass:(Class)viewControllerClass
{
if ([viewControllerClass isSubclassOfClass:[UIViewController class]])
{
id viewController = [[viewControllerClass alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:viewController animated:YES];
}
}
And call it like this:
Class viewControllerClass = [MyViewController class];
[self pushViewControllerClass:viewControllerClass];
Just make sure that the nib is named the same as the class if you are using one.
You can write [UIViewController class] to get the class object.
There should be a class method on most classes to get the class.
If you find yourself doing this often, perhaps revisit your design?

iOS Property not found on object of type 'AppDelegate *'

I have two viewControllers accessing a NSNumber on the AppDelegate. One of them can see it, and the other can't. I am totally confused by this.
The one with the problem has this code.
AppDelegate *dataStore = (AppDelegate *)[[UIApplication sharedApplication] delegate];
dataStore.downHUD = [NSNumber numberWithFloat:(float)progress];
The other has this.
AppDelegate *dataStore = (AppDelegate *)[[UIApplication sharedApplication] delegate];
dataStore.downHUD = [NSNumber numberWithFloat:(float)0];
Both imports the AppDelegate in the .m file but I end up with
Property 'downHUD' not found on object of type 'AppDelegate *'
with the first one.
Anyone that can help me see what's wrong?
I copied and pasted a lot of code into the AppDelegate by mistake, that has been corrected. Is there some sort of link that could got broken?
Maybe there is no such property in your AppDelegate class.
In your AppDelegate.h under interface declaration you need to have
#property (nonatomic, retain) NSNumber* downHUD;
In your AppDelegate.m under implementation declaration you need to have
#synthesize downHUD;
In this manner you define accessors (getter and setter) to access an instance variable called downHUD. This accessors are public and you can do
dataStore.downHUD = ...
Maybe this could be the error. But without AppDelegate code it's difficult to understand what is going on.
Hope it helps.
Edit:
It's no a good strategy to access data within the application delegate. I suggest you to use singletons like singletons-appdelegates-and-top-level.html
Edit 2:
#interface SingletonModel : NSObject {
NSNumber* downHUD_;
}
+ (id)sharedInstance;
#property (nonatomic, retain) NSNumber* downHUD;
#end
#import "SingletonModel.h"
#implementation SingletonModel
#synthesize downHUD = downHUD_;
static SingletonModel *sharedInstance = nil;
+ (SingletonModel *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
-(void)dealloc
{
[super dealloc];
}
+ (id)allocWithZone:(NSZone*)zone {
return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax;
}
- (oneway void)release {
}
- (id)autorelease {
return self;
}
#end
To set your model:
SingletonModel* model = [SingletonModel sharedInstance];
model.downHUD = ...
To read your model:
SingletonModel* model = [SingletonModel sharedInstance];
NSNumber* n = model.downHUD;
For other info read iphone-code-snippet-the-singleton-pattern and singleton-classes. About Singletons you can find in apple documentation at Cocoa Fundamentals Guide and at Singleton.
Your two view controllers may refer to different AppDelegate code. Even though the Xcode Project Navigator shows only one set of AppDelegate files, and Jump to Definition shows the same AppDelegate class definition in both cases, one of the view controllers may actually have different delegate code.
I had this very problem with a delegate class definition, where some member variables were only available in one view controller but not in the other.
Right-click on each ViewController.m file in the Project Navigator, and use Show in Finder to see whether they are both in the same location as the desired AppDelegate files. If not, move the VC files to the correct location and add them to the project.
If you're using expo this is how to get rid of the bug.
"reactDelegate" was introduced in sdk 44.0.0 so if you're using sdk 43.0.0
here's is how your code should look in your "AppDelegate.m" from line 35 - 46.
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:#"main" initialProperties:nil];
rootView.backgroundColor = [UIColor whiteColor];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
[super application:application didFinishLaunchingWithOptions:launchOptions];
If you have imported the header files then it should work. Did you try to clean and re-build your project? You can do that with CMD + Shift + K (or by selecting Clean from the Project menu).

obj-c, confusion why can't I add a setter to this class?

Normally I don't have any problem adding a setter method to a class.
However, I'm trying to add to library usage class, which must be causing the problem.
Heres the class I've added it to...
#interface GraphController : TKGraphController {
UIActivityIndicatorView *indicator;
NSMutableArray *data; //I've added
NSString *strChartType; //I've added
}
-(void)setContentType:(NSString*)value; //I've added
#end
#implementation GraphController
-(void)setContentType:(NSString*)value { //I've added
if (value != strChartType) {
[value retain];
[strChartType release];
strChartType = value;
NSLog(#"ChartType=%#", strChartType);
}
}
Heres where I'm getting a warning..
UIViewController *vc = [[GraphController alloc] init];
[vc setContentType:myGraphType]; //Warnings on this line see below
[self presentModalViewController:vc animated:NO];
[vc release];
myGraphType if defined in my constant class.
* Warnings *
warning: 'UIViewController' may not respond to '-setContentType:'
warning: (Messages without a matching method signature
I know the first warning appears when you haven't added the method to the implementation. but I have.
Where am I going wrong ?
UIViewController *vc = [[GraphController alloc] init];
means that vc points to an instance of GraphController but the variable itself is of type UIViewController *, and UIViewController doesn’t declare a -setContentType: method.
Replace that with
GraphController *vc = [[GraphController alloc] init];
to tell the compiler you’re working with a GraphController instance, and it will recognise your -setContentType: method.
You just have to let the compiler know that you're working with a class that it knows responds to the method. You can do it a couple of ways but the easiest one if you just want to eliminate the warning is to cast the object in line before you make the method call.
UIViewController *vc = [[GraphController alloc] init];
[(GraphController *)vc setContentType:myGraphType]; //No warning should appear now.
[self presentModalViewController:vc animated:NO];
[vc release];