How to receive a notification when a view will appear? - iphone

When I'm doing a half-page curl modal transition:
How can I tell when the page has been restored back to it's current state? I want to call something when the "settings" view has been closed.
I tried to use viewWillAppear:(BOOL)animated but it doesn't seem to get called when closing the view. Any ideas?

You can register an NSNotificationCenter observer on your master view and post the notification on your background view. And instead of viewWillAppear you can use viewDidLoad.
// EDIT: sample code to get a touch gesture in a given rect
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([[event allTouches]count] == 1) {
UITouch *t = [[touches allObjects]lastObject];
CGPoint p = [t locationInView:self.view];
if (p.y < 200) NSLog(#"above 200");
}
}

In your viewDidLoad, register a Notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateView:)
name:#"updateRootView"
object:nil];
Now this is the notification that we call
- (void) updateView:(NSNotification *) notification
{
/* notification received after the page is uncurled */
}
The calling method:
- (void) unCurlPage
{
// All instances of TestClass will be notified
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateRootView" object:self];
}
And don't forget to dealloc the notification
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}

ViewWilAppear is a UIViewController message/method. If you are only changing views, it won't get called. What does the code you are using to close the settings view look like?
Edit
It sounds like you need to refactor a bit. Assuming all this is handled by the parent UIViewController for this settings view, you could implement something like:
- (void)settingsPanelOpen {
// present the modal
// hook to inform of opening (if necessary)
}
- (void)settingsPanelClose {
// dismiss modal
// hook to inform of closing
}
Then settingsPanelClose could have a hook into it if you need to know when the settings closes.
The other thing you could do is subclass UIViewController as SettingsViewController and override the viewDidDisappear: method to kick off a SettingsDidSave notification or otherwise inform your app that it has closed.

Related

motionEnded not called in appDelegate

I want to integrate shake feature throughout the app. So I am doing everything in appDelegate. I need to push a viewController, I am able to push in motionBegan, but i wanted to do it motionEnded. yes motion ended does work in a view controller, but in app delegate it is not being called.
Doing as
- (void)applicationDidBecomeActive:(UIApplication *)application {
[self becomeFirstResponder];
}
- (BOOL)canBecomeFirstResponder{
return YES;
}
motionEnded not called
-(void) motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if(event.subtype==UIEventSubtypeMotionShake){
NSLog(#"motionEnded called");
}
}
motionBegan called
-(void) motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if(event.subtype==UIEventSubtypeMotionShake){
NSLog(#"motionBegan called");
}
}
you could basically register your viewController for applicationDidBecomeActiveNotification or any depending on your needs
for example in your viewController's viewDidLoad method you could register it for notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMethod)
name:UIApplicationDidBecomeActiveNotification object:nil];
and implement this method in your class, your myMethod will call everytime your application will become active
-(void) myMethod(){
// do your stuff
}
finally un-register viewController from the notification in dealloc method
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

what method within a ViewController's class can I call to check when it has been brought to the foreground?

