Sunday, October 7, 2012

Arduino Adventures - part II

OK, I am stuck trying to transmit an IR code to turn my air condition on (see previous post here: http://srooltheknife.blogspot.co.il/2012/10/adventures-with-arduino.html). It is getting hot, and I want to turn the damn thing on...

So, next step is trying to debug what's wrong with my IR transmission. I decided that I need to somehow visually see how my signal looks and compare it to the (working) original remote control signal. So decided to hack a little Oscilloscope - hey, I already made a wave printing arduino sketch in the last round. If I can just measure the signal from the IR receiver using Arduino analog in pin, decode it and send it over the serial connection to the Mac, I can display it nicely on the screen. Ummm... interesting... a new sub-project.

First, had to get the serial connection between the mac and arduino working. The power of the internet and the cool people helped. Found this:
http://code.google.com/p/xcode-arduino-serial-communication/
thanks to Pat OKeefe (and in turn to Andreas Mayer's AMSerialPort).

Next was a simple Arduino sketch to measure and send the analog input from pin 1 using the fastest baud rate possible. Quite simple:

// The Arduino code.

#define ANALOG_IN 1

void setup() {
  Serial.begin(115200); // possible values: 9600, 14400, 19200, 28800, 38400, 57600, or 115200
  pinMode(ANALOG_IN, INPUT);
}

void loop() {
  int val = analogRead(ANALOG_IN);
  Serial.write( val & 0xff);
}


Using the Mac code on this github repo [https://github.com/iroth/simplearduinoscope] you select and connect the right serial port, and can see the signal on the screen. If you select the wait for trigger (or click the re-trigger button), the scope will freeze when a signal is detected so you can examine the signal.

I added a copy to ref button, so I can record the original remote signal, display it as a reference and compare to my transmission.
Here is a screen dump of the two signals. I am not sure why my signal is not affecting the air condition, they look quite similar, no?



The top white line is the signal from the original remote control, the bottom is from my IR transmitter.
Note that my little scope has a scale up and down buttons too - this helps. I am not sure how accurate my scope is, but the signals look quite similar. Still not working.

Well, at least I hope the digital scope projects helps someone. It is not very accurate, but can be useful for debugging the IR codes (note that the signal above is the decoded signal from the TSOP382 sensor, not the 38 kHz modulated signal which is way too fast for this scope). It can be useful for simple audio frequency examination (but still at the low range of the audio spectrum).
I will need a much faster processor, and probably a much faster communication link to get the samples fast enough to the computer to really make it a useful scope for a wider range of frequencies.


Saturday, October 6, 2012

Adventures with Arduino

Attending Geekcon last month really energized me to get to do more "physical" kind of things. We built an arduino based drawing robot. An instructable was posted about it here: http://www.instructables.com/id/Artibot-a-portrait-painting-robot/

[Edit: part two of the story is available here: http://srooltheknife.blogspot.co.il/2012/10/arduino-adventures-part-ii.html]

So, I was thinking of a next project, and this idea came up - the weather here is quite hot and humid this time of year, so often I go to sleep with the air condition on. This is very wasteful and in addition, makes my wife environment too cold for her taste (is it true that women are often feeling more cold than men?).
So the idea was to make an arduino talk to the air condition mimicking its remote control, allowing me to turn the air condition for 10minutes, get the room pleasant, and while sleeping make the arduino kick the aircon on for 10 minutes or so every hour to keep the room pleasant (but not freezing cold).

So, got the following items to get going:
1. Arduino Uno board
2. IR sensor - TSOP382 from Vishay (if you are Israeli, this is an excellent source: http://www.4project.co.il/product/757?sectid=79). This is a very cool sensor that filters the IR modulation frequency used by the remote (38kHz) and gives you a clean signal to work with
3. IR led - http://www.4project.co.il/product/868

I started with trying to decode my existing air-con remote control signals. Here is the hardware setup I used for this (very simple... the item on the right is the TSOP382 withits input going to pin 2):

Circuit 1 - IR remote sensor circuit


This was much harder than I expected...
I started with this code:
http://www.instructables.com/id/Clone-a-Remote-with-Arduino/
simply recording the stream of signals
I assume playing this sequence back through the IR diode (modulating to 38 kHz) will work. But I first wanted to really understand the logic behind this sequence.
This proved to be quite difficult to begin with. First step forward was when I found this great library for reading all kind of IR remote protocols made by Ken Shirrif: http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
I was disappointed to find that it does not recognise my air-con remote at all. But it did recognise my Apple TV remote, so it gave me some hope...
I decided I need some way to visualize the remote signal. So I wrote this little sketch:

#include <IRremote.h>

int RECV_PIN = 2;
int BUTTON_PIN = 12;
int STATUS_PIN = 13;

IRrecv irrecv(RECV_PIN);
IRsend irsend;

decode_results results;

void setup()
{
  Serial.begin(9600);
  irrecv.enableIRIn(); // Start the receiver
  pinMode(BUTTON_PIN, INPUT);
  pinMode(STATUS_PIN, OUTPUT);
  Serial.println("Ready to receive");
}

// Storage for the recorded code
int codeType = -1; // The type of code
unsigned long codeValue; // The code value if not raw
unsigned int rawCodes[RAWBUF]; // The durations if raw
int codeLen; // The length of the code
int toggle = 0; // The RC5/6 toggle state

void printCodeWave() {
      char ch;
      for (int i = 0 ; i < codeLen ; i++) {
        if (i % 2) {
          ch = '_';
        }
        else {
          ch = '^';
        }
        int len = ceil(rawCodes[i] / 300.0);
        for (int j = 0 ; j < len ; j++) {
          Serial.print(ch);
        }
      }
      Serial.println(";");
}

void printCodeNumeric() {
      char ch;
      for (int i = 0 ; i < codeLen ; i++) {
        if (i % 2) {
          Serial.print("s");
        }
        else {
          Serial.print("m");
        }
        Serial.print(rawCodes[i], DEC);
        Serial.print(", ");
      }
      Serial.println(";");
}

void printCode() {
      boolean have_half = false;
      boolean half_val = false;
      for (int i = 0 ; i < codeLen ; i++) {
        boolean isHigh = ((i % 2) == 0);
        if (rawCodes[i] > 3400) {         //--------------- sync mark + half bit
          Serial.print("<SYN+>");
          have_half = true;
          half_val = isHigh;
        }
        else if (rawCodes[i] > 2400) {    //--------------- sync mark with no extra
          Serial.print("<SYN>");
          have_half = false;
        }
        else if (rawCodes[i] > 1400) {    //--------------- double len
           if (half_val) {
             Serial.print("0");
           }
           else {
             Serial.print("1");
           }
          have_half = true;
          half_val = isHigh;
        }
        else {
          if (have_half) {                //--------------- last in pair
             have_half = false;
             if (half_val) {
               Serial.print("0");
             }
             else {
               Serial.print("1");
             }
          }
          else { // first in pair
            have_half = true;
            half_val = isHigh;
          }
        }

      } // end loop

      Serial.println(";");
     printCodeNumeric();
     printCodeWave();

}

// Stores the code for later playback
// Most of this code is just logging
void storeCode(decode_results *results) {
  codeType = results->decode_type;
  int count = results->rawlen;

    codeLen = results->rawlen - 1;
    // To store raw codes:
    // Drop first value (gap)
    // Convert from ticks to microseconds
    // Tweak marks shorter, and spaces longer to cancel out IR receiver distortion
    for (int i = 1; i <= codeLen; i++) {
      if (i % 2) {
        // Mark
        rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK - MARK_EXCESS;
      } 
      else {
        // Space
        rawCodes[i - 1] = results->rawbuf[i]*USECPERTICK + MARK_EXCESS;
      }
    }
    codeValue = results->value;

}

void loop() {
  if (irrecv.decode(&results)) {
    digitalWrite(STATUS_PIN, HIGH);
    storeCode(&results);
    printCode();
    irrecv.resume(); // resume receiver
    digitalWrite(STATUS_PIN, LOW);
  }
}


Opening the serial monitor and clicking the temperature down button twice, gave me this:


<SYN><SYN>0001000000010110000000000000000010<SYN><SYN>0001000000010110000000000000000010<SYN><SYN>;
m3000, s3050, m900, s1000, m850, s1100, m850, s2050, m1800, s1100, m850, s1050, m900, s1050, m850, s1050, m850, s1150, m800, s1100, m850, s2000, m1850, s2000, m900, s1100, m1750, s1150, m850, s1000, m900, s1100, m800, s1100, m850, s1050, m850, s1100, m900, s1000, m900, s1100, m800, s1100, m850, s1100, m850, s1050, m850, s1050, m900, s1050, m850, s1050, m850, s1150, m800, s1100, m850, s2050, m1800, s1100, m2800, s3100, m850, s1050, m850, s1100, m850, s2000, m1900, s1000, m900, s1050, m850, s1100, m850, s1100, m800, s1100, m850, s1050, m850, s2100, m1800, s2000, m850, s1100, m1850, s1050, m850, s1100, m800, s1100, m850, s1050, m900, s1100, m850, s1050, m900, s1000, m850, s1100, m850, s1050, m900, s1050, m850, s1100, m850, s1050, m850, s1100, m850, s1100, m800, s1100, m900, s1000, m900, s2000, m1850, s1050, m2900, s3000, m900, ;
^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^^_______^^^____^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^____^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^_______^^^____^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^^____^^^^^^^^^^__________^^^;

<SYN><SYN>0001000000010100000000000000000010<SYN><SYN>0001000000010100000000000000000010<SYN><SYN>;
m3000, s3050, m850, s1050, m850, s1100, m800, s2100, m1800, s1100, m850, s1050, m900, s1000, m850, s1100, m850, s1100, m800, s1150, m850, s2000, m1800, s2100, m1850, s1000, m850, s1100, m850, s1050, m850, s1100, m850, s1100, m800, s1100, m900, s1050, m800, s1100, m850, s1100, m850, s1100, m800, s1100, m850, s1100, m850, s1050, m900, s1000, m850, s1100, m850, s1100, m850, s1050, m850, s2050, m1800, s1150, m2850, s3050, m800, s1100, m850, s1100, m850, s2050, m1800, s1100, m800, s1100, m850, s1100, m800, s1100, m850, s1100, m800, s1100, m950, s1950, m1800, s2050, m1900, s1000, m850, s1050, m850, s1100, m850, s1100, m800, s1100, m850, s1100, m850, s1050, m850, s1100, m850, s1100, m850, s1100, m800, s1100, m800, s1100, m900, s1050, m850, s1050, m850, s1100, m850, s1050, m950, s1000, m850, s2050, m1850, s1050, m2850, s3050, m900, ;
^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^_______^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^_______^^^^^^____^^^^^^^^^^___________^^^____^^^____^^^_______^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^^_______^^^^^^_______^^^^^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^____^^^^____^^^_______^^^^^^^____^^^^^^^^^^___________^^^;


In the output avobe, the first raw (starting with <syn>) shows my attempt at decoding the bits, the second raw is the numeric times of marks and spaces, and the bottom one is a graphical view which helped me a lot (trying to understand from the times the code was impossible).

Seing that bit string and assuming the whole remote control status is transmitted, I was able to decode what each bit (or the important ones) mean.

This is what I was able to make of it:

normal mode:
| p | mmm | ff | 0HV00 | tttt |0000 0000 0000 0000 010 |

I feel mode:
| p | mmm | ff | 1HV11 | tttt |0000 0000 0000 0000 010 |


each character above is a bit where:
p = 1 when the power button is pressed (same message for on and off) ; 0  for any other button
mmm = mode where 001 = cool, 010 = heat, 011 is a drop (not sure what this means), 100 is fan only, 101 is "recycle" symbol (not sure what this means either).
ff = fan speed where 0 is low, 1 is medium, 2 is high and 3 is auto
H and V are the vertical and horizontal sweeping blades state
tttt = Temperature in degrees celsius - 15 (i.e. 0010 = 17, 0110 = 21, etc.)

At this stage I was ready to try and control my air condition. I used a 2n3904 transistor to switch on the IR LED (after finding out that it is a bad idea to drive it directly from the Arduino pin). Here is the hardware setup:

Circuit 2 - IR LED push button transmitter

It has a push button connected to pin 12 (with a 10k resistor to ground), and pin 3 driving with PWM the IR LED via a 10k resistor going to the base of the 2N3904 transistor and an 82 ohm resistor to limit the current to the LED.

Started experimenting with the following sketch which takes a command according to the bit fields I explained above (in this case an on/off command with temperature set at 24 degrees and fan speed at low).

#include <IRremote.h>

int BUTTON_PIN = 12;
int STATUS_PIN = 13;

IRsend irsend;

decode_results results;

void setup()
{
  Serial.begin(9600);
  pinMode(BUTTON_PIN, INPUT);
  pinMode(STATUS_PIN, OUTPUT);
  Serial.println("Ready to send");
}

unsigned int rawCodes[RAWBUF]; // The durations if raw
int codeLen; // The length of the code

unsigned char turnOnOffCmd[34] = {
  1,         // power on
  0, 0, 1,   // cool (010 - heat, 011 - drop, 100 - fan, 101 - circ)
  0, 0,      // fan (00 - low, 01 - med, 10 - high, 11 - auto)
  0,         // i feel off (1 - on)
  0,         // horiz vents movement
  0,         // up-down vents movement
  0, 0,
  1, 0, 0, 1,  // 24 deg = T(C) - 15
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 0, 0, 0,
  0, 1, 0
};

int pulseWidthM = 1000;
int pulseWidthS = 950;

void sendCode(int repeat) {
    codeLen = 0;
    rawCodes[codeLen++] = 2900;  // sync high
    rawCodes[codeLen++] = 2900;  // sync low

    unsigned char lastVal = 0;
    unsigned char lead, trail;
    int leadW, trailW;
    for (int i = 0 ; i < 34 ; i++) {
      unsigned char bit = turnOnOffCmd[i];
      if (bit) {
        lead = 0;
        trail = 1;
        leadW = pulseWidthS;
        trailW = pulseWidthM;
      }
      else {
        lead = 1;
        trail = 0;
        leadW = pulseWidthM;
        trailW = pulseWidthS;
      }
      if (lead == lastVal) {  // add to last valuse and emit short for next
        rawCodes[codeLen-1] += leadW;
        rawCodes[codeLen++] = trailW;
      }
      else {                  // emit two short pulse
        rawCodes[codeLen++] = leadW;
        rawCodes[codeLen++] = trailW;
      }
      lastVal = trail;
    }
    // Assume 38 KHz
    irsend.sendRaw3Times(rawCodes, codeLen);

}

int lastButtonState;

void loop() {
  // If button pressed, send the code.
  int buttonState = digitalRead(BUTTON_PIN);
  if (lastButtonState == HIGH && buttonState == LOW) {
    Serial.println("Released");
  }

  if (buttonState) {
    Serial.println("Pressed, sending");
    digitalWrite(STATUS_PIN, HIGH);
    sendCode(lastButtonState == buttonState);
    digitalWrite(STATUS_PIN, LOW);
    delay(50); // Wait a bit between retransmissions
  } 
  lastButtonState = buttonState;
}


Note that in the code above I am using the IRRemote library of Ken Shirriff, but I am using a method not included with the library - sendRaw3Times. This is part of my attempts to make this thing work. So far it does not... I am stuck here, and I hope that the decoding part was useful enough for you. If any of the readers can help with that - it would be great if you add this to the comments section below. Otherwise, stay tuned to my continuing adventrures... I do not intend to stop here...

OK, made some progress (not there yet, but had lots of fun). see the second part of this here: http://srooltheknife.blogspot.co.il/2012/10/arduino-adventures-part-ii.html


And a message from our advertisers:


Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):
https://www.toptal.com/#engage-honest-computer-engineers-today



Sunday, May 13, 2012

Background loading a Cocos2d Sprite from URL

Based on Steffen Itterheim's excellent article here, I created a CCSprite class to download itself in the background. You create such a sprite giving it a default image:

 LGNetworkSprite *myImage = [LGNetworkSprite spriteWithFile:@"fbDefault.png"];
 myImage.position = ccp(_screenSize.width * 0.75, _screenSize.height * 0.6);
 [self addChild:myImage];

Then, you can set the URL of the image to download, and it will do the magic in the background:
Note: the local file name should be unique if you download different images.

 [myImage loadFromURLString:imgUrl withLocalFileName:myID];


The code itself is quite simple (once you learn Steffen's teachings...):
//
//  LGNetworkSprite.h
//  CatchTheBall
//
//  Created by Israel Roth on 5/12/12.
//  Based on Steffen Iterheim code:
//  http://www.learn-cocos2d.com/2012/02/cocos2d-webcam-viewer-part-2-asynchronous-texture-loading/
//  Copyright 2012 Labgoo LTD. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface LGNetworkSprite : CCSprite {
}

- (void) loadFromServerAddress: (NSString*) serverAddr fileName: (NSString *) fileName;
- (void) loadFromURLString: (NSString *) urlString withLocalFileName: (NSString *) fileName;

@end


And the m file:
//
//  LGNetworkSprite.m
//  CatchTheBall
//
//  Created by Israel Roth on 5/12/12.
//  Based on Steffen Iterheim code:
//  http://www.learn-cocos2d.com/2012/02/cocos2d-webcam-viewer-part-2-asynchronous-texture-loading/
//  Copyright 2012 Labgoo LTD. All rights reserved.
//

#import "LGNetworkSprite.h"
#import "AsyncFileDownloadData.h"

@implementation LGNetworkSprite

-(NSString*) cacheDirectory
{
 NSString* cacheDirectory = nil;
 NSArray* pathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                                 NSUserDomainMask, YES);
 if ([pathArray count] > 0)
 {
  cacheDirectory = [pathArray objectAtIndex:0];
 }
 return cacheDirectory;
}

- (void) loadFromURLString: (NSString *) urlString withLocalFileName: (NSString *) fileName {
 NSString* localFile = [[self cacheDirectory] stringByAppendingPathComponent:fileName];
 AsyncFileDownloadData* afd = [[[AsyncFileDownloadData alloc] init] autorelease];
 afd.url = [NSURL URLWithString:urlString];
 afd.localFile = localFile;
 [self performSelectorInBackground:@selector(downloadFileFromServerInBackground:) withObject:afd];
}

-(void) logError:(NSError*)error {
 if (error) {
  CCLOG(@"%@: %@", error, [error localizedDescription]);
 }
}

-(void) downloadFileFromServerInBackground:(AsyncFileDownloadData*)afd
{
 NSError* error = nil;
 NSData* data = [NSData dataWithContentsOfURL:afd.url options:NSDataReadingMappedIfSafe error:&error];
 [self logError:error];
 
 [data writeToFile:afd.localFile options:NSDataWritingAtomic error:&error];
 [self logError:error];
 
 // wait until done in this case means that the background thread waits for completion of the task
 // manipulating or creating sprites must be done on the main thread
 [self performSelectorOnMainThread:@selector(updateTexturesWithAsyncData:) withObject:afd waitUntilDone:NO];
}

-(void) updateTexturesWithAsyncData:(AsyncFileDownloadData*)afd
{
 [self updateTexturesFromFile:afd.localFile];
}

-(void) updateTexturesFromFile:(NSString*)file
{
 CCTextureCache* texCache = [CCTextureCache sharedTextureCache];
 [texCache addImageAsync:file 
      target:self
       selector:@selector(asyncTextureLoadDidFinish:)];
}

-(void) asyncTextureLoadDidFinish:(CCTexture2D*)texture
{
 CCTextureCache* texCache = [CCTextureCache sharedTextureCache];
 [texCache removeTexture:self.texture];
 CGSize prevSize = [self contentSize];
 self.texture = texture;
 CGSize size = [texture contentSize];
 [self setTextureRect:CGRectMake(0.0f, 0.0f, size.width,size.height)];
 [self setScale:prevSize.width / size.width];
}

@end

Note: Since I am updating the sprite itself, I do not need to keep the sprite tag. Also, I am using the Library/Caches directory to keep the local file. I think it is the right thing to do (in face, Apple will reject your app if you save to the Documents directory files that should not be there unless you mark them specifically).
Thank you Steffen, and to all of you doing cocos2d - go get yourself some help by purchasing Steffen's book here:

Sunday, March 4, 2012

Custom Transition Animation for Modal View Controllers

Needed to implement a custom animation (push from right/left when presenting a modal view controller, and then pushing out to the other direction when dismissing the dialog).
Yosi found this snippet: http://blog.radi.ws/post/5924267283/custom-modal-uiviewcontroller-transitions
by Evadne Wu. This was so cool, and I think I made it a bit more fun by creating a UIViewController category to do that (I was a bit lazy, so I only implemented the Push in/out transition, but this can be easily extended):


//
//  UIViewController+Transitions.h
//  Labgoo Misc
//
//  Created by Israel Roth on 3/4/12.
//  Copyright (c) 2012 Labgoo LTD. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIViewController(Transitions)

- (void) presentModalViewController:(UIViewController *)modalViewController withPushDirection: (NSString *) direction;
- (void) dismissModalViewControllerWithPushDirection:(NSString *) direction;

@end


and the implementation:

//
//  UiViewController+Transitions.m
//  Labgoo Misc
//
//  Created by Israel Roth on 3/4/12.
//  Copyright (c) 2012 Labgoo LTD. All rights reserved.
//

#import "UIViewController+Transitions.h"

@implementation UIViewController(Transitions)

- (void) presentModalViewController:(UIViewController *)modalViewController withPushDirection: (NSString *) direction {

    [CATransaction begin];
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = direction;
    transition.duration = 0.25f;
    transition.fillMode = kCAFillModeForwards;
    transition.removedOnCompletion = YES;
    
    [[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"transition"];        
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
    [CATransaction setCompletionBlock: ^ {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(transition.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
            [[UIApplication sharedApplication] endIgnoringInteractionEvents];        
        });
    }];
    
    [self presentModalViewController:modalViewController animated:NO];
    
    [CATransaction commit];

}

- (void) dismissModalViewControllerWithPushDirection:(NSString *) direction {

    [CATransaction begin];
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = direction;
    transition.duration = 0.25f;
    transition.fillMode = kCAFillModeForwards;
    transition.removedOnCompletion = YES;

    [[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"transition"];        
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
    [CATransaction setCompletionBlock: ^ {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(transition.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
            [[UIApplication sharedApplication] endIgnoringInteractionEvents];        
        });
    }];
    
    [self dismissModalViewControllerAnimated:NO];
    
    [CATransaction commit];
    
}

@end



to use it, you import "UIViewController+Transitions.h" in your file and call the presentModalViewController:withPushDirection:
the direction parameter receives core animation transition sub-types such as kCATransitionFromRight

Enjoy...




And a message from our advertisers:


Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):
https://www.toptal.com/#engage-honest-computer-engineers-today