├── .gitignore ├── AB_MultiBackButtonItem.h ├── AB_MultiBackButtonItem.m ├── LICENSE ├── MultiBackButton ├── MultiBackButton.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── MultiBackButton │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ └── Main.storyboard │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── icons │ │ ├── close.imageset │ │ ├── Contents.json │ │ └── close-icon@3x.png │ │ ├── commit.imageset │ │ ├── Contents.json │ │ └── commit-icon@3x.png │ │ ├── dir-status.imageset │ │ ├── Contents.json │ │ └── dir-status-icon@3x.png │ │ ├── dir.imageset │ │ ├── Contents.json │ │ └── dir-icon@2x.png │ │ ├── doc.imageset │ │ ├── Contents.json │ │ └── doc-icon@2x.png │ │ ├── file.imageset │ │ ├── Contents.json │ │ └── file-icon@2x.png │ │ ├── img.imageset │ │ ├── Contents.json │ │ └── img-icon@2x.png │ │ ├── link.imageset │ │ ├── Contents.json │ │ └── link-icon@2x.png │ │ ├── repo-dir.imageset │ │ ├── Contents.json │ │ └── repo-dir-icon@3x.png │ │ ├── repo-list.imageset │ │ ├── Contents.json │ │ └── repo-list-icon@3x.png │ │ ├── repo-status.imageset │ │ ├── Contents.json │ │ └── repo-status-icon@3x.png │ │ ├── settings.imageset │ │ ├── Contents.json │ │ └── cell-settings-icon@3x.png │ │ ├── sound.imageset │ │ ├── Contents.json │ │ └── sound-icon@2x.png │ │ ├── src.imageset │ │ ├── Contents.json │ │ └── src-icon@2x.png │ │ ├── text.imageset │ │ ├── Contents.json │ │ └── text-icon@2x.png │ │ ├── video.imageset │ │ ├── Contents.json │ │ └── video-icon@2x.png │ │ ├── web.imageset │ │ ├── Contents.json │ │ └── web-icon@2x.png │ │ └── zip.imageset │ │ ├── Contents.json │ │ └── zip-icon@2x.png │ ├── Info.plist │ ├── SmudgyWindow.h │ ├── SmudgyWindow.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── README.md └── example.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /AB_MultiBackButtonItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // AB_MultiBackButtonItem.h 3 | // 4 | // Anders Borum @palmin 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2015 Anders Borum 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | #import 29 | 30 | @interface AB_MultiBackButtonItem : UIBarButtonItem 31 | 32 | @end 33 | 34 | @interface UIViewController (AB_MultiBackButtonItem) 35 | 36 | // when view-controller would normally have a back-button because it is part of navigation 37 | // controller hierarchy and not at the root we set navigationItem.leftBarButtonItem and 38 | // in other cases nothing is done. Configuration takes care not to do double work if called 39 | // on ViewController that already has a MultiBackButtonItem 40 | -(void)configureMultiBackButton; 41 | 42 | // You configure the image used in cells for this view-controller, 43 | // setting it in either in IB or perhaps viewDidLoad. The popup table will look much nicer if 44 | // you supply images for all view-controllers that take part and 25x25 images work well. 45 | @property (nonatomic, strong) IBInspectable UIImage* multiBackButtonImage; 46 | 47 | // The name of view-controller shown in popup is taken from navigationItem.title or title of 48 | // view-controller itself, but if you want something different set this property in 49 | // IB or viewDidLoad. You rarely need to set this property. 50 | @property (nonatomic, strong) IBInspectable NSString* multiBackButtonTitle; 51 | 52 | #pragma mark Previous item 53 | 54 | // Sometimes it makes sense to have a item before the root of the view-controller, 55 | // such as settings. You configure a "previous" item for the root controller to support this. 56 | 57 | // Install navigation item before view-controller itself, and when picking this the view-hierarchy 58 | // is rolled back to this view-controller (that must be hierarchy root) and the action block is called. 59 | // You remove any previous entry if either title or block is nil. 60 | -(void)configurePreviousTitle:(NSString*)title image:(UIImage*)image action:(void (^)(void))block; 61 | 62 | // used internally to get results from call to configurePreviousTitle:image:action: 63 | // but can be used to check by outside parties to check if something has already been configured 64 | // in which case it returns non-nil. 65 | -(NSArray*)previousInfo; 66 | 67 | #pragma mark - 68 | 69 | @end -------------------------------------------------------------------------------- /AB_MultiBackButtonItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // AB_MultiBackButtonItem.m 3 | // 4 | // Anders Borum @palmin 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2015 Anders Borum 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | #import 29 | #import "AB_MultiBackButtonItem.h" 30 | 31 | #define RotateChevron NO 32 | 33 | // background color matches popup that contains the table 34 | #define BackgroundColor [UIColor colorWithRed:247.0/255 green:247.0/255 blue:248.0/255 alpha:1] 35 | 36 | #define PopupWidth 290 37 | 38 | @interface AB_MultiBackButtonView : UIButton { 40 | NSTimeInterval touchStart; 41 | CGPoint pointStart; 42 | BOOL significantMovement; 43 | 44 | NSIndexPath* hoverIndexPath; // the one selected because finger is above it 45 | } 46 | 47 | @property (nonatomic, strong) UIImageView* chevron; 48 | //@property (nonatomic, strong) UILabel* label; 49 | @property (nonatomic, strong) AB_MultiBackButtonItem* item; 50 | 51 | @property (nonatomic, weak) UIViewController* viewController; 52 | @property (nonatomic, strong) UITableViewController* tableController; 53 | @property (nonatomic, strong) NSArray* cells; 54 | 55 | @end 56 | 57 | @implementation AB_MultiBackButtonView 58 | 59 | static NSString* titleForViewController(UIViewController* controller) { 60 | // make sure view is loaded 61 | [controller view]; 62 | 63 | NSString* title = controller.multiBackButtonTitle; 64 | if(title == nil) { 65 | title = controller.navigationItem.title; 66 | if(title == nil) title = controller.title; 67 | } 68 | 69 | return title; 70 | } 71 | 72 | static UIImage* imageForController(UIViewController* controller) { 73 | // make sure view is loaded 74 | [controller view]; 75 | 76 | return controller.multiBackButtonImage; 77 | } 78 | 79 | +(UIImage*)chevronImage { 80 | static UIImage* _image = nil; 81 | 82 | if(_image == nil) { 83 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(13, 21), NO, 0); 84 | 85 | //// Bezier Drawing 86 | UIBezierPath* bezierPath = UIBezierPath.bezierPath; 87 | [bezierPath moveToPoint: CGPointMake(12, 1)]; 88 | [bezierPath addLineToPoint: CGPointMake(2, 10.5)]; 89 | [bezierPath addLineToPoint: CGPointMake(12, 20)]; 90 | [[UIColor blackColor] setStroke]; 91 | bezierPath.lineWidth = 3; 92 | [bezierPath stroke]; 93 | 94 | UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); 95 | UIGraphicsEndImageContext(); 96 | 97 | // should be tint-colored 98 | _image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 99 | } 100 | 101 | return _image; 102 | } 103 | 104 | -(id)initWithFrame:(CGRect)frame { 105 | self = [super initWithFrame:frame]; 106 | if(self) { 107 | self.accessibilityLabel = NSLocalizedString(@"Back", nil); 108 | 109 | UIImage* image = [AB_MultiBackButtonView chevronImage]; 110 | UIImageView* chevron = [[UIImageView alloc] initWithImage:image]; 111 | chevron.contentMode = UIViewContentModeCenter; 112 | [self addSubview:chevron]; 113 | self.chevron = chevron; 114 | 115 | //UILabel* label = [[UILabel alloc] initWithFrame:self.bounds]; 116 | //label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin; 117 | //label.lineBreakMode = NSLineBreakByTruncatingTail; 118 | //[self addSubview: label]; 119 | //self.label = label; 120 | } 121 | return self; 122 | } 123 | 124 | -(void)layoutSubviews { 125 | CGSize pSize = self.bounds.size; 126 | 127 | // image is to the left and vertically centered 128 | CGSize iSize = self.chevron.bounds.size; 129 | self.chevron.frame = CGRectMake(-8, 0.5 * (pSize.height - iSize.height), iSize.width, iSize.height); 130 | 131 | // label is left of image and vertically centered 132 | 133 | //CGSize lSize = self.label.bounds.size; 134 | //CGFloat x = ceil(5.0 + iSize.width); 135 | //self.label.frame = CGRectMake(x, 0.5 * (pSize.height - lSize.height), pSize.width - x, lSize.height); 136 | } 137 | 138 | -(void)setTitle:(NSString*)title { 139 | //self.label.text = title; 140 | //self.label.textColor = self.tintColor; 141 | //[self.label sizeToFit]; 142 | [self sizeToFit]; 143 | [self setNeedsLayout]; 144 | } 145 | 146 | -(UINavigationBar*)navigationBar { 147 | UIView* view = self; 148 | while (view != nil) { 149 | if([view isKindOfClass:[UINavigationBar class]]) { 150 | return (UINavigationBar*)view; 151 | } 152 | view = view.superview; 153 | } 154 | return nil; 155 | } 156 | 157 | - (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller { 158 | return UIModalPresentationNone; 159 | } 160 | 161 | - (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController { 162 | self.tableController = nil; 163 | self.cells = nil; 164 | } 165 | 166 | -(void)presentSelection { 167 | NSArray* viewControllers = self.viewController.navigationController.viewControllers; 168 | NSUInteger index = [viewControllers indexOfObject:self.viewController]; 169 | if(index == NSNotFound) return; 170 | 171 | // make sure we have view controller with hierarchy 172 | if(self.tableController == nil) { 173 | NSMutableArray* cells = [NSMutableArray new]; 174 | for(NSInteger k = index; k >= 0; --k) { 175 | UITableViewController* controller = [viewControllers objectAtIndex:k]; 176 | UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 177 | cell.textLabel.text = titleForViewController(controller); 178 | cell.imageView.image = imageForController(controller); 179 | [cells addObject:cell]; 180 | } 181 | 182 | // we add final cell if there is previous info for root controller 183 | UIViewController* root = [viewControllers firstObject]; 184 | NSArray* previousInfo = [root previousInfo]; 185 | if(previousInfo) { 186 | NSObject* perhapsImage = [previousInfo objectAtIndex:1]; 187 | 188 | UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 189 | cell.textLabel.text = [previousInfo objectAtIndex:0]; 190 | if([perhapsImage isKindOfClass:[UIImage class]]) { 191 | cell.imageView.image = (UIImage*)perhapsImage; 192 | } 193 | [cells addObject:cell]; 194 | } 195 | 196 | self.cells = cells; 197 | 198 | self.tableController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain]; 199 | UITableView* tableView = self.tableController.tableView; 200 | tableView.backgroundColor = BackgroundColor; 201 | tableView.dataSource = self; 202 | tableView.delegate = self; 203 | tableView.alwaysBounceVertical = NO; 204 | [tableView sizeToFit]; 205 | CGRect f = tableView.frame; 206 | f.size.width = PopupWidth; 207 | tableView.frame = f; 208 | self.tableController.preferredContentSize = f.size; 209 | 210 | self.tableController.modalPresentationStyle = UIModalPresentationPopover; 211 | self.tableController.popoverPresentationController.delegate = self; 212 | self.tableController.popoverPresentationController.sourceView = self.chevron; 213 | self.tableController.popoverPresentationController.sourceRect = self.chevron.bounds; 214 | 215 | [self.viewController presentViewController:self.tableController animated:YES completion:nil]; 216 | } 217 | } 218 | 219 | -(void)removeSelectionAnimated:(BOOL)animated completion:(void (^)(void))block { 220 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(presentSelection) object:nil]; 221 | 222 | if(RotateChevron) { 223 | if(animated) { 224 | [UIView animateWithDuration:0.2 animations:^{ 225 | self.chevron.transform = CGAffineTransformIdentity; 226 | }]; 227 | } else { 228 | self.chevron.transform = CGAffineTransformIdentity; 229 | } 230 | } 231 | 232 | if(self.tableController) { 233 | [self.viewController dismissViewControllerAnimated:animated completion:^{ 234 | self.tableController = nil; 235 | self.cells = nil; 236 | if(block) block(); 237 | }]; 238 | } else { 239 | if(block) block(); 240 | } 241 | } 242 | 243 | // which cell is below touch, nil if none 244 | -(NSIndexPath*)IndexPathPoint:(CGPoint)pt { 245 | UITableView* tableView = self.tableController.tableView; 246 | CGPoint point = [self convertPoint:pt toView: tableView]; 247 | NSIndexPath* indexPath = [tableView indexPathForRowAtPoint: point]; 248 | return indexPath; 249 | } 250 | 251 | -(void)clearHoverSelection { 252 | if(hoverIndexPath) { 253 | UITableViewCell* cell = [self.tableController.tableView cellForRowAtIndexPath:hoverIndexPath]; 254 | [cell setSelected:NO animated:NO]; 255 | hoverIndexPath = nil; 256 | } 257 | } 258 | 259 | #pragma mark Touch handling 260 | 261 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 262 | UITouch* touch = [touches anyObject]; 263 | touchStart = [NSDate timeIntervalSinceReferenceDate]; 264 | pointStart = [touch locationInView:self]; 265 | significantMovement = NO; 266 | touchStart = [NSDate timeIntervalSinceReferenceDate]; 267 | 268 | NSTimeInterval delay = 0.3; 269 | 270 | // animate chevron to point down 271 | if(RotateChevron) { 272 | [UIView animateWithDuration:delay delay: 0.5 * delay usingSpringWithDamping:0.3 initialSpringVelocity:0 273 | options:0 animations:^{ 274 | self.chevron.transform = CGAffineTransformMakeRotation(-M_PI_2); 275 | } completion:nil]; 276 | } 277 | 278 | [self performSelector:@selector(presentSelection) withObject:nil afterDelay:delay]; 279 | } 280 | 281 | -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 282 | UITouch* touch = [touches anyObject]; 283 | CGPoint point = [touch locationInView:self]; 284 | if(!significantMovement) { 285 | CGFloat dx = point.x - pointStart.x, dy = point.y - pointStart.y; 286 | CGFloat sqrdist = dx*dx + dy*dy; 287 | significantMovement = sqrdist >= 50 * 50; 288 | if(significantMovement) { 289 | [self presentSelection]; 290 | } 291 | } 292 | 293 | // indicate selection when above cell 294 | NSIndexPath* indexPath = [self IndexPathPoint: point]; 295 | if(indexPath) { 296 | if([indexPath isEqual: hoverIndexPath]) return; 297 | [self clearHoverSelection]; 298 | hoverIndexPath = indexPath; 299 | 300 | UITableViewCell* cell = [self.tableController.tableView cellForRowAtIndexPath:indexPath]; 301 | [cell setSelected:YES animated:NO]; 302 | } else { 303 | [self clearHoverSelection]; 304 | } 305 | } 306 | 307 | -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { 308 | [self removeSelectionAnimated:NO completion:nil]; 309 | } 310 | 311 | -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 312 | UITouch* touch = [touches anyObject]; 313 | CGPoint point = [touch locationInView:self]; 314 | NSIndexPath* indexPath = [self IndexPathPoint: point]; 315 | if(indexPath) { 316 | [self tableView:self.tableController.tableView didSelectRowAtIndexPath:indexPath]; 317 | return; 318 | } 319 | 320 | // if we have had much movement but no selection we just dismiss without going back 321 | if(significantMovement) { 322 | [self removeSelectionAnimated: YES completion:nil]; 323 | return; 324 | } 325 | 326 | // if we touched more than a half second, we assume user wants to use popup 327 | NSTimeInterval secsSinceBegan = [NSDate timeIntervalSinceReferenceDate] - touchStart; 328 | if(secsSinceBegan >= 0.3) return; 329 | 330 | // if we end quick touch we do regular back 331 | [self removeSelectionAnimated:YES completion:^{ 332 | [self.viewController.navigationController popViewControllerAnimated:YES]; 333 | }]; 334 | } 335 | 336 | #pragma mark UITableViewDataSource 337 | 338 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 339 | return self.cells.count; 340 | } 341 | 342 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 343 | return [self.cells objectAtIndex:indexPath.row]; 344 | } 345 | 346 | #pragma mark UITableViewDelegate 347 | 348 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 349 | [self removeSelectionAnimated:YES completion:^{ 350 | 351 | NSArray* viewControllers = self.viewController.navigationController.viewControllers; 352 | NSInteger index = [viewControllers indexOfObject:self.viewController]; 353 | if(index != NSNotFound) { 354 | index -= indexPath.row; 355 | if(index < viewControllers.count) { 356 | UITableViewController* controller = [viewControllers objectAtIndex:index]; 357 | [self.viewController.navigationController popToViewController:controller animated:YES]; 358 | } else if(index == -1) { 359 | // this might be the previous Info, which we then act upon 360 | UITableViewController* root = [viewControllers firstObject]; 361 | NSArray* previousInfo = [root previousInfo]; 362 | void (^action)(void) = [previousInfo objectAtIndex:2]; 363 | if(action) { 364 | // we have action and we execute this when done popping 365 | if(self.viewController == root) { 366 | // this is right now when already at correct level 367 | action(); 368 | } else { 369 | // we use Core Animation to know when pop animation completes 370 | [CATransaction begin]; 371 | [CATransaction setCompletionBlock: action]; 372 | [self.viewController.navigationController popToViewController:root animated:YES]; 373 | [CATransaction commit]; 374 | } 375 | } 376 | } 377 | } 378 | }]; 379 | } 380 | 381 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 382 | cell.backgroundColor = BackgroundColor; 383 | } 384 | 385 | #pragma mark - 386 | 387 | // handle swipe left gesture, which we allow unless popup is shown 388 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { 389 | return self.tableController == nil; 390 | } 391 | 392 | @end 393 | 394 | @implementation AB_MultiBackButtonItem 395 | 396 | -(AB_MultiBackButtonView*)view { 397 | return (AB_MultiBackButtonView*)self.customView; 398 | } 399 | 400 | -(void)refreshTitle { 401 | NSString* title = nil; 402 | 403 | NSArray* viewControllers = self.view.viewController.navigationController.viewControllers; 404 | NSUInteger index = [viewControllers indexOfObject:self.view.viewController]; 405 | if(index > 0 && index != NSNotFound) { 406 | UIViewController* before = [viewControllers objectAtIndex:index-1]; 407 | title = titleForViewController(before); 408 | } 409 | 410 | AB_MultiBackButtonView* view = (AB_MultiBackButtonView*)self.customView; 411 | [view setTitle:title]; 412 | } 413 | 414 | +(AB_MultiBackButtonItem*)backButtonForController:(UIViewController*)controller { 415 | AB_MultiBackButtonView* view = [[AB_MultiBackButtonView alloc] initWithFrame:CGRectMake(0,0, 75, 45)]; 416 | 417 | AB_MultiBackButtonItem* backButton = [[AB_MultiBackButtonItem alloc] initWithCustomView:view]; 418 | backButton.width = view.frame.size.width; 419 | backButton.view.viewController = controller; 420 | view.item = backButton; 421 | [backButton refreshTitle]; 422 | return backButton; 423 | } 424 | 425 | +(void)useForViewController:(UIViewController*)viewController { 426 | // stop early if we already are configured 427 | if([viewController.navigationItem.leftBarButtonItem isKindOfClass:[AB_MultiBackButtonItem class]]) { 428 | return; 429 | } 430 | 431 | // we only set back-button if part of navigation stack and not at the root 432 | NSInteger index = [viewController.navigationController.viewControllers indexOfObject:viewController]; 433 | if(index == NSNotFound) return; // this happens if there is nav-controller, but view-controller is not in stack, 434 | // which will perhaps never happen 435 | if(index == 0) return; // this happens both when there is no nav-controller or when at the root 436 | 437 | UIBarButtonItem* item = [AB_MultiBackButtonItem backButtonForController: viewController]; 438 | __weak AB_MultiBackButtonView* weak_view = (AB_MultiBackButtonView*)item.customView; 439 | 440 | viewController.navigationItem.leftBarButtonItem = item; 441 | viewController.navigationController.interactivePopGestureRecognizer.delegate = weak_view; 442 | viewController.navigationController.interactivePopGestureRecognizer.delaysTouchesBegan = NO; 443 | viewController.navigationController.interactivePopGestureRecognizer.cancelsTouchesInView = YES; 444 | } 445 | 446 | @end 447 | 448 | @implementation UIViewController (AB_MultiBackButtonItem) 449 | 450 | static char associationMultiBackButtonImage, associationMultiBackButtonTitle, associationPreviousInfo; 451 | 452 | -(void)setMultiBackButtonImage:(UIImage *)multiBackButtonImage { 453 | objc_setAssociatedObject (self, &associationMultiBackButtonImage, multiBackButtonImage, OBJC_ASSOCIATION_RETAIN); 454 | } 455 | 456 | -(UIImage*)multiBackButtonImage { 457 | return objc_getAssociatedObject(self, &associationMultiBackButtonImage); 458 | } 459 | 460 | -(void)setMultiBackButtonTitle:(NSString *)multiBackButtonTitle { 461 | objc_setAssociatedObject (self, &associationMultiBackButtonTitle, multiBackButtonTitle, OBJC_ASSOCIATION_RETAIN); 462 | } 463 | 464 | -(NSString*)multiBackButtonTitle { 465 | return objc_getAssociatedObject(self, &associationMultiBackButtonTitle); 466 | } 467 | 468 | -(void)configureMultiBackButton { 469 | [AB_MultiBackButtonItem useForViewController:self]; 470 | } 471 | 472 | -(NSArray*)previousInfo { 473 | return objc_getAssociatedObject(self, &associationPreviousInfo); 474 | } 475 | 476 | -(void)configurePreviousTitle:(NSString*)title image:(UIImage*)image action:(void (^)(void))block { 477 | NSArray* info = nil; 478 | if(title != nil && block != nil) { 479 | info = @[title != nil ? title : nil, 480 | image != nil ? image : [NSNull null], 481 | [block copy]]; 482 | } 483 | 484 | objc_setAssociatedObject (self, &associationPreviousInfo, info, OBJC_ASSOCIATION_RETAIN); 485 | } 486 | 487 | @end 488 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Anders Borum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BE70E3121AA1BD8000922610 /* AB_MultiBackButtonItem.m in Sources */ = {isa = PBXBuildFile; fileRef = BE70E3111AA1BD8000922610 /* AB_MultiBackButtonItem.m */; }; 11 | BED998861AA09BA300F7FE5D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BED998851AA09BA300F7FE5D /* main.m */; }; 12 | BED998891AA09BA300F7FE5D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BED998881AA09BA300F7FE5D /* AppDelegate.m */; }; 13 | BED9988C1AA09BA300F7FE5D /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BED9988B1AA09BA300F7FE5D /* ViewController.m */; }; 14 | BED9988F1AA09BA300F7FE5D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BED9988D1AA09BA300F7FE5D /* Main.storyboard */; }; 15 | BED998911AA09BA300F7FE5D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BED998901AA09BA300F7FE5D /* Images.xcassets */; }; 16 | BED998B01AA0A86900F7FE5D /* SmudgyWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = BED998AF1AA0A86900F7FE5D /* SmudgyWindow.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | BE70E30E1AA1B4DF00922610 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 21 | BE70E3101AA1BD8000922610 /* AB_MultiBackButtonItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AB_MultiBackButtonItem.h; path = ../AB_MultiBackButtonItem.h; sourceTree = ""; }; 22 | BE70E3111AA1BD8000922610 /* AB_MultiBackButtonItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AB_MultiBackButtonItem.m; path = ../AB_MultiBackButtonItem.m; sourceTree = ""; }; 23 | BED998801AA09BA300F7FE5D /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | BED998841AA09BA300F7FE5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | BED998851AA09BA300F7FE5D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 26 | BED998871AA09BA300F7FE5D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 27 | BED998881AA09BA300F7FE5D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 28 | BED9988A1AA09BA300F7FE5D /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 29 | BED9988B1AA09BA300F7FE5D /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 30 | BED9988E1AA09BA300F7FE5D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | BED998901AA09BA300F7FE5D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 32 | BED998AE1AA0A86900F7FE5D /* SmudgyWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmudgyWindow.h; sourceTree = ""; }; 33 | BED998AF1AA0A86900F7FE5D /* SmudgyWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SmudgyWindow.m; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | BED9987D1AA09BA300F7FE5D /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | BED998771AA09BA300F7FE5D = { 48 | isa = PBXGroup; 49 | children = ( 50 | BE70E3101AA1BD8000922610 /* AB_MultiBackButtonItem.h */, 51 | BE70E3111AA1BD8000922610 /* AB_MultiBackButtonItem.m */, 52 | BE70E30E1AA1B4DF00922610 /* README.md */, 53 | BED998821AA09BA300F7FE5D /* Example */, 54 | BED998811AA09BA300F7FE5D /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | BED998811AA09BA300F7FE5D /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | BED998801AA09BA300F7FE5D /* Example.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | BED998821AA09BA300F7FE5D /* Example */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | BED998871AA09BA300F7FE5D /* AppDelegate.h */, 70 | BED998881AA09BA300F7FE5D /* AppDelegate.m */, 71 | BED998AE1AA0A86900F7FE5D /* SmudgyWindow.h */, 72 | BED998AF1AA0A86900F7FE5D /* SmudgyWindow.m */, 73 | BED9988A1AA09BA300F7FE5D /* ViewController.h */, 74 | BED9988B1AA09BA300F7FE5D /* ViewController.m */, 75 | BED9988D1AA09BA300F7FE5D /* Main.storyboard */, 76 | BED998901AA09BA300F7FE5D /* Images.xcassets */, 77 | BED998831AA09BA300F7FE5D /* Supporting Files */, 78 | ); 79 | name = Example; 80 | path = MultiBackButton; 81 | sourceTree = ""; 82 | }; 83 | BED998831AA09BA300F7FE5D /* Supporting Files */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | BED998841AA09BA300F7FE5D /* Info.plist */, 87 | BED998851AA09BA300F7FE5D /* main.m */, 88 | ); 89 | name = "Supporting Files"; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | BED9987F1AA09BA300F7FE5D /* Example */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = BED998A31AA09BA300F7FE5D /* Build configuration list for PBXNativeTarget "Example" */; 98 | buildPhases = ( 99 | BED9987C1AA09BA300F7FE5D /* Sources */, 100 | BED9987D1AA09BA300F7FE5D /* Frameworks */, 101 | BED9987E1AA09BA300F7FE5D /* Resources */, 102 | ); 103 | buildRules = ( 104 | ); 105 | dependencies = ( 106 | ); 107 | name = Example; 108 | productName = MultiBackButton; 109 | productReference = BED998801AA09BA300F7FE5D /* Example.app */; 110 | productType = "com.apple.product-type.application"; 111 | }; 112 | /* End PBXNativeTarget section */ 113 | 114 | /* Begin PBXProject section */ 115 | BED998781AA09BA300F7FE5D /* Project object */ = { 116 | isa = PBXProject; 117 | attributes = { 118 | LastUpgradeCheck = 0610; 119 | TargetAttributes = { 120 | BED9987F1AA09BA300F7FE5D = { 121 | CreatedOnToolsVersion = 6.1.1; 122 | }; 123 | }; 124 | }; 125 | buildConfigurationList = BED9987B1AA09BA300F7FE5D /* Build configuration list for PBXProject "MultiBackButton" */; 126 | compatibilityVersion = "Xcode 3.2"; 127 | developmentRegion = English; 128 | hasScannedForEncodings = 0; 129 | knownRegions = ( 130 | en, 131 | Base, 132 | ); 133 | mainGroup = BED998771AA09BA300F7FE5D; 134 | productRefGroup = BED998811AA09BA300F7FE5D /* Products */; 135 | projectDirPath = ""; 136 | projectRoot = ""; 137 | targets = ( 138 | BED9987F1AA09BA300F7FE5D /* Example */, 139 | ); 140 | }; 141 | /* End PBXProject section */ 142 | 143 | /* Begin PBXResourcesBuildPhase section */ 144 | BED9987E1AA09BA300F7FE5D /* Resources */ = { 145 | isa = PBXResourcesBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | BED9988F1AA09BA300F7FE5D /* Main.storyboard in Resources */, 149 | BED998911AA09BA300F7FE5D /* Images.xcassets in Resources */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXResourcesBuildPhase section */ 154 | 155 | /* Begin PBXSourcesBuildPhase section */ 156 | BED9987C1AA09BA300F7FE5D /* Sources */ = { 157 | isa = PBXSourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | BED9988C1AA09BA300F7FE5D /* ViewController.m in Sources */, 161 | BE70E3121AA1BD8000922610 /* AB_MultiBackButtonItem.m in Sources */, 162 | BED998891AA09BA300F7FE5D /* AppDelegate.m in Sources */, 163 | BED998B01AA0A86900F7FE5D /* SmudgyWindow.m in Sources */, 164 | BED998861AA09BA300F7FE5D /* main.m in Sources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXSourcesBuildPhase section */ 169 | 170 | /* Begin PBXVariantGroup section */ 171 | BED9988D1AA09BA300F7FE5D /* Main.storyboard */ = { 172 | isa = PBXVariantGroup; 173 | children = ( 174 | BED9988E1AA09BA300F7FE5D /* Base */, 175 | ); 176 | name = Main.storyboard; 177 | sourceTree = ""; 178 | }; 179 | /* End PBXVariantGroup section */ 180 | 181 | /* Begin XCBuildConfiguration section */ 182 | BED998A11AA09BA300F7FE5D /* Debug */ = { 183 | isa = XCBuildConfiguration; 184 | buildSettings = { 185 | ALWAYS_SEARCH_USER_PATHS = NO; 186 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 187 | CLANG_CXX_LIBRARY = "libc++"; 188 | CLANG_ENABLE_MODULES = YES; 189 | CLANG_ENABLE_OBJC_ARC = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_CONSTANT_CONVERSION = YES; 192 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 193 | CLANG_WARN_EMPTY_BODY = YES; 194 | CLANG_WARN_ENUM_CONVERSION = YES; 195 | CLANG_WARN_INT_CONVERSION = YES; 196 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 197 | CLANG_WARN_UNREACHABLE_CODE = YES; 198 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 199 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 200 | COPY_PHASE_STRIP = NO; 201 | ENABLE_STRICT_OBJC_MSGSEND = YES; 202 | GCC_C_LANGUAGE_STANDARD = gnu99; 203 | GCC_DYNAMIC_NO_PIC = NO; 204 | GCC_OPTIMIZATION_LEVEL = 0; 205 | GCC_PREPROCESSOR_DEFINITIONS = ( 206 | "DEBUG=1", 207 | "$(inherited)", 208 | ); 209 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 212 | GCC_WARN_UNDECLARED_SELECTOR = YES; 213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 214 | GCC_WARN_UNUSED_FUNCTION = YES; 215 | GCC_WARN_UNUSED_VARIABLE = YES; 216 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 217 | MTL_ENABLE_DEBUG_INFO = YES; 218 | ONLY_ACTIVE_ARCH = YES; 219 | SDKROOT = iphoneos; 220 | }; 221 | name = Debug; 222 | }; 223 | BED998A21AA09BA300F7FE5D /* Release */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 228 | CLANG_CXX_LIBRARY = "libc++"; 229 | CLANG_ENABLE_MODULES = YES; 230 | CLANG_ENABLE_OBJC_ARC = YES; 231 | CLANG_WARN_BOOL_CONVERSION = YES; 232 | CLANG_WARN_CONSTANT_CONVERSION = YES; 233 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 238 | CLANG_WARN_UNREACHABLE_CODE = YES; 239 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 240 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 241 | COPY_PHASE_STRIP = YES; 242 | ENABLE_NS_ASSERTIONS = NO; 243 | ENABLE_STRICT_OBJC_MSGSEND = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu99; 245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 247 | GCC_WARN_UNDECLARED_SELECTOR = YES; 248 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 249 | GCC_WARN_UNUSED_FUNCTION = YES; 250 | GCC_WARN_UNUSED_VARIABLE = YES; 251 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 252 | MTL_ENABLE_DEBUG_INFO = NO; 253 | SDKROOT = iphoneos; 254 | VALIDATE_PRODUCT = YES; 255 | }; 256 | name = Release; 257 | }; 258 | BED998A41AA09BA300F7FE5D /* Debug */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 262 | INFOPLIST_FILE = MultiBackButton/Info.plist; 263 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 264 | PRODUCT_NAME = "$(TARGET_NAME)"; 265 | }; 266 | name = Debug; 267 | }; 268 | BED998A51AA09BA300F7FE5D /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 272 | INFOPLIST_FILE = MultiBackButton/Info.plist; 273 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 274 | PRODUCT_NAME = "$(TARGET_NAME)"; 275 | }; 276 | name = Release; 277 | }; 278 | /* End XCBuildConfiguration section */ 279 | 280 | /* Begin XCConfigurationList section */ 281 | BED9987B1AA09BA300F7FE5D /* Build configuration list for PBXProject "MultiBackButton" */ = { 282 | isa = XCConfigurationList; 283 | buildConfigurations = ( 284 | BED998A11AA09BA300F7FE5D /* Debug */, 285 | BED998A21AA09BA300F7FE5D /* Release */, 286 | ); 287 | defaultConfigurationIsVisible = 0; 288 | defaultConfigurationName = Release; 289 | }; 290 | BED998A31AA09BA300F7FE5D /* Build configuration list for PBXNativeTarget "Example" */ = { 291 | isa = XCConfigurationList; 292 | buildConfigurations = ( 293 | BED998A41AA09BA300F7FE5D /* Debug */, 294 | BED998A51AA09BA300F7FE5D /* Release */, 295 | ); 296 | defaultConfigurationIsVisible = 0; 297 | defaultConfigurationName = Release; 298 | }; 299 | /* End XCConfigurationList section */ 300 | }; 301 | rootObject = BED998781AA09BA300F7FE5D /* Project object */; 302 | } 303 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // 4 | // Anders Borum @palmin 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2015 Anders Borum 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | #import 29 | 30 | @interface AppDelegate : UIResponder 31 | @property (strong, nonatomic) UIWindow *window; 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // 4 | // Anders Borum @palmin 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2015 Anders Borum 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | #import "AppDelegate.h" 29 | #import "SmudgyWindow.h" 30 | 31 | @interface AppDelegate () 32 | 33 | @end 34 | 35 | @implementation AppDelegate 36 | 37 | // You do not want this in a production app, but it is nice to see where user touches the screen when recording a demo. 38 | - (UIWindow *)window { 39 | if (!_window) { 40 | _window = [[SmudgyWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 41 | } 42 | return _window; 43 | } 44 | 45 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 46 | // Override point for customization after application launch. 47 | return YES; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "close-icon@3x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/close.imageset/close-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/close.imageset/close-icon@3x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/commit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "commit-icon@3x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/commit.imageset/commit-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/commit.imageset/commit-icon@3x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/dir-status.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "dir-status-icon@3x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/dir-status.imageset/dir-status-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/dir-status.imageset/dir-status-icon@3x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/dir.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "dir-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/dir.imageset/dir-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/dir.imageset/dir-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/doc.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "doc-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/doc.imageset/doc-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/doc.imageset/doc-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/file.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "file-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/file.imageset/file-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/file.imageset/file-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/img.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "img-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/img.imageset/img-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/img.imageset/img-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/link.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "link-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/link.imageset/link-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/link.imageset/link-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-dir.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "repo-dir-icon@3x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-dir.imageset/repo-dir-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-dir.imageset/repo-dir-icon@3x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-list.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "repo-list-icon@3x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-list.imageset/repo-list-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-list.imageset/repo-list-icon@3x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-status.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "repo-status-icon@3x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-status.imageset/repo-status-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/repo-status.imageset/repo-status-icon@3x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "cell-settings-icon@3x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/settings.imageset/cell-settings-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/settings.imageset/cell-settings-icon@3x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/sound.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "sound-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/sound.imageset/sound-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/sound.imageset/sound-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/src.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "src-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/src.imageset/src-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/src.imageset/src-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/text.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "text-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/text.imageset/text-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/text.imageset/text-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/video.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "video-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/video.imageset/video-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/video.imageset/video-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/web.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "web-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/web.imageset/web-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/web.imageset/web-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/zip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x", 14 | "filename" : "zip-icon@2x.png" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Images.xcassets/icons/zip.imageset/zip-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/MultiBackButton/MultiBackButton/Images.xcassets/icons/zip.imageset/zip-icon@2x.png -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.appliedphasor.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/SmudgyWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // SmudgyWindow.h 3 | // SmudgeKit 4 | // 5 | // Created by Hans Petter Eikemo on 29.09.14. 6 | // Copyright (c) 2014 Ideon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * The SmudgyWindow class is a drop in replacement for UIWindow which 13 | * draws a visual representation of all touch events onto the screen. 14 | */ 15 | @interface SmudgyWindow : UIWindow 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/SmudgyWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // SmudgyWindow.m 3 | // SmudgeKit 4 | // 5 | // Created by Hans Petter Eikemo on 29.09.14. 6 | // Copyright (c) 2014 Ideon. All rights reserved. 7 | // 8 | 9 | #import "SmudgyWindow.h" 10 | 11 | #pragma mark - Private SmudgeLayer implementation - 12 | 13 | @interface SmudgeLayer : CAShapeLayer 14 | 15 | ///Called when the related event changes. Override to update internal state. Position is set automatically by the superlayer. 16 | - (void)appear; 17 | 18 | ///Called after smudge creation when a touch begins. Override to customize appear animation. 19 | - (void)disappear; 20 | 21 | ///Called after chen the related touch event ends or is cancelled. Override to customize disappear animation, the user must call removeFromSuperview on the instance. 22 | - (void)updateWithTouch:(UITouch *)touch; 23 | 24 | @end 25 | 26 | @interface SmudgeLayer () 27 | 28 | @property CGPoint velocity; 29 | @property CGPoint previousPosition; 30 | @property NSTimeInterval previousTime; 31 | 32 | @end 33 | 34 | @implementation SmudgeLayer 35 | 36 | - (instancetype)init 37 | { 38 | if (self = [super init]) { 39 | self.velocity = CGPointZero; 40 | self.contentsScale = [UIScreen mainScreen].scale; 41 | 42 | CGFloat radius = 20.0; 43 | self.path = CGPathCreateWithEllipseInRect(CGRectMake(-radius, -radius, radius*2, radius*2), NULL); 44 | self.fillColor = [UIColor colorWithWhite:0.4 alpha:0.9].CGColor; 45 | self.strokeColor = [UIColor colorWithWhite:0.0 alpha:0.35].CGColor; 46 | self.lineWidth = 1; 47 | 48 | self.shadowPath = self.path; 49 | self.shadowColor = [UIColor blackColor].CGColor; 50 | self.shadowOpacity = 0.15; 51 | self.shadowRadius = 3.0; 52 | self.shadowOffset = CGSizeMake(0.0, 1.0); 53 | } 54 | return self; 55 | } 56 | 57 | - (void)updateWithTouch:(UITouch *)touch { 58 | NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; 59 | NSTimeInterval secs = now - self.previousTime; 60 | if(secs > 0.0 && self.previousTime > 0.0) { 61 | self.velocity = CGPointMake((self.velocity.x + (self.position.x - self.previousPosition.x) / secs) * 0.5 , 62 | (self.velocity.y + (self.position.y - self.previousPosition.y) / secs) * 0.5 ); 63 | } 64 | 65 | self.previousPosition = self.position; 66 | self.previousTime = now; 67 | } 68 | 69 | - (void)appear { 70 | self.previousTime = [NSDate timeIntervalSinceReferenceDate]; 71 | 72 | [CATransaction begin]; 73 | [CATransaction setDisableActions:YES]; 74 | 75 | self.opacity = 0.0; 76 | self.transform = CATransform3DMakeScale(1.5, 1.5, 1); 77 | 78 | [CATransaction commit]; 79 | 80 | [CATransaction begin]; 81 | [CATransaction setAnimationDuration:0.1]; 82 | 83 | self.opacity = 1.0; 84 | self.transform = CATransform3DIdentity; 85 | 86 | [CATransaction commit]; 87 | 88 | } 89 | 90 | - (void)disappear 91 | { 92 | [CATransaction begin]; 93 | [CATransaction setAnimationDuration:0.2]; 94 | 95 | __weak typeof(self) weakSelf = self; 96 | [CATransaction setCompletionBlock:^{ 97 | [weakSelf removeFromSuperlayer]; 98 | }]; 99 | 100 | //if(self.velocity.x * self.velocity.x + self.velocity.y * self.velocity.y <= 100.0) { 101 | // self.velocity = CGPointZero; 102 | //} 103 | //self.velocity = CGPointZero; 104 | 105 | self.opacity = 0.0; 106 | self.transform = CATransform3DMakeScale(1.5, 1.5, 1); 107 | self.position = CGPointMake( self.position.x + self.velocity.x * 0.1 , self.position.y + self.velocity.y * 0.1 ); 108 | 109 | [CATransaction commit]; 110 | 111 | self.velocity = CGPointZero; 112 | self.previousTime = 0; 113 | } 114 | 115 | 116 | @end 117 | 118 | 119 | #pragma mark - Private SmudgeContainer implementation - 120 | 121 | @interface SmudgeContainerLayer : CALayer 122 | 123 | - (void)updateWithEvent:(UIEvent *)event; 124 | 125 | @end 126 | 127 | @interface SmudgeContainerLayer () 128 | 129 | @property (readonly,nonatomic) NSMutableDictionary *touchSmudgeTable; 130 | 131 | @end 132 | 133 | @implementation SmudgeContainerLayer 134 | 135 | - (instancetype)init 136 | { 137 | if (self = [super init]) { 138 | _touchSmudgeTable = @{}.mutableCopy; 139 | } 140 | return self; 141 | } 142 | 143 | - (void)updateWithEvent:(UIEvent *)event 144 | { 145 | for (UITouch *touch in event.allTouches) { 146 | NSValue* touchKey = [NSValue valueWithNonretainedObject:touch]; 147 | SmudgeLayer* smudgeLayer = self.touchSmudgeTable[touchKey]; 148 | 149 | [CATransaction begin]; 150 | [CATransaction setDisableActions:YES]; 151 | 152 | if (!smudgeLayer) { 153 | smudgeLayer = [SmudgeLayer layer]; 154 | self.touchSmudgeTable[touchKey] = smudgeLayer; 155 | [self addSublayer:smudgeLayer]; 156 | } 157 | 158 | smudgeLayer.position = [touch locationInView:nil]; 159 | [smudgeLayer updateWithTouch:touch]; 160 | 161 | [CATransaction commit]; 162 | 163 | switch (touch.phase) { 164 | case UITouchPhaseBegan: 165 | [smudgeLayer appear]; 166 | //NSLog(@"UITouchPhaseBegan"); 167 | break; 168 | case UITouchPhaseCancelled: 169 | case UITouchPhaseEnded: 170 | [smudgeLayer disappear]; 171 | [self.touchSmudgeTable removeObjectForKey:touchKey]; 172 | break; 173 | case UITouchPhaseStationary: 174 | //NSLog(@"UITouchPhaseStationary"); 175 | break; 176 | default: 177 | break; 178 | } 179 | 180 | } 181 | } 182 | 183 | @end 184 | 185 | 186 | #pragma mark - SmudgyWindow - 187 | 188 | @interface SmudgyWindow () 189 | 190 | @property (readonly,nonatomic) SmudgeContainerLayer *smudgeContainer; 191 | 192 | @end 193 | 194 | @implementation SmudgyWindow 195 | 196 | - (void)sendEvent:(UIEvent *)event 197 | { 198 | [super sendEvent:event]; 199 | [self renderTouchesForEvent:event]; 200 | } 201 | 202 | - (void)renderTouchesForEvent:(UIEvent *)event 203 | { 204 | if (!self.smudgeContainer) { 205 | _smudgeContainer = [SmudgeContainerLayer layer]; 206 | [self.layer addSublayer:self.smudgeContainer]; 207 | } 208 | [self.smudgeContainer updateWithEvent:event]; 209 | } 210 | 211 | @end 212 | 213 | 214 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // 4 | // Anders Borum @palmin 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2015 Anders Borum 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | #import 29 | 30 | @interface ViewController : UITableViewController 31 | 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // 4 | // Anders Borum @palmin 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright (c) 2015 Anders Borum 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | #import "ViewController.h" 29 | #import "AB_MultiBackButtonItem.h" 30 | 31 | @interface ViewController () { 32 | NSMutableArray* titles; 33 | NSMutableArray* images; 34 | } 35 | 36 | @end 37 | 38 | @implementation ViewController 39 | 40 | +(NSString*)randomTitle { 41 | NSArray* nouns = @[@"exchange",@"planes",@"afterthought",@"ladybug",@"meat",@"snails",@"bomb",@"discussion", @"reward", 42 | @"nerve",@"payment",@"walk",@"moon",@"boy",@"shoe",@"cushion",@"car",@"system",@"shop", @"current", 43 | @"stamp",@"memory",@"engine",@"sponge",@"arithmetic",@"control",@"scarf",@"visitor",@"idea",@"yard"]; 44 | int index = rand() % nouns.count; 45 | return nouns[index]; 46 | } 47 | 48 | +(NSString*)randomImageName { 49 | NSArray* names = @[@"settings", @"close", @"commit", @"dir", @"dir-status", @"doc", @"file", @"img", @"web", 50 | @"link", @"repo-dir", @"repo-list", @"repo-status", @"sound", @"src", @"text", @"video", @"zip"]; 51 | int index = rand() % names.count; 52 | return names[index]; 53 | } 54 | 55 | - (void)viewDidLoad { 56 | [super viewDidLoad]; 57 | 58 | // configure back button 59 | [self configureMultiBackButton]; 60 | 61 | // Create random cell data 62 | titles = [NSMutableArray new]; 63 | images = [NSMutableArray new]; 64 | 65 | int count = 5 + rand() % 20; 66 | for(int k = 0; k < count; ++k) { 67 | NSString* title = [ViewController randomTitle]; 68 | [titles addObject:title]; 69 | 70 | UIImage* image = [UIImage imageNamed:[ViewController randomImageName]]; 71 | [images addObject:image]; 72 | } 73 | 74 | } 75 | 76 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 77 | return titles.count; 78 | } 79 | 80 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 81 | UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 82 | cell.textLabel.text = titles[indexPath.row]; 83 | cell.imageView.image = images[indexPath.row]; 84 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 85 | return cell; 86 | } 87 | 88 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 89 | UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; 90 | UIViewController* controller = [storyboard instantiateViewControllerWithIdentifier:@"table"]; 91 | 92 | controller.title = titles[indexPath.row]; 93 | controller.multiBackButtonImage = images[indexPath.row]; 94 | [self.navigationController pushViewController:controller animated:YES]; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /MultiBackButton/MultiBackButton/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MultiBackButton 4 | // 5 | // Created by Anders Borum on 27/02/15. 6 | // 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ios-multi-back-button 2 | 3 | Replacement for the built-in UINavigationController back-button that allows going back multiple 4 | levels. It requires iOS 8 as it uses popover presentation of view-controllers on iPhone. 5 | 6 | Users long-tap the back-button to display a table with the viewControllers in the current navigation 7 | stack. They either lift the finger and tap a table-cell or move it above the table and release above 8 | the cell for the view-controller you want to navigate to. If they do a regular tap, the back-button 9 | will bring you back a single level as usual. 10 | 11 | 12 | 13 | 14 | 15 | You configure a view-controller to use there buttons with something like 16 | 17 | ```` 18 | #import "AB_MultiBackButtonItem.h" 19 | ⋮ 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | [self configureMultiBackButton]; 24 | self.multiBackButtonImage = [UIImage imageNamed: @"myImage"]; 25 | } 26 | ```` 27 | 28 | You just need to include `AB_MultiBackButtonItem.h` and `AB_MultiBackButtonItem.m` in your project 29 | and ``#import "AB_MultiBackButtonItem.h"``. 30 | 31 | Because I have no reliable way to determine how much space is available in the navigation bar, the 32 | back-button never shows the title of the previous view controller. I would much rather it included a title 33 | if there was sufficient space for this and you can find some traces of attempts at this. Suggestions on 34 | how to do it are very welcome. 35 | 36 | You can reach me as [@palmin](https://twitter.com/palmin) on Twitter 37 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palmin/ios-multi-back-button/16affb6f7865996582957d37a04d355a0ee251de/example.gif --------------------------------------------------------------------------------