Swiping and Sliding Multi-view Framework

It’s been a few months since I’ve posted, and I wanted to make up for it. I’ve built upon my previous Multi-view Framework, in order to allow navigating to different views via swiping. This project was built for the iPhone, although it works just as well (and perhaps better) on the iPad.

The following diagram shows how three views are loaded and navigated.

initial set up of three views with swiping directions

The first view (“1″) appears first. If you swipe your finger to the left, it will reveal the view immediately to the right (“2″). If you then go back to the first view, you can swipe up to reveal the view on the bottom (“3″).

(You can also try swiping opposite from where there are views, to see how it snaps back to the current view).

The code is very similar to the Multiview Framework posted before, so I’m only going to highlight the main View Controller. The only other major change is that each of the separate views are subclasses of UIView, not UIViewController.

MultiSlideViewController.h

#import 
 
#define SWIPE_DIRECTION_HORIZONTAL 1
#define SWIPE_DIRECTION_VERTICAL 2
 
#define SWIPE_TO_THE_LEFT 3
#define SWIPE_TO_THE_RIGHT 4
#define SWIPE_TO_THE_UP 1
#define SWIPE_TO_THE_DOWN 2
 
@interface MultiSlideViewController : UIViewController {
    UIView  *viewCurrent, *viewLeft, *viewRight, *viewUp, *viewDown;
    
    int intCurrentView, intLastView;
    int intSwipeDirection;
    
    BOOL isSwiping;
    CGFloat floatSwipeStartX, floatSwipeStartY;
    
    int intCurrentUp, intCurrentDown, intCurrentLeft, intCurrentRight;
    int intNewDirection;
}
 
- (void) displayView:(int)intNewView;
 
@end

And this is where the magic happens:

MultiSlideViewController.m

#import "MultiSlideViewController.h"
 
#import "View_01.h"
#import "View_02.h"
#import "View_03.h"
 
@implementation MultiSlideViewController
 
// *****************************
// Set up views below
 
- (UIView *) loadView:(int) intIndex {
    
    UIView *result = nil;
    
    switch (intIndex) {
        case 1:
            result = [[View_01 alloc] init];
            break;
        case 2:
            result = [[View_02 alloc] init];
            break;
        case 3:
            result = [[View_03 alloc] init];
            break;
        default:
            result = nil;
    }
    
    return result;
    
}
 
// Set up destinations (where to go when a view is swiped) below
 
- (void) getSurroundingViewValues:(int) intIndex {
    switch (intIndex) {
        case 1:
            intCurrentUp = 0;
            intCurrentDown = 3;
            intCurrentLeft = 0;
            intCurrentRight = 2;
            break;
        case 2:
            intCurrentUp = 0;
            intCurrentDown = 0;
            intCurrentLeft = 1;
            intCurrentRight = 0;
            break;
        case 3:
            intCurrentUp = 1;
            intCurrentDown = 0;
            intCurrentLeft = 0;
            intCurrentRight = 0;
            break;
        default:
            intCurrentUp = intCurrentDown = intCurrentLeft = intCurrentRight = 0;
    }
}
// *****************************
 
- (CGSize) setContentSize {
    CGSize contentSize = self.view.frame.size;
    UIDeviceOrientation myOrientation = [[UIDevice currentDevice] orientation];
    if (myOrientation == UIDeviceOrientationLandscapeLeft || myOrientation == UIDeviceOrientationLandscapeRight) {
        float newHeight = contentSize.width;
        contentSize.width = contentSize.height;
        contentSize.height = newHeight;
    }
    return (contentSize);
}
 
- (void) setContentFrames {
    CGSize contentSize = [self setContentSize];
    
    viewUp.frame = CGRectMake(0.0f, -contentSize.height, contentSize.width, contentSize.height);
    viewLeft.frame = CGRectMake(-contentSize.width, 0.0f, contentSize.width, contentSize.height);
    viewCurrent.frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height);
    viewRight.frame = CGRectMake(contentSize.width, 0.0f, contentSize.width, contentSize.height);
    viewDown.frame = CGRectMake(0.0f, contentSize.height, contentSize.width, contentSize.height);
}
 
- (void) loadViewUp {
    NSLog (@"up   : %i", intCurrentUp);
    if (viewUp) {
        [viewUp removeFromSuperview];
        [viewUp release];
    }
    viewUp = [self loadView:intCurrentUp];
    [self.view addSubview:viewUp];
}
 
- (void) loadViewDown {
    NSLog (@"down : %i", intCurrentDown);
    if (viewDown) {
        [viewDown removeFromSuperview];
        [viewDown release];
    }
    viewDown = [self loadView:intCurrentDown];
    [self.view addSubview:viewDown];
}
 
- (void) loadViewLeft {
    NSLog (@"left : %i", intCurrentLeft);
    if (viewLeft) {
        [viewLeft removeFromSuperview];
        [viewLeft release];
    }
    viewLeft = [self loadView:intCurrentLeft];
    [self.view addSubview:viewLeft];
}
 
- (void) loadViewRight {
    NSLog (@"right: %i", intCurrentRight);
    if (viewRight) {
        [viewRight removeFromSuperview];
        [viewRight release];
    }
    viewRight = [self loadView:intCurrentRight];
    [self.view addSubview:viewRight];
}
 
- (void) displayView:(int)intNewView {
    
    if (viewCurrent) {
        [viewCurrent removeFromSuperview];
        [viewCurrent release];
    }
    
    NSLog (@"Current View: %i", intNewView);
    
    viewCurrent = [self loadView:intNewView];
    [self getSurroundingViewValues:intNewView];
    [self.view addSubview:viewCurrent];
    
    [self loadViewUp];
    [self loadViewDown];
    [self loadViewLeft];
    [self loadViewRight];
    
    [self setContentFrames];
    
}
 
 
- (void) updateViews {
    
    // set the new views
    if (intCurrentView > 0 && intCurrentView != intLastView) {
        
        switch (intNewDirection) {
            case SWIPE_TO_THE_RIGHT:
                NSLog (@"SWIPE TO THE RIGHT");
                [viewRight removeFromSuperview];
                [viewRight release];
                viewRight = viewCurrent;
                viewCurrent = viewLeft;
                viewLeft = nil;
                [self getSurroundingViewValues:intCurrentView];
                [self loadViewLeft];
                [self loadViewUp];
                [self loadViewDown];
                viewLeft.hidden = YES;
                viewUp.hidden = YES;
                viewDown.hidden = YES;
                
                break;
            case SWIPE_TO_THE_LEFT:
                NSLog (@"SWIPE TO THE LEFT");
                [viewLeft removeFromSuperview];
                [viewLeft release];
                viewLeft = viewCurrent;
                viewCurrent = viewRight;
                viewRight = nil;
                [self getSurroundingViewValues:intCurrentView];
                [self loadViewRight];
                [self loadViewUp];
                [self loadViewDown];
                viewRight.hidden = YES;
                viewUp.hidden = YES;
                viewDown.hidden = YES;
                
                break;
            case SWIPE_TO_THE_UP:
                NSLog (@"SWIPE TO THE UP");
                [viewDown removeFromSuperview];
                [viewDown release];
                viewDown = viewCurrent;
                viewCurrent = viewUp;
                viewUp = nil;
                [self getSurroundingViewValues:intCurrentView];
                [self loadViewUp];
                [self loadViewLeft];
                [self loadViewRight];
                viewUp.hidden = YES;
                viewLeft.hidden = YES;
                viewRight.hidden = YES;
                break;
            case SWIPE_TO_THE_DOWN:
                NSLog (@"SWIPE TO THE DOWN");
                [viewUp removeFromSuperview];
                [viewUp release];
                viewUp = viewCurrent;
                viewCurrent = viewDown;
                viewDown = nil;
                [self getSurroundingViewValues:intCurrentView];
                [self loadViewDown];
                [self loadViewLeft];
                [self loadViewRight];
                viewDown.hidden = YES;
                viewLeft.hidden = YES;
                viewRight.hidden = YES;
                break;
        }
        
    }
    
    viewCurrent.hidden = NO;
    
    intLastView = intCurrentView;
    
    [UIView beginAnimations:@"swipe" context:NULL];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [UIView setAnimationDuration:0.3f];
    
    [self setContentFrames];
    
    [UIView commitAnimations];
    
}
 
 
- (void)layoutSubviews {
    if (isSwiping)
        return;
    
    [self setContentFrames];
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if ([touches count] != 1)
        return;
    
    floatSwipeStartX = [[touches anyObject] locationInView:self.view].x;
    floatSwipeStartY = [[touches anyObject] locationInView:self.view].y;
    
    intSwipeDirection = 0;
    
    isSwiping = YES;
    
    viewUp.hidden = NO;
    viewLeft.hidden = NO;
    viewCurrent.hidden = NO;
    viewRight.hidden = NO;
    viewDown.hidden = NO;
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if (! isSwiping || [touches count] != 1)
        return;
    
    CGFloat swipeDistanceX = [[touches anyObject] locationInView:self.view].x - floatSwipeStartX;
    CGFloat swipeDistanceY = [[touches anyObject] locationInView:self.view].y - floatSwipeStartY;
    
    CGSize contentSize = [self setContentSize];
    
    if (! intSwipeDirection) {
        
        if (abs(swipeDistanceX) > abs(swipeDistanceY)) { // swipe left or right
            intSwipeDirection = SWIPE_DIRECTION_HORIZONTAL;
        } else {
            intSwipeDirection = SWIPE_DIRECTION_VERTICAL;
        }
        
    }
    
    if (intSwipeDirection == SWIPE_DIRECTION_HORIZONTAL) {
        viewLeft.frame = CGRectMake(swipeDistanceX - contentSize.width, 0.0f, contentSize.width, contentSize.height);
        viewCurrent.frame = CGRectMake(swipeDistanceX, 0.0f, contentSize.width, contentSize.height);
        viewRight.frame = CGRectMake(swipeDistanceX + contentSize.width, 0.0f, contentSize.width, contentSize.height);
    } else {
        viewUp.frame = CGRectMake(0.0f, swipeDistanceY - contentSize.height, contentSize.width, contentSize.height);
        viewCurrent.frame = CGRectMake(0.0f, swipeDistanceY, contentSize.width, contentSize.height);
        viewDown.frame = CGRectMake(0.0f, swipeDistanceY + contentSize.height, contentSize.width, contentSize.height);
    }
    
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    
    CGFloat swipeDistanceX = [[touches anyObject] locationInView:self.view].x - floatSwipeStartX;
    CGFloat swipeDistanceY = [[touches anyObject] locationInView:self.view].y - floatSwipeStartY;
    
    if (! isSwiping || (swipeDistanceX == 0 && swipeDistanceY == 0)) {
        
        [self updateViews];
        
        return;
    }
    
    if (intSwipeDirection == SWIPE_DIRECTION_HORIZONTAL) {
        
        if (swipeDistanceX > 50.0f) {
            intNewDirection = SWIPE_TO_THE_RIGHT;
            intCurrentView = intCurrentLeft;
        } else if (swipeDistanceX < -50.0f) {
            intNewDirection = SWIPE_TO_THE_LEFT;
            intCurrentView = intCurrentRight;
        }
        
    } else { // vertical
        
        
        if (swipeDistanceY > 50.0f) {
            intNewDirection = SWIPE_TO_THE_UP;
            intCurrentView = intCurrentUp;
        } else if (swipeDistanceY < -50.0f) {
            intNewDirection = SWIPE_TO_THE_DOWN;
            intCurrentView = intCurrentDown;
        }
        
    }
    
    [self updateViews];
    
    isSwiping = NO;
}
 
 
- (void)viewDidLoad {
    
    self.view.backgroundColor = [UIColor blackColor];
    
    viewUp = nil;
    viewLeft = nil;
    viewCurrent = nil;
    viewRight = nil;
    viewDown = nil;
    
    intCurrentView = 1;
    intLastView = 1;
    
    [self displayView:intCurrentView];
    
    [super viewDidLoad];
}
 