what method within a ViewController's class can I call to check when it has been brought to the foreground?
For example Im looking at a page on my application and I decide to close the application and go back to it later. When I go back to it the same view as I was looking at was on the screen. However... As soon as I open the application I want to segue over to another view.
How can I do this?
Currently trying this:
- (void) applicationDidBecomeActive:(NSNotification*) notification
{
[self checkActivity];
// Do your stuff here
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
return self;
}
- (void)checkActivity{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSLog(#"Checking if re-authentication required...");
if([[defaults objectForKey:#"shouldgotologin"] isEqualToString:#"yes"]){
NSLog(#"View Should go to login...performing segue");
[defaults setObject:#"no" forKey:#"shouldgotologin"];
[defaults synchronize];
[self performSegueWithIdentifier:#"backtologin" sender:self];
} else {
NSLog(#"Should go to login is not true.");
}
}
Register your view controller to observe UIApplicationWillEnterForegroundNotification:
1) Inside view controller's init method:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
2) Inside view controller's dealloc method:
[[NSNotificationCenter defaultCenter] removeObserver:self];
3) Also, have your view controller implement this method:
- (void) applicationWillEnterForeground:(NSNotification*) notification
{
// This method will be called just before entering the foreground;
// Do your stuff here
}
If the timing of UIApplicationWillEnterForegroundNotification doesn't suit you, check all the available notifications for UIApplication here:
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html
Jump To ApplicationDelegate File, you will find following methods.
- (void)applicationWillResignActive:(UIApplication *)application
{
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
}
Remember, It is not the viewController who receives the notifications related to the Application states like willResignActive,didEnterBackground,willEnterForeground. ApplicationDelegate object is going to handle those notifications. So, Try putting your logic in above methods.
Hope that helps. If not, add your queries using comments below my answer.

Race condition in refreshing view when application becomes active

On Facebook's iPhone app, the news feed refreshes every time the app becomes active. I would like to do something similar, but I'm concerned about a race condition. The general bootstrapping of my app is as follows:
UIApplicationDelegate
- (void)applicationDidFinishLaunching:(UIApplication*)application
{
[window addSubview:[self.navigationController view];
[window makeKeyAndVisible];
}
- (void)applicationDidBecomeActive:(UIApplication*)application
{
[rootViewController refresh];
}
RootViewController
#pragma mark custom
- (void)refresh
{
if (self.newsFeedModel == nil) {
self.newsFeedModel = [[NewsFeedModel alloc] initWithDelegate:self];
}
[self.newsFeedModel request];
}
#pragma mark UIViewController
- (void)viewDidLoad
{
// initialize the table
// add subviews and whatnot
}
#pragma mark NewsFeedDelegate
- (void)newsFeedSucceeded:(NSMutableArray*)feed
{
// reload table view with new feed data
}
After sprinkling NSLog everywhere, I determined the order of operations to be:
applicationDidFinishLaunching
applicationDidBecomeActive
refresh
viewDidLoad
newsFeedSucceeded
Notice how refresh is called before the root view has been loaded. While we're busy querying the server, the root view loads. When the server responds, the root view is populated with the feed. This works in most cases because the network operation takes a long time. However, if the network operation finishes faster than view can be loaded, then I will be attempting to construct the news feed before the view has been loaded. This would be bad. What is the best Cocoa Touch practice for solving this race condition? I would just set a bunch of flags to determine what state we're in and refresh the news feed depending on the state, but I'm wondering if there were built in events in Cocoa Touch to handle this for me.
I think you want to take a look at applicationWillEnterForeground: instead.
applicationDidBecomeActive: can be called while your app is still running in the foreground. For instance if a text message comes while your app is in the foreground and the user dismisses it, applicationDidBecomeActive: will get called.
You can subscribe to the UIApplicationWillEnterForegroundNotification event in your RootViewController using NSNotificationCenter. I would do this in RootViewController initWithNibName: or whichever init method you are using.
Now you just need to call refresh in 2 places. Once at the end of viewDidLoad and again whenever applicationWillEnterForeground: is called.
This should solve your race condition problem. Since RootViewController is handling it's own refreshing when it knows it is ok to do so.
RootViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
return self;
}
- (void)viewDidLoad
{
// initialize the table
// add subviews and whatnot
[self refresh];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self refresh];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}

detect when shakes an iPhone

i have questions regarding the shake detection that posted here before,
here is a reminder:
"Now ... I wanted to do something similar (in iPhone OS 3.0+), only in my case I wanted it app-wide so I could alert various parts of the app when a shake occurred. Here's what I ended up doing.
First, I subclassed UIWindow. This is easy peasy. Create a new class file with an interface such as MotionWindow : UIWindow (feel free to pick your own, natch). Add a method like so:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"DeviceShaken" object:self];
}
}
Now, if you use a MainWindow.xib (stock Xcode template stuff), go in there and change the class of your Window object from UIWindow to MotionWindow or whatever you called it. Save the xib. If you set up UIWindow programmatically, use your new Window class there instead.
Now your app is using the specialized UIWindow class. Wherever you want to be told about a shake, sign up for them notifications! Like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(deviceShaken) name:#"DeviceShaken" object:nil];
To remove yourself as an observer:
[[NSNotificationCenter defaultCenter] removeObserver:self];
questions:
where to put the notifications (i have a view based app) ?
do i have to remove myself as an observe, what does it mean ?
what is the if statement that i use to check if the shake accrued?
how can i know if the shake event know it is "already in progress" ?
thanks.
In iPhone OS 3.x it is simple to receive motion events form any view that is set as the first responder.
In you view class override the method motionEnded:, like this:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if(motion == UIEventSubtypeMotionShake && [self isViewLoaded])
{
//handle shake here...
}
}
In addition, you will need to become the First Responder when the view is loaded and appears:
- (void)viewDidAppear:(BOOL)animated
{
[self becomeFirstResponder];
[super viewDidAppear:animated];
//any extra set up code...
}
You may also have to respond to the canBecomeFirstResponder method.
- (BOOL)canBecomeFirstResponder
{
return YES;
}
These can be used in any object that inherits form UIView.

(iPhone) How to handle touches on a UITextView?

I'm trying to handle touches on a iPhone's UITextView. I successfully managed to handle taps and other touch events by creating a subclass of UIImageViews for example and implementing the touchesBegan method...however that doesn't work with the UITextView apparently :(
The UITextView has user interaction and multi touch enabled, just to be sure...no no joy. Anyone managed to handle this?
UITextView (subclass of UIScrollView) includes a lot of event processing. It handles copy and paste and data detectors. That said, it is probably a bug that it does not pass unhandled events on.
There is a simple solution: you can subclass UITextView and impement your own touchesEnded (and other event handling messages) in your own versions, you should call[super touchesBegan:touches withEvent:event]; inside every touch handling method.
#import "MyTextView.h" //MyTextView:UITextView
#implementation MyTextView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"touchesBegan");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
NSLog(#"touchesMoved");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"****touchesEnded");
[self.nextResponder touchesEnded: touches withEvent:event];
NSLog(#"****touchesEnded");
[super touchesEnded:touches withEvent:event];
NSLog(#"****touchesEnded");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[super touches... etc];
NSLog(#"touchesCancelled");
}
If you want to handle single/double/triple tap on UITextView, you can delegate UIGestureRecongnizer and add gesture recognizers on your textview.
Heres sameple code (in viewDidLoad):
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap)];
//modify this number to recognizer number of tap
[singleTap setNumberOfTapsRequired:1];
[self.textView addGestureRecognizer:singleTap];
[singleTap release];
and
-(void)handleSingleTap{
//handle tap in here
NSLog(#"Single tap on view");
}
Hope this help :D
Better solution (Without swizzling anything or using any Private API :D )
As explained below, adding new UITapGestureRecognizers to the textview does not have the expected results, handler methods are never called. That is because the UITextView has some tap gesture recognizer setup already and I think their delegate does not allow my gesture recognizer to work properly and changing their delegate could lead to even worse results, I believe.
Luckily the UITextView has the gesture recognizer I want already setup, the problem is that it changes according to the state of the view (i.e.: set of gesture recognizers are different when inputing Japanese than when inputing English and also when not being in editing mode).
I solved this by overriding these in a subclass of UITextView:
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
[super addGestureRecognizer:gestureRecognizer];
// Check the new gesture recognizer is the same kind as the one we want to implement
// Note:
// This works because `UITextTapRecognizer` is a subclass of `UITapGestureRecognizer`
// and the text view has some `UITextTapRecognizer` added :)
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 1 &&
[tgr numberOfTouchesRequired] == 1) {
// If found then add self to its targets/actions
[tgr addTarget:self action:#selector(_handleOneFingerTap:)];
}
}
}
- (void)removeGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
// Check the new gesture recognizer is the same kind as the one we want to implement
// Read above note
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 1 &&
[tgr numberOfTouchesRequired] == 1) {
// If found then remove self from its targets/actions
[tgr removeTarget:self action:#selector(_handleOneFingerTap:)];
}
}
[super removeGestureRecognizer:gestureRecognizer];
}
- (void)_handleOneFingerTap:(UITapGestureRecognizer *)tgr
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:tgr forKey:#"UITapGestureRecognizer"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TextViewOneFingerTapNotification" object:self userInfo:userInfo];
// Or I could have handled the action here directly ...
}
By doing this way, no matter when the textview changes its gesture recognizers, we will always catch the tap gesture recognizer we want → Hence, our handler method will be called accordingly :)
Conclusion:
If you want to add a gesture recognizers to the UITextView, you have to check the text view does not have it already.
If it does not have it, just do the regular way. (Create your gesture recognizer, set it up, and add it to the text view) and you are done!.
If it does have it, then you probably need to do something similar as above.
Old Answer
I came up with this answer by swizzling a private method because previous answers have cons and they don't work as expected. Here, rather than modifying the tapping behavior of the UITextView, I just intercept the called method and then call the original method.
Further Explanation
UITextView has a bunch of specialized UIGestureRecognizers, each of these has a target and a action but their target is not the UITextView itself, it's an object of the forward class UITextInteractionAssistant. (This assistant is a #package ivar of UITextView but is forward definition is in the public header: UITextField.h).
UITextTapRecognizer recognizes taps and calls oneFingerTap: on the UITextInteractionAssistant so we want to intercept that call :)
#import <objc/runtime.h>
// Prototype and declaration of method that is going be swizzled
// When called: self and sender are supposed to be UITextInteractionAssistant and UITextTapRecognizer objects respectively
void proxy_oneFingerTap(id self, SEL _cmd, id sender);
void proxy_oneFingerTap(id self, SEL _cmd, id sender){
[[NSNotificationCenter defaultCenter] postNotificationName:#"TextViewOneFinderTap" object:self userInfo:nil];
if ([self respondsToSelector:#selector(proxy_oneFingerTap:)]) {
[self performSelector:#selector(proxy_oneFingerTap:) withObject:sender];
}
}
...
// subclass of UITextView
// Add above method and swizzle it with.
- (void)doTrickForCatchingTaps
{
Class class = [UITextInteractionAssistant class]; // or below line to avoid ugly warnings
//Class class = NSClassFromString(#"UITextInteractionAssistant");
SEL new_selector = #selector(proxy_oneFingerTap:);
SEL orig_selector = #selector(oneFingerTap:);
// Add method dynamically because UITextInteractionAssistant is a private class
BOOL success = class_addMethod(class, new_selector, (IMP)proxy_oneFingerTap, "v#:#");
if (success) {
Method originalMethod = class_getInstanceMethod(class, orig_selector);
Method newMethod = class_getInstanceMethod(class, new_selector);
if ((originalMethod != nil) && (newMethod != nil)){
method_exchangeImplementations(originalMethod, newMethod); // Method swizzle
}
}
}
//... And in the UIViewController, let's say
[textView doTrickForCatchingTaps];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textViewWasTapped:) name:#"TextViewOneFinderTap" object:nil];
- (void)textViewWasTapped:(NSNotification *)noti{
NSLog(#"%#", NSStringFromSelector:#selector(_cmd));
}
You need to assign the UITextView instance.delegate = self (assuming you want to take care of the events in the same controller)
And make sure to implement the UITextViewDelegate protocol in the interface... ex:
#interface myController : UIViewController <UITextViewDelegate>{
}
Then you can implement any of the following
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
- (BOOL)textViewShouldEndEditing:(UITextView *)textView;
- (void)textViewDidBeginEditing:(UITextView *)textView;
- (void)textViewDidEndEditing:(UITextView *)textView;
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
- (void)textViewDidChange:(UITextView *)textView;
- (void)textViewDidChangeSelection:(UITextView *)textView;
I'm using a textview as a subview of a larger view. I need the user to be able to scroll the textview, but not edit it. I want to detect a single tap on the textview's superview, including on the textview itself.
Of course, I ran into the problem that the textview swallows up the touches that begin on it. Disabling user interaction would fix this, but then the user won't be able to scroll the textview.
My solution was to make the textview editable and use the textview's shouldBeginEditing delegate method to detect a tap in the textview. I simply return NO, thereby preventing editing, but now I know that the textview (and thus the superview) has been tapped. Between this method and the superview's touchesEnded method I have what I need.
I know that this won't work for people who want to get access to the actual touches, but if all you want to do is detect a tap, this approach works!
How about make a UIScrollView and [scrollView addSubview: textview] which makes it possible to scroll textview?
You can also send a Touch Down event. Wire-up this event through the Interface Builder.
Then add code in your event handler
- (IBAction)onAppIDTap:(id)sender {
//Your code
}