Full Code of MassiveHealth/Opaque for AI

master 3b127a828991 cached
17 files
54.4 KB
15.3k tokens
1 symbols
1 requests
Download .txt
Repository: MassiveHealth/Opaque
Branch: master
Commit: 3b127a828991
Files: 17
Total size: 54.4 KB

Directory structure:
gitextract_svz_bv_q/

├── Opaque/
│   ├── MHAppDelegate.h
│   ├── MHAppDelegate.m
│   ├── MHFoldingLayer.h
│   ├── MHFoldingLayer.m
│   ├── MHPaperFoldViewController.h
│   ├── MHPaperFoldViewController.m
│   ├── Opaque-Info.plist
│   ├── Opaque-Prefix.pch
│   ├── en.lproj/
│   │   └── InfoPlist.strings
│   └── main.m
├── Opaque.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcuserdata/
│   │       └── michael.xcuserdatad/
│   │           ├── UserInterfaceState.xcuserstate
│   │           └── WorkspaceSettings.xcsettings
│   └── xcuserdata/
│       └── michael.xcuserdatad/
│           └── xcschemes/
│               ├── Opaque.xcscheme
│               └── xcschememanagement.plist
└── README.mdown

================================================
FILE CONTENTS
================================================

================================================
FILE: Opaque/MHAppDelegate.h
================================================
//
//  MHAppDelegate.h
//  Opaque
//
//  Created by Michael Margolis on 2/15/12.
//  Copyright (c) 2012 Massive Health. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//  associated documentation files (the "Software"), to deal in the Software without restriction, 
//  including without limitation the rights to use, copy, modify, merge, publish, distribute, 
//  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all copies or 
//  substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//  AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import <UIKit/UIKit.h>

@class MHViewController;

@interface MHAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIViewController *viewController;

@end


================================================
FILE: Opaque/MHAppDelegate.m
================================================
//  
//  MHAppDelegate.m
//  Opaque
//  
//  Created by Michael Margolis on 2/15/12.
//  Copyright (c) 2012 Massive Health. All rights reserved.
//  
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//  associated documentation files (the "Software"), to deal in the Software without restriction, 
//  including without limitation the rights to use, copy, modify, merge, publish, distribute, 
//  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all copies or 
//  substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//  AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import "MHAppDelegate.h"
#import "MHPaperFoldViewController.h"

@implementation MHAppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    application.statusBarHidden = YES;
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.viewController = [[MHPaperFoldViewController alloc] init];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

@end


================================================
FILE: Opaque/MHFoldingLayer.h
================================================
//
//  MHFoldingLayer.h
//  Opaque
//
//  Created by Michael Margolis on 2/15/12.
//  Copyright (c) 2012 Massive Health. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//  associated documentation files (the "Software"), to deal in the Software without restriction, 
//  including without limitation the rights to use, copy, modify, merge, publish, distribute, 
//  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all copies or 
//  substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//  AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import <QuartzCore/QuartzCore.h>

typedef enum {
    MHLayerStyleFlat,     // Flat
    MHLayerStyleFoldBack, // Top of a list
    MHLayerStylePinch,    // Middle of a list & pinched
    MHLayerStyleFoldUp    // Bottom of a list
} MHLayerStyle;

@interface MHFoldingLayer : CALayer

@property (nonatomic, assign) CGFloat fullHeight;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, assign) MHLayerStyle layerStyle;

@end


================================================
FILE: Opaque/MHFoldingLayer.m
================================================
//
//  MHFoldingLayer.m
//  Opaque
//
//  Created by Michael Margolis on 2/15/12.
//  Copyright (c) 2012 Massive Health. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//  associated documentation files (the "Software"), to deal in the Software without restriction, 
//  including without limitation the rights to use, copy, modify, merge, publish, distribute, 
//  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all copies or 
//  substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//  AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import "MHFoldingLayer.h"

@interface MHFoldingLayer ()

@property (nonatomic, strong) CALayer* topHalfLayer;
@property (nonatomic, strong) CALayer* bottomHalfLayer;
@property (nonatomic, strong) CATextLayer *topTextLayer;
@property (nonatomic, strong) CATextLayer *bottomTextLayer;
@property (nonatomic, strong) CALayer *lineLayer;
@end

@implementation MHFoldingLayer

@synthesize fullHeight = _fullHeight;
@synthesize color = _color;
@synthesize layerStyle = _transitionStyle;
@synthesize title = _title;

@synthesize topHalfLayer = _topHalfLayer;
@synthesize bottomHalfLayer = _bottomHalfLayer;
@synthesize topTextLayer = _topTextLayer;
@synthesize bottomTextLayer = _bottomTextLayer;
@synthesize lineLayer = _lineLayer;

- (id)init
{
    self = [super init];
    
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1/200.0;
    self.sublayerTransform = transform;
    
    self.topHalfLayer = [CALayer layer];
    self.topHalfLayer.anchorPoint = CGPointMake(0.5,1);
    [self addSublayer:self.topHalfLayer];
    
    self.bottomHalfLayer = [CALayer layer];
    self.bottomHalfLayer.anchorPoint = CGPointMake(0.5,0);
    [self addSublayer:self.bottomHalfLayer];

    // If we wanted to get fancier we could render all subviews into a CGImage
    // and use that as the contents of both layers with a contentsRect.
    // This would allow for arbitrarily complex folding view/layer hierarchies
    CGFloat y = 18.0;
    CGFloat textHeight = 30.0;
    self.topTextLayer = [CATextLayer layer];
    self.topTextLayer.string = nil;
    self.topTextLayer.fontSize = 20;
    self.topTextLayer.font = CGFontCreateWithFontName(CFSTR("HelveticaNeue-Bold"));
    self.topTextLayer.contentsScale = [[UIScreen mainScreen] scale];
    self.topTextLayer.frame = CGRectMake(20,y,300,textHeight);
    [self.topHalfLayer addSublayer:self.topTextLayer];

    self.bottomTextLayer = [CATextLayer layer];
    self.bottomTextLayer.string = nil;
    self.bottomTextLayer.fontSize = self.topTextLayer.fontSize;
    self.bottomTextLayer.font = self.topTextLayer.font;
    self.bottomTextLayer.contentsScale = self.topTextLayer.contentsScale;
    self.bottomTextLayer.frame = CGRectMake(20,0,300,textHeight);
    self.bottomTextLayer.contentsRect = CGRectMake(0,(textHeight - y)/textHeight,1,1);
    self.bottomTextLayer.rasterizationScale = self.bottomTextLayer.contentsScale;
    [self.bottomHalfLayer addSublayer:self.bottomTextLayer];
    
    self.lineLayer = [CALayer layer];
    self.lineLayer.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2].CGColor;
    self.lineLayer.delegate = self;
    self.lineLayer.zPosition = 1;
    [self addSublayer:self.lineLayer];
    
    return self;
}

