QuadCurveMenu is a menu with the same look as the Path app's menu's story menu.
This is a fork of levey's AwesomeMenu. I proposed a pull request and this was not what the original author had intended to create. This fork has some notable differences that I outline in the pull request:
I really love the menu and wanted to use any data source (not just an array), more touch events, and the ability to manipulate the images and animations. I also wanted the menu items to be able to hold some data object that I could store and retrieve on selection instead of relying on an index.
Ultimately this is a much more modular library that allows you to define new functionality for the menu without having to rip the guts out of the existing one.
- Componentized into multiple files
QuadCurveMenu is defined in multiple files to separate concerns and make it easier to maintain and the code clearer. This does increase the burden when the code is included in a project.
-
Converted project to ARC
-
AppDelegate in the example is no longer responsible for the menu and an example view controller was created.
-
Menu will generate delegate events for
willExpand
,didExpand
,willClose
, anddidClose
. -
Menu will ask a delegate
shouldExpand
before expanding andshouldClose
before closing -
Menu will generate events for tap (
didTapMenu
) and long press (didLongPressMenu
) -
Menu is populated from a Data Source Delegate
-
Menu is designed by a MenuItemFactory
-
Menu is composed with individual, definable animations for expand, close, selected, unselected
-
Menu animations are now in their own separate classes
-
Menu can display menu items with a Radial, Linear or custom style
-
Menu items will generate events for tap (
didTapMenuItem
) and long press (didLongPressMenuItem
) -
Menu items are automatically medallionized (AGMedallionView) so custom medallionized images do not have to be created.
-
Menu items are designed by a MenuItemFactory
-
Menu items contain a dataObject
QuadCurveMenu defines a simple initializer that generates a radial menu, 360 degrees, centered within the given frame showing menu items for each element in the provided array.
@implementation AwesomeViewController
- (void)viewDidLoad {
[super viewDidLoad]
NSArray *menuItemArray = [NSArray arrayWithObjects:@"1",@"2",nil];
QuadCurveMenu *menu = [[QuadCurveMenu alloc] initWithFrame:self.view.bounds withArray:menuItemArray];
[self.view addSubview:menu];
}
@end
You may find an NSArray limiting so the menu can be defined with a custom data source.
First you define a data source, or have an existing data source,
that adheres to the QuadCurveDataSourceDelegate
protocol.
By default when you use the array implementation it uses a
QuadCurveDefaultDataSource
which simply wraps an NSArray.
@interface AwesomeDataSource : NSObject <QuadCurveDataSourceDelegate> {
NSMutableArray *dataItems;
}
@end
@implementation AwesomeDataSource
- (id)init {
self = [super init];
if (self) {
dataItems = [NSMutableArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5",@"6", nil];
}
return self;
}
#pragma mark - QuadCurveDataSourceDelegate Adherence
- (int)numberOfMenuItems {
return [dataItems count];
}
- (id)dataObjectAtIndex:(NSInteger)itemIndex {
return [dataItems objectAtIndex:itemIndex];
}
Creating a QuadCurveMenu with a custom data source:
@implementation AwesomeViewController
- (void)viewDidLoad {
[super viewDidLoad]
AwesomeDataSource *dataSource = [[AwesomeDataSource alloc] init];
QuadCurveMenu *menu = [[QuadCurveMenu alloc] initWithFrame:self.view.bounds dataSource:dataSource];
[self.view addSubview:menu];
}
@end
Setup a delegate object, this will usually be the view controller showing the
QuadCurveMenu, that adheres to the QuadCurveMenuDelegate
protocol.
@interface AwesomeViewController : UIViewController <QuadCurveMenuDelegate>
@end
@implementation AwesomeViewController
- (void)viewDidLoad {
[super viewDidLoad]
AwesomeDataSource *dataSource = [[AwesomeDataSource alloc] init];
QuadCurveMenu *menu = [[QuadCurveMenu alloc] initWithFrame:self.view.bounds dataSource:dataSource];
menu.delegate = self;
[self.view addSubview:menu];
}
- (void)quadCurveMenu:(QuadCurveMenu *)menu didTapMenu:(QuadCurveMenuItem *)mainMenuItem {
NSLog(@"Menu - Tapped");
}
- (void)quadCurveMenu:(QuadCurveMenu *)menu didLongPressMenu:(QuadCurveMenuItem *)mainMenuItem {
NSLog(@"Menu - Long Pressed");
}
- (void)quadCurveMenu:(QuadCurveMenu *)menu didTapMenuItem:(QuadCurveMenuItem *)menuItem {
NSLog(@"Menu Item (%@) - Tapped",menuItem.dataObject);
}
- (void)quadCurveMenu:(QuadCurveMenu *)menu didLongPressMenuItem:(QuadCurveMenuItem *)menuItem {
NSLog(@"Menu Item (%@) - Long Pressed",menuItem.dataObject);
}
- (void)quadCurveMenuWillExpand:(QuadCurveMenu *)menu {
NSLog(@"Menu - Will Expand");
}
- (void)quadCurveMenuDidExpand:(QuadCurveMenu *)menu {
NSLog(@"Menu - Did Expand");
}
- (void)quadCurveMenuWillClose:(QuadCurveMenu *)menu {
NSLog(@"Menu - Will Close");
}
- (void)quadCurveMenuDidClose:(QuadCurveMenu *)menu {
NSLog(@"Menu - Did Close");
}
- (BOOL)quadCurveMenuShouldClose:(QuadCurveMenu *)menu {
// Returning YES will allow the menu to close; NO to prevent it from closing.
return YES;
}
- (BOOL)quadCurveMenuShouldExpand:(QuadCurveMenu *)menu {
// Returning YES will allow the menu to expand; NO to prevent it from expanding.
return YES;
}
@end
You can configure the look of the center, main menu item, and the menu items that appear from the main menu.
By default the QuadCurveMenu uses
[QuadCurveDefaultMenuItemFactory defaultMainMenuItemFactory]
for the main menu and[QuadCurveDefaultMenuItemFactory defaultMenuItemFactory]
for each menu item. These are defined to look like the path application.
You can define customized instances of QuadCurveDefaultMenuItemFactory
:
@implementation AwesomeViewController
- (void)viewDidLoad {
[super viewDidLoad]
AwesomeDataSource *dataSource = [[AwesomeDataSource alloc] init];
QuadCurveMenu *menu = [[QuadCurveMenu alloc] initWithFrame:self.view.bounds dataSource:dataSource];
menu.delegate = self;
// Use a facebook center button for the menu
[menu setMainMenuItemFactory:[[QuadCurveDefaultMenuItemFactory alloc] initWithImage:[UIImage imageNamed:@"facebook.png"] highlightImage:[UIImage imageNamed:nil]]];
// Use an unknown user button for the menu items
[menu setMenuItemFactory:[[QuadCurveDefaultMenuItemFactory alloc] initWithImage:[UIImage imageNamed:@"unknown-user.png"] highlightImage:[UIImage imageNamed:nil]]];
[self.view addSubview:menu];
}
You can also define your own object that adheres to the protocol QuadCurveMenuItemFactory
.
#pragma mark - QuadCurveMenuItemFactory Adherence
- (QuadCurveMenuItem *)createMenuItemWithDataObject:(id)dataObject {
QuadCurveMenuItem *item = [[QuadCurveMenuItem alloc] initWithImage:image
highlightedImage:highlightImage
contentImage:contentImage
highlightedContentImage:highlightContentImage];
[item setDataObject:dataObject];
return item;
}
By default QuadCurveMenu displays in a 360 degree radial menu.
This can be customized by confuring an existing menu directory
or defining a custom QuadCurveMotionDirector
This is a departure from the original source which defined a number of attributes on the menu which controlled the layout.
You can define a a custom radial director with the options:
-
rotateAngle
- initial starting angle of the menu (default: 0 degrees) -
menuWholeAngle
- the total available angle that the menu items will be displayed (default: 360 degrees) -
endRadius
- the final distance from the main menu center (where the menu items will sit) -
nearRadius
- the closest distance from the main menu center that the menu items will travel -
farRadius
- the furthest distance from the main menu center that the menu items will travel
You can define a custom linear director with the options:
angle
- the angle at which to display the menu itemspadding
- the space between each menu item
If a radial or linear layout is not powerful enough, you can define
a custom director that adheres to the QuadCurveMotionDirector
interface:
@protocol QuadCurveMotionDirector <NSObject>
- (void)positionMenuItem:(QuadCurveMenuItem *)item
atIndex:(int)index
ofCount:(int)count
fromMenu:(QuadCurveMenuItem *)mainMenuItem;
@end
Several of the animations are customizable through properties. Viewing the
example project you should see an Animations group which contains the
default animations used in the application. You can customize them there or
define your own and set them through properties on the QuadCurveMenu
.
Here is an example of swapping the default selected and unselected animations:
menu.selectedAnimation = [[QuadCurveShrinkAnimation alloc] init]
menu.unselectedanimation = [[QuadCurveBlowupAnimation alloc] init]
An animation is an object that adheres to the protocol QuadCurveAnimation
.
- (NSString *)animationName {
return @"blowup";
}
- (CAAnimationGroup *)animationForItem:(QuadCurveMenuItem *)item {
CGPoint point = item.center;
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.values = [NSArray arrayWithObjects:[NSValue valueWithCGPoint:point], nil];
positionAnimation.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithFloat:.3], nil];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(3, 3, 1)];
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.toValue = [NSNumber numberWithFloat:0.0f];
CAAnimationGroup *animationgroup = [CAAnimationGroup animation];
animationgroup.animations = [NSArray arrayWithObjects:positionAnimation, scaleAnimation, opacityAnimation, nil];
animationgroup.duration = 0.3f;
return animationgroup;
}
The name is used as the name for the animation within the layer. The animation
itself is called with the QuadCurveMenuItem
and should return the animation
group that will be performed.