Skip to content

Instantly share code, notes, and snippets.

@nrichards
Last active August 29, 2015 14:01
Show Gist options
  • Save nrichards/bd71c23e68dd41d24d8d to your computer and use it in GitHub Desktop.
Save nrichards/bd71c23e68dd41d24d8d to your computer and use it in GitHub Desktop.
Simple back button handling for Apportable using UIResponder
//
// UIResponder+BackButton.h
//
// Created by Nick Richards
//
#import <UIKit/UIKit.h>
typedef void(^UIResponderBackButtonHandler)(void);
@interface UIResponder (BackButton)
-(void) setBackButtonHandler:(UIResponderBackButtonHandler) handler;
@end
//
// 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