I'm dynamically creating buttons on UIView.
I can move them by dragging using this code
- (IBAction)draggedOut: (id)sender withEvent: (UIEvent *) event: (NSSet *)touches {
UIButton *selected = (UIButton *)sender;
selected.center = [[[event allTouches] anyObject] locationInView:hallView];
}
I can have more then one button. When I'm dragging some button, I need to check are there any intersections with other buttons?
How can I do this?
You should cycle through the other buttons in the view and, for each of them, check whether in intersect the dragged one via:
CGRectIntersectsRect (draggedButton.frame, anotherButton.frame);
You could use a function like:
- (BOOL)isThereButtonsIntersectionInView:(UIView *)containerView
forButton:(UIButton *)draggedButton
{
for(UIView *view in containerView.subviews){
if([view isKindOfClass:[UIButton class]] &&
view != draggedButton &&
CGRectIntersectsRect (view.frame, draggedButton.frame)){
return YES;
}
}
}
return NO;
}
Call this method passing as a parameter the view containing the buttons and the dragged button.
You can get frames of buttons and check using this
if(CGRectIntersectsRect(firstButton.frame, secondButton.frame)) {
return YES;
}
You simply need to compare the frame of this button with the frame of all the other buttons.
Here is a bit of sample code, which should probably go in your view controller because it requires knowledge of the other buttons.
- (BOOL) button:(UIButton*)button intersectsWithButtons:(NSArray*)moreButtons
{
CGRect buttonFrame = button.frame;
for (UIButton *checkButton in moreButtons) {
if (button != checkButton && CGRectIntersectsRect(buttonFrame, checkButton.frame))
return YES;
}
return NO;
}
Related
I have two UIViews. On second "UIView" I have one UIButton, but it must be over the both views.
When I click lower middle UIButton - event is fire, but when I click above the middle - nothing works.
How to fix it?
example image
move the button outside of both views and center it vertically
You should create a subclass of UIView for UIView2 and override hitTest like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.clipsToBounds && !self.hidden && self.alpha > 0) {
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result != nil) {
return result;
}
}
}
return nil;
}
I'm having a hard time finding the right documentation for how to handle touch events in order to support similar behavior to the keyboard.
What I want is a button that when I long press it, it shows a custom view controller above the button, but I want the user to be able to drag their finger to one of the other buttons (without taking their finger off the screen).
I have the button with a long press and it's custom view controller all setup and working. What I can't figure is how to support dragging from the first button over to the other button in the view controller to be able to select it.
I've tried using a subclassed UIButton where I tried this:
[self addTarget:self action:#selector(onDragOver:) forControlEvents:UIControlEventTouchDragEnter];
But that doesn't work.
I also found this question How to track button selection after long press? which is precisely the functionality I'm trying to duplicate. But there are no answers.
Here's my solution. The trick is you have to use hitTest:.
First you add a gesture recognizer to the button that is a normal button - the button that you want to open a context menu / custom view controller.
Then in your gesture recognizer callback, you use hitTest: to figure out if the user is over a custom button of yours and update it's state manually.
- (id) init {
//add a long press gesture recognizer
UILongPressureGestureRecognizer * gesture = [[UILongPressureGestureRecognizer alloc] initWithTarget:self action:#selector(onLongTap:)];
[self.myButton addGestureRecognizer:gesture];
}
- (void) onLongTap:(UIGestureRecognizer *) gesture {
if(gesture.state == UIGestureRecognizerStateBegan) {
//display your view controller / context menu over the button
}
if(gesture.state == UIGestureRecognizerStateEnded) {
//gesture stopped, use hitTest to find if their finger was over a context button
CGPoint location = [gesture locationInView:self.view];
CGPoint superviewLocation = [self.view.superview convertPoint:location fromView:self.view];
UIView * view = [self.view.superview hitTest:superviewLocation withEvent:nil];
if([view isKindOfClass:[MMContextMenuButton class]]) {
//their finger was over my custom button, tell the button to send actions
MMContextMenuButton * button = (MMContextMenuButton *) view;
[self hideAndSendControlEvents:UIControlEventTouchUpInside];
if(self.draggedContextMenuButton == button) {
self.draggedContextMenuButton = nil;
}
}
if(self.draggedContextMenuButton) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
}
self.draggedContextMenuButton = nil;
}
if(gesture.state == UIGestureRecognizerStateChanged) {
//gesture changed, use hitTest to see if their finger
//is over a button. Manually have to tell the button
//that it should update it's state.
CGPoint location = [gesture locationInView:self.view];
CGPoint superviewLocation = [self.view.superview convertPoint:location fromView:self.view];
UIView * view = [self.view.superview hitTest:superviewLocation withEvent:nil];
if([view isKindOfClass[MMContextMenuButton class]]) {
MMContextMenuButton * button = (MMContextMenuButton *) view;
if(self.draggedContextMenuButton != button) {
[self.draggedContextMenuButton dragOut];
}
self.draggedContextMenuButton = button;
[button dragOver];
}
}
}
//////////////
#import "MMContextMenuButton.h"
#import "MMContextMenus.h"
#implementation MMContextMenuButton
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
self.layer.cornerRadius = 4;
self.adjustsImageWhenHighlighted = FALSE;
self.adjustsImageWhenDisabled = FALSE;
self.backgroundColor = [UIColor clearColor];
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[self setTitleColor:[UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1] forState:UIControlStateNormal];
[self addTarget:self action:#selector(onHighlight:) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:#selector(onRelease:) forControlEvents:UIControlEventTouchUpOutside&UIControlEventTouchUpOutside];
return self;
}
- (void) onHighlight:(id) sender {
self.backgroundColor = [UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1];
}
- (void) onRelease:(id) sender {
self.backgroundColor = [UIColor clearColor];
}
- (void) hideAndSendControlEvents:(UIControlEvents) events {
[self dragOut];
[self sendActionsForControlEvents:events];
[[MMContextMenus instance] hideContextMenus];
}
- (void) dragOver {
self.highlighted = TRUE;
self.backgroundColor = [UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1];
}
- (void) dragOut {
self.highlighted = FALSE;
self.backgroundColor = [UIColor clearColor];
}
#end
I have UIBUtton in UICollectionViewCell as show in image. I had set touchUpInside Action in xib. I had set background image of button is pink and lable as yellow. UIButton comes at top of the label. Now the problem is that UIButton in not getting touch event in pink area it only get touch events on orange area. why it is so.
- (IBAction)checkButtonTap:(id)sender event:(id)event
{
DLog(#"%s",__func__);
}
O a hidden view is overlapping the button due to which it is touch event was not reaching to the button.
Just to be sure I understand your issue, you have your UICollectionView in yellow and your UIbutton in pink, correct ?
If so, it seems you want to intercept the touchUpInside event outside your button's superview in yellow. You can look at this answer which deal with kind of problem.
Even if you finally find the solution to your problem, to clarify my answer, if you want to interact with an UIButton which is not inside its superview's frame, you may need to need to implement the method - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event of the superview (here the UICollectionViewCell):
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.clipsToBounds && !self.hidden && self.alpha > 0) {
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result != nil) {
return result;
}
}
}
return nil;
}
(thanks Noam!)
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:)];
[buttonname addGestureRecognizer:tapGesture];
buttonname.userInteractionEnabled = YES;
(IBAction)imageTapped:(UITapGestureRecognizer *)gestureRecognizer {
if(gestureRecognizer.state == UIGestureRecognizerStateRecognized) {
[buttonname becomeFirstResponder];
}
}
O sorry, there was hidden view that is overlapping the button due to which touch event was not reaching to the button.
I'm creating a container view controller where the child view controllers are shown like a paged scrollView. In this container controller I want to change between pages scrolling horizontally using two fingers. So I used a UIScrollView and I have set it like that:
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.delegate = self;
[self.scrollView setContentOffset:[self rectForPage:1].origin];
for (UIGestureRecognizer *gestureRecognizer in self.scrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
I also have implemented the - (void)scrollViewDidScroll:(UIScrollView *)sender method to avoid the vertical scrolling
- (void)scrollViewDidScroll:(UIScrollView *)sender {
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x +pageWidth/2) / pageWidth);
self.pageControl.currentPage = page;
[self.scrollView setContentOffset: CGPointMake(self.scrollView.contentOffset.x, 0)];
}
Now the problem is that the childViewControllers subviews can't receive the touches from the user. I want the single touch to be passed throw the view hierarchy. I have read about subclassing UIScrollView to overwrite the method - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view but it's only called if the subviews are UIControl. How I can do it?
The purpose is that this ChildViewControllers have UIButtons inside and ScrollViews that should be used with singleTouches
maybe not exactly, but this might help you. Create a scroll view subclass and implement this;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// UIView will be "transparent" for touch events if we return NO
for (id subview in self.subviews) {
if ([subview isKindOfClass:[YourViewController class]]) {
YourViewController *VC = (YourViewController*) subview;
if (CGRectContainsPoint(YourViewController.frame, point)) {
return YES;
}
}
}
return NO;
}
In my app I should implement the drag and drop of a imageView; the problem is that my imageView is inside a scrollview; it's my code
- (void) viewDidLoad{
[super viewDidLoad];
UILongPressGestureRecognizer *downwardGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(dragGestureChanged:)];
[scrollViewAlfabeto addGestureRecognizer:downwardGesture];
for (UIGestureRecognizer *gestureRecognizer in myscrollView.gestureRecognizers)
{
[gestureRecognizer requireGestureRecognizerToFail:downwardGesture];
}
}
- (void) dragGestureChanged:(UIPanGestureRecognizer*)gesture
{
CGPoint point = [gesture locationInView:scrollViewAlfabeto];
if (gesture.state == UIGestureRecognizerStateBegan)
{
[imageViewToMove removeFromSuperview];
[self.view addSubview:imageViewToMove];
UIView *draggedView = [myscrollView hitTest:point withEvent:nil];
if ([draggedView isKindOfClass:[UIImageView class]])
{
imageViewToMove = (UIImageView*)draggedView;
}
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
imageToMove.center = point;
}
else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateFailed)
{
// Determine if dragged view is in an OK drop zone
// If so, then do the drop action, if not, return it to original location
NSLog(#"point.x final:%f", point.x);
NSLog(#"point.y final:%f", point.y);
if (CGRectContainsPoint(goal.frame, point)){
imageToMove.frame = CGRectMake(167, 159, 100, 100);
}
else{
[imageToMove removeFromSuperview];
[myscrollView addSubview:imageToMove];
[imageToMove setFrame:CGRectMake(12, 38, 100, 100)];
imageToMove = nil;
}
}
}
Then with my code I'm able to take an imageView from scrollView with longpress, drag it inside "self.view"; also if I drop this imageView over another imageView in self.view it puts on it. It work fine. But I have two problems:
1- when I drag this imageView and I do
[imageViewToMove removeFromSuperview];
[self.view addSubview:imageViewToMove];
the image view don't appear under my finger but in other position and I want that it remains under my finger
2- This code work only the first time when I launch my viewcontroller, because If I don't drop the imageview over other imageview inside self.view, itr return inside scrollview, but If I want drag it a second time, it don't work.
Can you help me?
I'm not advanced in objc, but...
Allow user ineraction [self.view userInteractionEnabled:YES];
Try [self.view setMultipleTouchEnabled:YES];
Taken from the documentation of UIView in relation to userInteractionEnabled:
Discussion
When set to NO, user events—such as touch and
keyboard—intended for the view are ignored and removed from the event
queue. When set to YES, events are delivered to the view normally. The
default value of this property is YES.
During an animation, user interactions are temporarily disabled for
all views involved in the animation, regardless of the value in this
property. You can disable this behavior by specifying the
UIViewAnimationOptionAllowUserInteraction option when configuring the
animation.
Hope it works