iphone storyboard
Change-Id: I08ffe80fc228997b8a6618248116b52506100a79
This commit is contained in:
Binary file not shown.
BIN
ios/iosremote/iosremote/.DS_Store
vendored
BIN
ios/iosremote/iosremote/.DS_Store
vendored
Binary file not shown.
@@ -1,64 +0,0 @@
|
|||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import "MyListController.h"
|
|
||||||
|
|
||||||
@class Book;
|
|
||||||
@class EditableTableViewCell;
|
|
||||||
|
|
||||||
// Constants representing the book's fields.
|
|
||||||
//
|
|
||||||
enum {
|
|
||||||
BookTitle,
|
|
||||||
BookLastName,
|
|
||||||
BookFirstName,
|
|
||||||
BookYear,
|
|
||||||
BookImagePath
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constants representing the various sections of our grouped table view.
|
|
||||||
//
|
|
||||||
enum {
|
|
||||||
TitleSection,
|
|
||||||
AuthorSection,
|
|
||||||
YearSection,
|
|
||||||
ImageSection
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef NSUInteger BookAttribute;
|
|
||||||
|
|
||||||
@interface MyDetailController : UITableViewController <UITextFieldDelegate>
|
|
||||||
{
|
|
||||||
Book *_book;
|
|
||||||
MyListController *_listController;
|
|
||||||
EditableDetailCell *_titleCell;
|
|
||||||
EditableDetailCell *_firstNameCell;
|
|
||||||
EditableDetailCell *_lastNameCell;
|
|
||||||
EditableDetailCell *_yearCell;
|
|
||||||
EditableDetailCell *_imagePathCell;
|
|
||||||
}
|
|
||||||
|
|
||||||
@property (nonatomic, retain) Book *book;
|
|
||||||
|
|
||||||
// Watch out for retain cycles (objects that retain each other can never
|
|
||||||
// be deallocated). If the list controller were to retain the detail controller,
|
|
||||||
// we should change 'retain' to 'assign' below to avoid a cycle. (Note that in
|
|
||||||
// that case, dealloc shouldn't release the list controller.)
|
|
||||||
//
|
|
||||||
@property (nonatomic, retain) MyListController *listController;
|
|
||||||
|
|
||||||
@property (nonatomic, retain) EditableDetailCell *titleCell;
|
|
||||||
@property (nonatomic, retain) EditableDetailCell *firstNameCell;
|
|
||||||
@property (nonatomic, retain) EditableDetailCell *lastNameCell;
|
|
||||||
@property (nonatomic, retain) EditableDetailCell *yearCell;
|
|
||||||
@property (nonatomic, retain) EditableDetailCell *imagePathCell;
|
|
||||||
|
|
||||||
- (BOOL)isModal;
|
|
||||||
|
|
||||||
- (EditableDetailCell *)newDetailCellWithTag:(NSInteger)tag;
|
|
||||||
|
|
||||||
// Action Methods
|
|
||||||
//
|
|
||||||
- (void)save;
|
|
||||||
- (void)cancel;
|
|
||||||
|
|
||||||
@end
|
|
@@ -1,14 +0,0 @@
|
|||||||
//
|
|
||||||
// TPKeyboardAvoidingScrollView.h
|
|
||||||
//
|
|
||||||
// Created by Michael Tyson on 11/04/2011.
|
|
||||||
// Copyright 2011 A Tasty Pixel. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
@interface TPKeyboardAvoidingScrollView : UIScrollView
|
|
||||||
@property (assign, nonatomic) BOOL autoAdjustsContentSizeToBounds;
|
|
||||||
- (BOOL)focusNextTextField;
|
|
||||||
- (void)scrollToActiveTextField;
|
|
||||||
@end
|
|
@@ -1,285 +0,0 @@
|
|||||||
//
|
|
||||||
// TPKeyboardAvoidingScrollView.m
|
|
||||||
//
|
|
||||||
// Created by Michael Tyson on 11/04/2011.
|
|
||||||
// Copyright 2011 A Tasty Pixel. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "TPKeyboardAvoidingScrollView.h"
|
|
||||||
|
|
||||||
#define _UIKeyboardFrameEndUserInfoKey (&UIKeyboardFrameEndUserInfoKey != NULL ? UIKeyboardFrameEndUserInfoKey : @"UIKeyboardBoundsUserInfoKey")
|
|
||||||
|
|
||||||
@interface TPKeyboardAvoidingScrollView () <UITextFieldDelegate, UITextViewDelegate> {
|
|
||||||
UIEdgeInsets _priorInset;
|
|
||||||
BOOL _priorInsetSaved;
|
|
||||||
BOOL _keyboardVisible;
|
|
||||||
CGRect _keyboardRect;
|
|
||||||
CGSize _originalContentSize;
|
|
||||||
}
|
|
||||||
- (UIView*)findFirstResponderBeneathView:(UIView*)view;
|
|
||||||
- (UIEdgeInsets)contentInsetForKeyboard;
|
|
||||||
- (CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space;
|
|
||||||
- (CGRect)keyboardRect;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation TPKeyboardAvoidingScrollView
|
|
||||||
|
|
||||||
#pragma mark - Setup/Teardown
|
|
||||||
|
|
||||||
- (void)setup {
|
|
||||||
_priorInsetSaved = NO;
|
|
||||||
if ( CGSizeEqualToSize(self.contentSize, CGSizeZero) ) {
|
|
||||||
self.contentSize = self.bounds.size;
|
|
||||||
}
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(id)initWithFrame:(CGRect)frame {
|
|
||||||
if ( !(self = [super initWithFrame:frame]) ) return nil;
|
|
||||||
[self setup];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)awakeFromNib {
|
|
||||||
[self setup];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)dealloc {
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
[super dealloc];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)setFrame:(CGRect)frame {
|
|
||||||
[super setFrame:frame];
|
|
||||||
|
|
||||||
[self handleContentSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)setContentSize:(CGSize)contentSize {
|
|
||||||
_originalContentSize = contentSize;
|
|
||||||
|
|
||||||
[self handleContentSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)setAutoAdjustsContentSizeToBounds:(BOOL)autoAdjustsContentSizeToBounds {
|
|
||||||
_autoAdjustsContentSizeToBounds = autoAdjustsContentSizeToBounds;
|
|
||||||
|
|
||||||
[self handleContentSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Responders, events
|
|
||||||
|
|
||||||
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
|
|
||||||
[[self findFirstResponderBeneathView:self] resignFirstResponder];
|
|
||||||
[super touchesEnded:touches withEvent:event];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)keyboardWillShow:(NSNotification*)notification {
|
|
||||||
_keyboardRect = [[[notification userInfo] objectForKey:_UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
|
||||||
_keyboardVisible = YES;
|
|
||||||
|
|
||||||
UIView *firstResponder = [self findFirstResponderBeneathView:self];
|
|
||||||
if ( !firstResponder ) {
|
|
||||||
// No child view is the first responder - nothing to do here
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_priorInsetSaved) {
|
|
||||||
_priorInset = self.contentInset;
|
|
||||||
_priorInsetSaved = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shrink view's inset by the keyboard's height, and scroll to show the text field/view being edited
|
|
||||||
[UIView beginAnimations:nil context:NULL];
|
|
||||||
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
|
|
||||||
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]];
|
|
||||||
|
|
||||||
self.contentInset = [self contentInsetForKeyboard];
|
|
||||||
[self setContentOffset:CGPointMake(self.contentOffset.x,
|
|
||||||
[self idealOffsetForView:firstResponder withSpace:[self keyboardRect].origin.y - self.bounds.origin.y])
|
|
||||||
animated:YES];
|
|
||||||
[self setScrollIndicatorInsets:self.contentInset];
|
|
||||||
|
|
||||||
[UIView commitAnimations];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)keyboardWillHide:(NSNotification*)notification {
|
|
||||||
_keyboardRect = CGRectZero;
|
|
||||||
_keyboardVisible = NO;
|
|
||||||
|
|
||||||
// Restore dimensions to prior size
|
|
||||||
[UIView beginAnimations:nil context:NULL];
|
|
||||||
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
|
|
||||||
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]];
|
|
||||||
self.contentInset = _priorInset;
|
|
||||||
[self setScrollIndicatorInsets:self.contentInset];
|
|
||||||
_priorInsetSaved = NO;
|
|
||||||
[UIView commitAnimations];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
|
|
||||||
if ( ![self focusNextTextField] ) {
|
|
||||||
[textField resignFirstResponder];
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)textFieldDidBeginEditing:(UITextField *)textField {
|
|
||||||
[self scrollToActiveTextField];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)textViewDidBeginEditing:(UITextView *)textView {
|
|
||||||
[self scrollToActiveTextField];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)layoutSubviews {
|
|
||||||
[super layoutSubviews];
|
|
||||||
[self initializeViewsBeneathView:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Utilities
|
|
||||||
|
|
||||||
- (BOOL)focusNextTextField {
|
|
||||||
UIView *firstResponder = [self findFirstResponderBeneathView:self];
|
|
||||||
if ( !firstResponder ) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGFloat minY = CGFLOAT_MAX;
|
|
||||||
UIView *view = nil;
|
|
||||||
[self findTextFieldAfterTextField:firstResponder beneathView:self minY:&minY foundView:&view];
|
|
||||||
|
|
||||||
if ( view ) {
|
|
||||||
[view becomeFirstResponder];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)scrollToActiveTextField {
|
|
||||||
if ( !_keyboardVisible ) return;
|
|
||||||
|
|
||||||
CGFloat visibleSpace = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom;
|
|
||||||
|
|
||||||
CGPoint idealOffset = CGPointMake(0, [self idealOffsetForView:[self findFirstResponderBeneathView:self] withSpace:visibleSpace]);
|
|
||||||
|
|
||||||
[self setContentOffset:idealOffset animated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Helpers
|
|
||||||
|
|
||||||
-(void)handleContentSize {
|
|
||||||
CGSize contentSize = _autoAdjustsContentSizeToBounds ? self.bounds.size : _originalContentSize;
|
|
||||||
contentSize.width = MAX(contentSize.width, self.frame.size.width);
|
|
||||||
contentSize.height = MAX(contentSize.height, self.frame.size.height);
|
|
||||||
[super setContentSize:contentSize];
|
|
||||||
|
|
||||||
if ( _keyboardVisible ) {
|
|
||||||
self.contentInset = [self contentInsetForKeyboard];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIView*)findFirstResponderBeneathView:(UIView*)view {
|
|
||||||
// Search recursively for first responder
|
|
||||||
for ( UIView *childView in view.subviews ) {
|
|
||||||
if ( [childView respondsToSelector:@selector(isFirstResponder)] && [childView isFirstResponder] ) return childView;
|
|
||||||
UIView *result = [self findFirstResponderBeneathView:childView];
|
|
||||||
if ( result ) return result;
|
|
||||||
}
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)findTextFieldAfterTextField:(UIView*)priorTextField beneathView:(UIView*)view minY:(CGFloat*)minY foundView:(UIView**)foundView {
|
|
||||||
// Search recursively for text field or text view below priorTextField
|
|
||||||
CGFloat priorFieldOffset = CGRectGetMinY([self convertRect:priorTextField.frame fromView:priorTextField.superview]);
|
|
||||||
for ( UIView *childView in view.subviews ) {
|
|
||||||
if ( childView.hidden ) continue;
|
|
||||||
if ( ([childView isKindOfClass:[UITextField class]] || [childView isKindOfClass:[UITextView class]]) ) {
|
|
||||||
CGRect frame = [self convertRect:childView.frame fromView:view];
|
|
||||||
if ( childView != priorTextField && CGRectGetMinY(frame) >= priorFieldOffset && CGRectGetMinY(frame) < *minY ) {
|
|
||||||
*minY = CGRectGetMinY(frame);
|
|
||||||
*foundView = childView;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[self findTextFieldAfterTextField:priorTextField beneathView:childView minY:minY foundView:foundView];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)initializeViewsBeneathView:(UIView*)view {
|
|
||||||
for ( UIView *childView in view.subviews ) {
|
|
||||||
if ( ([childView isKindOfClass:[UITextField class]] || [childView isKindOfClass:[UITextView class]]) ) {
|
|
||||||
[self initializeView:childView];
|
|
||||||
} else {
|
|
||||||
[self initializeViewsBeneathView:childView];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIEdgeInsets)contentInsetForKeyboard {
|
|
||||||
UIEdgeInsets newInset = self.contentInset;
|
|
||||||
CGRect keyboardRect = [self keyboardRect];
|
|
||||||
newInset.bottom = keyboardRect.size.height - ((keyboardRect.origin.y+keyboardRect.size.height) - (self.bounds.origin.y+self.bounds.size.height));
|
|
||||||
return newInset;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space {
|
|
||||||
|
|
||||||
// Convert the rect to get the view's distance from the top of the scrollView.
|
|
||||||
CGRect rect = [view convertRect:view.bounds toView:self];
|
|
||||||
|
|
||||||
// Set starting offset to that point
|
|
||||||
CGFloat offset = rect.origin.y;
|
|
||||||
|
|
||||||
|
|
||||||
if ( self.contentSize.height - offset < space ) {
|
|
||||||
// Scroll to the bottom
|
|
||||||
offset = self.contentSize.height - space;
|
|
||||||
} else {
|
|
||||||
if ( view.bounds.size.height < space ) {
|
|
||||||
// Center vertically if there's room
|
|
||||||
offset -= floor((space-view.bounds.size.height)/2.0);
|
|
||||||
}
|
|
||||||
if ( offset + space > self.contentSize.height ) {
|
|
||||||
// Clamp to content size
|
|
||||||
offset = self.contentSize.height - space;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset < 0) offset = 0;
|
|
||||||
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGRect)keyboardRect {
|
|
||||||
CGRect keyboardRect = [self convertRect:_keyboardRect fromView:nil];
|
|
||||||
if ( keyboardRect.origin.y == 0 ) {
|
|
||||||
CGRect screenBounds = [self convertRect:[UIScreen mainScreen].bounds fromView:nil];
|
|
||||||
keyboardRect.origin = CGPointMake(0, screenBounds.size.height - keyboardRect.size.height);
|
|
||||||
}
|
|
||||||
return keyboardRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)initializeView:(UIView*)view {
|
|
||||||
if ( ([view isKindOfClass:[UITextField class]] || [view isKindOfClass:[UITextView class]]) && (![(id)view delegate] || [(id)view delegate] == self) ) {
|
|
||||||
[(id)view setDelegate:self];
|
|
||||||
|
|
||||||
if ( [view isKindOfClass:[UITextField class]] ) {
|
|
||||||
UIView *otherView = nil;
|
|
||||||
CGFloat minY = CGFLOAT_MAX;
|
|
||||||
[self findTextFieldAfterTextField:view beneathView:self minY:&minY foundView:&otherView];
|
|
||||||
|
|
||||||
if ( otherView ) {
|
|
||||||
((UITextField*)view).returnKeyType = UIReturnKeyNext;
|
|
||||||
} else {
|
|
||||||
((UITextField*)view).returnKeyType = UIReturnKeyDone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@@ -1,13 +0,0 @@
|
|||||||
//
|
|
||||||
// TPKeyboardAvoidingTableView.h
|
|
||||||
//
|
|
||||||
// Created by Michael Tyson on 11/04/2011.
|
|
||||||
// Copyright 2011 A Tasty Pixel. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
@interface TPKeyboardAvoidingTableView : UITableView
|
|
||||||
- (BOOL)focusNextTextField;
|
|
||||||
- (void)scrollToActiveTextField;
|
|
||||||
@end
|
|
@@ -1,284 +0,0 @@
|
|||||||
//
|
|
||||||
// TPKeyboardAvoidingTableView.m
|
|
||||||
//
|
|
||||||
// Created by Michael Tyson on 11/04/2011.
|
|
||||||
// Copyright 2011 A Tasty Pixel. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "TPKeyboardAvoidingTableView.h"
|
|
||||||
|
|
||||||
#define _UIKeyboardFrameEndUserInfoKey (&UIKeyboardFrameEndUserInfoKey != NULL ? UIKeyboardFrameEndUserInfoKey : @"UIKeyboardBoundsUserInfoKey")
|
|
||||||
|
|
||||||
@interface TPKeyboardAvoidingTableView () <UITextFieldDelegate, UITextViewDelegate> {
|
|
||||||
UIEdgeInsets _priorInset;
|
|
||||||
BOOL _priorInsetSaved;
|
|
||||||
BOOL _keyboardVisible;
|
|
||||||
CGRect _keyboardRect;
|
|
||||||
CGSize _originalContentSize;
|
|
||||||
}
|
|
||||||
- (UIView*)findFirstResponderBeneathView:(UIView*)view;
|
|
||||||
- (UIEdgeInsets)contentInsetForKeyboard;
|
|
||||||
- (CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space;
|
|
||||||
- (CGRect)keyboardRect;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation TPKeyboardAvoidingTableView
|
|
||||||
|
|
||||||
#pragma mark - Setup/Teardown
|
|
||||||
|
|
||||||
- (void)setup {
|
|
||||||
_priorInsetSaved = NO;
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(id)initWithFrame:(CGRect)frame {
|
|
||||||
if ( !(self = [super initWithFrame:frame]) ) return nil;
|
|
||||||
[self setup];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(id)initWithFrame:(CGRect)frame style:(UITableViewStyle)withStyle {
|
|
||||||
if ( !(self = [super initWithFrame:frame style:withStyle]) ) return nil;
|
|
||||||
[self setup];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)awakeFromNib {
|
|
||||||
[self setup];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)dealloc {
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
[super dealloc];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)setFrame:(CGRect)frame {
|
|
||||||
[super setFrame:frame];
|
|
||||||
|
|
||||||
CGSize contentSize = _originalContentSize;
|
|
||||||
contentSize.width = MAX(contentSize.width, self.frame.size.width);
|
|
||||||
contentSize.height = MAX(contentSize.height, self.frame.size.height);
|
|
||||||
[super setContentSize:contentSize];
|
|
||||||
|
|
||||||
if ( _keyboardVisible ) {
|
|
||||||
self.contentInset = [self contentInsetForKeyboard];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)setContentSize:(CGSize)contentSize {
|
|
||||||
_originalContentSize = contentSize;
|
|
||||||
|
|
||||||
contentSize.width = MAX(contentSize.width, self.frame.size.width);
|
|
||||||
contentSize.height = MAX(contentSize.height, self.frame.size.height);
|
|
||||||
[super setContentSize:contentSize];
|
|
||||||
|
|
||||||
if ( _keyboardVisible ) {
|
|
||||||
self.contentInset = [self contentInsetForKeyboard];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Responders, events
|
|
||||||
|
|
||||||
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
|
|
||||||
[[self findFirstResponderBeneathView:self] resignFirstResponder];
|
|
||||||
[super touchesEnded:touches withEvent:event];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)keyboardWillShow:(NSNotification*)notification {
|
|
||||||
_keyboardRect = [[[notification userInfo] objectForKey:_UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
|
||||||
_keyboardVisible = YES;
|
|
||||||
|
|
||||||
UIView *firstResponder = [self findFirstResponderBeneathView:self];
|
|
||||||
if ( !firstResponder ) {
|
|
||||||
// No child view is the first responder - nothing to do here
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_priorInsetSaved) {
|
|
||||||
_priorInset = self.contentInset;
|
|
||||||
_priorInsetSaved = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shrink view's inset by the keyboard's height, and scroll to show the text field/view being edited
|
|
||||||
[UIView beginAnimations:nil context:NULL];
|
|
||||||
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
|
|
||||||
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]];
|
|
||||||
|
|
||||||
self.contentInset = [self contentInsetForKeyboard];
|
|
||||||
[self setContentOffset:CGPointMake(self.contentOffset.x,
|
|
||||||
[self idealOffsetForView:firstResponder withSpace:[self keyboardRect].origin.y - self.bounds.origin.y])
|
|
||||||
animated:YES];
|
|
||||||
[self setScrollIndicatorInsets:self.contentInset];
|
|
||||||
|
|
||||||
[UIView commitAnimations];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)keyboardWillHide:(NSNotification*)notification {
|
|
||||||
_keyboardRect = CGRectZero;
|
|
||||||
_keyboardVisible = NO;
|
|
||||||
|
|
||||||
// Restore dimensions to prior size
|
|
||||||
[UIView beginAnimations:nil context:NULL];
|
|
||||||
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
|
|
||||||
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]];
|
|
||||||
self.contentInset = _priorInset;
|
|
||||||
[self setScrollIndicatorInsets:self.contentInset];
|
|
||||||
_priorInsetSaved = NO;
|
|
||||||
[UIView commitAnimations];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
|
|
||||||
if ( ![self focusNextTextField] ) {
|
|
||||||
[textField resignFirstResponder];
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)textFieldDidBeginEditing:(UITextField *)textField {
|
|
||||||
[self scrollToActiveTextField];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)textViewDidBeginEditing:(UITextView *)textView {
|
|
||||||
[self scrollToActiveTextField];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)layoutSubviews {
|
|
||||||
[super layoutSubviews];
|
|
||||||
[self initializeViewsBeneathView:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Utilities
|
|
||||||
|
|
||||||
- (BOOL)focusNextTextField {
|
|
||||||
UIView *firstResponder = [self findFirstResponderBeneathView:self];
|
|
||||||
if ( !firstResponder ) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGFloat minY = CGFLOAT_MAX;
|
|
||||||
UIView *view = nil;
|
|
||||||
[self findTextFieldAfterTextField:firstResponder beneathView:self minY:&minY foundView:&view];
|
|
||||||
|
|
||||||
if ( view ) {
|
|
||||||
[view becomeFirstResponder];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)scrollToActiveTextField {
|
|
||||||
if ( !_keyboardVisible ) return;
|
|
||||||
|
|
||||||
CGFloat visibleSpace = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom;
|
|
||||||
|
|
||||||
CGPoint idealOffset = CGPointMake(0, [self idealOffsetForView:[self findFirstResponderBeneathView:self] withSpace:visibleSpace]);
|
|
||||||
|
|
||||||
[self setContentOffset:idealOffset animated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Helpers
|
|
||||||
|
|
||||||
- (UIView*)findFirstResponderBeneathView:(UIView*)view {
|
|
||||||
// Search recursively for first responder
|
|
||||||
for ( UIView *childView in view.subviews ) {
|
|
||||||
if ( [childView respondsToSelector:@selector(isFirstResponder)] && [childView isFirstResponder] ) return childView;
|
|
||||||
UIView *result = [self findFirstResponderBeneathView:childView];
|
|
||||||
if ( result ) return result;
|
|
||||||
}
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)findTextFieldAfterTextField:(UIView*)priorTextField beneathView:(UIView*)view minY:(CGFloat*)minY foundView:(UIView**)foundView {
|
|
||||||
// Search recursively for text field or text view below priorTextField
|
|
||||||
CGFloat priorFieldOffset = CGRectGetMinY([self convertRect:priorTextField.frame fromView:priorTextField.superview]);
|
|
||||||
for ( UIView *childView in view.subviews ) {
|
|
||||||
if ( childView.hidden ) continue;
|
|
||||||
if ( ([childView isKindOfClass:[UITextField class]] || [childView isKindOfClass:[UITextView class]]) ) {
|
|
||||||
CGRect frame = [self convertRect:childView.frame fromView:view];
|
|
||||||
if ( childView != priorTextField && CGRectGetMinY(frame) >= priorFieldOffset && CGRectGetMinY(frame) < *minY ) {
|
|
||||||
*minY = CGRectGetMinY(frame);
|
|
||||||
*foundView = childView;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[self findTextFieldAfterTextField:priorTextField beneathView:childView minY:minY foundView:foundView];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)initializeViewsBeneathView:(UIView*)view {
|
|
||||||
for ( UIView *childView in view.subviews ) {
|
|
||||||
if ( ([childView isKindOfClass:[UITextField class]] || [childView isKindOfClass:[UITextView class]]) ) {
|
|
||||||
[self initializeView:childView];
|
|
||||||
} else {
|
|
||||||
[self initializeViewsBeneathView:childView];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIEdgeInsets)contentInsetForKeyboard {
|
|
||||||
UIEdgeInsets newInset = self.contentInset;
|
|
||||||
CGRect keyboardRect = [self keyboardRect];
|
|
||||||
newInset.bottom = keyboardRect.size.height - ((keyboardRect.origin.y+keyboardRect.size.height) - (self.bounds.origin.y+self.bounds.size.height));
|
|
||||||
return newInset;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space {
|
|
||||||
|
|
||||||
// Convert the rect to get the view's distance from the top of the scrollView.
|
|
||||||
CGRect rect = [view convertRect:view.bounds toView:self];
|
|
||||||
|
|
||||||
// Set starting offset to that point
|
|
||||||
CGFloat offset = rect.origin.y;
|
|
||||||
|
|
||||||
|
|
||||||
if ( self.contentSize.height - offset < space ) {
|
|
||||||
// Scroll to the bottom
|
|
||||||
offset = self.contentSize.height - space;
|
|
||||||
} else {
|
|
||||||
if ( view.bounds.size.height < space ) {
|
|
||||||
// Center vertically if there's room
|
|
||||||
offset -= floor((space-view.bounds.size.height)/2.0);
|
|
||||||
}
|
|
||||||
if ( offset + space > self.contentSize.height ) {
|
|
||||||
// Clamp to content size
|
|
||||||
offset = self.contentSize.height - space;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset < 0) offset = 0;
|
|
||||||
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGRect)keyboardRect {
|
|
||||||
CGRect keyboardRect = [self convertRect:_keyboardRect fromView:nil];
|
|
||||||
if ( keyboardRect.origin.y == 0 ) {
|
|
||||||
CGRect screenBounds = [self convertRect:[UIScreen mainScreen].bounds fromView:nil];
|
|
||||||
keyboardRect.origin = CGPointMake(0, screenBounds.size.height - keyboardRect.size.height);
|
|
||||||
}
|
|
||||||
return keyboardRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)initializeView:(UIView*)view {
|
|
||||||
if ( ([view isKindOfClass:[UITextField class]] || [view isKindOfClass:[UITextView class]]) && (![(id)view delegate] || [(id)view delegate] == self) ) {
|
|
||||||
[(id)view setDelegate:self];
|
|
||||||
|
|
||||||
if ( [view isKindOfClass:[UITextField class]] ) {
|
|
||||||
UIView *otherView = nil;
|
|
||||||
CGFloat minY = CGFLOAT_MAX;
|
|
||||||
[self findTextFieldAfterTextField:view beneathView:self minY:&minY foundView:&otherView];
|
|
||||||
|
|
||||||
if ( otherView ) {
|
|
||||||
((UITextField*)view).returnKeyType = UIReturnKeyNext;
|
|
||||||
} else {
|
|
||||||
((UITextField*)view).returnKeyType = UIReturnKeyDone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
Reference in New Issue
Block a user