iPhone->类似于popoverView的一个菜单组件

池永长
2023-12-01

一.说明

在iPad上,苹果提供的有专门的菜单显示的控制器。而在iphone上却没有。所以需要我们自定义。下面就是我所介绍的这么一个菜单组件的大概样子:
![组件概览](https://img-blog.csdn.net/20151204180632512)

二.组件代码

  1. KxMenu.h
#import <Foundation/Foundation.h>

@interface KxMenuItem : NSObject

@property (readwrite, nonatomic, strong) UIImage *image;
@property (readwrite, nonatomic, strong) NSString *title;
@property (readwrite, nonatomic) id target;
@property (readwrite, nonatomic) SEL action;
@property (readwrite, nonatomic, strong) UIColor *foreColor;
@property (readwrite, nonatomic) NSTextAlignment alignment;

+ (instancetype) menuItem:(NSString *) title
                    image:(UIImage *) image
                   target:(id)target
                   action:(SEL) action;

@end

@interface KxMenu : NSObject

+ (void) showMenuInView:(UIView *)view
               fromRect:(CGRect)rect
              menuItems:(NSArray *)menuItems;

+ (void) dismissMenu;

+ (UIColor *) tintColor;
+ (void) setTintColor: (UIColor *) tintColor;

+ (UIFont *) titleFont;
+ (void) setTitleFont: (UIFont *) titleFont;

@end
#import "KxMenu.h"
#import <QuartzCore/QuartzCore.h>

const CGFloat kArrowSize = 12.f;




@interface KxMenuView : UIView
@end

@interface KxMenuOverlay : UIView
@end

@implementation KxMenuOverlay

// - (void) dealloc { NSLog(@"dealloc %@", self); }

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.opaque = NO;
    }
    return self;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UIView *touched = [[touches anyObject] view];
    if (touched == self) {

        for (UIView *v in self.subviews) {
            if ([v isKindOfClass:[KxMenuView class]]
                && [v respondsToSelector:@selector(dismissMenu:)]) {

                [v performSelector:@selector(dismissMenu:) withObject:@(YES)];
            }
        }
    }
}

@end




@implementation KxMenuItem

+ (instancetype) menuItem:(NSString *) title
                    image:(UIImage *) image
                   target:(id)target
                   action:(SEL) action
{
    return [[KxMenuItem alloc] init:title
                              image:image
                             target:target
                             action:action];
}

- (id) init:(NSString *) title
      image:(UIImage *) image
     target:(id)target
     action:(SEL) action
{
    NSParameterAssert(title.length || image);

    self = [super init];
    if (self) {

        _title = title;
        _image = image;
        _target = target;
        _action = action;
    }
    return self;
}

- (BOOL) enabled
{
    return _target != nil && _action != NULL;
}

- (void) performAction
{
    __strong id target = self.target;

    if (target && [target respondsToSelector:_action]) {

        [target performSelectorOnMainThread:_action withObject:self waitUntilDone:YES];
    }
}

- (NSString *) description
{
    return [NSString stringWithFormat:@"<%@ #%p %@>", [self class], self, _title];
}

@end




typedef enum {

    KxMenuViewArrowDirectionNone,
    KxMenuViewArrowDirectionUp,
    KxMenuViewArrowDirectionDown,
    KxMenuViewArrowDirectionLeft,
    KxMenuViewArrowDirectionRight,

} KxMenuViewArrowDirection;

@implementation KxMenuView {

    KxMenuViewArrowDirection    _arrowDirection;
    CGFloat                     _arrowPosition;
    UIView                      *_contentView;
    NSArray                     *_menuItems;
}

- (id)init
{
    self = [super initWithFrame:CGRectZero];    
    if(self) {

        self.backgroundColor = [UIColor clearColor];
        self.opaque = YES;
        self.alpha = 0;

//        self.layer.shadowOpacity = 0.5;
//        self.layer.shadowOffset = CGSizeMake(2, 2);
//        self.layer.shadowRadius = 2;
    }

    return self;
}

