The following does not work — handleSwipeUpTriple is never called:
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UISwipeGestureRecognizer *swipeUpTripleRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeUpTriple:)];
swipeUpTripleRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
swipeUpTripleRecognizer.numberOfTouchesRequired = 3; // triple finger
// window is in nib
[self.window addGestureRecognizer:swipeUpTripleRecognizer];
[swipeUpTripleRecognizer release];
}
- (void) handleSwipeUpTriple:(UISwipeGestureRecognizer *)sender {
printf("\nhandleSwipUpTrpl called."); // never happens
if (sender.state == UIGestureRecognizerStateEnded)
printf("\n SwipeUpTriple recognized.");
}
}
The weird thing is that if I change the numberOfTouchesRequired to 1 or even 2, it works. Only 3 (or 4) fingers seem to be out of bounds. Since I see a number of posts regarding 3-finger gestures, I don’t see why this should be.
self.window.multipleTouchEnabled is YES.
For testing purposes, I have removed all subviews. There’s nothing but self.window on the screen.
I’m still using iOS 4.3, but since UISwipeGestureRecognizer was available by iOS 3.2, I don’t see why that should be a problem.
I know that the non-iPad devices sometimes are running system gesture recognizers for 3 fingers (usually zoom). Depending on your settings the system may have reserved this number of fingers for itself.
As we found, you can fix this by going to General > Accessibility and disabling the three-finger gestures.
Related
I want to make my iOS application support iPhone 5. So I created a separate xib set for iPhone 5 size. Then I load each xib by checking the screen height.
This is the splash screen loading code inside the AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
UIViewController *viewController1;
if ([UIScreen mainScreen].bounds.size.height==480) {
viewController1 = [[SplashScreen alloc] initWithNibName:#"SplashScreen" bundle:nil];
}
if ([UIScreen mainScreen].bounds.size.height==568) {
viewController1 = [[SplashScreen alloc] initWithNibName:#"SplashScreen5" bundle:nil];
}
self.window.rootViewController = viewController1;
[self.window makeKeyAndVisible];
return YES;
}
But when I change the simulator into Retina 4-inch, my code doesn't get the emulator size. It always executes the 480 if condition.
But other apps I created like this are working properly.
What is the reason for this?
I'm having the exact same problem right now (at the worst moment, of course....).
It did work properly for several weeks, and for a unknown reason, the simulator suddenly considers the 4in simulated device as a 3.5in screen.
cleaning, reset, reboot : same situation...
EDIT : ok, problem solved. T'was because of a missing Default image in the -568#2x format. I knew that was a condition to make the system work, but xcode had apparently decided to get rid off the one I chose. oh well...
Using the GestureRecognizer attached to a view triggers my app to crash with EXC_BAD_ACCESS error. Here's the classes involved
• BoardViewController - Displaying a board (as background) set as rootViewController in the AppDelegate. It instantiates multiple objects of the "TaskViewcontroller".
//BoardViewController.h
#interface BoardViewController : UIViewController {
NSMutableArray* allTaskViews; //for storing taskViews to avoid having them autoreleased
}
//BoardViewController.m - Rootviewcontroller, instantiating TaskViews
- (void)viewDidLoad
{
[super viewDidLoad];
TaskViewController* taskA = [[TaskViewController alloc]init];
[allTaskViews addObject:taskA];
[[self view]addSubview:[taskA view]];
}
• TaskViewController - An indivual box displayed on the board. It should be draggable. Therefore I attached UIPanGestureRecoginzer to its view
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer* panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[[self view] addGestureRecognizer:panRecognizer];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
NSLog(#"PAN!");
}
The .xib file is a simple view.
All programming with the gesture recognizer I'd prefer to do in code. Any idea how to fix the error causing the app crash?
The method handlePan is on your view controller, not on your view. You should set the target to self:
UIPanGestureRecognizer* panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
EDIT (in response to the edit of the question) As omz has correctly noted, your TaskViewController gets released upon BoardViewController's viewDidLoad: exit. There are two ways of dealing with it:
Fold the handlePan method into the parent view controller, along with the code of viewDidLoad:, or
Make an instance variable for TaskViewController *taskA, rather than making it a local variable.
This is my way to use Gesture Recognizer. I think this way is easy and with low risk.
At first, you drag and drop Gesture Recognizer into the view.
Then, you wire the Gesture Recognizer icon to code.
Finally, you write code for this IBAction like below:
- (IBAction)handlePan:(id)sender {
NSLog(#"PAN!");
}
You can download this project from GitHub and just run it.
https://github.com/weed/p120812_PanGesture
I'm working on an application using a map on which the user have to add an annotation. Very simple. Problem is, right after I added a tap recognizer on my map, the zoom supposed to be triggered after a double tap no longer works. It seems to be a pretty common issue, as I found (and tried, I swear) a lot of potential solutions without any success.
The latest solution I tried (and I felt very clever after finding it by myself) was trying to retrieve the "built-in double tap" recognizer of the MkMapView, so I can reuse it in my "single tap" gesture recognizer. This is what I did :
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *gestures = [map gestureRecognizers];
for (UIGestureRecognizer *r in gestures) {
if ([r class] == [UITapGestureRecognizer class]) {
if (2 == [(UITapGestureRecognizer *)r numberOfTapsRequired]) {
builtInDoubleTap = (UITapGestureRecognizer *)r;
NSLog(#"BOOM! Found it!");
return;
}
}
}
NSLog(#"Guess view did load");
[self enableTapRecognizer];
}
And in my "enableTapRecognizer" method :
- (void)enableTapRecognizer {
UITapGestureRecognizer *mapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handlePosition:)];
if(builtInDoubleTap) {
NSLog(#"require to fail builtInDoubleTap");
[mapGestureRecognizer requireGestureRecognizerToFail: builtInDoubleTap];
}
[map addGestureRecognizer: mapGestureRecognizer];
}
Problem is, it never shows my "Boom! Found it!" debug message. How come it can zoom after a double tap without any gesture recognizer? That doesn't really make sense, does it?
(I also tried using a UIGestureRecognizerDelegate so my gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer method returns YES...)
I'm kinda desperate right now, so if somebody has an idea... maybe something wrong in my map configuration?
I'm targeting iOS5 and running latest xCode.
Thanks !
I'm running xcode-4.2 and the project is based for ios5 using storyboards.
I've created a single view application , using the template provided by Apple.
In the storyboard I the removed the viewcontroller created for me and added a UITabBarController.
Next I added a new class MyTabBarController which is a subclass of UITabBarController.
Now I want to show a splashscreen before the TabBar appears. So I can do some loading and calculation in the background.
I thought AppDelegate.m would be a good place for this. Since that's the place where my rootview get's loaded not ? Or should a show the splashscreen from the rootviewcontroller which is MyTabBarController in my case ?
So I created a xib file. I'm surprised you can add .xib files to ios5 storyboard projects. The xib file is called SplashView.xib it has a single view with an image on it.
Code in AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_splashScreen = [[UIViewController alloc] initWithNibName:#"SplashView" bundle:nil];
//_splashScreen is defined as:#property (strong, nonatomic) UIViewController *splashScreen;
[_window.rootViewController presentModalViewController:_splashScreen animated:NO];
[self performSelector:#selector(hideSplash) withObject:nil afterDelay:2];
return YES;
}
The problem is nothing happens. Even if I change the value from 2 to 200. The application starts up as if there is no splashscreen.
As you might have noticed I'm still struggling with the design of objective-c and iphone application. I hope a decent answer to my question will bring some clarity to the subject.
Thanks in advance!
Splash screens are built into iOS apps. All you need to do is create a file called Default.png and Default#2x.png (for retina displays) and it will work as a splash screen for when the app launches.
You can also set what these images will be in your apps info.plist.
I've dealt with a few clients who wanted to use an animated splash.
Though I'm totally against this, following Apple's HIG,
those clients just don't understand...
Anyway, since - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; has to return boolean,
it's very important not to halt it for anything.
Also, since launch time is measured by iOS, if it's taking too long, the app will be terminated by iOS!
For this reason, I often use - (void)applicationDidBecomeActive:(UIApplication *)application;
with some kind of flag to indicate if it happened at launch or at returning from background mode.
Or, you should use a NSTimer or - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
so, didFinishLaunchingWithOptions can return without being blocked for processing your animated splash.
This delayed performSelector should be implemented not only for hiding action (like the way you intended it), but also for starting the animation.
If you are using storyboard, you can just add the splash UIImageView to your window.rootViewController.view like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIImage *splashImage = [UIImage autoAdjustImageNamed:#"Default.png"];
UIImageView *splashImageView = [[UIImageView alloc] initWithImage:splashImage];
[self.window.rootViewController.view addSubview:splashImageView];
[self.window.rootViewController.view bringSubviewToFront:splashImageView];
[UIView animateWithDuration:1.5f
delay:2.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
splashImageView.alpha = .0f;
CGFloat x = -60.0f;
CGFloat y = -120.0f;
splashImageView.frame = CGRectMake(x,
y,
splashImageView.frame.size.width-2*x,
splashImageView.frame.size.height-2*y);
} completion:^(BOOL finished){
if (finished) {
[splashImageView removeFromSuperview];
}
}];
return YES;
}
I think the reason why directly just add the UIImageView to window is because iOS will bring the rootViewController.view to front when the default splash will hide. And this will overlap the animation. This means the animation does happen but it's behind the rootViewController.
I just add an identical image to the launch image to my first view controller and then fade it (or whatever animation you require) - this avoids pausing the app load in the AppDelegate.
You need to ensure that the image has the same size and origin as your launch image e.g. to set the image to display on my first view controller which is a tableViewController:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.tableView.bounds];
imageView.image = [UIImage imageNamed:#"[imagename]"];
[self.tableView addSubview:imageView];
[self.tableView bringSubviewToFront:imageView];
// Fade the image
[self fadeView:imageView];
-(void)fadeView:(UIView*)viewToFade
{
[UIView animateWithDuration:FADE_DURATION
animations:^ {
viewToFade.alpha = 0.0;
}
];
}
here's the problem:
I'd like to move to using UIGestureRecognizer in my Apps.
For this reason I'd like to ditch TouchBegan/TouchEnded event's from my views.
However I don't understand how to manage when the touch began (user puts its finger on the screen) with UIGestureRecognizers.
The simplest one is UITapGestureRecognizer but the selector associated gets fired only when the TapGesture is completed (Well... it makes completely sense of course). But still the problem remains: how can I stop using touchesBegan and get that event anyway from UIGestureRecognizer?
Thanks!
Here is an example:
//Pan gesture
recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
((UIPanGestureRecognizer *)recognizer).minimumNumberOfTouches = 3; //number of fingers
recognizer.delegate = self;
[self.view addGestureRecognizer:recognizer];
[recognizer release];
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan)
{
//do something
} else if (recognizer.state == UIGestureRecognizerStateEnded)
{
//do something
}
}
Also implement UIGestureRecognizerDelegate in .h file. May be you need to do self.view.userInteractionEnabled = YES depending on the view you're using. e.g., if it's UIImageView, the you need to set userInteractionEnabled = YES, default is NO
For what you are tryin ti do you can't. The gesture recoginizers are for high level gestures so they behaive the same across all apps (think swipes, the timing required for a double tap, etc). For low level control and to do things that the recognizers can't you will still have to implement logic in touchesbegan, touchesEnded, etc.
Why not implement your own touchesBegan in a UIGestureRecognizer subclass -- intercept the message, extract the information you'd like, and then pass the message along to super's touchesBegan?