Ive been hitting the wall for two days on this hard (but simple for you) problem.
The problem is as follows:
I am posting a notification in the appdelegate
I am attempting to receive that notification in a viewcontroller but I cannot receive it.
Here is the code for this.
In the appdelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) NSString * someString;
#end
In the appdelegate.m
#import "AppDelegate.h"
#import "SomeContextExample.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.someString = #"Hello!";
NSDictionary * userInfo = #{SomeContextExampleRef : self.someString};
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomeContextExample"
object:nil
userInfo:userInfo];
return YES;
}
The "SomeContextExampleRef" is coming from a .h file as follows:
#ifndef SampleNotWorking_SomeContextExample_h
#define SampleNotWorking_SomeContextExample_h
#define SomeContextExampleRef #"SomeContextExampleRef"
#endif
Finally, in the viewController.m:
#import "ViewController.h"
#import "SomeContextExample.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[[NSNotificationCenter defaultCenter] addObserverForName:#"SomeContextExample"
object:nil
queue:mainQueue
usingBlock:^(NSNotification *note)
{
NSLog(#"got the notification!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! %#", note.userInfo);
}];
}
My full code is attached here:
https://github.com/moomoo23/SampleNotWorking
Thank you for helping a beginner!
Try posting your notification when you are confident your "ViewController" object has instantiated and/or come into view. E.G. why not try it out by putting it into an "IBAction" method fired by some UIButton?
The observing view controller may or may not (more likely not in your case) be existing at the end of "application:didFinishLaunchingWithOptions:" in your app delegate.
Another alternative is to post the notification after a delay using the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: method.
Now, conceptually, why would you post a notification when the appFinsihedLaunching?.... just curious.... e
Related
I am trying to develop an iPhone app using xcode13 with iPhone 11 simulator. I used Xcode template to create a basic app. Now I created a new class (not derived from UIViewController) and tried to get orientation notifications, but I am getting nothing. I tried to add the same code on class that derived from UIViewController and it worked. why ? why ?
Why it is only working from the UIViewController's derived class?
Can I make it work from a class that isn't derived from UIViewController ?
here is my code that is not working :
main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Orientation/Orientation.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
#autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
NSLog(#"Hello world !");
OrientationService * os = [[OrientationService alloc] init];
[os onStart];
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Orientation/Orientation.h
#ifndef Orientation_h
#define Orientation_h
#interface OrientationService : NSObject {
NSInteger * orientation_state;
}
- (void) onStart;
- (void) orientationStateDidChange:(NSNotification*) notification;
#end
Orientation/Orientation.m
#endif /* Orientation_h */
#import <Foundation/Foundation.h>
#import "Orientation.h"
#import <UIKit/Uikit.h>
#implementation OrientationService
-(void) orientationStateDidChange:(NSNotification*) notification {
NSLog(#"got notified");
}
-(instancetype)init{
self = [super init];
NSLog(#"OrientationService alloc");
return self;
}
-(void)onStart{
NSLog(#"starting orientation service");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationStateDidChange:) name:(UIDeviceOrientationDidChangeNotification) object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
}
#end
I spent so long on this but it just hit me! It's because os in main() isn't being retained in memory. Instead, either implement the functionality in the App Delegate, or retain an instance of your OrientationService object in the App Delegate. For example:
AppDelegate.h
#import <UIKit/UIKit.h>
#include "Orientation.h"
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) OrientationService *os;
#end
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize os;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
os = [[OrientationService alloc] init];
[os onStart];
return YES;
}
#end
I've recently realised that in my projects it's often necessary to react to keyboard events, usually adjusting some layout or making some other changes. In order to achieve this I have to complete 3 steps each time:
Register a VC as an observer for keyboard notifications;
Create a couple of corresponding methods to handle notifications;
Finally remove VC from observers list at appropriate time (dealloc,willDissappear, etc.);
I started to think, how can I make this steps into a simple reusable instrument.
The most obvious solution is to subclass a UIViewController, add all three steps to it, and then just override handling methods. This will work, but it's not very flexible. What if I want to stop observing at different point? Another argument against a superclass is that most of my controllers already have 2 parent classes before UIViewController. One for scrolling management and another one for tableView management. Is this a good practice to have such a subclass tree? Any disadvantages?
Second solution that comes to mind is a EventManager class that takes blocks to complete event handling. Here's the code for it:
.h
#import <Foundation/Foundation.h>
#import UIKit;
#interface NKKeyboardEventsManager : NSObject
#property (nonatomic, assign, getter=isObserving) BOOL observing;
+(instancetype)managerWithShowBlock:(void (^)(CGFloat kbHeight))showBlock hideBlock:(void (^)(void))hideBlock ;
- (void)startEventsObserving;
- (void)stopEventsObserving;
#end
.m
#interface NKKeyboardEventsManager ()
#property (nonatomic, copy) void (^showBlock)(CGFloat kbHeight);
#property (nonatomic, copy) void (^hideBlock)(void);
#end
#implementation NKKeyboardEventsManager
+ (instancetype)managerWithShowBlock:(void (^)(CGFloat kbHeight))showBlock hideBlock:(void (^)(void))hideBlock {
NKKeyboardEventsManager *manager = [super new];
if (manager) {
manager.showBlock = showBlock;
manager.hideBlock = hideBlock;
[manager startEventsObserving];
}
return manager;
}
- (void)startEventsObserving {
if (self.isObserving) return;
self.observing = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_handleKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_handleKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)stopEventsObserving {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Private
- (void)_handleKeyboardWillShow:(NSNotification *)paramNotification
{
NSDictionary* info = [paramNotification userInfo];
CGFloat kbHeightNew = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
if (self.showBlock) {
self.showBlock(kbHeightNew);
}
}
- (void)_handleKeyboardWillHide:(NSNotification *)paramNotification
{
if (self.hideBlock) {
self.hideBlock();
}
}
#end
It works okish, not to much boilerplate code:
#import "NKKeyboardEventsManager.h"
#interface ViewController ()
#property (nonatomic, weak) IBOutlet NSLayoutConstraint *textViewBottomConstraint;
#property (nonatomic, strong) NKKeyboardEventsManager *keyboardManager;
#end
#implementation ViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
__weak typeof(self) weakSelf = self;
self.keyboardManager = [NKKeyboardEventsManager managerWithShowBlock:^(CGFloat kbHeight) {
weakSelf.textViewBottomConstraint.constant = kbHeight + 20;
[weakSelf.view layoutIfNeeded];
} hideBlock:^{
weakSelf.textViewBottomConstraint.constant = 20;
[weakSelf.view layoutIfNeeded];
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.keyboardManager stopEventsObserving];
}
But this solution comes with some blocks problem. Everything within the block is captured by the block, and the block itself is captured by the manager which is captured by the VC. I'm walking on a thin ice of retain cycles. This is why I had to set manager to nil to break the cycle.
UPDATE:
I incorporated a tip from #Ben Pious to use weakSelf inside the block.
I'm looking for any suggestions to both solutions. Both do not seem great to me. Throw anything, some minor tweaks or a completely new approach, like some clever #define macro with block parameters or something.
I'm new to development and been trying to figure this out,but after trying various different solutions, I'm still unable to get the result I'm looking for.
I would like to update the UILabel in a ViewController from another class.
here is a little demo program that I cannot get to work.
I have a view controller which has 3 UILabels, one is updated from the viewDidLoad and the other two I would like to update from the other class called Hello which is called from ViewController, I can the see the class is being called correctly as the console is logging the NSLog entry but I cannot get the syntax for updating the UILabel.
Thanks in advance.
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (nonatomic, retain) IBOutlet UILabel *firstLabelBox;
#property (nonatomic, retain) IBOutlet UILabel *secondLabelBox;
#property (nonatomic, retain) IBOutlet UILabel *thirdLabelBox;
#end
ViewController.m
#import "ViewController.h"
#import "Hello.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize firstLabelBox;
#synthesize secondLabelBox;
#synthesize thirdLabelBox;
- (void)viewDidLoad {
[super viewDidLoad];
firstLabelBox.text=#"Hello";
[Hello updatedisplay];
[Hello getStringToDisplay];
}
#end
Hello.h
#import <Foundation/Foundation.h>
#class ViewController;
#interface Hello : NSObject
+(void)updatedisplay;
+(void) getStringToDisplay;
#end
Hello.m
#import "Hello.h"
#import "ViewController.h"
#implementation Hello
+ (void)updatedisplay
{
NSLog(#"NewClass - updatedisplay");
ViewController *labelupdate02 = [[ViewController alloc]init];
labelupdate02.secondLabelBox.text = #"Apple";
}
+ (void) getStringToDisplay
{
NSLog(#"Inside getString function - updatedisplay");
ViewController *labelupdate03 = [[ViewController alloc]init];
labelupdate03.thirdLabelBox.text = #"World";
}
#end
First update method of Hello class
+ (void)updatedisplay
{
NSLog(#"NewClass - updatedisplay");
[[NSNotificationCenter defaultCenter] postNotificationName:#"CHANGE_SECOND_LABEL" object:nil];
}
+ (void) getStringToDisplay
{
NSLog(#"Inside getString function - updatedisplay");
[[NSNotificationCenter defaultCenter] postNotificationName:#"CHANGE_THIRD_LABEL" object:nil];
}
And then update viewDidLoad()
- (void)viewDidLoad
{
[super viewDidLoad];
firstLabelBox.text=#"Hello";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(secondLabel) name:#"CHANGE_SECOND_LABEL" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(thirdLabel) name:#"CHANGE_THIRD_LABEL" object:nil];
[Hello updatedisplay];
[Hello getStringToDisplay];
}
Implement below methods in ViewController.m.
- (void)secondLabel
{
secondLabelBox.text = #"Apple";
}
- (void)thirdLabel
{
thirdLabelBox.text = #"World";
}
Is there a way i can execute the
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
method while the app is running from a different ViewController?
Maintain a separate method in your app delegate for setting nav bar appearance.
In your AppDelegate.h file, declare the same method.
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
-(void)setNavBarAppearance;
#end
In the appDelegate.m file, write the functionality for that method
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setNavBarAppearance];
}
-(void)setNavBarAppearance {
//Do what is required here.
}
#end
Then, wherever you need to call the same method:
#import "AppDelegate.h"
AppDelegate* appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate setNavBarAppearance];
You should not call the delegate methods by your own, You can use NSNotificationCenter
Write the code you want to execute in a different method (someThingInterestingHappened) in our case.
Register the class to notification by calling addObserver on defaultNotificationCenter , i.e. [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(someThingInterestingHappened:) name:#"desiredEventHappend" object:nil];
Post the notification from any class [[NSNotificationCenter defaultCenter] postNotificationName:#"desiredEventHappend" object:nil];
I have a simple test app to help me learn how to persist data from a NSMutableArray to a plist. Everything seems to be working well, until I try to save the data by calling a ViewController method called "saveData" in my AppDelegate.m file:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[ViewController saveData];
}
I get a "No known class method for selector 'saveData', although the method is clearly declared in ViewController.h, like so:
//
// ViewController.h
// PlistTest
//
// Created by Tim Jones on 10/30/13.
// Copyright (c) 2013 TDJ. All rights reserved.
//
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#property (weak, nonatomic) IBOutlet UILabel *timeLabel;
#property NSMutableArray *mainActivityArray;
- (IBAction)buttonHit:(id)sender;
-(NSString *) getFilePath;
-(void) saveData;
-(void) loadData;
#end
and implemented in ViewController.m, thusly:
//
// ViewController.m
// PlistTest
//
// Created by Tim Jones on 10/30/13.
// Copyright (c) 2013 TDJ. All rights reserved.
//
#import "ViewController.h"
#import "DataClass.h"
#interface ViewController ()
#end
#implementation ViewController
-(NSString *) getFilePath
{
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"PlistTestData"];
}
-(void) saveData
{
[self.mainActivityArray writeToFile: self.getFilePath atomically:YES];
}
I imported the ViewController.h into AppDelegate.h.
I'm pretty green, so I expect the problem may be obvious to many here. Would sure appreciate some help.
Issue:
[ViewController saveData];
You are calling saveData method using class name ViewController.
But saveData is an instance method, not class method.
-(void) saveData;
Fixes:
1) Declare saveData as class method
+(void) saveData;
2) Call saveData using the object of ViewController.
ViewController *vControl = [[ViewController alloc] init];
[vControl saveData];
Previous answer (Midhun #2) can work, but I think you would be better off using the Application Did Enter Background Notification and skip the delegate.
Just add this to "view did load": it will call saveData whenever the app goes to background.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveData) name:UIApplicationDidEnterBackgroundNotification object:nil];
Hope this helps.
As others have noted, your issue is that your [ViewController saveData] method invocation clearly suggests that you're trying to call a "class method", and you undoubtedly want to call your "instance method" (because it's your instance of your view controller that has the data you want to save). To achieve that, you have two basic choices:
You can have your app delegate call the saveData method in your view controller.
In various comments, you mention that you've "tried instantiating the VC every way and in every place I can imagine." Don't. Midhun's example was a conceptual one, illustrating the difference between a class and instance method. But, while you want to call the instance method, you want to call this for your existing instance of your view controller, definitely not instantiating a new view controller.
So, you may ask, how do you get a reference to the existing instance of your view controller? What you'll want to do is to (a) create a property in your app delegate to hold the reference to the view controller with the saveData method; (b) have that view controller set that property of the app delegate. So, first, create a property in your app delegate's .h file to reference the view controller:
#property (weak, nonatomic) ViewController *viewController;
Obviously, don't forget the #import "ViewController.h" line early in the .h file.
Second, have the viewDidLoad method of the view controller update the app delegate's viewController property:
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
delegate.viewController = self;
}
Again, don't forget to #import "AppDelegate.h" at the top of your ViewController.m file.
Having done that, then app delegate's applicationDidEnterBackground can now reference this property that you set in viewDidLoad
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.viewController saveData];
}
Frankly, if you did this technique, I might suggest further refinements, notably employing a delegate-protocol pattern, but I'm going to defer that conversation until after you have mastered the above technique.
Even easier than the above is to eliminate this app delegate applicationDidEnterBackground code altogether, and just have your view controller, itself, respond to the system notification that is associated with the app entering background. And, needless to say, you put this code right in the view controller itself.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveData) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)saveData
{
// save your data here
}
As you can see, I register to observe UIApplicationDidEnterBackgroundNotification in viewDidLoad, but also make sure to remove my observer in dealloc. I also made sure that my #selector method name exactly matched my method name (e.g., in my example, no parameters, and hence no colon).