iOS: Sliding UIView on/off screen

13,004

After playing around with this a bunch and banging my head against the wall, I finally came to the conclusion that my code wasn't wrong unless I misunderstood something about blocks. Stepping through my code in debug yielded answers exactly like I expected, but between the end of the animation block and the beginning of the completion block, an EXC_BAD_ACCESS kept popping up.

Anyway, I'm not sure where I got the idea, but I figured I might want to try doing my mathematical calculations (for changing the frames) outside of my animation block.

Guess what? IT WORKED!

So, without further ado, here's the working code for what I wanted:

- (IBAction)showHidePressed:(id)sender
{
    switch ([self CurrentState]) {
        case kShown:
        {
            // Hide the panel and change the button's text
            CGRect currLeftPanelRect = [[self LeftPanel] frame];
            currLeftPanelRect.origin.x -= currLeftPanelRect.size.width / 2;
            CGRect currRightPanelRect = [[self RightPanel] frame];
            currRightPanelRect.origin.x = 0;
            currRightPanelRect.size.width += currLeftPanelRect.size.width;
            // 1. Hide the panel
            [UIView animateWithDuration:0.5 
              animations:^{
              // b. Move left panel from (0, 0, w, h) to (-w, 0, w, h)
              [[self LeftPanel] setFrame:currLeftPanelRect];
              // c. Expand right panel from (x, 0, w, h) to (0, 0, w + x, h)
              [[self RightPanel] setFrame:currRightPanelRect];
              }
              completion:^(BOOL finished){ if(finished) {
              [[self ShowHideButton] setTitle:@"Show Panel" forState:UIControlStateNormal];
              [self setCurrentState:kHidden];
              }
              }];
        }            
            break;
        case kHidden:
        {
            // Show the panel and change the button's text
            // 1. Show the panel
            [UIView animateWithDuration:0.5 
              animations:^{
              // b. Move left panel from (-w, 0, w, h) to (0, 0, w, h)
              CGRect currLeftPanelRect = [[self LeftPanel] frame];
              currLeftPanelRect.origin.x += currLeftPanelRect.size.width / 2;
              [[self LeftPanel] setFrame:currLeftPanelRect];
              // c. Expand right panel from (0, 0, w, h) to (leftWidth, 0, w - leftWidth, h)
              CGRect currRightPanelRect = [[self RightPanel] frame];
              currRightPanelRect.origin.x = currLeftPanelRect.size.width;
              currRightPanelRect.size.width -= currLeftPanelRect.size.width;
              [[self RightPanel] setFrame:currRightPanelRect];
              }
              completion:^(BOOL finished){ if(finished) {
              [[self ShowHideButton] setTitle:@"Hide Panel" forState:UIControlStateNormal];
              [self setCurrentState:kShown];
              }
              }];
        }
            break;
        default:
            break;
    }
}
Share:
13,004
mbm29414
Author by

mbm29414

I began working as a professional developer with on-the-job training at a small document management firm in the Atlanta area. I began learning to program for iOS as a side project late in 2009 when iPhone OS 3 was the latest and greatest! In the Summer of 2010, I moved into a new role at a large research medical university hospital, where I was tasked with writing functionality for a SQL Server-based electronic medical record software suite. During this time, I continued writing iOS applications in the medical industry. After working at this job for several years, I decided to strike out on my own. I am now the owner of a software consulting firm specializing in .NET Desktop development and iOS apps. I have professional relationships with several local businesses and two nationally-recognized research medical universities. If you ever find my code posts to be useful, please consider this to my be license (copied wholesale from Sinan Ünür): All original source snippets I post on StackOverflow.com, and other sites in the StackExchange network, are dedicated to the public domain. If you do find value in my answers, I would very much appreciate an attribution and acknowledgement where possible.

Updated on July 19, 2022

