VLDContextSheet

[翻译] VLDContextSheet-LMLPHP

效果:

[翻译] VLDContextSheet-LMLPHP

A clone of the Pinterest iOS app context menu.

复制了Pinterest应用的菜单效果。

Example Usage - 使用样例

VLDContextSheetItem *item1 = [[VLDContextSheetItem alloc] initWithTitle: @"Gift"
image: [UIImage imageNamed: @"gift"]
highlightedImage: [UIImage imageNamed: @"gift_highlighted"]]; VLDContextSheetItem *item2 = ... VLDContextSheetItem *item3 = ... self.contextSheet = [[VLDContextSheet alloc] initWithItems: @[ item1, item2, item3 ]];
self.contextSheet.delegate = self;

Show - 显示

- (void) longPressed: (UIGestureRecognizer *) gestureRecognizer {
if(gestureRecognizer.state == UIGestureRecognizerStateBegan) { [self.contextSheet startWithGestureRecognizer: gestureRecognizer
inView: self.view];
}
}

Delegate method - 代理方法

- (void) contextSheet: (VLDContextSheet *) contextSheet didSelectItem: (VLDContextSheetItem *) item {
NSLog(@"Selected item: %@", item.title);
}

Hide - 隐藏

[self.contextSheet end];

For more info check the Example project.

你可以在示例代码里面查看更多的信息。

附录源码:

VLDContextSheetItem.h 与 VLDContextSheetItem.m

//
// VLDContextSheetItem.h
//
// Created by Vladimir Angelov on 2/10/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import <Foundation/Foundation.h> @interface VLDContextSheetItem : NSObject - (id) initWithTitle: (NSString *) title
image: (UIImage *) image
highlightedImage: (UIImage *) highlightedImage; @property (strong, readonly) NSString *title;
@property (strong, readonly) UIImage *image;
@property (strong, readonly) UIImage *highlightedImage; @property (assign, readwrite, getter = isEnabled) BOOL enabled; @end
//
// VLDContextSheetItem.m
//
// Created by Vladimir Angelov on 2/10/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import "VLDContextSheetItem.h" @implementation VLDContextSheetItem - (id) initWithTitle: (NSString *) title
image: (UIImage *) image
highlightedImage: (UIImage *) highlightedImage { self = [super init]; if(self) {
_title = title;
_image = image;
_highlightedImage = highlightedImage;
_enabled = YES;
} return self;
} @end

VLDContextSheetItem.h 与 VLDContextSheetItem.m

//
// VLDContextSheetItem.h
//
// Created by Vladimir Angelov on 2/9/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import <Foundation/Foundation.h> @class VLDContextSheetItem; @interface VLDContextSheetItemView : UIView @property (strong, nonatomic) VLDContextSheetItem *item;
@property (readonly) BOOL isHighlighted; - (void) setHighlighted: (BOOL) highlighted animated: (BOOL) animated; @end
//
// VLDContextSheetItemView.m,
//
// Created by Vladimir Angelov on 2/9/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import "VLDContextSheetItemView.h"
#import "VLDContextSheetItem.h" #import <CoreImage/CoreImage.h> static const NSInteger VLDTextPadding = ; @interface VLDContextSheetItemView () @property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImageView *highlightedImageView;
@property (nonatomic, strong) UILabel *label;
@property (nonatomic, assign) NSInteger labelWidth; @end @implementation VLDContextSheetItemView @synthesize item = _item; - (id) initWithFrame: (CGRect) frame {
self = [super initWithFrame: CGRectMake(, , , )]; if(self) {
[self createSubviews];
} return self;
} - (void) createSubviews {
_imageView = [[UIImageView alloc] init];
[self addSubview: _imageView]; _highlightedImageView = [[UIImageView alloc] init];
_highlightedImageView.alpha = 0.0;
[self addSubview: _highlightedImageView]; _label = [[UILabel alloc] init];
_label.clipsToBounds = YES;
_label.font = [UIFont systemFontOfSize: ];
_label.textAlignment = NSTextAlignmentCenter;
_label.layer.cornerRadius = ;
_label.backgroundColor = [UIColor colorWithWhite: 0.0 alpha: 0.4];
_label.textColor = [UIColor whiteColor];
_label.alpha = 0.0;
[self addSubview: _label];
} - (void) layoutSubviews {
[super layoutSubviews]; self.imageView.frame = CGRectMake(, (self.frame.size.height - self.frame.size.width) / , self.frame.size.width, self.frame.size.width);
self.highlightedImageView.frame = self.imageView.frame;
self.label.frame = CGRectMake((self.frame.size.width - self.labelWidth) / 2.0, , self.labelWidth, );
} - (void) setItem:(VLDContextSheetItem *)item {
_item = item; [self updateImages];
[self updateLabelText];
} - (void) updateImages {
self.imageView.image = self.item.image;
self.highlightedImageView.image = self.item.highlightedImage; self.imageView.alpha = self.item.isEnabled ? 1.0 : 0.3;
} - (void) updateLabelText {
self.label.text = self.item.title;
self.labelWidth = * VLDTextPadding + ceil([self.label.text sizeWithAttributes: @{ NSFontAttributeName: self.label.font }].width);
[self setNeedsDisplay];
} - (void) setHighlighted: (BOOL) highlighted animated: (BOOL) animated {
if (!self.item.isEnabled) {
return;
} _isHighlighted = highlighted; [UIView animateWithDuration: animated ? 0.3 : 0.0
delay: 0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
self.highlightedImageView.alpha = (highlighted ? 1.0 : 0.0);
self.imageView.alpha = - self.highlightedImageView.alpha;
self.label.alpha = self.highlightedImageView.alpha; }
completion: nil]; } @end