- (void)dealloc
{
    self.topHalfLayer.delegate = nil;
    self.bottomHalfLayer.delegate = nil;
    self.lineLayer.delegate = nil;
}

- (void)setDelegate:(id)delegate
{
    [super setDelegate:delegate];
    self.topHalfLayer.delegate = delegate;
    self.bottomHalfLayer.delegate = delegate;
}

- (void)setColor:(UIColor *)color
{
    _color = color;    
    [self setNeedsLayout];
}

- (void)setTitle:(NSString *)title
{
    _title = title;
    
    // These can be attributed strings if we wanted but this example uses vanilla strings
    self.topTextLayer.string = title;
    self.bottomTextLayer.string = title;    
}

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
    // Never animate the line around, it should always stick to the bottom
    if ( layer == self.lineLayer )
        return (id)[NSNull null];
    
    return nil;
}

- (void)layoutSublayers
{
    [super layoutSublayers];

    CGSize size = self.bounds.size;   
    
    // Configure the layer bounds and midpoints
    CGRect halfRect = CGRectMake(0,0,size.width, 0.5 * self.fullHeight);
    self.topHalfLayer.bounds = halfRect;
    self.bottomHalfLayer.bounds = halfRect;
    CGPoint midPoint = CGPointMake(0.5 * size.width, 0.5 * size.height);    
    self.topHalfLayer.position = midPoint;
    self.bottomHalfLayer.position = midPoint;
    
    // Update the colors
    // If these layers had contents (images, etc) we would want to have an additional transparent 
    // layer with alpha that darkened everything instead of mutating the color.
    CGFloat h,s,b,a;
    CGFloat f = 1 - self.bounds.size.height / self.fullHeight;    
    [self.color getHue:&h saturation:&s brightness:&b alpha:&a];    
    CGFloat tb = b * ( 1 - f * 0.35 ); // scale from 100% - 65% brightness
    CGFloat bb = b * ( 1 - f * 0.15 ); // scale from 100% - 85% brightness
    UIColor *topColor = [UIColor colorWithHue:h saturation:s brightness:tb alpha:a];
    UIColor *bottomColor  = [UIColor colorWithHue:h saturation:s brightness:bb alpha:a];    
    if ( self.layerStyle == MHLayerStyleFoldUp )
        topColor = bottomColor;
    else if ( self.layerStyle == MHLayerStyleFoldBack || self.layerStyle == MHLayerStyleFlat )
        bottomColor = topColor;
    
    self.topHalfLayer.backgroundColor = topColor.CGColor;
    self.bottomHalfLayer.backgroundColor = bottomColor.CGColor;    
    
    self.lineLayer.frame = CGRectMake(0,size.height-1, size.width, 1);
    
    // We are totally done if we have no transition style
    if ( self.layerStyle == MHLayerStyleFlat )
        return;
    
    // All three transition styles share the same exact math. The only difference is if we
    // reflect the top or bottom angle to create a plane on the top, plane on the bottom, 
    // or two planes intersecting each other. 
    CGFloat l = 0.5 * self.fullHeight;
    CGFloat y = 0.5 * self.bounds.size.height;
    CGFloat theta = acosf(y/l);
    CGFloat z = l * sinf(theta);    
    CGFloat topAngle = theta;
    CGFloat bottomAngle = theta;
    
    if ( self.layerStyle == MHLayerStylePinch )
    {
        topAngle *= -1;
    }
    else if ( self.layerStyle == MHLayerStyleFoldUp )
    {
        topAngle *= -1;
        bottomAngle *= -1;
    }
    
    CATransform3D transform = CATransform3DMakeTranslation(0.0, 0.0, -z);
    self.topHalfLayer.transform = CATransform3DRotate(transform, topAngle, 1.0, 0.0, 0.0);
    self.bottomHalfLayer.transform = CATransform3DRotate(transform, bottomAngle, 1.0, 0.0, 0.0);
}

@end


================================================
FILE: Opaque/MHPaperFoldViewController.h
================================================
//
//  MHPaperFoldViewController.h
//  Opaque
//
//  Created by Michael Margolis on 2/15/12.
//  Copyright (c) 2012 Massive Health. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//  associated documentation files (the "Software"), to deal in the Software without restriction, 
//  including without limitation the rights to use, copy, modify, merge, publish, distribute, 
//  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all copies or 
//  substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//  AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import <UIKit/UIKit.h>

@interface MHPaperFoldViewController : UIViewController <UIScrollViewDelegate>

@end


================================================
FILE: Opaque/MHPaperFoldViewController.m
================================================
//
//  MHPaperFoldViewController.m
//  Opaque
//
//  Created by Michael Margolis on 2/15/12.
//  Copyright (c) 2012 Massive Health. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//  associated documentation files (the "Software"), to deal in the Software without restriction, 
//  including without limitation the rights to use, copy, modify, merge, publish, distribute, 
//  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all copies or 
//  substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
//  AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#import "MHPaperFoldViewController.h"
#import <QuartzCore/QuartzCore.h>
#import "MHFoldingLayer.h"

@interface MHPaperFoldViewController ()

@property (nonatomic, strong) UIView *topHalfView;
@property (nonatomic, strong) UIView *bottomHalfView;
@property (nonatomic, strong) UIView *containerView;

@property (nonatomic, strong) CALayer *listLayer;
@property (nonatomic, strong) MHFoldingLayer *currentLayer;

@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) NSMutableArray *foldingLayers;
@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer;

- (MHFoldingLayer*)foldingLayerWithFrame:(CGRect)frame;
- (void)updateLayerColors;
- (void)updateLayerTransitionStyles;
- (UIColor*)layerColorForIndex:(int)index count:(int)count;

@end

#define kFoldingLayerHeight 60
#define kFoldingLayerWidth 320
#define kFoldingLayerCount 7

@implementation MHPaperFoldViewController

@synthesize topHalfView = _topHalfView;
@synthesize bottomHalfView = _bottomHalfView;
@synthesize containerView = _containerView;
@synthesize foldingLayers = _foldingLayers;
@synthesize currentLayer = _currentLayer;
@synthesize scrollView = _scrollView;
@synthesize pinchRecognizer = _pinchRecognizer;
@synthesize listLayer = _listLayer;

- (id)init
{
    self = [super init];
    self.foldingLayers = [NSMutableArray array];
    return self;
}

- (void)viewDidLoad
{
    self.view.backgroundColor = [UIColor blackColor];
    
    // Our list is not a tableview, it is a scroll view full of CALayers
    CGRect scrollFrame = self.view.frame;
    scrollFrame.origin = CGPointZero;
    self.scrollView = [[UIScrollView alloc] initWithFrame:scrollFrame];
    self.scrollView.userInteractionEnabled = YES;
    self.scrollView.bounces = YES;
    self.scrollView.alwaysBounceVertical = YES;
    self.scrollView.delegate = self;
    
    [self.view addSubview:self.scrollView];
    
    // Set up and layout a bunch of layers
    CGFloat y = 0;
    NSArray *titles = [NSArray arrayWithObjects:@"Go to the store", @"Implement Clear-like demo", 
                       @"And another thing…", @"Mostly Harmless", @"Marvin, the Paranoid Android", @"Zaphod Beeblebrox", @"Massive Health", nil];
    
    for ( int i = 0; i < kFoldingLayerCount; i++ )
    {
        MHFoldingLayer *foldingLayer = [self foldingLayerWithFrame:CGRectMake(0,y,kFoldingLayerWidth,kFoldingLayerHeight)];
        foldingLayer.title = [titles objectAtIndex:i];
        y+= foldingLayer.frame.size.height;

        [self.scrollView.layer addSublayer:foldingLayer];
        [self.foldingLayers addObject:foldingLayer];
    }
    
    [self updateLayerColors];
    
    // Configure our gesture recognizers. 
    // Avoid creating a second pan gesture recognizer, use the scrollview's preexisting one.
    [self.scrollView.panGestureRecognizer addTarget:self action:@selector(panHandler:)];    
    self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchHandler:)];
    [self.scrollView addGestureRecognizer:self.pinchRecognizer];
    
    [super viewDidLoad];
}

- (void)dealloc
{
    [self.pinchRecognizer removeTarget:nil action:nil];    
    [self.pinchRecognizer.view removeGestureRecognizer:self.pinchRecognizer];
    
    [self.scrollView.panGestureRecognizer removeTarget:self action:@selector(panHandler:)];
    
    for ( MHFoldingLayer *layer in self.foldingLayers )
    {
        layer.delegate = nil;
        [layer removeFromSuperlayer];
    }
}

- (MHFoldingLayer*)layerAtPoint:(CGPoint)point
{
    for ( MHFoldingLayer *layer in self.foldingLayers )
        if ( CGRectContainsPoint(layer.frame, point ) )
            return layer;
    
    return nil;
}

// This function is a bit too large as it handles all possible drag and pan states
// A future version would probably move all of the layout code into a CALayer subclass or layout manager
// This version just uses the scale of the pinch rather than tracking the individual finger movements
- (void)handleGesture:(UIGestureRecognizer*)gesture withPinch:(BOOL)pinch andScale:(float)scale
{
    BOOL ended = NO;
    
    switch ( gesture.state )
    {
        case UIGestureRecognizerStateBegan:
        {
            // Handle the easier case of panning first            
            if ( !pinch )
            {
                // Don't create a new layer if we are scrolling up
                if ( self.scrollView.contentOffset.y > 0 )
                    break;
                
                self.currentLayer = [self foldingLayerWithFrame:CGRectMake(0,0,kFoldingLayerWidth,0)];                
                self.currentLayer.delegate = self;
                self.currentLayer.layerStyle = MHLayerStyleFoldBack;
                [self.scrollView.layer insertSublayer:self.currentLayer atIndex:0];
                [self.foldingLayers insertObject:self.currentLayer atIndex:0];
                self.currentLayer.color = [self layerColorForIndex:0 count:self.foldingLayers.count];
                break;
            }
            
            // Pinching
            CGPoint firstTouch = [gesture locationOfTouch:0 inView:self.view];
            CGPoint secondTouch = [gesture locationOfTouch:1 inView:self.view]; 
            CGPoint midPoint = CGPointMake(0.5 * ( firstTouch.x + secondTouch.x), 0.5 * ( firstTouch.y + secondTouch.y ) );
            midPoint.y += self.scrollView.contentOffset.y; // We may be further down the list
            
            MHFoldingLayer *layer = [self layerAtPoint:midPoint];
            if ( layer == nil )
                return;
            
            if ( scale < 1 )
            {
                self.currentLayer = layer;
                [self.currentLayer setValue:[NSNumber numberWithBool:NO] forKey:@"creating"];
                break;
            }
            
            self.currentLayer = [self foldingLayerWithFrame:CGRectMake(0,CGRectGetMaxY(layer.frame),kFoldingLayerWidth,0)];
            
            [self.currentLayer setValue:[NSNumber numberWithBool:YES] forKey:@"creating"];
            
            // If the pinch is in the top half of the layer, create the cell above it. Else, below.
            int layerIndex = [self.foldingLayers indexOfObject:layer];
            if ( midPoint.y < CGRectGetMidY(layer.frame) )
                layerIndex--;
            
            [self.scrollView.layer insertSublayer:self.currentLayer atIndex:layerIndex+1];
            [self.foldingLayers insertObject:self.currentLayer atIndex:layerIndex+1];
            
            self.currentLayer.color = [self layerColorForIndex:layerIndex count:self.foldingLayers.count];
            
            if ( self.currentLayer == [self.foldingLayers lastObject] )
                self.currentLayer.layerStyle = MHLayerStyleFoldUp;
            
            break;
        }
            
        case UIGestureRecognizerStateChanged:
        {
            if ( !self.currentLayer )
                break;
            
            CGFloat frameScale = scale;                        
            
            BOOL creating = [[self.currentLayer valueForKey:@"creating"] boolValue];
            if ( pinch && creating )
                frameScale = fmaxf(frameScale - 1, 0);
            
            frameScale = fminf(frameScale, 1.0);
            
            // Animate in a cell instead of pulling the scrollview down        
            if ( !pinch && ( frameScale > 0.0 && frameScale < 1.0 ) )
                self.scrollView.contentOffset = CGPointZero;                
            
            frameScale = fmaxf(frameScale, 0);
            
            CGRect frame = self.currentLayer.frame;
            frame.size.height = self.currentLayer.fullHeight * frameScale;
            self.currentLayer.frame = frame;
            
            break;
        }
            
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed:
        {   
            if ( !self.currentLayer )
                break;
            
            ended = YES;
            CGRect endFrame = self.currentLayer.frame;
            CGFloat height = self.currentLayer.frame.size.height;
            endFrame.size.height = 0;
            
            if ( height / self.currentLayer.fullHeight < 0.75 )
                [self.currentLayer setValue:[NSNumber numberWithBool:YES] forKey:@"toRemove"];
            else
                endFrame.size.height = self.currentLayer.fullHeight;
            
            self.currentLayer.frame = endFrame;            
            
            break;
        }
            
        default:
            NSLog(@"Unexpected gesture recognizer %@ state %d", gesture, gesture.state);
    }
    
    
    CGFloat y = 0;
    BOOL creating = [[self.currentLayer valueForKey:@"creating"] boolValue];    
    
    [CATransaction begin];
    
    if ( ended ) {    
        [CATransaction setCompletionBlock:^{
            
            // Update our scrollview's content size
            CALayer *lastLayer = [self.foldingLayers lastObject];
            CGSize contentSize = CGSizeZero;
            if ( lastLayer )
                contentSize = CGSizeMake(self.scrollView.frame.size.width, CGRectGetMaxY(lastLayer.frame));
            
            self.scrollView.contentSize = contentSize;
            
            if ( creating && contentSize.height > CGRectGetMaxY(self.scrollView.bounds) )
                [self.scrollView flashScrollIndicators];               
        }];
    }
    
    for ( MHFoldingLayer *layer in self.foldingLayers )
    {   
        CGRect frame = layer.frame;
        if ( pinch && layer == self.currentLayer && scale > 1 && gesture.state == UIGestureRecognizerStateChanged )
        {
            if ( creating )
                scale = fmaxf(scale - 1, 0);
            
            CGFloat dy = layer.fullHeight * scale;
            if ( creating && scale < 1 )
                frame.origin.y = y;
            else
                frame.origin.y = y + 0.5 * (dy - layer.fullHeight );
            
            layer.frame = frame;
            y += dy;
        }
        else
        {
            frame.origin.y = y;
            layer.frame = frame;
            y += frame.size.height;
        }        
    }
    
    if ( ended )
    {
        if ( [self.currentLayer valueForKey:@"toRemove"] )
            [self.foldingLayers removeObject:self.currentLayer];
        
        [self.currentLayer setValue:[NSNumber numberWithBool:NO] forKey:@"creating"];
        self.currentLayer = nil;
        
        [self updateLayerColors];
        [self updateLayerTransitionStyles];        
    }
    
    [CATransaction commit];
}

- (void)panHandler:(UIPanGestureRecognizer*)pan
{
    CGFloat translation = [pan translationInView:pan.view].y;
    CGFloat scale = translation / self.currentLayer.fullHeight;
    
    [self handleGesture:pan withPinch:NO andScale:scale];    
}

- (void)pinchHandler:(UIPinchGestureRecognizer*)pinch
{
    if ( pinch.state == UIGestureRecognizerStateChanged && pinch.numberOfTouches < 2 )
    {
        // Cancel the pinch if the user lifts a finger and we only have a single touch
        pinch.enabled = NO;
        pinch.enabled = YES;
        return;
    }
    
    [self handleGesture:pinch withPinch:YES andScale:pinch.scale];
}

- (MHFoldingLayer*)foldingLayerWithFrame:(CGRect)frame
{
    MHFoldingLayer *foldingLayer = [MHFoldingLayer layer];
    
    foldingLayer.frame = frame; 
    foldingLayer.fullHeight = foldingLayer.frame.size.height;
    if ( foldingLayer.fullHeight == 0 )
        foldingLayer.fullHeight = kFoldingLayerHeight;
    
    foldingLayer.title = @"Massive Health";    
    foldingLayer.layerStyle = MHLayerStylePinch;
    foldingLayer.delegate = self;
    
    return foldingLayer;
}

- (UIColor*)layerColorForIndex:(int)index count:(int)count
{
    // Values derived from the Heat Map theme in Clear
    CGFloat sr = 217.0/255.0, sg = 0.0, sb = 22.0/255.0;
    CGFloat er = 234.0/255.0, eg = 175.0/255.0 , eb = 28.0/255.0;
    
    int cutoff = 7;
    CGFloat delta = 1.0 / cutoff;
    if ( count > cutoff )
        delta = 1.0 / count;
    else
        count = cutoff;
    
    CGFloat s = delta * (count - index);
    CGFloat e = delta * index;
    
    CGFloat red = sr * s + er * e;
    CGFloat green = sg * s + eg * e;
    CGFloat blue = sb * s + eb * e;
    return [UIColor colorWithRed:red green:green blue:blue alpha:1];
}

- (void)updateLayerColors
{
    for ( int i = 0; i < self.foldingLayers.count; i++ )
    {
        MHFoldingLayer *layer = [self.foldingLayers objectAtIndex:i];
        layer.color = [self layerColorForIndex:i count:self.foldingLayers.count];
    }
}

- (void)updateLayerTransitionStyles
{
    // Now that we are done, reset all of the transition styles
    for ( int layerIndex = 0; layerIndex < self.foldingLayers.count; layerIndex++ )
    {
        MHFoldingLayer *layer = [self.foldingLayers objectAtIndex:layerIndex];
        
        if ( layerIndex == 0 )
            layer.layerStyle = MHLayerStyleFoldBack;
        else if ( layerIndex == self.foldingLayers.count - 1 )
            layer.layerStyle = MHLayerStyleFoldUp;
        else 
            layer.layerStyle = MHLayerStylePinch;
    }
}

#pragma mark - CALayer Animation Delegate

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
    UIPanGestureRecognizer *pan = self.scrollView.panGestureRecognizer;
    UIPinchGestureRecognizer *pinch = self.pinchRecognizer;
    
    // Set up an animation delegate when we are removing a layer so we can remove it from the
    // view hierarchy when it is done
    if ( [layer valueForKey:@"toRemove"] )
    {
        CABasicAnimation *animation = [CABasicAnimation animation]; 
        if ( [event isEqualToString:@"bounds"] )
            animation.delegate = self;
        return animation;
    }
    
    // Don't allow core animation to animate while dragging, otherwise it gets all
    // wibbly-wobbly timey-wimey and interpolates through the wrong rotated plane.
    if ( pan.state == UIGestureRecognizerStateChanged || pan.state == UIGestureRecognizerStateBegan )
        return (id)[NSNull null];
    
    if ( pinch.state == UIGestureRecognizerStateChanged || pinch.state == UIGestureRecognizerStateBegan )
        return (id)[NSNull null];
    
    return nil;
}

#pragma mark - CAAnimationDelegate

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    // Remove any sublayers marked for removal
    NSMutableArray *layersToDelete = [NSMutableArray array];
    for ( CALayer *layer in self.scrollView.layer.sublayers )
        if ( [[layer valueForKey:@"toRemove"] boolValue] )
            [layersToDelete addObject:layer];
    
    for ( CALayer *layer in layersToDelete )
        [layer removeFromSuperlayer];
}

@end


================================================
FILE: Opaque/Opaque-Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
	<string>${PRODUCT_NAME}</string>
	<key>CFBundleExecutable</key>
	<string>${EXECUTABLE_NAME}</string>
	<key>CFBundleIconFiles</key>
	<array>
		<string>MassiveHealth.png</string>
		<string>MassiveHealth@2x.png</string>
	</array>
	<key>CFBundleIcons</key>
	<dict>
		<key>CFBundlePrimaryIcon</key>
		<dict>
			<key>CFBundleIconFiles</key>
			<array>
				<string>MassiveHealth.png</string>
				<string>MassiveHealth@2x.png</string>
			</array>
			<key>UIPrerenderedIcon</key>
			<false/>
		</dict>
	</dict>
	<key>CFBundleIdentifier</key>
	<string>MassiveHealth.${PRODUCT_NAME:rfc1034identifier}</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>${PRODUCT_NAME}</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1.0</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIStatusBarHidden</key>
	<true/>
</dict>
</plist>


================================================
FILE: Opaque/Opaque-Prefix.pch
================================================
//
// Prefix header for all source files of the 'Opaque' target in the 'Opaque' project
//

#import <Availability.h>

#ifndef __IPHONE_4_0
#warning "This project uses features only available in iOS SDK 4.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
#endif


================================================
FILE: Opaque/en.lproj/InfoPlist.strings
================================================
/* Localized versions of Info.plist keys */



================================================
FILE: Opaque/main.m
================================================
//
//  main.m
//  Opaque
//
//  Created by Michael Margolis on 2/21/12.
//  Copyright (c) 2012 Massive Health. All rights reserved.
//

#import <UIKit/UIKit.h>

#import "MHAppDelegate.h"

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([MHAppDelegate class]));
    }
}


================================================
FILE: Opaque.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 46;
	objects = {

/* Begin PBXBuildFile section */
		4C93F4B114F45C52009B1853 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C93F4B014F45C52009B1853 /* UIKit.framework */; };
		4C93F4B314F45C52009B1853 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C93F4B214F45C52009B1853 /* Foundation.framework */; };
		4C93F4B514F45C52009B1853 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C93F4B414F45C52009B1853 /* CoreGraphics.framework */; };
		4C93F4BB14F45C52009B1853 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C93F4B914F45C52009B1853 /* InfoPlist.strings */; };
		4C93F4BD14F45C52009B1853 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F4BC14F45C52009B1853 /* main.m */; };
		4C93F4C114F45C52009B1853 /* MHAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F4C014F45C52009B1853 /* MHAppDelegate.m */; };
		4C93F4D114F45C74009B1853 /* MHPaperFoldViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F4CE14F45C73009B1853 /* MHPaperFoldViewController.m */; };
		4C93F4D214F45C74009B1853 /* MHFoldingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F4D014F45C74009B1853 /* MHFoldingLayer.m */; };
		4C93F4D414F45CBC009B1853 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C93F4D314F45CBC009B1853 /* QuartzCore.framework */; };
		4C93F50214F475BF009B1853 /* MassiveHealth@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C93F50114F475BF009B1853 /* MassiveHealth@2x.png */; };
		4C93F50414F475F5009B1853 /* MassiveHealth.png in Resources */ = {isa = PBXBuildFile; fileRef = 4C93F50314F475F5009B1853 /* MassiveHealth.png */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		4C93F4AC14F45C52009B1853 /* Opaque.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Opaque.app; sourceTree = BUILT_PRODUCTS_DIR; };
		4C93F4B014F45C52009B1853 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
		4C93F4B214F45C52009B1853 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
		4C93F4B414F45C52009B1853 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
		4C93F4B814F45C52009B1853 /* Opaque-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Opaque-Info.plist"; sourceTree = "<group>"; };
		4C93F4BA14F45C52009B1853 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
		4C93F4BC14F45C52009B1853 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
		4C93F4BE14F45C52009B1853 /* Opaque-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Opaque-Prefix.pch"; sourceTree = "<group>"; };
		4C93F4BF14F45C52009B1853 /* MHAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MHAppDelegate.h; sourceTree = "<group>"; };
		4C93F4C014F45C52009B1853 /* MHAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MHAppDelegate.m; sourceTree = "<group>"; };
		4C93F4CD14F45C73009B1853 /* MHPaperFoldViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MHPaperFoldViewController.h; sourceTree = "<group>"; };
		4C93F4CE14F45C73009B1853 /* MHPaperFoldViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MHPaperFoldViewController.m; sourceTree = "<group>"; };
		4C93F4CF14F45C73009B1853 /* MHFoldingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MHFoldingLayer.h; sourceTree = "<group>"; };
		4C93F4D014F45C74009B1853 /* MHFoldingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MHFoldingLayer.m; sourceTree = "<group>"; };
		4C93F4D314F45CBC009B1853 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
		4C93F50114F475BF009B1853 /* MassiveHealth@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "MassiveHealth@2x.png"; path = "../MassiveHealth@2x.png"; sourceTree = "<group>"; };
		4C93F50314F475F5009B1853 /* MassiveHealth.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = MassiveHealth.png; path = ../MassiveHealth.png; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		4C93F4A914F45C52009B1853 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				4C93F4D414F45CBC009B1853 /* QuartzCore.framework in Frameworks */,
				4C93F4B114F45C52009B1853 /* UIKit.framework in Frameworks */,
				4C93F4B314F45C52009B1853 /* Foundation.framework in Frameworks */,
				4C93F4B514F45C52009B1853 /* CoreGraphics.framework in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		4C93F4A114F45C52009B1853 = {
			isa = PBXGroup;
			children = (
				4C93F4B614F45C52009B1853 /* Opaque */,
				4C93F4AF14F45C52009B1853 /* Frameworks */,
				4C93F4AD14F45C52009B1853 /* Products */,
			);
			sourceTree = "<group>";
		};
		4C93F4AD14F45C52009B1853 /* Products */ = {
			isa = PBXGroup;
			children = (
				4C93F4AC14F45C52009B1853 /* Opaque.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		4C93F4AF14F45C52009B1853 /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				4C93F4D314F45CBC009B1853 /* QuartzCore.framework */,
				4C93F4B014F45C52009B1853 /* UIKit.framework */,
				4C93F4B214F45C52009B1853 /* Foundation.framework */,
				4C93F4B414F45C52009B1853 /* CoreGraphics.framework */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		4C93F4B614F45C52009B1853 /* Opaque */ = {
			isa = PBXGroup;
			children = (
				4C93F4BF14F45C52009B1853 /* MHAppDelegate.h */,
				4C93F4C014F45C52009B1853 /* MHAppDelegate.m */,
				4C93F4CD14F45C73009B1853 /* MHPaperFoldViewController.h */,
				4C93F4CE14F45C73009B1853 /* MHPaperFoldViewController.m */,
				4C93F4CF14F45C73009B1853 /* MHFoldingLayer.h */,
				4C93F4D014F45C74009B1853 /* MHFoldingLayer.m */,
				4C93F4B714F45C52009B1853 /* Supporting Files */,
			);
			path = Opaque;
			sourceTree = "<group>";
		};
		4C93F4B714F45C52009B1853 /* Supporting Files */ = {
			isa = PBXGroup;
			children = (
				4C93F50314F475F5009B1853 /* MassiveHealth.png */,
				4C93F50114F475BF009B1853 /* MassiveHealth@2x.png */,
				4C93F4B814F45C52009B1853 /* Opaque-Info.plist */,
				4C93F4B914F45C52009B1853 /* InfoPlist.strings */,
				4C93F4BC14F45C52009B1853 /* main.m */,
				4C93F4BE14F45C52009B1853 /* Opaque-Prefix.pch */,
			);
			name = "Supporting Files";
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		4C93F4AB14F45C52009B1853 /* Opaque */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 4C93F4CA14F45C52009B1853 /* Build configuration list for PBXNativeTarget "Opaque" */;
			buildPhases = (
				4C93F4A814F45C52009B1853 /* Sources */,
				4C93F4A914F45C52009B1853 /* Frameworks */,
				4C93F4AA14F45C52009B1853 /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = Opaque;
			productName = Opaque;
			productReference = 4C93F4AC14F45C52009B1853 /* Opaque.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		4C93F4A314F45C52009B1853 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastUpgradeCheck = 0420;
				ORGANIZATIONNAME = "Massive Health";
			};
			buildConfigurationList = 4C93F4A614F45C52009B1853 /* Build configuration list for PBXProject "Opaque" */;
			compatibilityVersion = "Xcode 3.2";
			developmentRegion = English;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
			);
			mainGroup = 4C93F4A114F45C52009B1853;
			productRefGroup = 4C93F4AD14F45C52009B1853 /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				4C93F4AB14F45C52009B1853 /* Opaque */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		4C93F4AA14F45C52009B1853 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				4C93F4BB14F45C52009B1853 /* InfoPlist.strings in Resources */,
				4C93F50214F475BF009B1853 /* MassiveHealth@2x.png in Resources */,
				4C93F50414F475F5009B1853 /* MassiveHealth.png in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		4C93F4A814F45C52009B1853 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				4C93F4BD14F45C52009B1853 /* main.m in Sources */,
				4C93F4C114F45C52009B1853 /* MHAppDelegate.m in Sources */,
				4C93F4D114F45C74009B1853 /* MHPaperFoldViewController.m in Sources */,
				4C93F4D214F45C74009B1853 /* MHFoldingLayer.m in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXVariantGroup section */
		4C93F4B914F45C52009B1853 /* InfoPlist.strings */ = {
			isa = PBXVariantGroup;
			children = (
				4C93F4BA14F45C52009B1853 /* en */,
			);
			name = InfoPlist.strings;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		4C93F4C814F45C52009B1853 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
				CLANG_ENABLE_OBJC_ARC = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
				GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 5.0;
				SDKROOT = iphoneos;
			};
			name = Debug;
		};
		4C93F4C914F45C52009B1853 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				ARCHS = "$(ARCHS_STANDARD_32_BIT)";
				CLANG_ENABLE_OBJC_ARC = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
				COPY_PHASE_STRIP = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
				GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 5.0;
				OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
				SDKROOT = iphoneos;
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		4C93F4CB14F45C52009B1853 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				GCC_PRECOMPILE_PREFIX_HEADER = YES;
				GCC_PREFIX_HEADER = "Opaque/Opaque-Prefix.pch";
				INFOPLIST_FILE = "Opaque/Opaque-Info.plist";
				PRODUCT_NAME = "$(TARGET_NAME)";
				WRAPPER_EXTENSION = app;
			};
			name = Debug;
		};
		4C93F4CC14F45C52009B1853 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				GCC_PRECOMPILE_PREFIX_HEADER = YES;
				GCC_PREFIX_HEADER = "Opaque/Opaque-Prefix.pch";
				INFOPLIST_FILE = "Opaque/Opaque-Info.plist";
				PRODUCT_NAME = "$(TARGET_NAME)";
				WRAPPER_EXTENSION = app;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		4C93F4A614F45C52009B1853 /* Build configuration list for PBXProject "Opaque" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				4C93F4C814F45C52009B1853 /* Debug */,
				4C93F4C914F45C52009B1853 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		4C93F4CA14F45C52009B1853 /* Build configuration list for PBXNativeTarget "Opaque" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				4C93F4CB14F45C52009B1853 /* Debug */,
				4C93F4CC14F45C52009B1853 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = 4C93F4A314F45C52009B1853 /* Project object */;
}


================================================
FILE: Opaque.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:Opaque.xcodeproj">
   </FileRef>
</Workspace>


================================================
FILE: Opaque.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEWorkspaceUserSettings_HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
	<true/>
	<key>IDEWorkspaceUserSettings_SnapshotAutomaticallyBeforeSignificantChanges</key>
	<false/>
</dict>
</plist>


================================================
FILE: Opaque.xcodeproj/xcuserdata/michael.xcuserdatad/xcschemes/Opaque.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "4C93F4AB14F45C52009B1853"
               BuildableName = "Opaque.app"
               BlueprintName = "Opaque"
               ReferencedContainer = "container:Opaque.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
      shouldUseLaunchSchemeArgsEnv = "YES"
      buildConfiguration = "Debug">
      <Testables>
      </Testables>
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "4C93F4AB14F45C52009B1853"
            BuildableName = "Opaque.app"
            BlueprintName = "Opaque"
            ReferencedContainer = "container:Opaque.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
   </TestAction>
   <LaunchAction
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      buildConfiguration = "Debug"
      debugDocumentVersioning = "YES"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "4C93F4AB14F45C52009B1853"
            BuildableName = "Opaque.app"
            BlueprintName = "Opaque"
            ReferencedContainer = "container:Opaque.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
      <AdditionalOptions>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      buildConfiguration = "Release"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "4C93F4AB14F45C52009B1853"
            BuildableName = "Opaque.app"
            BlueprintName = "Opaque"
            ReferencedContainer = "container:Opaque.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: Opaque.xcodeproj/xcuserdata/michael.xcuserdatad/xcschemes/xcschememanagement.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>SchemeUserState</key>
	<dict>
		<key>Opaque.xcscheme</key>
		<dict>
			<key>orderHint</key>
			<integer>0</integer>
		</dict>
	</dict>
	<key>SuppressBuildableAutocreation</key>
	<dict>
		<key>4C93F4AB14F45C52009B1853</key>
		<dict>
			<key>primary</key>
			<true/>
		</dict>
	</dict>
</dict>
</plist>


================================================
FILE: README.mdown
================================================
Opaque
=======

[Clear][], a todo-list app by [Realmac Software][] recently took the tech and design community by storm with its sexy new take on no-button, gestural interfaces. For those who haven’t seen it, it’s worth watching their [intro video][]. It exudes class. We tip our collective [Massive Health][] hats to Clear’s great work.

In addition to the novelty of the gestural interface, Clear had an exceptional new visual metaphor for manipulating and adding todo items: simple paper-like folds. Being a fan of wonderful products that surprises and delights, we spent a day recreating some of Clear’s primary user interactions and wanted to share what we learned (and our code) with the wider community.

Please don’t take the code and just recreate Clear—that’s not nice—but do use the techniques as a source of inspiration for your next project.

We implemented a number of the interactions: pull to create, pinch-open to unfold a new todo, pinch-close to fold away a todo, and of course a way to do it all programmatically. It looks like this:

![http://vimeo.com/37752521](http://s3.amazonaws.com/massivehealth/Opaque.png)

To recreate Clear’s effects, all it takes is some Core Animation layers, trigonometry, gesture recognition, and more trigonometry. Sitting comfortably? Here we go.

Clear has four different cell states which we’ve named fold back, pinch, fold up, and flat:

![](https://img.skitch.com/20120301-nydunqf23w4266xj1rt6gx2k7y.png)

All four of these states don’t need to be special cased in our code, although it may appear that way at first glace. If we lay the problem out correctly they can all be represented with the same five lines of code. We start by splitting the layer into two parts and defining two anchor points: at the bottom of the top half, and the top of the bottom half. Here’s the code (we’re taking advantage of Core Animation’s 3D transforms):

    CGFloat theta = acosf(y/l);
    CGFloat z = h * sinf(theta);    

    /* ... */
    
    CATransform3D transform = CATransform3DMakeTranslation(0.0, 0.0, z);
    self.topHalfLayer.transform = CATransform3DRotate(transform, topAngle, 1.0, 0.0, 0.0);
    self.bottomHalfLayer.transform = CATransform3DRotate(transform, bottomAngle, 1.0, 0.0, 0.0);

What’s going on? Each state can be constructed by translating the top or bottom layer by its anchor point by [z=h*sin(θ)] and rotating by angle [θ=cos-1(y/h)] where y = ½ the current cell height and h = ½ the original cell height.

![](https://img.skitch.com/20120301-x4sg844pycwpxxim53y2yw6a3s.png)

In order to make the 3D projections line up properly and dramatically simplify the math, we change the anchor points of the layers to align with the center of our parent folding layer. For those of you following along at home, that’s (0.5, 1) for the top layer and (0.5,0) for the bottom layer. If we don’t transform anything we already have the flat layer.

One down, three to go.

As we shrink the height of our layer, we need to rotate the layers forward by the appropriate θ so their edges touch the front plane. You might be tempted to just skip the 3D and use 2D transforms to fake the effect. Trust us, that approach will end in tears. To get the pinch effect we rotate the top and bottom layers by the appropriate θ and -θ so the layer halves stay the same height. This puts the layers in front of the screen so we need to translate the layers back by z. That’s 2 down. If we want to fold instead of pinch, translate the layers back by z and rotate both layers by θ or -θ and they will line up. As you can see above, that’s just one CATransform3DMakeTranslation and one CATransform3DRotate per layer half and we’re done.

What about pinching and stuff?
------------------------------

The use of contextually appropriate gestures are what make Clear really shine. For gesture management in iOS5 we can use UIPinchGestureRecognizer & UIPanGestureRecognizer. This example uses a full screen UIScrollView for bouncing and scrolling large lists and adds a bunch of our folding layers directly to the scrollview’s layer. The general idea behind the UI is to create a new layer when a gesture begins, adjust the size of your layer when the gesture changes, and finalize/delete the layer when the gesture has ended. At the end of each gesture change state we relayout our layer frames. Thanks to Core Animation our layers will animate between states for you automatically.

Want more specifics? The nitty-gritty is all in the code.


Give me the code!
-----------------

Want to do something like this in your project? Want to contribute? The source is all right here. We call it Opaque. Feel free to fork it and make it your own or submit a pull request and improve this one.

Read the source for a full understanding of how it works—this read me talks about the most important parts, but clearly doesn’t cover everything. The code is also just a prototype. If this were a production app, some obvious improvements would be:

- Layers should track finger locations when pinching to create layers rather than using the pinch scale.
- Use UIViews backed by custom CALayers instead of using raw layers in our view hierarchy.
- Support arbitrary view hierarchies and avoid the two-text-field trick we used by rendering the cell contents into an image and using that as the layer contents while animating.
- Create a MHFoldingTableView with a datasource and delegate and move our layer/cell layout into that view instead of the gesture recognizer.
- Interested in building awesome software to save lives and change the world for the better? So are we, and we’re hiring! Head over to our [Jobs page][] and send us your resume.

Please send thoughts, feedback, improvements, ideas, haikus, hatemail, and suggestions to [michael@massivehealth.com][] and you can find me on Twitter at [@yipe][]. [Massive Health][] is not affiliated in any way with Realmac Software. We hope you take some of these ideas and build innovative new products with it!

[Jobs page]: http://massivehealth.com/jobs
[Massive Health]: http://www.massivehealth.com
[Realmac Software]: http://www.realmacsoftware.com/
[Clear]: http://www.realmacsoftware.com/clear/
[intro video]: http://vimeo.com/35693267
[michael@massivehealth.com]: mailto:michael@massivehealth.com
[github]: http://www.github.com/MassiveHealth/Opaque
[Jobs page]: http://massivehealth.com/jobs-front-end
[@yipe]: http://twitter.com/yipe
Download .txt
gitextract_svz_bv_q/

├── Opaque/
│   ├── MHAppDelegate.h
│   ├── MHAppDelegate.m
│   ├── MHFoldingLayer.h
│   ├── MHFoldingLayer.m
│   ├── MHPaperFoldViewController.h
│   ├── MHPaperFoldViewController.m
│   ├── Opaque-Info.plist
│   ├── Opaque-Prefix.pch
│   ├── en.lproj/
│   │   └── InfoPlist.strings
│   └── main.m
├── Opaque.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcuserdata/
│   │       └── michael.xcuserdatad/
│   │           ├── UserInterfaceState.xcuserstate
│   │           └── WorkspaceSettings.xcsettings
│   └── xcuserdata/
│       └── michael.xcuserdatad/
│           └── xcschemes/
│               ├── Opaque.xcscheme
│               └── xcschememanagement.plist
└── README.mdown
Download .txt
SYMBOL INDEX (1 symbols across 1 files)

FILE: Opaque/MHFoldingLayer.h
  type MHLayerStyle (line 25) | typedef enum {
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (59K chars).
[
  {
    "path": "Opaque/MHAppDelegate.h",
    "chars": 1461,
    "preview": "//\n//  MHAppDelegate.h\n//  Opaque\n//\n//  Created by Michael Margolis on 2/15/12.\n//  Copyright (c) 2012 Massive Health. "
  },
  {
    "path": "Opaque/MHAppDelegate.m",
    "chars": 1827,
    "preview": "//  \n//  MHAppDelegate.m\n//  Opaque\n//  \n//  Created by Michael Margolis on 2/15/12.\n//  Copyright (c) 2012 Massive Heal"
  },
  {
    "path": "Opaque/MHFoldingLayer.h",
    "chars": 1716,
    "preview": "//\n//  MHFoldingLayer.h\n//  Opaque\n//\n//  Created by Michael Margolis on 2/15/12.\n//  Copyright (c) 2012 Massive Health."
  },
  {
    "path": "Opaque/MHFoldingLayer.m",
    "chars": 7437,
    "preview": "//\n//  MHFoldingLayer.m\n//  Opaque\n//\n//  Created by Michael Margolis on 2/15/12.\n//  Copyright (c) 2012 Massive Health."
  },
  {
    "path": "Opaque/MHPaperFoldViewController.h",
    "chars": 1350,
    "preview": "//\n//  MHPaperFoldViewController.h\n//  Opaque\n//\n//  Created by Michael Margolis on 2/15/12.\n//  Copyright (c) 2012 Mass"
  },
  {
    "path": "Opaque/MHPaperFoldViewController.m",
    "chars": 16069,
    "preview": "//\n//  MHPaperFoldViewController.m\n//  Opaque\n//\n//  Created by Michael Margolis on 2/15/12.\n//  Copyright (c) 2012 Mass"
  },
  {
    "path": "Opaque/Opaque-Info.plist",
    "chars": 1610,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Opaque/Opaque-Prefix.pch",
    "chars": 315,
    "preview": "//\n// Prefix header for all source files of the 'Opaque' target in the 'Opaque' project\n//\n\n#import <Availability.h>\n\n#i"
  },
  {
    "path": "Opaque/en.lproj/InfoPlist.strings",
    "chars": 45,
    "preview": "/* Localized versions of Info.plist keys */\n\n"
  },
  {
    "path": "Opaque/main.m",
    "chars": 347,
    "preview": "//\n//  main.m\n//  Opaque\n//\n//  Created by Michael Margolis on 2/21/12.\n//  Copyright (c) 2012 Massive Health. All right"
  },
  {
    "path": "Opaque.xcodeproj/project.pbxproj",
    "chars": 13101,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Opaque.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 151,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:Opaque.xcodepro"
  },
  {
    "path": "Opaque.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/WorkspaceSettings.xcsettings",
    "chars": 383,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Opaque.xcodeproj/xcuserdata/michael.xcuserdatad/xcschemes/Opaque.xcscheme",
    "chars": 3044,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n "
  },
  {
    "path": "Opaque.xcodeproj/xcuserdata/michael.xcuserdatad/xcschemes/xcschememanagement.plist",
    "chars": 478,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "README.mdown",
    "chars": 6420,
    "preview": "Opaque\n=======\n\n[Clear][], a todo-list app by [Realmac Software][] recently took the tech and design community by storm "
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the MassiveHealth/Opaque GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (54.4 KB), approximately 15.3k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!