Comments

  • mbm29414
    mbm29414 almost 2 years

    I'm working on an app where having a "drawer" on the left-hand side would be very beneficial. I'm doing some initial testing to see how I would best accomplish this, and I'm having some very basic trouble.

    My Setup
    1. I am using a single-view application template in Xcode 4.
    2. To the xib's "main/border" view, I've added 2 UIViews (LeftPanel and RightPanel) and a UIButton (ShowHideButton).
    3. I've colored the LeftPanel green and the RightPanel blue for easier visibility.
    4. When the view is loaded, both panels are visible and the UIButton has the text "Hide Panel".
    5. Upon pressing the button, LeftPanel should slide off the screen (to the left) and RightPanel should expand to take up its original space plus the space vacated by LeftPanel.
    6. At this point, ShowHideButton should change its text to "Show Panel".
    7. Upon pressing the button again, LeftPanel should slide back onto the screen (from the left) and RightPanel should shrink to "give it back" its original space.
    8. At this point, ShowHideButton should change its text back to "Hide Panel".

    I'm implementing the animation using animateWithDuration:animations:completion:. So far, the transition OFF the screen is working fine (very well, actually).

    What's troubling me is that when I then try to bring LeftPanel "back", I'm getting an EXC_BAD_ACCESS. I've posted my code below, and I've looked at it, but I really can't see what I'm accessing that's been released (or whatever is causing the EXC_BAD_ACCESS).

    DrawerTestingViewController.h
    #import <UIKit/UIKit.h>
    
    typedef enum {
        kHidden,
        kShown
    } PanelState;
    
    @interface DrawerTestingViewController : UIViewController {
    
        PanelState   currentState;
    
        UIButton    *showHideButton;
    
        UIView      *leftPanel;
        UIView      *rightPanel;
    }
    
    @property (assign, nonatomic)          PanelState   CurrentState;
    
    @property (strong, nonatomic) IBOutlet UIButton     *ShowHideButton;
    
    @property (strong, nonatomic) IBOutlet UIView       *LeftPanel;
    @property (strong, nonatomic) IBOutlet UIView       *RightPanel;
    
    - (IBAction)showHidePressed:(id)sender;
    
    @end
    


    DrawerTestingViewController.m
    #import "DrawerTestingViewController.h"
    
    @implementation DrawerTestingViewController
    
    @synthesize CurrentState    = currentState;
    @synthesize LeftPanel       = leftPanel;
    @synthesize RightPanel      = rightPanel;
    @synthesize ShowHideButton  = showHideButton;
    
    #pragma mark - My Methods
    
    - (IBAction)showHidePressed:(id)sender
    {
        switch ([self CurrentState]) {
            case kShown:
                // Hide the panel and change the button's text
                // 1. Hide the panel
                [UIView animateWithDuration:0.5 
                    animations:^{
                    // b. Move left panel from (0, 0, w, h) to (-w, 0, w, h)
                    CGRect currLeftPanelRect = [[self LeftPanel] frame];
                    currLeftPanelRect.origin.x = -1 * currLeftPanelRect.size.width;
                    [[self LeftPanel] setFrame:currLeftPanelRect];
                    // c. Expand right panel from (x, 0, w, h) to (0, 0, w + x, h)
                    CGRect currRightPanelRect = [[self RightPanel] frame];
                    currRightPanelRect.origin.x = 0;
                    currRightPanelRect.size.width += currLeftPanelRect.size.width;
                    [[self RightPanel] setFrame:currRightPanelRect];}
                    completion:NULL];
                // 2. Change the button's text
                [[self ShowHideButton] setTitle:@"Show Panel" forState:UIControlStateNormal];
                // 3. Flip [self CurrentState]
                [self setCurrentState:kHidden];
                break;
            case kHidden:
                // Show the panel and change the button's text
                // 1. Show the panel
                [UIView animateWithDuration:0.5 
                    animations:^{
                    // b. Move left panel from (-w, 0, w, h) to (0, 0, w, h)
                    CGRect currLeftPanelRect = [[self LeftPanel] frame];
                    currLeftPanelRect.origin.x = 0;
                    [[self LeftPanel] setFrame:currLeftPanelRect];
                    // c. Expand right panel from (0, 0, w, h) to (leftWidth, 0, w - leftWidth, h)
                    CGRect currRightPanelRect = [[self RightPanel] frame];
                    currRightPanelRect.origin.x = currLeftPanelRect.size.width;
                    currRightPanelRect.size.width -= currLeftPanelRect.size.width;
                    [[self RightPanel] setFrame:currRightPanelRect];}
                    completion:NULL];
                // 2. Change the button's text
                [[self ShowHideButton] setTitle:@"Hide Panel" forState:UIControlStateNormal];
                // 3. Flip [self CurrentState]
                [self setCurrentState:kShown];
                break;
            default:
                break;
        }
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        [self setCurrentState:kShown];
    }
    
    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        switch ([self CurrentState]) {
            case kShown:
                [[self ShowHideButton] setTitle:@"Hide Panel" forState:UIControlStateNormal];
                break;
            case kHidden:
                [[self ShowHideButton] setTitle:@"Show Panel" forState:UIControlStateNormal];
                break;
            default:
                break;
        }
    }
    
    @end
    

    Am I missing something super-basic? Can anybody help?

    Thanks!

    Edit: I've tried 2 more things:
    1. The problem seems to be related to bringing the off-screen view on-screen, as starting with LeftPanel off-screen gives me the same problem.
    2. Stepping through the code reliably causes Xcode (4 Beta for Lion) to crash. Here are the details (same for every crash):

    ASSERTION FAILURE in /SourceCache/DVTFoundation/DVTFoundation-867/Framework/Classes/FilePaths/DVTFilePath.m:373 Details: empty string is not a valid path Object: Method: +_filePathForParent:fileSystemRepresentation:length:allowCreation: Thread: {name = (null), num = 55} Hints: None Backtrace: 0 0x00000001068719a6 -[IDEAssertionHandler handleFailureInMethod:object:fileName:lineNumber:messageFormat:arguments:] (in IDEKit) 1 0x0000000105f3e324 _DVTAssertionFailureHandler (in DVTFoundation) 2 0x0000000105edd16f +[DVTFilePath _filePathForParent:fileSystemRepresentation:length:allowCreation:] (in DVTFoundation) 3 0x0000000105edcd4d +[DVTFilePath _filePathForParent:pathString:] (in DVTFoundation) 4 0x0000000105ede141 +[DVTFilePath filePathForPathString:] (in DVTFoundation) 5 0x00000001064a8dde -[IDEIndex queryProviderForFile:highPriority:] (in IDEFoundation) 6 0x000000010655193b -[IDEIndex(IDEIndexQueries) symbolsMatchingName:inContext:withCurrentFileContentDictionary:] (in IDEFoundation) 7 0x000000010aca6166 __68-[IDESourceCodeEditor symbolsForExpression:inQueue:completionBlock:]_block_invoke_01561 (in IDESourceEditor) 8 0x00007fff93fb490a _dispatch_call_block_and_release (in libdispatch.dylib) 9 0x00007fff93fb615a _dispatch_queue_drain (in libdispatch.dylib) 10 0x00007fff93fb5fb6 _dispatch_queue_invoke (in libdispatch.dylib) 11 0x00007fff93fb57b0 _dispatch_worker_thread2 (in libdispatch.dylib) 12 0x00007fff8bb5e3da _pthread_wqthread (in libsystem_c.dylib) 13 0x00007fff8bb5fb85 start_wqthread (in libsystem_c.dylib)

    Update: Screen Shots
    Panel Shown (startup state) Panel Shown
    Panel Hidden (successful transition after button press) Panel Hidden
    Error: Pressing button again causes failure Error