VLDContextSheet.h 与 VLDContextSheet.m

//
// VLDContextSheet.h
//
// Created by Vladimir Angelov on 2/7/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import <Foundation/Foundation.h> @class VLDContextSheet;
@class VLDContextSheetItem; @protocol VLDContextSheetDelegate <NSObject> - (void) contextSheet: (VLDContextSheet *) contextSheet didSelectItem: (VLDContextSheetItem *) item; @end @interface VLDContextSheet : UIView @property (assign, nonatomic) NSInteger radius;
@property (assign, nonatomic) CGFloat rotation;
@property (assign, nonatomic) CGFloat rangeAngle;
@property (strong, nonatomic) NSArray *items;
@property (assign, nonatomic) id<VLDContextSheetDelegate> delegate; - (id) initWithItems: (NSArray *) items; - (void) startWithGestureRecognizer: (UIGestureRecognizer *) gestureRecognizer
inView: (UIView *) view;
- (void) end; @end
//
// VLDContextSheet.m
//
// Created by Vladimir Angelov on 2/7/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import "VLDContextSheetItemView.h"
#import "VLDContextSheet.h" typedef struct {
CGRect rect;
CGFloat rotation;
} VLDZone; static const NSInteger VLDMaxTouchDistanceAllowance = ;
static const NSInteger VLDZonesCount = ; static inline VLDZone VLDZoneMake(CGRect rect, CGFloat rotation) {
VLDZone zone; zone.rect = rect;
zone.rotation = rotation; return zone;
} static CGFloat VLDVectorDotProduct(CGPoint vector1, CGPoint vector2) {
return vector1.x * vector2.x + vector1.y * vector2.y;
} static CGFloat VLDVectorLength(CGPoint vector) {
return sqrt(vector.x * vector.x + vector.y * vector.y);
} static CGRect VLDOrientedScreenBounds() {
CGRect bounds = [UIScreen mainScreen].bounds; if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) &&
bounds.size.width < bounds.size.height) { bounds.size = CGSizeMake(bounds.size.height, bounds.size.width);
} return bounds;
} @interface VLDContextSheet () @property (strong, nonatomic) NSArray *itemViews;
@property (strong, nonatomic) UIView *centerView;
@property (strong, nonatomic) UIView *backgroundView;
@property (strong, nonatomic) VLDContextSheetItemView *selectedItemView;
@property (assign, nonatomic) BOOL openAnimationFinished;
@property (assign, nonatomic) CGPoint touchCenter;
@property (strong, nonatomic) UIGestureRecognizer *starterGestureRecognizer; @end @implementation VLDContextSheet { VLDZone zones[VLDZonesCount];
} - (id) initWithFrame: (CGRect) frame {
return [self initWithItems: nil];
} - (id) initWithItems: (NSArray *) items {
self = [super initWithFrame: VLDOrientedScreenBounds()]; if(self) {
_items = items;
_radius = ;
_rangeAngle = M_PI / 1.6; [self createSubviews];
} return self;
} - (void) dealloc {
[self.starterGestureRecognizer removeTarget: self action: @selector(gestureRecognizedStateObserver:)];
} - (void) createSubviews {
_backgroundView = [[UIView alloc] initWithFrame: CGRectZero];
_backgroundView.backgroundColor = [UIColor colorWithWhite: alpha: 0.6];
[self addSubview: self.backgroundView]; _itemViews = [[NSMutableArray alloc] init]; for(VLDContextSheetItem *item in _items) {
VLDContextSheetItemView *itemView = [[VLDContextSheetItemView alloc] init];
itemView.item = item; [self addSubview: itemView];
[(NSMutableArray *) _itemViews addObject: itemView];
} VLDContextSheetItemView *sampleItemView = _itemViews[]; _centerView = [[UIView alloc] initWithFrame: CGRectMake(, , sampleItemView.frame.size.width, sampleItemView.frame.size.width)];
_centerView.layer.cornerRadius = ;
_centerView.layer.borderWidth = ;
_centerView.layer.borderColor = [UIColor grayColor].CGColor;
[self addSubview: _centerView]; } - (void) layoutSubviews {
[super layoutSubviews]; self.backgroundView.frame = self.bounds;
} - (void) setCenterViewHighlighted: (BOOL) highlighted {
_centerView.backgroundColor = highlighted ? [UIColor colorWithWhite: 0.5 alpha: 0.4] : nil;
} - (void) createZones {
CGRect screenRect = self.bounds; NSInteger rowHeight1 = ; zones[] = VLDZoneMake(CGRectMake(, , , rowHeight1), 0.8);
zones[] = VLDZoneMake(CGRectMake(zones[].rect.size.width, , , rowHeight1), 0.4); zones[] = VLDZoneMake(CGRectMake(zones[].rect.origin.x + zones[].rect.size.width, , screenRect.size.width - *(zones[].rect.size.width + zones[].rect.size.width), rowHeight1), ); zones[] = VLDZoneMake(CGRectMake(zones[].rect.origin.x + zones[].rect.size.width, , zones[].rect.size.width, rowHeight1), -zones[].rotation);
zones[] = VLDZoneMake(CGRectMake(zones[].rect.origin.x + zones[].rect.size.width, , zones[].rect.size.width, rowHeight1), -zones[].rotation); NSInteger rowHeight2 = screenRect.size.height - zones[].rect.size.height; zones[] = VLDZoneMake(CGRectMake(, zones[].rect.size.height, zones[].rect.size.width, rowHeight2), M_PI - zones[].rotation);
zones[] = VLDZoneMake(CGRectMake(zones[].rect.size.width, zones[].rect.origin.y, zones[].rect.size.width, rowHeight2), M_PI - zones[].rotation);
zones[] = VLDZoneMake(CGRectMake(zones[].rect.origin.x + zones[].rect.size.width, zones[].rect.origin.y, zones[].rect.size.width, rowHeight2), M_PI - zones[].rotation);
zones[] = VLDZoneMake(CGRectMake(zones[].rect.origin.x + zones[].rect.size.width, zones[].rect.origin.y, zones[].rect.size.width, rowHeight2), M_PI - zones[].rotation);
zones[] = VLDZoneMake(CGRectMake(zones[].rect.origin.x + zones[].rect.size.width, zones[].rect.origin.y, zones[].rect.size.width, rowHeight2), M_PI - zones[].rotation);
} /* Only used for testing the touch zones */
- (void) drawZones {
for(int i = ; i < VLDZonesCount; i++) {
UIView *zoneView = [[UIView alloc] initWithFrame: zones[i].rect]; CGFloat hue = ( arc4random() % / 256.0 );
CGFloat saturation = ( arc4random() % / 256.0 ) + 0.5;
CGFloat brightness = ( arc4random() % / 256.0 ) + 0.5;
UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:]; zoneView.backgroundColor = color;
[self addSubview: zoneView];
}
} - (void) updateItemView: (UIView *) itemView
touchDistance: (CGFloat) touchDistance
animated: (BOOL) animated { if(!animated) {
[self updateItemViewNotAnimated: itemView touchDistance: touchDistance];
}
else {
[UIView animateWithDuration: 0.4
delay:
usingSpringWithDamping: 0.45
initialSpringVelocity: 7.5
options: UIViewAnimationOptionBeginFromCurrentState
animations: ^{
[self updateItemViewNotAnimated: itemView
touchDistance: touchDistance];
}
completion: nil];
}
} - (void) updateItemViewNotAnimated: (UIView *) itemView touchDistance: (CGFloat) touchDistance {
NSInteger itemIndex = [self.itemViews indexOfObject: itemView];
CGFloat angle = -0.65 + self.rotation + itemIndex * (self.rangeAngle / self.itemViews.count); CGFloat resistanceFactor = 1.0 / (touchDistance > ? 6.0 : 3.0); itemView.center = CGPointMake(self.touchCenter.x + (self.radius + touchDistance * resistanceFactor) * sin(angle),
self.touchCenter.y + (self.radius + touchDistance * resistanceFactor) * cos(angle)); CGFloat scale = + 0.2 * (fabs(touchDistance) / self.radius); itemView.transform = CGAffineTransformMakeScale(scale, scale);
} - (void) openItemsFromCenterView {
self.openAnimationFinished = NO; for(int i = ; i < self.itemViews.count; i++) {
VLDContextSheetItemView *itemView = self.itemViews[i];
itemView.transform = CGAffineTransformIdentity;
itemView.center = self.touchCenter;
[itemView setHighlighted: NO animated: NO]; [UIView animateWithDuration: 0.5
delay: i * 0.01
usingSpringWithDamping: 0.45
initialSpringVelocity: 7.5
options:
animations: ^{
[self updateItemViewNotAnimated: itemView touchDistance: 0.0]; }
completion: ^(BOOL finished) {
self.openAnimationFinished = YES;
}];
}
} - (void) closeItemsToCenterView {
[UIView animateWithDuration: 0.1
delay: 0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
self.alpha = 0.0;
}
completion:^(BOOL finished) {
[self removeFromSuperview];
self.alpha = 1.0;
}]; } - (void) startWithGestureRecognizer: (UIGestureRecognizer *) gestureRecognizer inView: (UIView *) view {
[view addSubview: self]; self.frame = VLDOrientedScreenBounds();
[self createZones]; self.starterGestureRecognizer = gestureRecognizer; self.touchCenter = [self.starterGestureRecognizer locationInView: self];
self.centerView.center = self.touchCenter;
self.selectedItemView = nil;
[self setCenterViewHighlighted: YES];
self.rotation = [self rotationForCenter: self.centerView.center]; [self openItemsFromCenterView]; [self.starterGestureRecognizer addTarget: self action: @selector(gestureRecognizedStateObserver:)];
} - (CGFloat) rotationForCenter: (CGPoint) center {
for(NSInteger i = ; i < ; i++) {
VLDZone zone = zones[i]; if(CGRectContainsPoint(zone.rect, center)) {
return zone.rotation;
}
} return ;
} - (void) gestureRecognizedStateObserver: (UIGestureRecognizer *) gestureRecognizer {
if(self.openAnimationFinished && gestureRecognizer.state == UIGestureRecognizerStateChanged) {
CGPoint touchPoint = [gestureRecognizer locationInView: self]; [self updateItemViewsForTouchPoint: touchPoint];
}
else if(gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
if(gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
self.selectedItemView = nil;
} [self end];
}
} - (CGFloat) signedTouchDistanceForTouchVector: (CGPoint) touchVector itemView: (UIView *) itemView {
CGFloat touchDistance = VLDVectorLength(touchVector); CGPoint oldCenter = itemView.center;
CGAffineTransform oldTransform = itemView.transform; [self updateItemViewNotAnimated: itemView touchDistance: self.radius + ]; if(!CGRectContainsRect(self.bounds, itemView.frame)) {
touchDistance = -touchDistance;
} itemView.center = oldCenter;
itemView.transform = oldTransform; return touchDistance;
} - (void) updateItemViewsForTouchPoint: (CGPoint) touchPoint {
CGPoint touchVector = {touchPoint.x - self.touchCenter.x, touchPoint.y - self.touchCenter.y};
VLDContextSheetItemView *itemView = [self itemViewForTouchVector: touchVector];
CGFloat touchDistance = [self signedTouchDistanceForTouchVector: touchVector itemView: itemView]; if(fabs(touchDistance) <= VLDMaxTouchDistanceAllowance) {
self.centerView.center = CGPointMake(self.touchCenter.x + touchVector.x, self.touchCenter.y + touchVector.y);
[self setCenterViewHighlighted: YES];
}
else {
[self setCenterViewHighlighted: NO]; [UIView animateWithDuration: 0.4
delay:
usingSpringWithDamping: 0.35
initialSpringVelocity: 7.5
options: UIViewAnimationOptionBeginFromCurrentState
animations: ^{
self.centerView.center = self.touchCenter; }
completion: nil];
} if(touchDistance > self.radius + VLDMaxTouchDistanceAllowance) {
[itemView setHighlighted: NO animated: YES]; [self updateItemView: itemView
touchDistance: 0.0
animated: YES]; self.selectedItemView = nil; return;
} if(itemView != self.selectedItemView) {
[self.selectedItemView setHighlighted: NO animated: YES]; [self updateItemView: self.selectedItemView
touchDistance: 0.0
animated: YES]; [self updateItemView: itemView
touchDistance: touchDistance
animated: YES]; [self bringSubviewToFront: itemView];
}
else {
[self updateItemView: itemView
touchDistance: touchDistance
animated: NO];
} if(fabs(touchDistance) > VLDMaxTouchDistanceAllowance) {
[itemView setHighlighted: YES animated: YES];
} self.selectedItemView = itemView;
} - (VLDContextSheetItemView *) itemViewForTouchVector: (CGPoint) touchVector {
CGFloat maxCosOfAngle = -;
VLDContextSheetItemView *resultItemView = nil; for(int i = ; i < self.itemViews.count; i++) {
VLDContextSheetItemView *itemView = self.itemViews[i];
CGPoint itemViewVector = {
itemView.center.x - self.touchCenter.x,
itemView.center.y - self.touchCenter.y
}; CGFloat cosOfAngle = VLDVectorDotProduct(itemViewVector, touchVector) / VLDVectorLength(itemViewVector); if(cosOfAngle > maxCosOfAngle) {
maxCosOfAngle = cosOfAngle;
resultItemView = itemView;
}
} return resultItemView;
} - (void) end {
[self.starterGestureRecognizer removeTarget: self action: @selector(gestureRecognizedStateObserver:)]; if(self.selectedItemView && self.selectedItemView.isHighlighted) {
[self.delegate contextSheet: self didSelectItem: self.selectedItemView.item];
} [self closeItemsToCenterView];
} @end

控制器源码:

//
// VLDExampleViewController.h
// VLDContextSheetExample
//
// Created by Vladimir Angelov on 11/2/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import <Foundation/Foundation.h>
#import "VLDContextSheet.h" @interface VLDExampleViewController : UIViewController <VLDContextSheetDelegate> @property (strong, nonatomic) VLDContextSheet *contextSheet; @end
//
// VLDExampleViewController.m
// VLDContextSheetExample
//
// Created by Vladimir Angelov on 11/2/14.
// Copyright (c) 2014 Vladimir Angelov. All rights reserved.
// #import "VLDExampleViewController.h"
#import "VLDContextSheetItem.h" @implementation VLDExampleViewController - (void) viewDidLoad {
[super viewDidLoad];
[self createContextSheet]; self.view.backgroundColor = [UIColor blackColor]; UIGestureRecognizer *gestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget: self
action: @selector(longPressed:)];
[self.view addGestureRecognizer: gestureRecognizer];
} - (void) createContextSheet {
VLDContextSheetItem *item1 = [[VLDContextSheetItem alloc] initWithTitle: @"Gift"
image: [UIImage imageNamed: @"gift"]
highlightedImage: [UIImage imageNamed: @"gift_highlighted"]]; VLDContextSheetItem *item2 = [[VLDContextSheetItem alloc] initWithTitle: @"Add to"
image: [UIImage imageNamed: @"add"]
highlightedImage: [UIImage imageNamed: @"add_highlighted"]]; VLDContextSheetItem *item3 = [[VLDContextSheetItem alloc] initWithTitle: @"Share"
image: [UIImage imageNamed: @"share"]
highlightedImage: [UIImage imageNamed: @"share_highlighted"]]; self.contextSheet = [[VLDContextSheet alloc] initWithItems: @[ item1, item2, item3 ]];
self.contextSheet.delegate = self;
} - (void) contextSheet: (VLDContextSheet *) contextSheet didSelectItem: (VLDContextSheetItem *) item {
NSLog(@"Selected item: %@", item.title);
} - (void) longPressed: (UIGestureRecognizer *) gestureRecognizer {
if(gestureRecognizer.state == UIGestureRecognizerStateBegan) { [self.contextSheet startWithGestureRecognizer: gestureRecognizer
inView: self.view];
}
} - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation
duration: (NSTimeInterval) duration { [super willRotateToInterfaceOrientation: toInterfaceOrientation duration: duration]; [self.contextSheet end];
} @end
05-11 17:49