Skip to content

Commit

Permalink
Moved code from KeyboardStateScroller project
Browse files Browse the repository at this point in the history
  • Loading branch information
fraserscottmorrison committed Jun 8, 2014
1 parent 873cbfe commit deee184
Show file tree
Hide file tree
Showing 33 changed files with 1,547 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# KeyboardStateScroller CHANGELOG

## 0.1.0

Initial release.
36 changes: 36 additions & 0 deletions Classes/IHKeyboardAvoiding.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// IHKeyboardAvoiding.h
// IHKeyboardAvoiding
//
// Created by Fraser Scott-Morrison on 29/03/13.
// Copyright (c) 2013 Idle Hands Apps. All rights reserved.
//

#import <Foundation/Foundation.h>

enum KeyboardAvoidingMode {
KeyboardAvoidingModeMaximum = 0,
KeyboardAvoidingModeMinimum = 1,
KeyboardAvoidingModeMinimumDelayed = 2
}
typedef KeyboardAvoidingMode;

@interface IHKeyboardAvoiding : NSObject

+ (void)setKeyboardAvoidingMode:(KeyboardAvoidingMode)keyboardAvoidingMode;

+ (void)setAvoidingView:(UIView *)avoidingView withTarget:(UIView *)targetView;
+ (void)addTarget:(UIView *)targetView;
+ (void)removeTarget:(UIView *)targetView;
+ (void)removeAll;

// utility method to find out if the keyboard is visible. Works for docked, undocked and split keyboards
+ (BOOL)isKeyboardVisible;

// If the visible keyboard plus the buffer intersect with the targetView, then the avoiding View will be moved. Default buffer is 0 points
+ (void)setBuffer:(int)buffer;

// padding to put between the keyboard and avoiding view. Default padding is 0 points
+ (void)setPadding:(int)padding;

@end
257 changes: 257 additions & 0 deletions Classes/IHKeyboardAvoiding.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
//
// IHKeyboardAvoiding.m
// IHKeyboardAvoiding
//
// Created by Fraser Scott-Morrison on 29/03/13.
// Copyright (c) 2013 Idle Hands Apps. All rights reserved.
//

#import "IHKeyboardAvoiding.h"

@implementation IHKeyboardAvoiding

static NSMutableArray *_targetViews;
static UIView *_avoidingView;
static NSMutableArray *_updatedConstraints;
static NSMutableArray *_updatedConstraintConstants;

static BOOL _isKeyboardVisible;
static BOOL _avoidingViewUsesAutoLayout;
static int _buffer = 0;
static int _padding = 0;
static float _defaultAnimationDuration = 0.3; // If keyboard is not animating, animate the avoiding view anyway
static KeyboardAvoidingMode _keyboardAvoidingMode = KeyboardAvoidingModeMinimum;
static float _minimumAnimationDuration;

+ (void)didChange:(NSNotification *)notification
{
BOOL isKeyBoardShowing = NO; // isKeyBoardShowing and is it merged and docked.
BOOL isPortrait = UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]);

// get the keyboard & window frames
CGRect keyboardFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect windowFrame = [UIApplication sharedApplication].keyWindow.frame;

// if split keyboard is being dragged, then skip notification
if (keyboardFrame.size.height == 0) {
CGRect keyboardBeginFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];

if (isPortrait) {
if (keyboardBeginFrame.origin.y + keyboardBeginFrame.size.height == windowFrame.size.height)
return;
} else {
if (keyboardBeginFrame.origin.x + keyboardBeginFrame.size.width == windowFrame.size.width)
return;
}
}

// calculate if we are to move up the avoiding view
if (isPortrait) {
if (keyboardFrame.origin.y == 0 || (keyboardFrame.origin.y + keyboardFrame.size.height == windowFrame.size.height)) {
isKeyBoardShowing = YES;
}
} else {
if (keyboardFrame.origin.x == 0 || (keyboardFrame.origin.x + keyboardFrame.size.width == windowFrame.size.width)) {
isKeyBoardShowing = YES;
}
}

// get animation duration
float animationDuration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
if (animationDuration == 0) {
animationDuration = _defaultAnimationDuration;
}

if (isKeyBoardShowing) {
for (int i = 0; i < _targetViews.count; i++) {
UIView *targetView = [_targetViews objectAtIndex:i];
//showing and docked
if (targetView) {
float diff = 0;
CGPoint originInWindow = [targetView.superview convertPoint:targetView.frame.origin toView:nil];
switch ([[UIApplication sharedApplication] statusBarOrientation]) {
case UIInterfaceOrientationPortrait:
diff = keyboardFrame.origin.y;
diff -= (originInWindow.y + targetView.frame.size.height);
break;
case UIInterfaceOrientationPortraitUpsideDown:
diff = windowFrame.size.height - keyboardFrame.size.height;
originInWindow.y = windowFrame.size.height - originInWindow.y;
diff -= (originInWindow.y + targetView.frame.size.height);
break;
case UIInterfaceOrientationLandscapeLeft:
diff = keyboardFrame.origin.x;
diff -= (originInWindow.x + targetView.frame.size.height);
break;
case UIInterfaceOrientationLandscapeRight:
diff = windowFrame.size.width - keyboardFrame.size.width;
originInWindow.x = windowFrame.size.width - originInWindow.x;
diff -= (originInWindow.x + targetView.frame.size.height);
default:
break;
}


if (diff < _buffer) {

float displacement = ( isPortrait ? -keyboardFrame.size.height : -keyboardFrame.size.width);
float delay = 0;

switch (_keyboardAvoidingMode) {
case KeyboardAvoidingModeMaximum:
{
_minimumAnimationDuration = animationDuration;
break;
}
case KeyboardAvoidingModeMinimumDelayed:
{
float minimumDisplacement = fmaxf(displacement, diff);
_minimumAnimationDuration = animationDuration * (minimumDisplacement / displacement);
displacement = minimumDisplacement - _padding;
delay = (animationDuration - _minimumAnimationDuration);
animationDuration = _minimumAnimationDuration;
break;
}
case KeyboardAvoidingModeMinimum:
default:
{
float minimumDisplacement = fmaxf(displacement, diff);
displacement = minimumDisplacement - _padding;
break;
}
}

if (_avoidingViewUsesAutoLayout) { // if view uses constrains
for (NSLayoutConstraint *constraint in _avoidingView.superview.constraints) {
if (constraint.secondItem == _avoidingView && (constraint.secondAttribute == NSLayoutAttributeCenterY || constraint.secondAttribute == NSLayoutAttributeTop || constraint.secondAttribute == NSLayoutAttributeBottom)) {
[_updatedConstraints addObject:constraint];
[_updatedConstraintConstants addObject:[NSNumber numberWithFloat:constraint.constant]];
constraint.constant -= displacement;
break;
}
}
[_avoidingView.superview setNeedsUpdateConstraints];
}

[UIView animateWithDuration:animationDuration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
if (_avoidingViewUsesAutoLayout) {
[_avoidingView.superview layoutIfNeeded]; // to animate constraint changes
}
else {
_avoidingView.transform = CGAffineTransformMakeTranslation(0, displacement);
}
}
completion:nil];

}
}
}

}
else if (_isKeyboardVisible) {
// hiding, undocking or splitting

switch (_keyboardAvoidingMode) {
case KeyboardAvoidingModeMaximum:
{

break;
}
case KeyboardAvoidingModeMinimumDelayed:
{
animationDuration = _minimumAnimationDuration;
break;
}
case KeyboardAvoidingModeMinimum:
default:
{
break;
}
}

// restore state
if (_avoidingViewUsesAutoLayout) { // if view uses constrains
for (int i = 0; i < _updatedConstraints.count; i++) {
NSLayoutConstraint *updatedConstraint = [_updatedConstraints objectAtIndex:i];
float updatedConstraintConstant = [[_updatedConstraintConstants objectAtIndex:i] floatValue];
updatedConstraint.constant = updatedConstraintConstant;

}
[_avoidingView setNeedsUpdateConstraints]; // to animate constraint changes
}

[UIView animateWithDuration:animationDuration + 0.075
delay:0
options:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] integerValue]
animations:^{
if (_avoidingViewUsesAutoLayout) {
[_avoidingView.superview layoutIfNeeded];
}
else {
_avoidingView.transform = CGAffineTransformIdentity;
}
} completion:^(BOOL finished){
[_updatedConstraints removeAllObjects];
[_updatedConstraintConstants removeAllObjects];
}];
}

_isKeyboardVisible = CGRectContainsRect(windowFrame, keyboardFrame);
}

+ (void)setAvoidingView:(UIView *)avoidingView withTarget:(UIView *)targetView;
{
[self init];

[_targetViews removeAllObjects];
[_targetViews addObject:targetView];
_avoidingView = avoidingView;
_avoidingViewUsesAutoLayout = _avoidingView.superview.constraints.count > 0;
}

+ (void)addTarget:(UIView *)targetView;
{
[_targetViews addObject:targetView];
}

+ (void)removeTarget:(UIView *)targetView;
{
[_targetViews removeObject:targetView];
}

+ (void)removeAll {
[_targetViews removeAllObjects];
_avoidingView = nil;
}

+ (BOOL)isKeyboardVisible {
return _isKeyboardVisible;
}

+ (void)setBuffer:(int)buffer {
_buffer = buffer;
}

+ (void)setPadding:(int)padding {
_padding = padding;
}

+ (void)setKeyboardAvoidingMode:(KeyboardAvoidingMode)keyboardAvoidingMode {
_keyboardAvoidingMode = keyboardAvoidingMode;
}

+ (void)init {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// make sure we only add this once
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
_targetViews = [[NSMutableArray alloc] init];
_updatedConstraints = [[NSMutableArray alloc] init];
_updatedConstraintConstants = [[NSMutableArray alloc] init];
});
}

@end
Binary file added Example/Default-568h@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Example/Default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Example/Default@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions Example/IHAppDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// IHAppDelegate.h
// IHKeyboardAvoiding
//
// Created by Fraser Scott-Morrison on 18/04/13.
// Copyright (c) 2013 Idle Hands Apps. All rights reserved.
//

#import <UIKit/UIKit.h>

@class IHViewController;

@interface IHAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) IHViewController *viewController;

@end
56 changes: 56 additions & 0 deletions Example/IHAppDelegate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// IHAppDelegate.m
// IHKeyboardAvoiding
//
// Created by Fraser Scott-Morrison on 18/04/13.
// Copyright (c) 2013 Idle Hands Apps. All rights reserved.
//

#import "IHAppDelegate.h"

#import "IHViewController.h"

@implementation IHAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[IHViewController alloc] initWithNibName:@"IHViewController_iPhone" bundle:nil];
} else {
self.viewController = [[IHViewController alloc] initWithNibName:@"IHViewController_iPad" bundle:nil];
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end
Loading

0 comments on commit deee184

Please sign in to comment.