From d015ea5cd4c470d6b442992b1d72518bbfa1986a Mon Sep 17 00:00:00 2001 From: Adam Huda Date: Tue, 17 Nov 2009 19:24:10 -0800 Subject: [PATCH] - Pickin up firstResponder fix --- src/TTGlobal.m | 15 ++- src/TTLabel.m | 16 +++ src/Three20.xcodeproj/project.pbxproj | 8 ++ src/Three20/TTGlobal.h | 1 + src/Three20/UIViewAdditions.h | 47 +++++++++ src/Three20/UIWindowAdditions.h | 19 ++++ src/UITableViewAdditions.m | 2 +- src/UIViewAdditions.m | 135 ++++++++++++++++++++++++++ src/UIWindowAdditions.m | 37 +++++++ 9 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 src/Three20/UIWindowAdditions.h create mode 100644 src/UIWindowAdditions.m diff --git a/src/TTGlobal.m b/src/TTGlobal.m index 4e04a0e47a..e859a7fb2b 100644 --- a/src/TTGlobal.m +++ b/src/TTGlobal.m @@ -38,8 +38,19 @@ BOOL TTIsEmptyString(NSObject* object) { } BOOL TTIsKeyboardVisible() { - UIWindow* window = [UIApplication sharedApplication].keyWindow; - return !![window performSelector:@selector(firstResponder)]; + NSArray *windows = [[UIApplication sharedApplication] windows]; + for( UIWindow *window in [windows reverseObjectEnumerator] ) + { + for( UIView *view in [window subviews] ) + { + if( !strcmp(object_getClassName(view), "UIKeyboard") ) + { + return YES; + } + } + } + + return NO; } UIDeviceOrientation TTDeviceOrientation() { diff --git a/src/TTLabel.m b/src/TTLabel.m index 41ad7cce47..aafda8a10c 100644 --- a/src/TTLabel.m +++ b/src/TTLabel.m @@ -56,6 +56,22 @@ - (CGSize)sizeThatFits:(CGSize)size { return [_style addToSize:CGSizeZero context:context]; } + +////////////////////////////////////////////////////////////////////////////////////////////////// +// UIAccessibility + +- (BOOL)isAccessibilityElement { + return YES; +} + +- (NSString *)accessibilityLabel { + return _text; +} + +- (UIAccessibilityTraits)accessibilityTraits { + return [super accessibilityTraits] | UIAccessibilityTraitStaticText; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// // TTStyleDelegate diff --git a/src/Three20.xcodeproj/project.pbxproj b/src/Three20.xcodeproj/project.pbxproj index a17ab24049..5508eda57b 100755 --- a/src/Three20.xcodeproj/project.pbxproj +++ b/src/Three20.xcodeproj/project.pbxproj @@ -139,6 +139,8 @@ BEF341970F8042520027E93C /* TTStyledTextLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = BEF341960F8042520027E93C /* TTStyledTextLabel.h */; }; FE15B9BB1015A0D500B5C4E6 /* UIFontAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = FE15B9B91015A0D500B5C4E6 /* UIFontAdditions.h */; }; FE15B9BC1015A0D500B5C4E6 /* UIFontAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = FE15B9BA1015A0D500B5C4E6 /* UIFontAdditions.m */; }; + FEF03C0710B39DBA00844C02 /* UIWindowAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = FEF03C0610B39DBA00844C02 /* UIWindowAdditions.m */; }; + FEF03C0910B39DC900844C02 /* UIWindowAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = FEF03C0810B39DC900844C02 /* UIWindowAdditions.h */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -275,6 +277,8 @@ BEF341960F8042520027E93C /* TTStyledTextLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TTStyledTextLabel.h; path = Three20/TTStyledTextLabel.h; sourceTree = ""; }; FE15B9B91015A0D500B5C4E6 /* UIFontAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UIFontAdditions.h; path = Three20/UIFontAdditions.h; sourceTree = ""; }; FE15B9BA1015A0D500B5C4E6 /* UIFontAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIFontAdditions.m; sourceTree = ""; }; + FEF03C0610B39DBA00844C02 /* UIWindowAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIWindowAdditions.m; sourceTree = ""; }; + FEF03C0810B39DC900844C02 /* UIWindowAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UIWindowAdditions.h; path = Three20/UIWindowAdditions.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -394,6 +398,8 @@ BEAF21210F4D329600D75F3B /* UIWebViewAdditions.m */, BEAF21390F4D32E100D75F3B /* UIToolbarAdditions.h */, BEAF211F0F4D329600D75F3B /* UIToolbarAdditions.m */, + FEF03C0810B39DC900844C02 /* UIWindowAdditions.h */, + FEF03C0610B39DBA00844C02 /* UIWindowAdditions.m */, ); name = Additions; sourceTree = ""; @@ -633,6 +639,7 @@ BEA69ECD0FAC0FEC00DA7DDC /* TTWebController.h in Headers */, BEA762500FBA1E290091B567 /* NSDateAdditions.h in Headers */, FE15B9BB1015A0D500B5C4E6 /* UIFontAdditions.h in Headers */, + FEF03C0910B39DC900844C02 /* UIWindowAdditions.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -741,6 +748,7 @@ BEA69ECB0FAC0FD600DA7DDC /* TTWebController.m in Sources */, BEA7624E0FBA1E220091B567 /* NSDateAdditions.m in Sources */, FE15B9BC1015A0D500B5C4E6 /* UIFontAdditions.m in Sources */, + FEF03C0710B39DBA00844C02 /* UIWindowAdditions.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/Three20/TTGlobal.h b/src/Three20/TTGlobal.h index 85858f8f87..cf01c24243 100644 --- a/src/Three20/TTGlobal.h +++ b/src/Three20/TTGlobal.h @@ -9,6 +9,7 @@ #import "Three20/UIImageAdditions.h" #import "Three20/UIViewControllerAdditions.h" #import "Three20/UIViewAdditions.h" +#import "Three20/UIWindowAdditions.h" #import "Three20/UITableViewAdditions.h" #import "Three20/UIWebViewAdditions.h" #import "Three20/UIToolbarAdditions.h" diff --git a/src/Three20/UIViewAdditions.h b/src/Three20/UIViewAdditions.h index 619a577cb3..1a7c177e57 100644 --- a/src/Three20/UIViewAdditions.h +++ b/src/Three20/UIViewAdditions.h @@ -20,6 +20,9 @@ @property(nonatomic,readonly) CGFloat screenViewY; @property(nonatomic,readonly) CGRect screenFrame; +@property(nonatomic) CGPoint origin; +@property(nonatomic) CGSize size; + @property(nonatomic,readonly) CGFloat orientationWidth; @property(nonatomic,readonly) CGFloat orientationHeight; @@ -31,6 +34,17 @@ - (UIView*)findChildWithDescendant:(UIView*)descendant; +/** + * Finds the first descendant view (including this view) that is a member of a particular class. + */ +- (UIView*)descendantOrSelfWithClass:(Class)cls; + +/** + * Finds the first ancestor view (including this view) that is a member of a particular class. + */ +- (UIView*)ancestorOrSelfWithClass:(Class)cls; + + /** * Removes all subviews. */ @@ -45,4 +59,37 @@ - (CGPoint)offsetFromView:(UIView*)otherView; +/** + * Calculates the offset of this view from another view in screen coordinates. + */ +- (CGPoint)offsetFromView:(UIView*)otherView; + +/** + * Calculates the frame of this view with parts that intersect with the keyboard subtracted. + * + * If the keyboard is not showing, this will simply return the normal frame. + */ +- (CGRect)frameWithKeyboardSubtracted:(CGFloat)plusHeight; + +/** + * Shows the view in a window at the bottom of the screen. + * + * This will send a notification pretending that a keyboard is about to appear so that + * observers who adjust their layout for the keyboard will also adjust for this view. + */ +- (void)presentAsKeyboardInView:(UIView*)containingView; + +/** + * Hides a view that was showing in a window at the bottom of the screen (via presentAsKeyboard). + * + * This will send a notification pretending that a keyboard is about to disappear so that + * observers who adjust their layout for the keyboard will also adjust for this view. + */ +- (void)dismissAsKeyboard:(BOOL)animated; + +/** + * The view controller whose view contains this view. + */ +- (UIViewController*)viewController; + @end diff --git a/src/Three20/UIWindowAdditions.h b/src/Three20/UIWindowAdditions.h new file mode 100644 index 0000000000..370c02ec16 --- /dev/null +++ b/src/Three20/UIWindowAdditions.h @@ -0,0 +1,19 @@ +// +// UIWindowAdditions.h +// Three20 +// +// Created by Mike on 10/31/09. +// Copyright 2009 Prime31 Studios. All rights reserved. +// + +#import +#import + + +@interface UIWindow (TTCategory) + +- (UIView*)findFirstResponder; + +- (UIView*)findFirstResponderInView:(UIView*)topView; + +@end diff --git a/src/UITableViewAdditions.m b/src/UITableViewAdditions.m index dc7b2239bb..dbc225fc6f 100644 --- a/src/UITableViewAdditions.m +++ b/src/UITableViewAdditions.m @@ -50,7 +50,7 @@ - (void)touchRowAtIndexPath:(NSIndexPath*)indexPath animated:(BOOL)animated { } - (void)scrollFirstResponderIntoView { - UIView* responder = [self.window performSelector:@selector(firstResponder)]; + UIView* responder = [self.window findFirstResponder]; UITableViewCell* cell = (UITableViewCell*)[responder firstParentOfClass:[UITableViewCell class]]; if (cell) { NSIndexPath* indexPath = [self indexPathForCell:cell]; diff --git a/src/UIViewAdditions.m b/src/UIViewAdditions.m index 3e250ef432..b40a56922e 100644 --- a/src/UIViewAdditions.m +++ b/src/UIViewAdditions.m @@ -216,6 +216,26 @@ - (CGRect)screenFrame { return CGRectMake(self.screenViewX, self.screenViewY, self.width, self.height); } +- (CGPoint)origin { + return self.frame.origin; +} + +- (void)setOrigin:(CGPoint)origin { + CGRect frame = self.frame; + frame.origin = origin; + self.frame = frame; +} + +- (CGSize)size { + return self.frame.size; +} + +- (void)setSize:(CGSize)size { + CGRect frame = self.frame; + frame.size = size; + self.frame = frame; +} + - (CGPoint)offsetFromView:(UIView*)otherView { CGFloat x = 0, y = 0; for (UIView* view = self; view && view != otherView; view = view.superview) { @@ -281,6 +301,29 @@ - (UIView*)findChildWithDescendant:(UIView*)descendant { return nil; } +- (UIView*)descendantOrSelfWithClass:(Class)cls { + if ([self isKindOfClass:cls]) + return self; + + for (UIView* child in self.subviews) { + UIView* it = [child descendantOrSelfWithClass:cls]; + if (it) + return it; + } + + return nil; +} + +- (UIView*)ancestorOrSelfWithClass:(Class)cls { + if ([self isKindOfClass:cls]) { + return self; + } else if (self.superview) { + return [self.superview ancestorOrSelfWithClass:cls]; + } else { + return nil; + } +} + - (void)removeSubviews { while (self.subviews.count) { UIView* child = self.subviews.lastObject; @@ -302,4 +345,96 @@ - (void)simulateTapAtPoint:(CGPoint)location { } #endif +- (CGRect)frameWithKeyboardSubtracted:(CGFloat)plusHeight { + CGRect frame = self.frame; + if( TTIsKeyboardVisible() ) + { + CGRect screenFrame = TTScreenBounds(); + CGFloat keyboardTop = (screenFrame.size.height - (TTKeyboardHeight() + plusHeight)); + CGFloat screenBottom = self.screenY + frame.size.height; + CGFloat diff = screenBottom - keyboardTop; + if (diff > 0) { + frame.size.height -= diff; + } + } + return frame; +} + +- (void)presentAsKeyboardAnimationDidStop { + CGRect screenFrame = TTScreenBounds(); + CGRect bounds = CGRectMake(0, 0, screenFrame.size.width, self.height); + CGPoint centerBegin = CGPointMake(floor(screenFrame.size.width/2 - self.width/2), + screenFrame.size.height + floor(self.height/2)); + CGPoint centerEnd = CGPointMake(floor(screenFrame.size.width/2 - self.width/2), + screenFrame.size.height - floor(self.height/2)); + + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [NSValue valueWithCGRect:bounds], UIKeyboardBoundsUserInfoKey, + [NSValue valueWithCGPoint:centerBegin], UIKeyboardCenterBeginUserInfoKey, + [NSValue valueWithCGPoint:centerEnd], UIKeyboardCenterEndUserInfoKey, + nil]; + + [[NSNotificationCenter defaultCenter] postNotificationName:@"UIKeyboardWillShowNotification" + object:self userInfo:userInfo]; +} + +- (void)dismissAsKeyboardAnimationDidStop { + [self removeFromSuperview]; +} + +- (void)presentAsKeyboardInView:(UIView*)containingView { + self.top = containingView.height; + [containingView addSubview:self]; + + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:TT_TRANSITION_DURATION]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(presentAsKeyboardAnimationDidStop)]; + self.top -= self.height; + [UIView commitAnimations]; +} + +- (void)dismissAsKeyboard:(BOOL)animated { + CGRect screenFrame = TTScreenBounds(); + CGRect bounds = CGRectMake(0, 0, screenFrame.size.width, self.height); + CGPoint centerBegin = CGPointMake(floor(screenFrame.size.width/2 - self.width/2), + screenFrame.size.height - floor(self.height/2)); + CGPoint centerEnd = CGPointMake(floor(screenFrame.size.width/2 - self.width/2), + screenFrame.size.height + floor(self.height/2)); + + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [NSValue valueWithCGRect:bounds], UIKeyboardBoundsUserInfoKey, + [NSValue valueWithCGPoint:centerBegin], UIKeyboardCenterBeginUserInfoKey, + [NSValue valueWithCGPoint:centerEnd], UIKeyboardCenterEndUserInfoKey, + nil]; + + [[NSNotificationCenter defaultCenter] postNotificationName:@"UIKeyboardWillHideNotification" + object:self userInfo:userInfo]; + + if (animated) { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:TT_TRANSITION_DURATION]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(dismissAsKeyboardAnimationDidStop)]; + } + + self.top += self.height; + + if (animated) { + [UIView commitAnimations]; + } else { + [self dismissAsKeyboardAnimationDidStop]; + } +} + +- (UIViewController*)viewController { + for (UIView* next = [self superview]; next; next = next.superview) { + UIResponder* nextResponder = [next nextResponder]; + if ([nextResponder isKindOfClass:[UIViewController class]]) { + return (UIViewController*)nextResponder; + } + } + return nil; +} + @end diff --git a/src/UIWindowAdditions.m b/src/UIWindowAdditions.m new file mode 100644 index 0000000000..c080cafcbc --- /dev/null +++ b/src/UIWindowAdditions.m @@ -0,0 +1,37 @@ +// +// UIWindowAdditions.m +// Three20 +// +// Created by Mike on 10/31/09. +// Copyright 2009 Prime31 Studios. All rights reserved. +// + +#import "UIWindowAdditions.h" + + +@implementation UIWindow (TTCategory) + +- (UIView*)findFirstResponder +{ + return [self findFirstResponderInView:self]; +} + + +- (UIView*)findFirstResponderInView:(UIView*)topView +{ + if( [topView isFirstResponder] ) + return topView; + + for( UIView *subView in topView.subviews ) + { + if( [subView isFirstResponder] ) + return subView; + + UIView *firstResponderCheck = [self findFirstResponderInView:subView]; + if( firstResponderCheck != nil ) + return firstResponderCheck; + } + return nil; +} + +@end