// - (void) dealloc { NSLog(@"dealloc %@", self); }

- (void) setupFrameInView:(UIView *)view
                 fromRect:(CGRect)fromRect
{
    const CGSize contentSize = _contentView.frame.size;

    const CGFloat outerWidth = view.bounds.size.width;
    const CGFloat outerHeight = view.bounds.size.height;

    const CGFloat rectX0 = fromRect.origin.x;
    const CGFloat rectX1 = fromRect.origin.x + fromRect.size.width;
    const CGFloat rectXM = fromRect.origin.x + fromRect.size.width * 0.5f;
    const CGFloat rectY0 = fromRect.origin.y;
    const CGFloat rectY1 = fromRect.origin.y + fromRect.size.height;
    const CGFloat rectYM = fromRect.origin.y + fromRect.size.height * 0.5f;;





    const CGFloat widthPlusArrow = contentSize.width + kArrowSize;
    const CGFloat heightPlusArrow = contentSize.height + kArrowSize;
    const CGFloat widthHalf = contentSize.width * 0.5f;
    const CGFloat heightHalf = contentSize.height * 0.5f;
    // NSLog(@"%f   %f   %f  \n%f  %f  %f  \n%f  %f   \n%f  %f",rectX0,rectX1,rectXM,rectY0,rectY1,rectYM,widthPlusArrow,heightPlusArrow,outerWidth,outerHeight);
    const CGFloat kMargin = 5.f;

    if (heightPlusArrow < (outerHeight - rectY1)) {

        if(rectX0 <= 0)
        {
            _arrowDirection = KxMenuViewArrowDirectionLeft;
            CGPoint point = (CGPoint){
                rectX1,
                rectYM - heightHalf
            };

            if (point.y < kMargin)
                point.y = kMargin;

            if ((point.y + contentSize.height + kMargin) > outerHeight)
                point.y = outerHeight - contentSize.height - kMargin;

            _arrowPosition = rectYM - point.y;
            _contentView.frame = (CGRect){kArrowSize, 0, contentSize};

            self.frame = (CGRect) {
                point,
                contentSize.width + kArrowSize,
                contentSize.height
            };
        }
        else
        {
            _arrowDirection = KxMenuViewArrowDirectionUp;
            CGPoint point = (CGPoint){
                rectXM - widthHalf,
                rectY1
            };

            if (point.x < kMargin)
                point.x = kMargin;

            if ((point.x + contentSize.width + kMargin) > outerWidth)
                point.x = outerWidth - contentSize.width - kMargin;

            _arrowPosition = rectXM - point.x;
            //_arrowPosition = MAX(16, MIN(_arrowPosition, contentSize.width - 16));
            _contentView.frame = (CGRect){0, kArrowSize, contentSize};

            self.frame = (CGRect) {

                point,
                contentSize.width,
                contentSize.height + kArrowSize
            };
        }


    } else if (heightPlusArrow < rectY0) {

        _arrowDirection = KxMenuViewArrowDirectionDown;
        CGPoint point = (CGPoint){
            rectXM - widthHalf,
            rectY0 - heightPlusArrow
        };

        if (point.x < kMargin)
            point.x = kMargin;

        if ((point.x + contentSize.width + kMargin) > outerWidth)
            point.x = outerWidth - contentSize.width - kMargin;

        _arrowPosition = rectXM - point.x;
        _contentView.frame = (CGRect){CGPointZero, contentSize};

        self.frame = (CGRect) {

            point,
            contentSize.width,
            contentSize.height + kArrowSize
        };

    } else if (widthPlusArrow < (outerWidth - rectX1)) {

        _arrowDirection = KxMenuViewArrowDirectionLeft;
        CGPoint point = (CGPoint){
            rectX1,
            rectYM - heightHalf
        };

        if (point.y < kMargin)
            point.y = kMargin;

        if ((point.y + contentSize.height + kMargin) > outerHeight)
            point.y = outerHeight - contentSize.height - kMargin;

        _arrowPosition = rectYM - point.y;
        _contentView.frame = (CGRect){kArrowSize, 0, contentSize};

        self.frame = (CGRect) {

            point,
            contentSize.width + kArrowSize,
            contentSize.height
        };

    } else if (widthPlusArrow < rectX0) {

        _arrowDirection = KxMenuViewArrowDirectionRight;
        CGPoint point = (CGPoint){
            rectX0 - widthPlusArrow,
            rectYM - heightHalf
        };

        if (point.y < kMargin)
            point.y = kMargin;

        if ((point.y + contentSize.height + 5) > outerHeight)
            point.y = outerHeight - contentSize.height - kMargin;

        _arrowPosition = rectYM - point.y;
        _contentView.frame = (CGRect){CGPointZero, contentSize};

        self.frame = (CGRect) {

            point,
            contentSize.width  + kArrowSize,
            contentSize.height
        };

    } else {

        _arrowDirection = KxMenuViewArrowDirectionNone;

        self.frame = (CGRect) {

            (outerWidth - contentSize.width)   * 0.5f,
            (outerHeight - contentSize.height) * 0.5f,
            contentSize,
        };
    }    
}

- (void)showMenuInView:(UIView *)view
              fromRect:(CGRect)rect
             menuItems:(NSArray *)menuItems
{
    if (_menuItems)
    {
        [_menuItems release];
        _menuItems = Nil;
    }
    _menuItems = menuItems.copy;

    _contentView = [self mkContentView];
    [self addSubview:_contentView];

    [self setupFrameInView:view fromRect:rect];

    KxMenuOverlay *overlay = [[KxMenuOverlay alloc] initWithFrame:view.bounds];
    [overlay addSubview:self];
    [view addSubview:overlay];

    _contentView.hidden = YES;
    const CGRect toFrame = self.frame;
    self.frame = (CGRect){self.arrowPoint, 1, 1};

    [UIView animateWithDuration:0.2
                     animations:^(void) {

                         self.alpha = 1.0f;
                         self.frame = toFrame;

                     } completion:^(BOOL completed) {
                         _contentView.hidden = NO;
                     }];

}

- (void)dismissMenu:(BOOL) animated
{
    if (self.superview) {

        if (animated) {

            _contentView.hidden = YES;            
            const CGRect toFrame = (CGRect){self.arrowPoint, 1, 1};

            [UIView animateWithDuration:0.2
                             animations:^(void) {

                                 self.alpha = 0;
                                 self.frame = toFrame;

                             } completion:^(BOOL finished) {

                                 if ([self.superview isKindOfClass:[KxMenuOverlay class]])
                                     [self.superview removeFromSuperview];
                                 [self removeFromSuperview];
                             }];

        } else {

            if ([self.superview isKindOfClass:[KxMenuOverlay class]])
                [self.superview removeFromSuperview];
            [self removeFromSuperview];
        }
    }
}

- (void)performAction:(id)sender
{
    [self dismissMenu:YES];

    UIButton *button = (UIButton *)sender;
    KxMenuItem *menuItem = _menuItems[button.tag];
    [menuItem performAction];
}

- (UIView *) mkContentView
{
    for (UIView *v in self.subviews) {
        [v removeFromSuperview];
    }

    if (!_menuItems.count)
        return nil;

    const CGFloat kMinMenuItemHeight = 32.f;
    const CGFloat kMinMenuItemWidth = 32.f;
    const CGFloat kMarginX = 10.f;
    const CGFloat kMarginY = 5.f;

    UIFont *titleFont = [KxMenu titleFont];
    if (!titleFont) titleFont = [UIFont boldSystemFontOfSize:16];

    CGFloat maxImageWidth = 0;    
    CGFloat maxItemHeight = 0;
    CGFloat maxItemWidth = 0;

    for (KxMenuItem *menuItem in _menuItems) {

        const CGSize imageSize = menuItem.image.size;        
        if (imageSize.width > maxImageWidth)
            maxImageWidth = imageSize.width;        
    }

    for (KxMenuItem *menuItem in _menuItems) {

        const CGSize titleSize = [menuItem.title sizeWithFont:titleFont];
        const CGSize imageSize = menuItem.image.size;

        const CGFloat itemHeight = MAX(titleSize.height, imageSize.height) + kMarginY * 2;
        const CGFloat itemWidth = (menuItem.image ? maxImageWidth + kMarginX : 0) + titleSize.width + kMarginX * 4;

        if (itemHeight > maxItemHeight)
            maxItemHeight = itemHeight;

        if (itemWidth > maxItemWidth)
            maxItemWidth = itemWidth;
    }

    maxItemWidth  = MAX(maxItemWidth, kMinMenuItemWidth);
    maxItemHeight = MAX(maxItemHeight, kMinMenuItemHeight);

    const CGFloat titleX = kMarginX * 2 + (maxImageWidth > 0 ? maxImageWidth + kMarginX : 0);
    const CGFloat titleWidth = maxItemWidth - titleX - kMarginX;

    UIImage *selectedImage = [KxMenuView selectedImage:(CGSize){maxItemWidth, maxItemHeight + 2}];
    UIImage *gradientLine = [KxMenuView gradientLine: (CGSize){maxItemWidth - kMarginX * 4, 1}];

    UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero];
    contentView.autoresizingMask = UIViewAutoresizingNone;
    contentView.backgroundColor = [UIColor clearColor];
    contentView.opaque = NO;

    CGFloat itemY = kMarginY * 2;
    NSUInteger itemNum = 0;

    for (KxMenuItem *menuItem in _menuItems) {

        const CGRect itemFrame = (CGRect){0, itemY, maxItemWidth, maxItemHeight};

        UIView *itemView = [[UIView alloc] initWithFrame:itemFrame];
        itemView.autoresizingMask = UIViewAutoresizingNone;
        itemView.backgroundColor = [UIColor clearColor];
        itemView.opaque = NO;

        [contentView addSubview:itemView];

        if (menuItem.enabled) {

            UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
            button.tag = itemNum;
            button.frame = itemView.bounds;
            button.enabled = menuItem.enabled;
            button.backgroundColor = [UIColor clearColor];
            button.opaque = NO;
            button.autoresizingMask = UIViewAutoresizingNone;

            [button addTarget:self
                       action:@selector(performAction:)
             forControlEvents:UIControlEventTouchUpInside];

            [button setBackgroundImage:selectedImage forState:UIControlStateHighlighted];

            [itemView addSubview:button];
        }

        if (menuItem.title.length) {

            CGRect titleFrame;

            if (!menuItem.enabled && !menuItem.image) {

                titleFrame = (CGRect){
                    kMarginX * 2,
                    kMarginY,
                    maxItemWidth - kMarginX * 4,
                    maxItemHeight - kMarginY * 2
                };

            } else {

                titleFrame = (CGRect){
                    titleX,
                    kMarginY,
                    titleWidth,
                    maxItemHeight - kMarginY * 2
                };
            }

            UILabel *titleLabel = [[UILabel alloc] initWithFrame:titleFrame];
            titleLabel.text = menuItem.title;
            titleLabel.font = titleFont;
            titleLabel.textAlignment = menuItem.alignment;
            titleLabel.textColor = menuItem.foreColor ? menuItem.foreColor : [UIColor whiteColor];
            titleLabel.backgroundColor = [UIColor clearColor];
            titleLabel.autoresizingMask = UIViewAutoresizingNone;
            //titleLabel.backgroundColor = [UIColor greenColor];
            [itemView addSubview:titleLabel];            
        }

        if (menuItem.image) {

            const CGRect imageFrame = {kMarginX * 2, kMarginY, maxImageWidth, maxItemHeight - kMarginY * 2};
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame];
            imageView.image = menuItem.image;
            imageView.clipsToBounds = YES;
            imageView.contentMode = UIViewContentModeCenter;
            imageView.autoresizingMask = UIViewAutoresizingNone;
            [itemView addSubview:imageView];
        }

        if (itemNum < _menuItems.count - 1) {

            UIImageView *gradientView = [[UIImageView alloc] initWithImage:gradientLine];
            gradientView.frame = (CGRect){kMarginX * 2, maxItemHeight + 1, gradientLine.size};
            gradientView.contentMode = UIViewContentModeLeft;
            [itemView addSubview:gradientView];

            itemY += 2;
        }

        itemY += maxItemHeight;
        ++itemNum;
    }    

    contentView.frame = (CGRect){0, 0, maxItemWidth, itemY + kMarginY * 2};

    return contentView;
}

- (CGPoint) arrowPoint
{
    CGPoint point;

    if (_arrowDirection == KxMenuViewArrowDirectionUp) {

        point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMinY(self.frame) };

    } else if (_arrowDirection == KxMenuViewArrowDirectionDown) {

        point = (CGPoint){ CGRectGetMinX(self.frame) + _arrowPosition, CGRectGetMaxY(self.frame)};

    } else if (_arrowDirection == KxMenuViewArrowDirectionLeft) {

        point = (CGPoint){ CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition  };

    } else if (_arrowDirection == KxMenuViewArrowDirectionRight) {

        point = (CGPoint){ CGRectGetMaxX(self.frame), CGRectGetMinY(self.frame) + _arrowPosition  };

    } else {

        point = self.center;
    }

    return point;
}

+ (UIImage *) selectedImage: (CGSize) size
{
    const CGFloat locations[] = {0,1};
    const CGFloat components[] = {
        0.216, 0.471, 0.871, 1,
        0.059, 0.353, 0.839, 1,
    };


    return [self gradientImageWithSize:size locations:locations components:components count:2];
}

+ (UIImage *) gradientLine: (CGSize) size
{
    const CGFloat locations[5] = {0,0.2,0.5,0.8,1};

//    const CGFloat R = 0.44f, G = 0.44f, B = 0.44f;
//        
//    const CGFloat components[20] = {
//        R,G,B,0.1,
//        R,G,B,0.4,
//        R,G,B,0.7,
//        R,G,B,0.4,
//        R,G,B,0.1
//    };
    const CGFloat R = 0, G = 0, B = 0;
    const CGFloat components[20] =
    {
        R,G,B,0.2,
        R,G,B,0.2,
        R,G,B,0.2,
        R,G,B,0.2,
        R,G,B,0.2
    };
    return [self gradientImageWithSize:size locations:locations components:components count:5];
}

+ (UIImage *) gradientImageWithSize:(CGSize) size
                          locations:(const CGFloat []) locations
                         components:(const CGFloat []) components
                              count:(NSUInteger)count
{
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef colorGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
    CGColorSpaceRelease(colorSpace);
    CGContextDrawLinearGradient(context, colorGradient, (CGPoint){0, 0}, (CGPoint){size.width, 0}, 0);
    CGGradientRelease(colorGradient);

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

- (void) drawRect:(CGRect)rect
{
    [self drawBackground:self.bounds
               inContext:UIGraphicsGetCurrentContext()];
}

- (void)drawBackground:(CGRect)frame
             inContext:(CGContextRef) context
{
    CGFloat R0 = 0.267, G0 = 0.303, B0 = 0.335;
    CGFloat R1 = 0.040, G1 = 0.040, B1 = 0.040;

    UIColor *tintColor = [KxMenu tintColor];
    if (tintColor) {

        CGFloat a;
        [tintColor getRed:&R0 green:&G0 blue:&B0 alpha:&a];
    }

    CGFloat X0 = frame.origin.x;
    CGFloat X1 = frame.origin.x + frame.size.width;
    CGFloat Y0 = frame.origin.y;
    CGFloat Y1 = frame.origin.y + frame.size.height;

    // render arrow

    UIBezierPath *arrowPath = [UIBezierPath bezierPath];
    [[UIColor whiteColor]setStroke];
    // fix the issue with gap of arrow's base if on the edge
    const CGFloat kEmbedFix = 3.f;

    if (_arrowDirection == KxMenuViewArrowDirectionUp) {

        const CGFloat arrowXM = _arrowPosition;
        const CGFloat arrowX0 = arrowXM - kArrowSize;
        const CGFloat arrowX1 = arrowXM + kArrowSize;
        const CGFloat arrowY0 = Y0;
        const CGFloat arrowY1 = Y0 + kArrowSize + kEmbedFix;

        [arrowPath moveToPoint:    (CGPoint){arrowXM, arrowY0}];
        [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
        [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY1}];
        [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY0}];

        [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:0.4] set];

        Y0 += kArrowSize;

    } else if (_arrowDirection == KxMenuViewArrowDirectionDown) {

        const CGFloat arrowXM = _arrowPosition;
        const CGFloat arrowX0 = arrowXM - kArrowSize;
        const CGFloat arrowX1 = arrowXM + kArrowSize;
        const CGFloat arrowY0 = Y1 - kArrowSize - kEmbedFix + 3.f;
        const CGFloat arrowY1 = Y1;

        [arrowPath moveToPoint:    (CGPoint){arrowXM, arrowY1}];
        [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
        [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowY0}];
        [arrowPath addLineToPoint: (CGPoint){arrowXM, arrowY1}];

        [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:0.4] set];

        Y1 -= kArrowSize;

    } else if (_arrowDirection == KxMenuViewArrowDirectionLeft) {

        const CGFloat arrowYM = _arrowPosition;        
        const CGFloat arrowX0 = X0;
        const CGFloat arrowX1 = X0 + kArrowSize + kEmbedFix;
        const CGFloat arrowY0 = arrowYM - kArrowSize;;
        const CGFloat arrowY1 = arrowYM + kArrowSize;

        [arrowPath moveToPoint:    (CGPoint){arrowX0, arrowYM}];
        [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
        [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
        [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];

        [[UIColor colorWithRed:R0 green:G0 blue:B0 alpha:0.4] set];

        X0 += kArrowSize;

    } else if (_arrowDirection == KxMenuViewArrowDirectionRight) {

        const CGFloat arrowYM = _arrowPosition;        
        const CGFloat arrowX0 = X1;
        const CGFloat arrowX1 = X1 - kArrowSize - kEmbedFix;
        const CGFloat arrowY0 = arrowYM - kArrowSize;;
        const CGFloat arrowY1 = arrowYM + kArrowSize;

        [arrowPath moveToPoint:    (CGPoint){arrowX0, arrowYM}];
        [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY0}];
        [arrowPath addLineToPoint: (CGPoint){arrowX1, arrowY1}];
        [arrowPath addLineToPoint: (CGPoint){arrowX0, arrowYM}];

        [[UIColor colorWithRed:R1 green:G1 blue:B1 alpha:0.4] set];

        X1 -= kArrowSize;
    }

    [arrowPath fill];
    [arrowPath stroke];
    // render body

    const CGRect bodyFrame = {X0, Y0, X1 - X0, Y1 - Y0};

    UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:bodyFrame
                                                          cornerRadius:8];

    const CGFloat locations[] = {0, 1};
    const CGFloat components[] = {
        R0, G0, B0, 0.4,
        R1, G1, B1, 0.4,
    };

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
                                                                 components,
                                                                 locations,
                                                                 sizeof(locations)/sizeof(locations[0]));
    CGColorSpaceRelease(colorSpace);


    [borderPath addClip];
    [borderPath stroke];

    CGPoint start, end;

    if (_arrowDirection == KxMenuViewArrowDirectionLeft ||
        _arrowDirection == KxMenuViewArrowDirectionRight) {

        start = (CGPoint){X0, Y0};
        end = (CGPoint){X1, Y0};

    } else {

        start = (CGPoint){X0, Y0};
        end = (CGPoint){X0, Y1};
    }

    CGContextDrawLinearGradient(context, gradient, start, end, 0);

    CGGradientRelease(gradient);    
}

