简体   繁体   中英

Adding multiple tappable shapes to UIView

I have a floor plan with many exhibitor stands. When a UIView loads, a UIImage is displayed with the floor plan and a database is checked for a list of exhibitors and their locations. The locations are loaded into an array and a UIButton is created for each exhibitor and placed over the floor plan where their stand is. When tapped, this button will show information about that exhibitor.

Here is a screenshot of the floor plan with boxes where the buttons are rendered.

在此处输入图片说明

This works fine as it is BUT I need the buttons to be irregular shapes (triangles, pentagons, circles etc). So I need a way of drawing these shapes and having them clickable in the same way the buttons were.

I have created a test class which generates a UIView which contains the shape and added it to my original UIView . I get the feeling this may not be the correct way to do this as I will need to have many buttons on the screen and this would mean many views stacked on each other. I don't know how I could check which shape was tapped as the UIViews would overlap each other.

Can all the shapes be drawn on one view and then the view added? What is the best approach to this?

In terms of UI the cleanest thing to do is to use actual buttons. Set their type to custom, and set their image property to the image you want to display. That way the buttons handle highlighting correctly, and manage IBActions just like regular buttons. You can set all the buttons to point to the same action, and use tag values to figure out which button is which.

You can create these buttons either from code or in IB - whichever fits your design better.

You could also do this with custom views or a single view that has drawing on it. IF you use views for each booth, you would need to attach a tap gesture recognizer to each view, and set it's userInteractionEnabled flag to YES.

If you want to use a drawing for the entire floor plan, you would need to add a tap gesture recognizer to the drawing view, and then interpret the coordinates of the tap to figure out which image it lands on.

override - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event method of your map representer view. So you will be able to compare current touch location with all of your shapes, and calculate in which shape your touch point is laying. There different approach for different shapes (eg is point inside a rectangle? )

ole has a great project: OBShapedButtons . He achieves it by checking the alpha value of the touched pixel and overwriting -pointInside:withEvent:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 
{
    // Return NO if even super returns NO (i.e., if point lies outside our bounds)
    BOOL superResult = [super pointInside:point withEvent:event];
    if (!superResult) {
        return superResult;
    }

    // Don't check again if we just queried the same point
    // (because pointInside:withEvent: gets often called multiple times)
    if (CGPointEqualToPoint(point, self.previousTouchPoint)) {
        return self.previousTouchHitTestResponse;
    } else {
        self.previousTouchPoint = point;
    }

    BOOL response = NO;

    if (self.buttonImage == nil && self.buttonBackground == nil) {
        response = YES;
    }
    else if (self.buttonImage != nil && self.buttonBackground == nil) {
        response = [self isAlphaVisibleAtPoint:point forImage:self.buttonImage];
    }
    else if (self.buttonImage == nil && self.buttonBackground != nil) {
        response = [self isAlphaVisibleAtPoint:point forImage:self.buttonBackground];
    }
    else {
        if ([self isAlphaVisibleAtPoint:point forImage:self.buttonImage]) {
            response = YES;
        } else {
            response = [self isAlphaVisibleAtPoint:point forImage:self.buttonBackground];
        }
    }

    self.previousTouchHitTestResponse = response;
    return response;
}

Another sample code that test if the point is within a layer mask, maybe you can adapt that more easily:

@implementation MyView
//
// ...
//
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGPoint p = [self convertPoint:point toView:[self superview]];
    if(self.layer.mask){
        if (CGPathContainsPoint([(CAShapeLayer *)self.layer.mask path], NULL, p, YES) )
            return YES;
    }else {
        if(CGRectContainsPoint(self.layer.frame, p))
            return YES;
    }
    return NO;
}

@end

The full article: http://blog.vikingosegundo.de/2013/10/01/hittesting-done-right/

In the end this is what I did:

  • Looped through all of the stand shapes I needed to have and created a dictionary with UIBezierpath of their coordinates on the floorplan image and the standNo for the shape.
  • I then added these dictionaries to an array.
  • When the screen was tapped I would note the X and Y of the tap position and looped through the array of dictionaries and it checked if the UIBezierpath contained a point made up of the X and Y tapped coordinates.
  • If a shape was found that had the X and Y coordinates within its bounds I would draw a CAShapelayer using the UIBezierpath and adding a fillColor so it showed up on the map. An alertView was then displayed which showed more information about the exhibitor.

This method seemed WAY more efficient than actually drawing out hundreds of UIButtons or even CAShaplayers and even with 300+ areas on the floorplan to check through the process appears instant.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM