For those who asked to see my latest project example. Here it is, a work in progress that will be available soon.
Note: this is GIF animation, the original graphics is real-time 60 FPS.
Stay tuned.
Cheers.
For those who asked to see my latest project example. Here it is, a work in progress that will be available soon.
Note: this is GIF animation, the original graphics is real-time 60 FPS.
Stay tuned.
Cheers.


// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// !!! Embed segues are processed before viewDidLoad().
switch segue.destinationViewController {
case let vc as MyHobbiesCollectionViewController:
// Note that this embedded VC has multiple instances,
// need additional id check (i.e. by segue.identifier).
switch segue.identifier {
case .Some("My1stHobbiesEmbedSegue"):
my1stHobbiesCVC = vc;
default:
my2ndHobbiesCVC = vc;
}
// Also prepare non-embed segues.
case let vc as EditHobbiesViewController:
break;
default: break;
}
}

import UIKit
class RootViewController: UIViewController, UIGestureRecognizerDelegate{
@IBOutlet weak var menuContainerView: UIView!
@IBOutlet weak var mainContainerView: UIView!
weak var menuVC: MenuViewController!;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Instead of fishing for an embedded controller in a prepareForSegue.
for vc in childViewControllers {
if let mvc = vc as? MenuViewController {
menuVC = mvc;
}
}
}
// MARK: - Gesture Recognizer
var initialProgress: Float = 0.0;
var ignoreGesture = false;
@IBAction func handlePanGesture(recognizer: UIPanGestureRecognizer) {
let vel = recognizer.velocityInView(view);
let velThreshold = menuVC.velocityThreshold;
switch(recognizer.state) {
case .Began:
ignoreGesture = abs(vel.y) > abs(vel.x);
if (ignoreGesture) {
return;
}
initialProgress = menuVC.progress;
recognizer.setTranslation(CGPointZero, inView: view)
case .Changed:
if (ignoreGesture) {
return;
}
let translation = recognizer.translationInView(view);
let tX = Float(translation.x);
let progress = initialProgress + tX / velThreshold;
// The following can be used to prevent menu progress overshot (not a good "responsive UI").
// progress = min(1.0, max(0.0, progress));
menuVC.progress = progress;
case .Ended:
if (ignoreGesture) {
return;
}
let velX = Float(vel.x);
let expand = ((menuVC.progress > 0.5 && velX > -velThreshold) || (menuVC.progress < 0.5 && velX > velThreshold));
menuVC.animateMenu(expand);
default:
break
}
}
}
import UIKit
class MenuViewController: UIViewController {
@IBOutlet weak var dimView: UIView!
@IBOutlet weak var menuView: UIView!
@IBOutlet weak var menuLeadingConstraint: NSLayoutConstraint!
var _useAnimation = false;
var _progress: Float = 0.0;
// To animate to a value set the _useAnimation true.
var progress: Float {
get { return _progress; }
set {
if (abs(newValue) > 1.0) {
_progress = min(1.5, pow(abs(newValue), 0.2) * (newValue >= 0.0 ? 1.0 : -1.0));
}
else {
_progress = newValue;
}
if (newValue > 1e-5 && !menuEnabled) {
// Don't just set to condition via assignment, "false" will break animation (0.0 will collapse the menu before animation begins).
menuEnabled = true;
}
let animations = {
self.dimView.alpha = CGFloat(min(1.0, self._progress) / 2);
let menuOriginX = CGFloat(self.velocityThreshold * self._progress) - self.menuView.frame.width;
self.menuLeadingConstraint.constant = menuOriginX;
self.view.layoutIfNeeded();
};
let completion: (Bool -> Void) = { finished in
if (newValue <= 1e-5) {
self.menuEnabled = false;
}
self._useAnimation = false;
};
if (_useAnimation) {
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseOut, animations: animations, completion: completion);
} else {
animations();
completion(true);
}
}
}
private(set) var velocityThreshold: Float = 100.0;
// Enables/disables container view.
var menuEnabled: Bool {
get {
return !view.superview!.hidden;
}
set {
view.superview!.hidden = !newValue;
}
}
func animateMenu(expand: Bool) {
_useAnimation = true;
if (expand) {
progress = 1;
} else {
progress = 0;
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Initial menu view offset is a design-time position for an "expanded" state.
velocityThreshold = Float(menuView.frame.width + menuView.frame.origin.x);
}
override func viewDidAppear(animated: Bool) {
menuEnabled = true;
progress = 1.0;
animateMenu(false);
}
@IBAction func tapDimView(sender: AnyObject) {
animateMenu(false);
}
}
#include <string>
namespace FileSystem
{
std::string DataPath();
std::string ReadFile(const char* filePath);
}
#import <Foundation/Foundation.h>
#include <iostream>
#include <fstream>
#import "FileSystem.hpp"
namespace FileSystem
{
using namespace std;
string DataPath()
{
string result;
NSBundle* bundle = [NSBundle mainBundle];
if (bundle == nil) {
#ifdef DEBUG
NSLog(@"Bundle is nil... which should never happen.");
#endif
} else {
NSString* path = [bundle bundlePath];
// Also, to get Documents directory:
// path = [NSHomeDirectory() stringByAppendingString: @"/Documents/"];
path = [NSString stringWithFormat: @"%@%s", path, "/Data/"];
result = string([path UTF8String]);
}
return result;
}
string ReadFile(const char* filePath)
{
string result;
ifstream ifs(filePath, ios::in | ios::binary | ios::ate);
if (!ifs) {
throw invalid_argument(string("Error opening '") + filePath + "'.");
}
auto fileSize = ifs.tellg();
result.resize((string::size_type)fileSize);
ifs.seekg(0, ios::beg);
auto buf = &result[0];
ifs.read(buf, (streamsize)fileSize);
return result;
}
}
//
// ALYViewController.m
//
#import "ALYViewController.h"
#import "ALYZoomAnimator.h"
NSString *const MySegueID = @"MySegueID";
@interface ALYViewController ()
@property (strong, nonatomic) ALYZoomAnimator *zoomAnimator;
- (IBAction)showModalViewTapped:(id)sender;
@end
@implementation ALYViewController
- (IBAction)showModalViewTapped:(id)sender {
[self performSegueWithIdentifier: MySegueID sender: nil];
}
@end
//
// ALYModalViewController.m
//
#import "ALYModalViewController.h"
@interface ALYModalViewController ()
- (IBAction)dismissTapped:(id)sender;
@end
@implementation ALYModalViewController
- (IBAction)dismissTapped:(id)sender {
[self dismissViewControllerAnimated: YES completion: nil];
}
@end
// // ALYZoomAnimator.h // #import <Foundation/Foundation.h> @interface ALYZoomAnimator : NSObject <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning> @end
//
// ALYZoomAnimator.m
//
#import "ALYZoomAnimator.h"
#define IN_DURATION 1.0
#define OUT_DURATION 0.3
@interface ALYZoomAnimator()
@property (weak, nonatomic) UIViewController *presentedVC;
@end
@implementation ALYZoomAnimator
// Returns true while the IN animation is active to reveal the modal sub-dialog.
- (BOOL) isBeingPresented: (id<UIViewControllerContextTransitioning>) transitionContext
{
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
return toVC == self.presentedVC;
}
#pragma mark - <UIViewControllerTransitioningDelegate>
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
self.presentedVC = presented;
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return self;
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return [self isBeingPresented: transitionContext] ? IN_DURATION : OUT_DURATION;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *container = transitionContext.containerView;
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
CGRect cb = container.bounds;
CGRect startFrame = CGRectMake(cb.size.width / 2, cb.size.height / 2 - 50, 0, 0);
CGRect endFrame = CGRectInset(startFrame, -100, -100);
UIView *snapshot;
if ([self isBeingPresented: transitionContext]) {
fromView.userInteractionEnabled = NO;
fromView.tintColor = [UIColor grayColor];
toView.frame = endFrame;
snapshot = [toView snapshotViewAfterScreenUpdates:YES];
snapshot.frame = startFrame;
snapshot.alpha = 0.0;
[container addSubview: snapshot];
[UIView animateWithDuration: [self transitionDuration: transitionContext]
delay: 0
usingSpringWithDamping: 500 initialSpringVelocity: 15
options: 0
animations: ^{
fromView.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
snapshot.frame = endFrame;
snapshot.alpha = 1.0;
}
completion: ^(BOOL finished) {
toView.frame = endFrame;
[container addSubview: toView];
[snapshot removeFromSuperview];
[transitionContext completeTransition:YES];
}];
} else {
snapshot = [fromView snapshotViewAfterScreenUpdates:YES];
snapshot.frame = endFrame;
[container addSubview: snapshot];
[fromView removeFromSuperview];
[UIView animateWithDuration: [self transitionDuration: transitionContext]
delay: 0
usingSpringWithDamping: 500 initialSpringVelocity: 15
options: 0
animations: ^{
toView.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
snapshot.frame = startFrame;
snapshot.alpha = 0.0;
}
completion: ^(BOOL finished) {
[snapshot removeFromSuperview];
toView.userInteractionEnabled = YES;
[transitionContext completeTransition:YES];
}];
}
}
@end
//
// ALYViewController.m
//
#import "ALYViewController.h"
#import "ALYZoomAnimator.h"
NSString *const MySegueID = @"MySegueID";
@interface ALYViewController ()
@property (strong, nonatomic) ALYZoomAnimator *zoomAnimator;
- (IBAction)showModalViewTapped:(id)sender;
@end
@implementation ALYViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if (![segue.identifier isEqualToString: MySegueID]) {
return;
}
if (!self.zoomAnimator) {
self.zoomAnimator = [[ALYZoomAnimator alloc] init];
}
[segue.destinationViewController setTransitioningDelegate: self.zoomAnimator];
[segue.destinationViewController setModalPresentationStyle: UIModalPresentationCustom];
}
- (IBAction)showModalViewTapped:(id)sender {
[self performSegueWithIdentifier: MySegueID sender: nil];
}
@end