@end




static KxMenu *gMenu;
static UIColor *gTintColor;
static UIFont *gTitleFont;

@implementation KxMenu {

    KxMenuView *_menuView;
    BOOL        _observing;
}

+ (instancetype) sharedMenu
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        gMenu = [[KxMenu alloc] init];
    });
    return gMenu;
}

- (id) init
{
    NSAssert(!gMenu, @"singleton object");

    self = [super init];
    if (self) {
    }
    return self;
}

- (void) dealloc
{
    if (_observing) {        
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
}

- (void) showMenuInView:(UIView *)view
               fromRect:(CGRect)rect
              menuItems:(NSArray *)menuItems
{
    NSParameterAssert(view);
    NSParameterAssert(menuItems.count);

    if (_menuView) {

        [_menuView dismissMenu:NO];
        _menuView = nil;
    }

    if (!_observing) {

        _observing = YES;

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(orientationWillChange:)
                                                     name:UIApplicationWillChangeStatusBarOrientationNotification
                                                   object:nil];
    }


    _menuView = [[KxMenuView alloc] init];
    [_menuView showMenuInView:view fromRect:rect menuItems:menuItems];    
}

- (void) dismissMenu
{
    if (_menuView) {

        [_menuView dismissMenu:NO];
        _menuView = nil;
    }

    if (_observing) {

        _observing = NO;
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
}

- (void) orientationWillChange: (NSNotification *) n
{
    [self dismissMenu];
}

+ (void) showMenuInView:(UIView *)view
               fromRect:(CGRect)rect
              menuItems:(NSArray *)menuItems
{
    [[self sharedMenu] showMenuInView:view fromRect:rect menuItems:menuItems];
}

+ (void) dismissMenu
{
    [[self sharedMenu] dismissMenu];
}

+ (UIColor *) tintColor
{
    return gTintColor;
}

+ (void) setTintColor: (UIColor *) tintColor
{
    if (tintColor != gTintColor) {
        gTintColor = tintColor;
    }
}

+ (UIFont *) titleFont
{
    return gTitleFont;
}

+ (void) setTitleFont: (UIFont *) titleFont
{
    if (titleFont != gTitleFont) {
        gTitleFont = titleFont;
    }
}

@end

三.具体用法

其用法非常简单,只需要在项目中导入以上文件。然后构造参数调用之:
以下代码的目的是让菜单显示在m_addbutton的下方:

CGRect addbtnframe = m_popupload.m_addbutton.frame;
    CGRect frame = CGRectMake(addbtnframe.origin.x, addbtnframe.origin.y-30, addbtnframe.size.width, addbtnframe.size.height);
    //frame.size.width -= 90;
    NSArray *menuItems =
    @[
      [KxMenuItem menuItem:NSLocalizedString(@"Album",nil)
                     image:nil//[UIImage imageNamed:@"action_icon"]
                    target:self
                    action:@selector(layonAlbumPicker)],
      [KxMenuItem menuItem:NSLocalizedString(@"Voice Memos",nil)
                     image:nil//[UIImage imageNamed:@"check_icon"]
                    target:self
                    action:@selector(layonVoiceMemos)],
      [KxMenuItem menuItem:@"哈哈哈"
                     image:nil
                    target:self
                    action:nil]

      ];
    [KxMenu showMenuInView:m_popupload.view
                  fromRect:frame
                 menuItems:menuItems];
 类似资料: