Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you switch from using one UIButton to another while still holding down finger?

Imagine your keyboard. Imagine yourself placing one finger down one key, then (while holding down) moving your finger all the way across to another key on the keyboard. Now, imagine that each key on the keyboard is a UIButton. when you are holding your finger on the current key, this key (UIButton) is highlighted. Then, when the user moves across to another key, the first key is no longer highlighted, and the current key that is pressed down, is highlighted.

Now, I have a 6 x 8 grid of UIButtons each 53 x 53 pixels. So, I have 48 UIButtons. I want to replicate this kind of idea. The button that the users finger is upon, will have a image that is slightly lighter (to look selected like), and all others will will not be slightly lighter.

Here is my idea of how to go about this,

1) Create all 48 UIButtons in viewDidLoad. Add the lighter image to UIControlStateHighlighted for all UIButtons.

2) Add some sort of target for touchUpOutside that somehow makes the current button not highlighted and not usable. (maybe set the highlighted and userInteractionEnabled to no). But then how can I tell the next button to be used? And how can I say that I want the specific UIButton that the users fingers are under to become highlighted and in use to detect gestures and stuff.

Also, this touchUpOutside method may not work because all the buttons are right next to each other, and I think you have to drag far out to run this touchUpOutside.

It's also important to note that the buttons are not placed in equal rows and columns. Sometimes one button will actually be a UIImage but look like a UIButton. So, doing some sort of fast enumeration comparing frames for some reason will not work.

like image 411
bmende Avatar asked Oct 06 '22 23:10

bmende


1 Answers

Two observations:

  1. I generally use gesture recognizers for stuff like this, not the various touchUp... methods.

  2. I know you said you don't want a fast enumeration through the subviews, but why not? Just because some are different classes than others is not a problem (because you could just test the isKindOfClass method). (And, as an aside, I'm not sure why some would be buttons and some would not.) Just because they don't line up really makes no difference, either.

Anyway, for stuff like this, I frequently will put the gesture recognizer on the shared parent view (e.g. typically the view controller's view), and then enumerate through the subviews to see in which the current CGPoint is contained?

CGPoint location = [sender locationInView:self.view];

for (UIView *subview in self.view.subviews)
{
    if (CGRectContainsPoint(subview.frame, location))
    {
        // do your appropriate highlighting

        if ([subview isKindOfClass:[UIButton class]])
        {
            // something for buttons
        }
        else if ([subview isKindOfClass:[UIImageView class]])
        {
            // something for imageviews
        }

        return;
    }
}

I don't know if any of this makes sense to you, but it seems easiest to me. If you clarify your intent, perhaps I can refine my answer further.

Just as a practical example, you could define a continuous gesture recognizer that kept track of the first and last subview that was clicked on (and if you don't start your gesture on a subview, no gesture is generated, and if you don't stop your gesture on a subview, it cancels the whole thing), e.g.:

@interface SubviewGestureRecognizer : UIGestureRecognizer

@property (nonatomic,strong) UIView *firstSubview;
@property (nonatomic,strong) UIView *currentSubview;

@end

@implementation SubviewGestureRecognizer

- (id) initWithTarget:(id)target action:(SEL)action
{
    self = [super initWithTarget:target action:action];
    if (self)
    {
        self.firstSubview = nil;
        self.currentSubview = nil;
    }

    return self;
}

// you might want to tweak this `identifySubview` to only look 
// for buttons and imageviews, or items with nonzero tag properties, 
// or recursively navigate if it encounters container UIViews 
// or whatever suits your app

- (UIView *)identifySubview:(NSSet *)touches
{
    CGPoint location = [[touches anyObject] locationInView:self.view];

    for (UIView *subview in self.view.subviews)
    {
        if (CGRectContainsPoint(subview.frame, location))
        {
            return subview;
        }
    }

    return nil;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];

    if ([touches count] != 1)
    {
        self.state = UIGestureRecognizerStateFailed;
        return;
    }

    self.firstSubview = [self identifySubview:touches];
    self.currentSubview = self.firstSubview;

    if (self.firstSubview == nil)
        self.state = UIGestureRecognizerStateFailed;
    else
        self.state = UIGestureRecognizerStateBegan;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];

    if (self.state == UIGestureRecognizerStateFailed) return;

    self.currentSubview = [self identifySubview:touches];

    self.state = UIGestureRecognizerStateChanged;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];

    self.currentSubview = [self identifySubview:touches];

    if (self.currentSubview != nil)
        self.state = UIGestureRecognizerStateEnded;
    else
        self.state = UIGestureRecognizerStateFailed;
}

- (void)reset
{
    [super reset];

    self.firstSubview = nil;
    self.currentSubview = nil;
}

You could then set this up in viewDidLoad:

SubviewGestureRecognizer *recognizer = [[SubviewGestureRecognizer alloc] initWithTarget:self action:@selector(handleTouches:)];
[self.view addGestureRecognizer:recognizer];

And then define your handler as such (this works with buttons and image views, taking advantage of the fact that both support setHighlighted):

- (void)handleTouches:(SubviewGestureRecognizer *)sender
{
    static UIControl *previousControl = nil;

    if (sender.state == UIGestureRecognizerStateBegan || sender.state == UIGestureRecognizerStateChanged)
    {
        if (sender.state == UIGestureRecognizerStateBegan)
            previousControl = nil;

        UIView *subview = sender.currentSubview;
        if (previousControl != subview)
        {
            // reset the old one (if any)

            [previousControl setHighlighted:NO];

            // highlight the new one

            previousControl = (UIControl *)subview;
            [previousControl setHighlighted:YES];
        }
    }
    else if (sender.state == UIGestureRecognizerStateEnded)
    {
        if (previousControl)
        {
            [previousControl setHighlighted:NO];
            NSLog(@"successfully touchdown on %@ and touchup on %@", sender.firstSubview, sender.currentSubview);
        }
    }
    else if (sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateFailed)
    {
        [previousControl setHighlighted:NO];
        NSLog(@"cancelled/failed gesture");
    }
}
like image 192
Rob Avatar answered Oct 10 '22 02:10

Rob