Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving a modally presented UIViewController up when keyboard appears on iPad with iOS8

I've got a UIViewController inside of a UINavigationController, presented modally on an iPad with iOS8. I'm using

self.myNavController.modalPresentationStyle = UIModalPresentationFormSheet;

Within my view controller, I have a text field. Because of the UI design of the view controller, when the user taps in the text field, and the keyboard appears, I need to move the view controller up on the screen. On iOS7, I had been animating a change to

self.myNavController.view.superview.frame.origin

in order to move the view controller up, and back down. This worked well.

However, on iOS8, this is no longer working. In fact, oddly, the behavior is for the view controller to move slightly down and then back up (all when I'm assigning self.myNavController.view.superview.frame.origin a CGPoint with a smaller valued Y coordinate).

Originally, I was working from a suggestion given in this SO question Resize ModalViewController and position it at center in iOS 7

And I recently tried the suggestion given in Moving dialog up when keyboard present works except when ipad is turned around but to little avail. Actually, with the transform idea in an answer there, I can at least get the view controller to move in the right direction (i.e., up), but it doesn't stay there. It quickly goes back down.

Ideas?

like image 992
Chris Prince Avatar asked Sep 21 '14 08:09

Chris Prince


People also ask

What is UIViewController in iOS?

The UIViewController class defines the shared behavior that's common to all view controllers. You rarely create instances of the UIViewController class directly. Instead, you subclass UIViewController and add the methods and properties needed to manage the view controller's view hierarchy.

What is swift view controller?

A view controller typically manages a single User Interface (UI) or “screen” in your app. It also manages interactions between the UI and the underlying data, better known as models. Let's take a look at the Swift code for a simple view controller: class MainViewController: UIViewController. {

What is parent class of UIViewController?

A UIViewController is an object which manages the view hierarchy of the UIKit application. The UIViewController defines the shared behavior and properties for all types of ViewController that are used in the iOS application. The UIViewController class inherits the UIResponder class.


1 Answers

We'll, I'm pretty stoked now. I've figured this out. This can be solved using the new, iOS8 UIPresentationController. I'll give some sample code below. And try to link in a working project zip file later.

The first view controller is the root view controller of the example app. Code for the ChangeFrameTransitioningDelegate follows below. "Dialog" is just an empty view controller class.

//
//  ViewController.m
//  TestPresentationController
//
//  Created by Christopher Prince on 9/21/14.
//  Copyright (c) 2014 Spastic Muffin, LLC. Use at your own discretion.
//

#import "ViewController.h"
#import "ChangeFrameTransitioningDelegate.h"
#import "Dialog.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void) viewDidAppear:(BOOL)animated;
{
    [super viewDidAppear:animated];

    ChangeFrameTransitioningDelegate *td = [ChangeFrameTransitioningDelegate new];
    td.viewControllerFrame = CGRectMake(100, 0, 100, 100);

    Dialog *d = [Dialog new];
    // Just to make the VC stand out.
    d.view.backgroundColor = [UIColor greenColor];

    // Need to make this custom in order to have the presentationControllerForPresentedViewController method of the transitioning delegate called.
    d.modalPresentationStyle = UIModalPresentationCustom;

    d.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
    d.transitioningDelegate = td;

    [self presentViewController:d animated:YES completion:^{

        // Any time after the view controller is presented you can change the
        // viewControllerFrame property to change the size and/or position of the 
        // presented view controller.
        [UIView animateWithDuration:0.5 animations:^{
            td.viewControllerFrame = CGRectMake(100, 800, 100, 100);
        }];
    }];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
//
//  ChangeFrameTransitioningDelegate.h
//  TestPresentationController
//
//  Created by Christopher Prince on 9/21/14.
//  Copyright (c) 2014 Spastic Muffin, LLC. Use at your own discretion.
//

// For iOS8 and later, use this to change the size/position of a view controller after it has been presented. See TextPresentationController example.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface ChangeFrameTransitioningDelegate : NSObject<UIViewControllerTransitioningDelegate>

// This is the size and position that the PresentationController used by this class will use when its frameOfPresentedViewInContainerView method is called. So, this is the size and position that your view controller will have.
@property (nonatomic) CGRect viewControllerFrame;

@end
//
//  ChangeFrameTransitioningDelegate.m
//  TestPresentationController
//
//  Created by Christopher Prince on 9/21/14.
//  Copyright (c) 2014 Spastic Muffin, LLC. Use at your own discretion.
//

// Some help from http://stackoverflow.com/questions/25903412/editing-bounds-of-uiview-when-presenting-hides-keyboard-in-ios-8
// http://stackoverflow.com/questions/25811199/ios-8-change-the-size-of-presented-modal-view-controller
// Some code from https://developer.apple.com/devcenter/download.action?path=/wwdc_2014/wwdc_2014_sample_code/lookinsidepresentationcontrollersadaptivityandcustomanimatorobjects.zip (though, beware, this example doesn't work straight out of the box).
// And see http://stackoverflow.com/questions/25957343/moving-a-modally-presented-uiviewcontroller-up-when-keyboard-appears-on-ipad-wit

#ifndef SPASLogDetail
#define SPASLogDetail NSLog
#endif

#import "ChangeFrameTransitioningDelegate.h"

@interface PresentationController : UIPresentationController
{
    UIView *_dimmingView;
}
// This is the size and position that the PresentationController will use when its frameOfPresentedViewInContainerView method is called.
@property (nonatomic) CGRect currentFrame;
@end

@implementation PresentationController

- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController;
{
    self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
    // Some random default size.
    self.currentFrame = CGRectMake(0, 0, 100, 100);
    [self prepareDimmingView];
    return self;
}

// 4/22/15; Added, for rotation.
- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator;
{
    CGSize increasedSize = size;
    // 4/22/15; I'm getting some odd effects: In a rotation transition from landscape to portrait, the RHS goes white momentarily, and from portrait to landscape, the bottom goes white momentarily. A hack, but increase the size of the dimming view to get around this.
    increasedSize.width *= 1.5;
    increasedSize.height *= 1.5;
    _dimmingView.frameSize = increasedSize;

    // There appears to be no transitionCoordinator available for this "transition". Therefore using the regular UIView animation, below.
    /*
    if([self.presentedViewController transitionCoordinator])
    {
        [[self.presentedViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
            recenterViewController();
        } completion:nil];
    }
    else
    {
        recenterViewController();
    }*/

    // 4/23/15; See [1]. Given that I've turned off resizing with the autoresizingMask, this has messed up the repositioning of the (x, y) coordinate of the presentedViewController. Make up for that here.
    [UIView animateWithDuration: coordinator.transitionDuration animations:^{
        // I'm assuming the size here refers to the entire window.
        CGPoint center = CGPointMake(size.width/2.0, size.height/2.0);
        self.presentedViewController.view.center = center;
    } completion:nil];
}

- (void)presentationTransitionWillBegin
{
    UIView* containerView = [self containerView];

    // 4/23/15; So, this seems to be related to my modal view resizing problem. When I disable autoresizesSubviews, I stop the resizing during the rotation. HOWEVER, this is at the price of two additional problems: (a) the (x, y) coordinate of the modal stops being changed during the rotation, and (b) the animation of the background (_dimmingView?) starts looking funky during the rotation. WHICH makes sense. The containerView size is the full window.
    //containerView.autoresizesSubviews = NO;

    UIViewController* presentedViewController = [self presentedViewController];

    // [1] 4/23/15; This is better. The _dimmingView animation is preserved. The (x, y) position of the presentedViewController, however isn't right after the rotation.
    presentedViewController.view.autoresizingMask = UIViewAutoresizingNone;

    [_dimmingView setFrame:[containerView bounds]];
    [_dimmingView setAlpha:0.0];

    [containerView insertSubview:_dimmingView atIndex:0];

    void (^setAlpha)() = ^{
        [_dimmingView setAlpha:1.0];
    };

    if([presentedViewController transitionCoordinator])
    {
        [[presentedViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
            setAlpha();
        } completion:nil];
    }
    else
    {
        setAlpha();
    }
}

- (void)dismissalTransitionWillBegin
{
    void (^resetAlpha)() = ^{
        [_dimmingView setAlpha:0.0];
    };

    if([[self presentedViewController] transitionCoordinator])
    {
        [[[self presentedViewController] transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
            resetAlpha();
        } completion:nil];
    }
    else
    {
        resetAlpha();
    }
}

- (CGRect)frameOfPresentedViewInContainerView
{
    SPASLogDetail(@"frameOfPresentedViewInContainerView: %@", NSStringFromCGRect(self.currentFrame));
    return self.currentFrame;
}

- (void) setCurrentFrame:(CGRect)currentFrame
{
    _currentFrame = currentFrame;
    // This is the important part for changing the frame of the presented view controller *after* the view is presented. For some odd reason, its not right to change the frame of the containerView.
    self.presentedView.frame = currentFrame;
}

- (void) containerViewWillLayoutSubviews;
{
    SPASLogDetail(@"containerViewWillLayoutSubviews");
}

- (void)prepareDimmingView
{
    _dimmingView = [[UIView alloc] init];
    [_dimmingView setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.4]];
    [_dimmingView setAlpha:0.0];

    //UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dimmingViewTapped:)];
    //[_dimmingView addGestureRecognizer:tap];
}

/*
- (void)dimmingViewTapped:(UIGestureRecognizer *)gesture
{
    if([gesture state] == UIGestureRecognizerStateRecognized)
    {
        [[self presentingViewController] dismissViewControllerAnimated:YES completion:NULL];
    }
}*/

@end

@interface ChangeFrameTransitioningDelegate()
@property (nonatomic, strong) PresentationController *presentationController;
@end

@implementation ChangeFrameTransitioningDelegate

- (instancetype) init;
{
    self = [super init];
    return self;
}

- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source
{
    self.presentationController = [[PresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
    self.presentationController.currentFrame = self.viewControllerFrame;

    return self.presentationController;
}

- (void) setViewControllerFrame:(CGRect)viewControllerFrame
{
    _viewControllerFrame = viewControllerFrame;
    self.presentationController.currentFrame = viewControllerFrame;
}

@end
like image 199
Chris Prince Avatar answered Oct 02 '22 10:10

Chris Prince