// Override to disallow orientations -- right now, it autorotates the views
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    [self performSelector:@selector(setContentFrames) withObject:nil afterDelay:0.1f];
    return YES;
}
 
- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}
 
- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}
 
 
- (void)dealloc {
    if (viewCurrent)
        [viewCurrent release];
    if (viewLeft)
        [viewLeft release];
    if (viewRight)
        [viewRight release];
    if (viewUp)
        [viewUp release];
    if (viewDown)
        [viewDown release];
    [super dealloc];
}
 
@end

The main code to be modified is the loadView: and the getSurroundingViewValues: functions.

The loadView function requires an index number for each view, and will load the appropriate view when it's called. (I wasn't sure of a better way to abstract this.)

The getSurroundingViewValues function returns the views that surround the view numbered by intIndex. For example, when View 1 is the current view, there is nothing if you reveal the top view (0), it goes to view 3 if you go down, there is nothing to the left (0), and it goes to view 2 to the right.

The reason I did it this way -- there may be instances where you want to swipe up to reveal a view, but swiping down would reveal a different view (instead of just going back). It might be counter-intuitive, but I can imagine some instances (such as a game) where this might be fun...

To show how this will work in practice, consider the following path:

set up of four views with swiping directions

To make View 3 reveal a view to the right (in this case, a copy of View_01), we would create a fourth view in the loadView function:

case 4:
            result = [[View_01 alloc] init];
            break;

and then modify getSurroundingViewValues:

case 3:
            intCurrentUp = 1;
            intCurrentDown = 0;
            intCurrentLeft = 0;
            intCurrentRight = 4;
            break;
        case 4:
            intCurrentUp = 0;
            intCurrentDown = 0;
            intCurrentLeft = 3;
            intCurrentRight = 0;
            break;

Then, if you decided to make it a round-robin (and have a circular path), you could simply modify getSurroundViewValues like so:

set up of four views with swiping directions in circular path

case 2:
            intCurrentUp = 0;
            intCurrentDown = 4;
            intCurrentLeft = 1;
            intCurrentRight = 0;
            break;
 
        case 4:
            intCurrentUp = 2;
            intCurrentDown = 0;
            intCurrentLeft = 3;
            intCurrentRight = 0;
            break;

You can download the multi-swipe iphone project here.

Please post comments if you use this in any published apps -- thank you!

jeffrey

About jeffrey

Jeffrey Berthiaume is a multimedia developer and internet architect who has designed and built award-winning websites, kiosks, and content management systems. He bridges the gap between creative and technology with an ability to balance the needs of designers and marketing with the capabilities of existing technology.
This entry was posted in iOS, Tutorials. Bookmark the permalink.

8 Responses to Swiping and Sliding Multi-view Framework

  1. Steve says:

    Thanks for sharing your architecture. By the way, the media links are currently all broken – no images and no zip file download.

  2. jeffrey jeffrey says:

    Thank you — I fixed this. Just migrated everything to WordPress this weekend…still a few things to sort out. :-)

  3. Ryan says:

    Wonderful template for multimedia “books” and such. Looking at it now and am curious if there’s an easy way to restrict all swipes to either horizontal or vertical motion (so that a horizontal-only or vertical-only sequence doesn’t have orthogonal bounce back).

  4. Mil says:

    Hello,

    im trying to load sound file with each view
    using this code

    NSString *soundpath = [[NSBundle mainBundle] pathForResource:@"sound" ofType:@"m4a"];
    theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:harfpath] error:NULL];
    [playAudio play];

    the problem when im in view 01 it is play view 01 + view 02 sound, when i swipe to view 02 it is play sound of view 03 .

    if im back from view 03 to view 02 it is play view 02 sound

    how i can solve this.

  5. ab says:

    I like pickles.

  6. ab says:

    I copied and pasted your code into XCODE and it doesnt work.
    Then I emailed it to myself on my iPhone. And it still doesnt work.

  7. ephix says:

    Hi, I’m wondering if this can be modified so that after swiping, the bottom half of view 1 and the top half of view 3 are both shown. So in effect the swipe only goes half way. is that possible?

    cheers

  8. Pingback: iOS Mashup of the Best Links and Tutorials - Max "Maximus" Palma Blog

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>