after reading some posts about implementing shaking on 3.0, I think I get the idea but I'm not getting any call to the:
motionBegan
motionEnded
motionCancelled
this is an example of what I've read:
how to detect and program around shakes for the iphone
I'm sure I've added the
[self becomeFirstResponder];
and the
-(BOOL)canBecomeFirstResponder {
NSLog(#"First responder");
return YES;
}
Should I enable a special delegate for those events ?
I understand that those events are controlled by the system, and they are passed to the first responder, and go on ...
any idea ?
thanks,
r.
I had loads of problems getting this to work and I finally gave up and followed jandrea's advice. He suggested subclassing UIWindow and implement the motionEnded there. This is a quote from his post here, look for it quite far down.
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];
}
}
Change #"DeviceShaken" to the
notification name of your choice. Save
the file.
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];
Where do you call becomeFirstResponder? You should do it in viewDidAppear. Does this get fired?
Related
I trying to figure out how to solve this (fairly) simple problem but I failing miserably, so I really need your advice.
My application consists of a uitabbar with several tabs. In one of them I have a bunch of UIImageViews each of which represents the thumbnail of a picture. Similarly as you remove apps from the iPhone by pressing for a second on the app icon, I implemented a UILongPressGestureRecognizer recognizer which starts wobbling the thumb. If the user taps on the 'X' that appears on the corner of the thumb the picture gets removed.
The logic that starts and stops the wobbling animation is inside a subclass of UIImageView that is used to show the thumb.
What I'm trying to do is cancel the wobble effect if the user presses anywhere else outside the thumb. Ideally, if possible, I would prefer to place the code that detects this cancel touch inside the UIImageView subclass.
To catch all touch events globally I ended up subclassing UIWindow as follows:
// CustomUIWindow.h
#import <UIKit/UIKit.h>
#define kTouchPhaseBeganCustomNotification #"TouchPhaseBeganCustomNotification"
#interface CustomUIWindow : UIWindow
#property (nonatomic, assign) BOOL enableTouchNotifications;
#end
// CustomUIWindow.m
#import "CustomUIWindow.h"
#implementation CustomUIWindow
#synthesize enableTouchNotifications = enableTouchNotifications_;
- (void)sendEvent:(UIEvent *)event
{
[super sendEvent:event]; // Apple says you must always call this!
if (self.enableTouchNotification) {
[[NSNotificationCenter defaultCenter] postNotificationName:kTouchPhaseBeganCustomNotification object:event];
}
}#end
Then whenever I need to start listening to all touches globally I do the following:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopThumbnailWobble:)
name:kTouchPhaseBeganCustomNotification
object:nil];
((CustomUIWindow *)self.window).enableTouchNotification = YES;
In stopThumbnailWobble I remove the observer and process the UITouch event to decide whether to remove the thumb or not:
- (void)stopThumbnailWobble:(NSNotification *)event
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:kTouchPhaseBeganCustomNotification
object:nil];
((CustomUIWindow *)self.window).enableTouchNotification = NO;
UIEvent *touchEvent = event.object;
// process touchEvent and decide what to do
...
Hope this helps others.
If you must include the code detection in your uiimageview subclass then I would tell the appdelegate that a touch was received and where. The app delegate could then either tell all your uiimageviews or tell the viewcontroller which would tell it's uiimageviews.
untested code:
appDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate touchedAt:(int)xPos yPos:(int)yPos];
I am writing iOS application for iPad that require custom layout.
The layout from portrait and landscape are totally difference, so it can't be solve by using UIAutoResizingMask.
I try to use the layoutSubview Method, but I detected that layout subview is called a lot (from UIScrollView).
How can i reduce the layoutSubview call to optimize the code , or I should call it by my self when ever the device is rotated.
Thank.
For different landscape and portrait design use view controllers methods such as
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
If you create your custom view depending on current orientation, check this orientation by UIDeviceOrientationDidChangeNotification notification and write appropriate code.
in one of the init~ methods:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didChangedOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];
And action
- (void) didChangedOrientation:(NSNotification *)sender{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (UIDeviceOrientationIsPortrait(orientation)){}}
The fact that layoutSubviews gets called by a child UIScrollView is very unfortunate, but there's an (ugly) workaround:
#interface MyClass : UIView {
BOOL reallyNeedsLayout_;
}
#end
#implementation MyClass
- (void)setNeedsLayout
{
[super setNeedLayout];
reallyNeedsLayout_ = YES;
}
- (void)setFrame:(CGRect)rect
{
[super setFrame:rect];
reallyNeedsLayout_ = YES;
}
- (void)layoutSubviews
{
if (!reallyNeedsLayout_) return;
reallyNeedsLayout_ = NO;
// Do layouting.
}
#end
Not the best solution but seems to work reasonably well.
You should not do expensive calculations in layoutSubviews:
Speaking from experience I would personally only adjust your layout based upon deviceDidRotateSelector notifications.
I have an updatePortrait method and an updateLandscape method and call whichever is necessary.
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.
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.
every UIViewController has a method called willRotateToInterface.
Is it possible to do this within a UIView too?
Does this match the idea of model view controller ?
The only way I can think of is to send the event from the UIViewController to the UIView.
Is there a global variable for the current orientation?
Observe UIDeviceOrientationDidChangeNotification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
...
- (void)orientationDidChange:(NSNotification *)note
{
NSLog(#"new orientation = %d", [[UIDevice currentDevice] orientation]);
}
UIDevice Class Reference
I should note that yo uneed to add -beginGeneratingDeviceOrientationNotifications when you want these notifications to be sent, and call -endGeneratingDeviceOrientationNotifications when you want them to stop. There is a battery impact of generating these, so you should only do so when your view is on screen. UIViewController does all this for you, so if you have a view controller, it is worth letting it do the work.
If you just want to adjust view to new size/layout when orientation changes, you should just override its layoutSubviews method.
It will be called whenever size of the view changes, which usually happens when view is rotated.
You can subscribe to a global notification and get a call when the device is rotated, it wont do anything for you though..
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:#"UIDeviceOrientationDidChangeNotification" object:nil];