i want to have a top view (subclass of UIView), which catches touches using
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
...
.h
#interface XView : UIView<UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView * tableView;
#end
this works fine, if the view is empty, but as soon as i insert (addSubview) lets say a UITableView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.tableView = [[UITableView alloc] initWithFrame:self.frame];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self addSubview:self.tableView];
}
return self;
}
Than the touch methods inside XView are not triggered
You would either need to subclass your UITableView or use a UITapGestureRecognizer to solve this. I'd personally go with the UITapGestureRecognizer.
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapCode:)];
[self.tableView addGestureRecognizer:tap];
and then you can handle your tap here:
- (void)tapCode:(UITapGestureRecognizer *)recognizer
{
// code goes here
}
If you subclass UITableView, you can put your touchesBegan:withEvent in there.
Related
I have declared a void touchesbegan method in my controller but it doesn't work! i don't know why. i have an image view which i plan to click it to move to next controller. so i set the touches began method. i have linked the image view in the xib file but when i click the image. nothing happens. Please help, Thanks.
ViewController.h
#import <UIKit/UIKit.h>
#interface imageViewViewController : UIViewController
{
IBOutlet UIImageView *testing;
}
#property(nonatomic, retain) IBOutlet UIImageView *testing;
#end
ViewController.m
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if(touch.view == testing)
{
TestViewController *testviewcontroller = [[TestViewController alloc]initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:testviewcontroller animated:YES];
}
}
#end
P.S.
here i tried another method using tap gestures.
testing is the name of my image view. as u can see i comment out the ns log in the imagedidtapped method. it works til that point. however when i tried to navigate it out to another page it fails.
- (void)viewDidLoad
{
UITapGestureRecognizer *tapRecognizer;
[testing setTag:0];
[testing setUserInteractionEnabled:TRUE];
tapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageViewDidTapped:)] autorelease];
tapRecognizer.numberOfTapsRequired = 1;
[testing addGestureRecognizer:tapRecognizer];
[self.view addSubview:testing];
[testing release];
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)imageViewDidTapped:(UIGestureRecognizer *)aGesture {
UITapGestureRecognizer *tapGesture = (UITapGestureRecognizer *)aGesture;
UIImageView *tappedImageView = (UIImageView *)[tapGesture view];
switch (tappedImageView.tag) {
case 0:
//NSLog(#"UIImageView 1 was tapped");
[self navigate];
break;
case 1:
NSLog(#"UIImageView 2 was tapped");
break;
default:
break;
}
}
-(void)navigate
{
TestViewController *testviewcontroller = [[TestViewController alloc]initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:testviewcontroller animated:YES];
}
The problem is that by default userInteractionEnabled is NO for a UIImageView so you won't get any touches. Set it to YES.
Also the message handling for touchesBegan is really complicated. You'll be much happier if you attach a UITapGestureRecognizer to the image view.
EDIT: Now you say your touches handling is working, but the navigation is not taking place. So let's concentrate on this part of your code:
-(void)navigate
{
TestViewController *testviewcontroller = [[TestViewController alloc]initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:testviewcontroller animated:YES];
}
Put logging in there to make sure navigate is being called! If it isn't being called, you need to figure out why your other code is not running and calling it. If it is being called, then the problem is probably that self.navigationController is nil, i.e. you are not inside a navigation interface to start with.
I've created a new project with two ViewControllers and imported a class that pushes ViewController from right to left instead LTR but can't manage to use it. I can see that pushViewController inside UIRightToLeft.m is not being called and I don't understand why.
My main goal is to get that working with RTL aniamtion.
#import "UIRightToLeft.h"
#implementation UIRightToLeft
- (id)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super initWithRootViewController:rootViewController];
if (!self)
return nil;
return self;
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
NSLog(#"pushViewController");
// Add the viewController and a fake controller without animation. Then pop the fake controller with animation.
UIViewController *fakeController = [[UIViewController alloc] init] ;
[super setViewControllers:[[self viewControllers] arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:viewController, fakeController, nil]] animated:NO];
[super popViewControllerAnimated:animated];
}
- (void)popViewControllerAnimatedStep2:(UIViewController *)viewController
{
// Push the new top controller with animation
[super pushViewController:viewController animated:YES];
// Remove the view that should have been popped
NSMutableArray *arr = [NSMutableArray arrayWithArray:[self viewControllers]];
[arr removeObjectAtIndex:[[self viewControllers] count]-2];
[super setViewControllers:[NSArray arrayWithArray:arr] animated:NO];
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
NSLog(#"popViewControllerAnimated");
if (animated)
{
// Save the controller that should be on top after this pop operation
UIViewController *newTopController = [[self viewControllers] objectAtIndex:[[self viewControllers] count]-2];
// Remove it from the stack. Leave the view that should be popped on top
NSMutableArray *arr = [NSMutableArray arrayWithArray:[self viewControllers]];
[arr removeObjectAtIndex:[[self viewControllers] count]-2];
[super setViewControllers:[NSArray arrayWithArray:arr] animated:NO];
// Schedule the next step
[self performSelector:#selector(popViewControllerAnimatedStep2:) withObject:newTopController afterDelay:0];
return [arr objectAtIndex:[arr count]-1];
}
return [super popViewControllerAnimated:NO];
}
#end
ViewController.m:
#import "ViewController.h"
#import "UIRightToLeft.h"
#import "SecondViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pressMe:(UIButton *)sender {
SecondViewController *next = [[SecondViewController alloc]init];
[self.navigationController pushViewController:next animated:YES];
}
#end
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
- (IBAction)pressMe:(UIButton *)sender;
#end
(In my ViewController there's only one button draged to the second ViewController with push)
After looking at your error log I think you actually need to have your navigation controller subclass UIRightToLeft.
If you are using a Storyboard, select your navigation controller and set Custom Class to UIRightToLeft.
I'm new to Cocoa development and suspect that this is a noob issue.
I'm creating a simple background canvas with a bespoke UIView. I was expecting that I would be able to create this canvas to be slightly indented down from the main view so that I could add a toolbar at a later date. However, when I use initWithFrame (and any other init for that matter), the canvas is always created to be the size of the full screen rather than slightly smaller than the full screen. Even if I completely override the values for CGRect it doesn't make a difference. I've set a touchesBegan event and set the background colour to green to be able to determine success but all I get are a completely green screen and a touch event that works everywhere.
Please help - this is driving me mad and I can't find the answer anywhere on the web.
The relevant code is as follows (there actually isn't much more code than this in the whole app so far):
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.objMainViewController = [[MainViewController alloc] init];
self.window.rootViewController = self.objMainViewController;
[self.window makeKeyAndVisible];
return YES;
}
MainViewController.h
#import <UIKit/UIKit.h>
#import "viewCanvas.h"
#interface MainViewController : UIViewController
#property (strong, nonatomic) viewCanvas *objViewCanvas;
-(id)init;
#end
MainViewController.m
#import "MainViewController.h"
#import "viewCanvas.h"
#implementation MainViewController
#synthesize objViewCanvas;
-(id)init
{
if (self = [super init]) {
CGRect frame = CGRectMake(100, 100, 100, 100);
objViewCanvas = [[viewCanvas alloc] initWithFrame:frame];
}
return self;
}
- (void)loadView {
self.view = objViewCanvas;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
viewCanvas.h
#import <UIKit/UIKit.h>
#import "viewCanvas.h"
#interface viewCanvas : UIView {
}
//Init
- (id) initWithFrame:(CGRect)frame;
//Events
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
#end
viewCanvas.m
#import "viewCanvas.h"
#implementation viewCanvas
//Standard inits
- (id) initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
[self setBackgroundColor:[UIColor greenColor]];
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Touches Began Canvas" message:#"Canvas Touches Began" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles: nil];
[alert show];
}
#end
I think by default the view of a view controller fills the whole screen, so you need to add a subview to that view that's your smaller viewCanvas object (BTW, you should conform to the naming rules and name your classes with capital letters, ViewCanvas). Try this in your MainViewController .m file:
-(id)init{
if (self = [super init]) {
CGRect frame = CGRectMake(100, 100, 100, 100);
objViewCanvas = [[ViewCanvas alloc] initWithFrame:frame];
self.contentView = [[UIView alloc] initWithFrame:CGRectMake(1, 1, 1, 1)]; //this frame doesn't matter
[self.contentView setBackgroundColor:[UIColor whiteColor]];
}
return self;
}
- (void)loadView {
self.view = self.contentView;
[self.view addSubview:objViewCanvas];
}
I have custom UIViewController which looks like this.
#implementation UIViewControllerCustom
- (id)init
{
self = [super init];
if (self) {
NSLog(#"init");
}
return self;
}
-(void)viewDidLoad
{
[super viewDidLoad];
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[infoButton setFrame:CGRectMake(290, 10, 16, 16)];
[infoButton addTarget:self action:#selector(showInfo) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:infoButton];
}
-(void)showInfo
{
About *about = [[About alloc]init];
NSLog(#"touched");
[about setModalTransitionStyle:UIModalPresentationFullScreen];
[self presentModalViewController:about animated:YES];
}
#end
Any view controller in my app inherits this controller. When info button is touched the modal window is present. The problem is that one of my view controllers use UIScrollView. In this controller info button is visible and it reacts on touches. The problem is that when I touch the button showInfo method of UIViewControllerCustom is not called. I have no such a problem in the other controller.
What can be wrong here ?
Because the UIScrollView handles the message being sent. In your case the touch on the button will be handled by the UIScrollView.
I would suggest you to subclass the UIScrollView and implement: - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view to determine if the UIScrollView should handle the event or not.
The implementation could be something along the lines of:
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view {
if ([view isKindOfClass:[UIButton class]]) {
return NO;
}
return YES;
}
Is there a way to get the UITouch objects associated with a gesture? UIGestureRecognizer doesn't seem to have any methods for this.
Jay's right... you'll want a subclass. Try this one for size, it's from one of my projects. In DragGestureRecognizer.h:
#interface DragGestureRecognizer : UILongPressGestureRecognizer {
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
#end
#protocol DragGestureRecognizerDelegate <UIGestureRecognizerDelegate>
- (void) gestureRecognizer:(UIGestureRecognizer *)gr movedWithTouches:(NSSet*)touches andEvent:(UIEvent *)event;
#end
And in DragGestureRecognizer.m:
#import "DragGestureRecognizer.h"
#implementation DragGestureRecognizer
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
if ([self.delegate respondsToSelector:#selector(gestureRecognizer:movedWithTouches:andEvent:)]) {
[(id)self.delegate gestureRecognizer:self movedWithTouches:touches andEvent:event];
}
}
#end
Of course, you'll need to implement the
- (void) gestureRecognizer:(UIGestureRecognizer *)gr movedWithTouches:(NSSet*)touches andEvent:(UIEvent *)event;
method in your delegate -- for example:
DragGestureRecognizer * gr = [[DragGestureRecognizer alloc] initWithTarget:self action:#selector(pressed:)];
gr.minimumPressDuration = 0.15;
gr.delegate = self;
[self.view addGestureRecognizer:gr];
[gr release];
- (void) gestureRecognizer:(UIGestureRecognizer *)gr movedWithTouches:(NSSet*)touches andEvent:(UIEvent *)event{
UITouch * touch = [touches anyObject];
self.mTouchPoint = [touch locationInView:self.view];
self.mFingerCount = [touches count];
}
If it is just the location you are interested in, you do not have to subclass, you will be notified of location changes of the tap from the UIGestureRecognizer.
Initialize with target:
self.longPressGestureRecognizer = [[[UILongPressGestureRecognizer alloc] initWithTarget: self action: #selector(handleGesture:)] autorelease];
[self.tableView addGestureRecognizer: longPressGestureRecognizer];
Handle UIGestureRecognizerStateChanged to get location changes:
- (void)handleGesture: (UIGestureRecognizer *)theGestureRecognizer {
switch (theGestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStateChanged:
{
CGPoint location = [theGestureRecognizer locationInView: self.tableView];
[self infoForLocation: location];
break;
}
case UIGestureRecognizerStateEnded:
{
NSLog(#"Ended");
break;
}
default:
break;
}
}
If you only need to find out the location of the gesture, you can call either locationInView: or locationOfTouch:inView: on the UIGestureRecognizer object. However if you want to do anything else, then you'll need to subclass.
If you're writing your own UIGestureRecognizer you can get the touch objects overriding:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
or the equivalent moved, ended or canceled
The Apple docs has lots of info on subclassing
Here is a method to get a long press added to an arbitrary UIView.
This lets you run longpress on any number of UIViews.
In this example I restrict access to the UIView method through tags but you could use isKindOfClass as well.
(From what I found you have to use touchesMoved for LongPress because touchesBegan fires before the LongPress becomes active)
subclass - .h
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface MyLongPressGestureRecognizer : UILongPressGestureRecognizer
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
#property (nonatomic) CGPoint touchPoint;
#property (strong, nonatomic) UIView* touchView;
#end
subclass - .m
#import "MyLongPress.h"
#implementation MyLongPressGestureRecognizer
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
{
UITouch * touch = [touches anyObject];
self.touchPoint = [touch locationInView:self.view];
self.touchView = [self.view hitTest:[touch locationInView:self.view] withEvent:event];
}
#pragma mark - Setters
-(void) setTouchPoint:(CGPoint)touchPoint
{
_touchPoint =touchPoint;
}
-(void) setTouchView:(UIView*)touchView
{
_touchView=touchView;
}
#end
viewcontroller - .h
//nothing special here
viewcontroller - .m
#import "ViewController.h"
//LOAD
#import "MyLongPress.h"
#interface ViewController ()
//lOAD
#property (strong, nonatomic) MyLongPressGestureRecognizer* longPressGesture;
#end
#implementation PDViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//LOAD
self.longPressGesture =[[MyLongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressHandler:)];
self.longPressGesture.minimumPressDuration = 1.2;
[[self view] addGestureRecognizer:self.longPressGesture];
}
//LOAD
-(void) setLongPressGesture:(MyLongPressGestureRecognizer *)longPressGesture {
_longPressGesture = longPressGesture;
}
- (void)longPressHandler:(MyLongPressGestureRecognizer *)recognizer {
if (self.longPressGesture.touchView.tag >= 100) /* arbitrary method for limiting tap */
{
//code goes here
}
}
Simple and fast:
NSArray *touches = [recognizer valueForKey:#"touches"];
Where recognizer is your UIGestureRecognizer