Instantly share code, notes, and snippets.
Last active
August 29, 2015 14:01
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save nrichards/bd71c23e68dd41d24d8d to your computer and use it in GitHub Desktop.
Simple back button handling for Apportable using UIResponder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIResponder+BackButton.h | |
// | |
// Created by Nick Richards | |
// | |
#import <UIKit/UIKit.h> | |
typedef void(^UIResponderBackButtonHandler)(void); | |
@interface UIResponder (BackButton) | |
-(void) setBackButtonHandler:(UIResponderBackButtonHandler) handler; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIResponder+BackButton.m | |
// | |
// Created by Nick Richards | |
// | |
// USAGE | |
// * For use with Apportable platform | |
// * Add UIResponder+BackButton.m and .h to the application's Xcode project | |
// * Customize UIResponder+BackButton.m's "AppDelegate" references to match the UIApplication's delegate class name | |
// * Alter the inheritance of the AppDelegate to derive from UIResponder to permit routing of back button events to the new back button handler blocks | |
// * Add [self setBackButtonHandler:^{ ... }] in desired UIViewControllers and UIViews to handle back button events according to the UIResponder chain | |
// | |
// // Example back button handling in view controller. Can also set in e.g viewDidLoad or AppDelegate. | |
// - (void)viewWillAppear:(BOOL)animated | |
// { | |
// [super viewWillAppear:animated]; | |
// __block __typeof__(self) weakSelf = self; | |
// [self setBackButtonHandler:^{ | |
// [weakSelf dismissViewControllerAnimated:YES completion:nil]; | |
// }]; | |
// } | |
#import <objc/runtime.h> | |
#import <UIKit/UIKit.h> | |
#import <UIKit/UIEvent.h> | |
#import "UIResponder+BackButton.h" | |
#import "AppDelegate.h" // CUSTOMIZE ME (#1) | |
#ifndef APPORTABLE | |
// FIXME enables building on non-Apportable targets | |
//const NSUInteger UIEventButtonCodeBack = NSUIntegerMax; | |
#endif | |
static int UIResponderBackButtonHandlerKey; | |
@implementation UIResponder (BackButton) | |
-(void) setBackButtonHandler:(UIResponderBackButtonHandler) handler { | |
objc_setAssociatedObject(self, &UIResponderBackButtonHandlerKey, [handler copy] , OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
-(UIResponderBackButtonHandler) backButtonHandler { | |
return objc_getAssociatedObject(self, &UIResponderBackButtonHandlerKey); | |
} | |
-(BOOL) searchAndInvokeBackButtonHandler { | |
UIResponderBackButtonHandler handler = [self backButtonHandler]; | |
if (handler) { | |
handler(); | |
return YES; | |
} | |
return [self.nextResponder searchAndInvokeBackButtonHandler]; | |
} | |
@end | |
@interface UIView (BackButton) | |
-(BOOL) _sendBackButtonEvent; | |
@end | |
@implementation UIView (BackButton) | |
-(UIViewController *) currentViewController { | |
UIResponder *current = self.nextResponder; | |
do { | |
if([current isKindOfClass: [UIViewController class]]) { | |
return (UIViewController *)current; | |
} | |
} while( (current = current.nextResponder) ); | |
return nil; | |
} | |
// FIXME navigate more than two levels deep into a navigation controller hierarchy | |
-(BOOL) _sendBackButtonEvent { | |
NSArray *subviews = self.subviews; | |
// Walk down the view tree | |
for (UIView *view in subviews.reverseObjectEnumerator) { | |
if ([view _sendBackButtonEvent]) { | |
return YES; | |
} | |
} | |
// Leaves and Branches both fall through | |
if ([self superview]) { | |
// Majority of nodes | |
// Handle only with this view's, or its view controller's handler | |
if ([self backButtonHandler]) { | |
[self backButtonHandler](); | |
return YES; | |
} | |
// Find the associated view controller | |
UIViewController *controller = [self currentViewController]; | |
if ([controller backButtonHandler]) { | |
[controller backButtonHandler](); | |
return YES; | |
} | |
// Navigates two levels deep into a navigation controller hierarchy | |
if ([controller isKindOfClass:[UINavigationController class]]) { | |
UINavigationController *navController = (UINavigationController*)controller; | |
UIViewController *childController = [[navController viewControllers] lastObject]; | |
// Find any presenting view controller | |
UIViewController *targetController = childController; | |
UIViewController *presentedViewController = [childController presentedViewController]; | |
if (nil != presentedViewController) { | |
targetController = presentedViewController; | |
} | |
if ([targetController backButtonHandler]) { | |
[targetController backButtonHandler](); | |
return YES; | |
} | |
}; | |
} else { | |
// Special case for root view | |
return [self searchAndInvokeBackButtonHandler]; | |
} | |
return NO; | |
} | |
@end | |
@interface AppDelegate (BackButton) // CUSTOMIZE ME (#2) | |
@end | |
@implementation AppDelegate (BackButton) // CUSTOMIZE ME (#3) | |
- (void)buttonUpWithEvent:(UIEvent *)event { | |
#if defined(APPORTABLE) | |
if ([event buttonCode] == UIEventButtonCodeBack) | |
[[UIApplication sharedApplication].keyWindow _sendBackButtonEvent]; | |
#endif | |
} | |
@end | |
@interface UIAlertView (BackButton) | |
@end | |
@implementation UIAlertView (BackButton) | |
-(void) _platformShow { | |
__block __typeof__(self) weakSelf = self; | |
[self setBackButtonHandler:^{ | |
[weakSelf dismissWithClickedButtonIndex:0 animated:YES]; | |
}]; | |
} | |
@end | |
@interface UIActionSheet (BackButton) | |
@end | |
@implementation UIActionSheet (BackButton) | |
-(void) _platformShow { | |
__block __typeof__(self) weakSelf = self; | |
[self setBackButtonHandler:^{ | |
if ([weakSelf.delegate respondsToSelector:@selector(alertView:clickedButtonAtIndex:)]) { | |
[(id<UIAlertViewDelegate>)weakSelf.delegate alertView:(id)weakSelf clickedButtonAtIndex:weakSelf.cancelButtonIndex]; | |
} | |
[weakSelf dismissWithClickedButtonIndex:weakSelf.cancelButtonIndex animated:YES]; | |
}]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment