PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Three20UINavigator/Sources/TTBaseNavigator.m

https://github.com/GetMoPix/three20
Objective C | 1014 lines | 660 code | 208 blank | 146 comment | 184 complexity | 2bb2df55b87774e32f93ea314c1ba9f2 MD5 | raw file
  1. //
  2. // Copyright 2009-2011 Facebook
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "Three20UINavigator/TTBaseNavigator.h"
  17. // UINavigator
  18. #import "Three20UINavigator/TTGlobalNavigatorMetrics.h"
  19. #import "Three20UINavigator/TTNavigatorDelegate.h"
  20. #import "Three20UINavigator/TTNavigatorRootContainer.h"
  21. #import "Three20UINavigator/TTBaseNavigationController.h"
  22. #import "Three20UINavigator/TTURLAction.h"
  23. #import "Three20UINavigator/TTURLMap.h"
  24. #import "Three20UINavigator/TTURLNavigatorPattern.h"
  25. #import "Three20UINavigator/UIViewController+TTNavigator.h"
  26. // UINavigator (private)
  27. #import "Three20UINavigator/private/TTBaseNavigatorInternal.h"
  28. // UICommon
  29. #import "Three20UICommon/UIView+TTUICommon.h"
  30. #import "Three20UICommon/UIViewControllerAdditions.h"
  31. // Core
  32. #import "Three20Core/TTGlobalCore.h"
  33. #import "Three20Core/TTCorePreprocessorMacros.h"
  34. #import "Three20Core/TTDebug.h"
  35. #import "Three20Core/TTDebugFlags.h"
  36. #import "Three20Core/NSDateAdditions.h"
  37. static TTBaseNavigator* gNavigator = nil;
  38. static NSString* kNavigatorHistoryKey = @"TTNavigatorHistory";
  39. static NSString* kNavigatorHistoryTimeKey = @"TTNavigatorHistoryTime";
  40. static NSString* kNavigatorHistoryImportantKey = @"TTNavigatorHistoryImportant";
  41. #ifdef __IPHONE_4_0
  42. UIKIT_EXTERN NSString *const UIApplicationDidEnterBackgroundNotification
  43. __attribute__((weak_import));
  44. UIKIT_EXTERN NSString *const UIApplicationWillEnterForegroundNotification
  45. __attribute__((weak_import));
  46. #endif
  47. ///////////////////////////////////////////////////////////////////////////////////////////////////
  48. ///////////////////////////////////////////////////////////////////////////////////////////////////
  49. ///////////////////////////////////////////////////////////////////////////////////////////////////
  50. @implementation TTBaseNavigator
  51. @synthesize delegate = _delegate;
  52. @synthesize URLMap = _URLMap;
  53. @synthesize window = _window;
  54. @synthesize rootViewController = _rootViewController;
  55. @synthesize persistenceKey = _persistenceKey;
  56. @synthesize persistenceExpirationAge = _persistenceExpirationAge;
  57. @synthesize persistenceMode = _persistenceMode;
  58. @synthesize supportsShakeToReload = _supportsShakeToReload;
  59. @synthesize opensExternalURLs = _opensExternalURLs;
  60. @synthesize rootContainer = _rootContainer;
  61. ///////////////////////////////////////////////////////////////////////////////////////////////////
  62. - (id)init {
  63. self = [super init];
  64. if (self) {
  65. _URLMap = [[TTURLMap alloc] init];
  66. _persistenceMode = TTNavigatorPersistenceModeNone;
  67. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  68. [center addObserver:self
  69. selector:@selector(applicationWillLeaveForeground:)
  70. name:UIApplicationWillTerminateNotification
  71. object:nil];
  72. #ifdef __IPHONE_4_0
  73. if (nil != &UIApplicationDidEnterBackgroundNotification) {
  74. [center addObserver:self
  75. selector:@selector(applicationWillLeaveForeground:)
  76. name:UIApplicationDidEnterBackgroundNotification
  77. object:nil];
  78. }
  79. #endif
  80. }
  81. return self;
  82. }
  83. ///////////////////////////////////////////////////////////////////////////////////////////////////
  84. - (void)dealloc {
  85. [[NSNotificationCenter defaultCenter] removeObserver:self];
  86. _delegate = nil;
  87. TT_RELEASE_SAFELY(_window);
  88. TT_RELEASE_SAFELY(_rootViewController);
  89. TT_RELEASE_SAFELY(_popoverController);
  90. TT_RELEASE_SAFELY(_delayedControllers);
  91. TT_RELEASE_SAFELY(_URLMap);
  92. TT_RELEASE_SAFELY(_persistenceKey);
  93. [super dealloc];
  94. }
  95. ///////////////////////////////////////////////////////////////////////////////////////////////////
  96. + (TTBaseNavigator*)globalNavigator {
  97. return gNavigator;
  98. }
  99. ///////////////////////////////////////////////////////////////////////////////////////////////////
  100. + (void)setGlobalNavigator:(TTBaseNavigator*)navigator {
  101. if (gNavigator != navigator) {
  102. [gNavigator release];
  103. gNavigator = [navigator retain];
  104. }
  105. }
  106. ///////////////////////////////////////////////////////////////////////////////////////////////////
  107. + (TTBaseNavigator*)navigatorForView:(UIView*)view {
  108. // If this is called with a UIBarButtonItem, we can't traverse a view hierarchy to find the
  109. // navigator, return the global navigator as a fallback.
  110. if (![view isKindOfClass:[UIView class]]) {
  111. return [TTBaseNavigator globalNavigator];
  112. }
  113. id<TTNavigatorRootContainer> container = nil;
  114. UIViewController* controller = nil; // The iterator.
  115. UIViewController* childController = nil; // The last iterated controller.
  116. for (controller = view.viewController;
  117. nil != controller;
  118. controller = controller.parentViewController) {
  119. if ([controller conformsToProtocol:@protocol(TTNavigatorRootContainer)]) {
  120. container = (id<TTNavigatorRootContainer>)controller;
  121. break;
  122. }
  123. childController = controller;
  124. }
  125. TTBaseNavigator* navigator = [container getNavigatorForController:childController];
  126. if (nil == navigator) {
  127. navigator = [TTBaseNavigator globalNavigator];
  128. }
  129. return navigator;
  130. }
  131. ///////////////////////////////////////////////////////////////////////////////////////////////////
  132. ///////////////////////////////////////////////////////////////////////////////////////////////////
  133. #pragma mark -
  134. #pragma mark Private
  135. ///////////////////////////////////////////////////////////////////////////////////////////////////
  136. /**
  137. * The goal of this method is to return the currently visible view controller, referred to here as
  138. * the "front" view controller. Tab bar controllers and navigation controllers are special-cased,
  139. * and when a controller has a modal controller, the method recurses as necessary.
  140. *
  141. * @private
  142. */
  143. + (UIViewController*)frontViewControllerForController:(UIViewController*)controller {
  144. if ([controller isKindOfClass:[UITabBarController class]]) {
  145. UITabBarController* tabBarController = (UITabBarController*)controller;
  146. if (tabBarController.selectedViewController) {
  147. controller = tabBarController.selectedViewController;
  148. } else {
  149. controller = [tabBarController.viewControllers objectAtIndex:0];
  150. }
  151. } else if ([controller isKindOfClass:[UINavigationController class]]) {
  152. UINavigationController* navController = (UINavigationController*)controller;
  153. controller = navController.topViewController;
  154. }
  155. if (controller.modalViewController) {
  156. return [TTBaseNavigator frontViewControllerForController:controller.modalViewController];
  157. } else {
  158. return controller;
  159. }
  160. }
  161. ///////////////////////////////////////////////////////////////////////////////////////////////////
  162. /**
  163. * Similar to frontViewControllerForController, this method attempts to return the "front"
  164. * navigation controller. This makes the assumption that a tab bar controller has navigation
  165. * controllers as children.
  166. * If the root controller isn't a tab controller or a navigation controller, then no navigation
  167. * controller will be returned.
  168. *
  169. * @private
  170. */
  171. - (UINavigationController*)frontNavigationController {
  172. if ([_rootViewController isKindOfClass:[UITabBarController class]]) {
  173. UITabBarController* tabBarController = (UITabBarController*)_rootViewController;
  174. if (tabBarController.selectedViewController) {
  175. return (UINavigationController*)tabBarController.selectedViewController;
  176. } else {
  177. return (UINavigationController*)[tabBarController.viewControllers objectAtIndex:0];
  178. }
  179. } else if ([_rootViewController isKindOfClass:[UINavigationController class]]) {
  180. return (UINavigationController*)_rootViewController;
  181. } else {
  182. return nil;
  183. }
  184. }
  185. ///////////////////////////////////////////////////////////////////////////////////////////////////
  186. - (UIViewController*)frontViewController {
  187. UINavigationController* navController = self.frontNavigationController;
  188. if (navController) {
  189. return [TTBaseNavigator frontViewControllerForController:navController];
  190. } else {
  191. return [TTBaseNavigator frontViewControllerForController:_rootViewController];
  192. }
  193. }
  194. ///////////////////////////////////////////////////////////////////////////////////////////////////
  195. - (void)setRootViewController:(UIViewController*)controller {
  196. if (controller != _rootViewController) {
  197. [_rootViewController release];
  198. _rootViewController = [controller retain];
  199. if (nil != _rootContainer) {
  200. [_rootContainer navigator:self setRootViewController:_rootViewController];
  201. } else {
  202. [self.window addSubview:_rootViewController.view];
  203. }
  204. }
  205. }
  206. ///////////////////////////////////////////////////////////////////////////////////////////////////
  207. /**
  208. * Prepare the given controller's parent controller and return it. Ensures that the parent
  209. * controller exists in the navigation hierarchy. If it doesn't exist, and the given controller
  210. * isn't a container, then a UINavigationController will be made the root controller.
  211. *
  212. * @private
  213. */
  214. - (UIViewController*)parentForController: (UIViewController*)controller
  215. isContainer: (BOOL)isContainer
  216. parentURLPath: (NSString*)parentURLPath {
  217. if (controller == _rootViewController) {
  218. return nil;
  219. } else {
  220. // If this is the first controller, and it is not a "container", forcibly put
  221. // a navigation controller at the root of the controller hierarchy.
  222. if (nil == _rootViewController && !isContainer) {
  223. [self setRootViewController:[[[[self navigationControllerClass] alloc] init] autorelease]];
  224. }
  225. if (nil != parentURLPath) {
  226. return [self openURLAction:[TTURLAction actionWithURLPath:parentURLPath]];
  227. } else {
  228. UIViewController* parent = self.topViewController;
  229. if (parent != controller) {
  230. return parent;
  231. } else {
  232. return nil;
  233. }
  234. }
  235. }
  236. }
  237. ///////////////////////////////////////////////////////////////////////////////////////////////////
  238. /**
  239. * A modal controller is a view controller that is presented over another controller and hides
  240. * the original controller completely. Classic examples include the Safari login controller when
  241. * authenticating on a network, creating a new contact in Contacts, and the Camera controller.
  242. *
  243. * If the controller that is being presented is not a UINavigationController, then a
  244. * UINavigationController is created and the controller is pushed onto the navigation controller.
  245. * The navigation controller is then displayed instead.
  246. *
  247. * @private
  248. */
  249. - (void)presentModalController: (UIViewController*)controller
  250. parentController: (UIViewController*)parentController
  251. animated: (BOOL)animated
  252. transition: (NSInteger)transition {
  253. controller.modalTransitionStyle = transition;
  254. if ([controller isKindOfClass:[UINavigationController class]]) {
  255. [parentController presentModalViewController: controller
  256. animated: animated];
  257. } else {
  258. UINavigationController* navController = [[[[self navigationControllerClass] alloc] init]
  259. autorelease];
  260. navController.modalTransitionStyle = transition;
  261. navController.modalPresentationStyle = controller.modalPresentationStyle;
  262. [navController pushViewController: controller
  263. animated: NO];
  264. [parentController presentModalViewController: navController
  265. animated: animated];
  266. }
  267. }
  268. ///////////////////////////////////////////////////////////////////////////////////////////////////
  269. - (void)presentPopoverController: (UIViewController*)controller
  270. sourceButton: (UIBarButtonItem*)sourceButton
  271. sourceView: (UIView*)sourceView
  272. sourceRect: (CGRect)sourceRect
  273. animated: (BOOL)animated {
  274. TTDASSERT(nil != sourceButton || nil != sourceView);
  275. if (nil == sourceButton && nil == sourceView) {
  276. return;
  277. }
  278. if (nil != _popoverController) {
  279. [_popoverController dismissPopoverAnimated:animated];
  280. TT_RELEASE_SAFELY(_popoverController);
  281. }
  282. _popoverController = [[UIPopoverController alloc] initWithContentViewController:controller];
  283. _popoverController.delegate = self;
  284. if (nil != sourceButton) {
  285. [_popoverController presentPopoverFromBarButtonItem: sourceButton
  286. permittedArrowDirections: UIPopoverArrowDirectionAny
  287. animated: animated];
  288. } else {
  289. [_popoverController presentPopoverFromRect: sourceRect
  290. inView: sourceView
  291. permittedArrowDirections: UIPopoverArrowDirectionAny
  292. animated: animated];
  293. }
  294. }
  295. ///////////////////////////////////////////////////////////////////////////////////////////////////
  296. /**
  297. * @return NO if the controller already has a super controller and is simply made visible.
  298. * YES if the controller is the new root or if it did not have a super controller.
  299. *
  300. * @private
  301. */
  302. - (BOOL)presentController: (UIViewController*)controller
  303. parentController: (UIViewController*)parentController
  304. mode: (TTNavigationMode)mode
  305. action: (TTURLAction*)action {
  306. BOOL didPresentNewController = YES;
  307. if (nil == _rootViewController) {
  308. [self setRootViewController:controller];
  309. } else {
  310. UIViewController* previousSuper = controller.superController;
  311. if (nil != previousSuper) {
  312. if (previousSuper != parentController) {
  313. // The controller already exists, so we just need to make it visible
  314. for (UIViewController* superController = previousSuper; controller; ) {
  315. UIViewController* nextSuper = superController.superController;
  316. [superController bringControllerToFront: controller
  317. animated: !nextSuper];
  318. controller = superController;
  319. superController = nextSuper;
  320. }
  321. }
  322. didPresentNewController = NO;
  323. } else if (nil != parentController) {
  324. [self presentDependantController: controller
  325. parentController: parentController
  326. mode: mode
  327. action: action];
  328. }
  329. }
  330. return didPresentNewController;
  331. }
  332. ///////////////////////////////////////////////////////////////////////////////////////////////////
  333. - (BOOL)presentController: (UIViewController*)controller
  334. parentURLPath: (NSString*)parentURLPath
  335. withPattern: (TTURLNavigatorPattern*)pattern
  336. action: (TTURLAction*)action {
  337. BOOL didPresentNewController = NO;
  338. if (nil != controller) {
  339. UIViewController* topViewController = self.topViewController;
  340. if (controller != topViewController) {
  341. UIViewController* parentController = [self parentForController: controller
  342. isContainer: [controller
  343. canContainControllers]
  344. parentURLPath: parentURLPath
  345. ? parentURLPath
  346. : pattern.parentURL];
  347. if (nil != parentController && parentController != topViewController) {
  348. [self presentController: parentController
  349. parentController: nil
  350. mode: TTNavigationModeNone
  351. action: [TTURLAction actionWithURLPath:nil]];
  352. }
  353. didPresentNewController = [self
  354. presentController: controller
  355. parentController: parentController
  356. mode: pattern.navigationMode
  357. action: action];
  358. }
  359. }
  360. return didPresentNewController;
  361. }
  362. ///////////////////////////////////////////////////////////////////////////////////////////////////
  363. /**
  364. * @protected
  365. */
  366. - (void)didRestoreController:(UIViewController*)controller {
  367. // Purposefully empty implementation. Meant to be overridden.
  368. }
  369. ///////////////////////////////////////////////////////////////////////////////////////////////////
  370. - (UIViewController*)openURLAction:(TTURLAction*)action {
  371. if (nil == action || nil == action.urlPath) {
  372. return nil;
  373. }
  374. // We may need to modify the urlPath, so let's create a local copy.
  375. NSString* urlPath = action.urlPath;
  376. NSURL* theURL = [NSURL URLWithString:urlPath];
  377. if ([_URLMap isAppURL:theURL]) {
  378. [[UIApplication sharedApplication] openURL:theURL];
  379. return nil;
  380. }
  381. if (nil == theURL.scheme) {
  382. if (nil != theURL.fragment) {
  383. urlPath = [self.URL stringByAppendingString:urlPath];
  384. } else {
  385. urlPath = [@"http://" stringByAppendingString:urlPath];
  386. }
  387. theURL = [NSURL URLWithString:urlPath];
  388. }
  389. // Allows the delegate to prevent opening this URL
  390. if ([_delegate respondsToSelector:@selector(navigator:shouldOpenURL:)]) {
  391. if (![_delegate navigator:self shouldOpenURL:theURL]) {
  392. return nil;
  393. }
  394. }
  395. // Allows the delegate to modify the URL to be opened, as well as reject it. This delegate
  396. // method is intended to supersede -navigator:shouldOpenURL:.
  397. if ([_delegate respondsToSelector:@selector(navigator:URLToOpen:)]) {
  398. NSURL *newURL = [_delegate navigator:self URLToOpen:theURL];
  399. if (!newURL) {
  400. return nil;
  401. } else {
  402. theURL = newURL;
  403. urlPath = newURL.absoluteString;
  404. }
  405. }
  406. if (action.withDelay) {
  407. [self beginDelay];
  408. }
  409. TTDCONDITIONLOG(TTDFLAG_NAVIGATOR, @"OPENING URL %@", urlPath);
  410. TTURLNavigatorPattern* pattern = nil;
  411. UIViewController* controller = [self viewControllerForURL: urlPath
  412. query: action.query
  413. pattern: &pattern];
  414. if (nil != controller) {
  415. if (nil != action.state) {
  416. [controller restoreView:action.state];
  417. controller.frozenState = action.state;
  418. [self didRestoreController:controller];
  419. }
  420. if ([_delegate respondsToSelector:@selector(navigator:willOpenURL:inViewController:)]) {
  421. [_delegate navigator: self
  422. willOpenURL: theURL
  423. inViewController: controller];
  424. }
  425. action.transition = action.transition ? action.transition : pattern.transition;
  426. BOOL wasNew = [self presentController: controller
  427. parentURLPath: action.parentURLPath
  428. withPattern: pattern
  429. action: action];
  430. if (action.withDelay && !wasNew) {
  431. [self cancelDelay];
  432. }
  433. } else if (_opensExternalURLs) {
  434. if ([_delegate respondsToSelector:@selector(navigator:willOpenURL:inViewController:)]) {
  435. [_delegate navigator: self
  436. willOpenURL: theURL
  437. inViewController: nil];
  438. }
  439. [[UIApplication sharedApplication] openURL:theURL];
  440. }
  441. return controller;
  442. }
  443. ///////////////////////////////////////////////////////////////////////////////////////////////////
  444. /**
  445. * @protected
  446. */
  447. - (Class)windowClass {
  448. return [UIWindow class];
  449. }
  450. ///////////////////////////////////////////////////////////////////////////////////////////////////
  451. ///////////////////////////////////////////////////////////////////////////////////////////////////
  452. #pragma mark -
  453. #pragma mark NSNotifications
  454. ///////////////////////////////////////////////////////////////////////////////////////////////////
  455. - (void)applicationWillLeaveForeground:(void *)ignored {
  456. if (_persistenceMode) {
  457. [self persistViewControllers];
  458. }
  459. }
  460. ///////////////////////////////////////////////////////////////////////////////////////////////////
  461. ///////////////////////////////////////////////////////////////////////////////////////////////////
  462. #pragma mark -
  463. #pragma mark Public
  464. ///////////////////////////////////////////////////////////////////////////////////////////////////
  465. - (UIWindow*)window {
  466. if (nil == _window) {
  467. UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow;
  468. if (nil != keyWindow) {
  469. _window = [keyWindow retain];
  470. } else {
  471. _window = [[[self windowClass] alloc] initWithFrame:TTScreenBounds()];
  472. [_window makeKeyAndVisible];
  473. }
  474. }
  475. return _window;
  476. }
  477. ///////////////////////////////////////////////////////////////////////////////////////////////////
  478. - (UIViewController*)visibleViewController {
  479. UIViewController* controller = _rootViewController;
  480. while (nil != controller) {
  481. UIViewController* child = controller.modalViewController;
  482. if (nil == child) {
  483. child = [self getVisibleChildController:controller];
  484. }
  485. if (nil != child) {
  486. controller = child;
  487. } else {
  488. return controller;
  489. }
  490. }
  491. return nil;
  492. }
  493. ///////////////////////////////////////////////////////////////////////////////////////////////////
  494. - (UIViewController*)topViewController {
  495. UIViewController* controller = _rootViewController;
  496. while (controller) {
  497. UIViewController* child = controller.popupViewController;
  498. if (!child || ![child canBeTopViewController]) {
  499. child = controller.modalViewController;
  500. }
  501. if (!child) {
  502. child = controller.topSubcontroller;
  503. }
  504. if (child) {
  505. if (child == _rootViewController) {
  506. return child;
  507. } else {
  508. controller = child;
  509. }
  510. } else {
  511. return controller;
  512. }
  513. }
  514. return nil;
  515. }
  516. ///////////////////////////////////////////////////////////////////////////////////////////////////
  517. - (NSString*)URL {
  518. return self.topViewController.navigatorURL;
  519. }
  520. ///////////////////////////////////////////////////////////////////////////////////////////////////
  521. - (void)setURL:(NSString*)URLPath {
  522. [self openURLAction:[[TTURLAction actionWithURLPath: URLPath]
  523. applyAnimated: YES]];
  524. }
  525. ///////////////////////////////////////////////////////////////////////////////////////////////////
  526. - (UIViewController*)openURLs:(NSString*)URL,... {
  527. UIViewController* controller = nil;
  528. va_list ap;
  529. va_start(ap, URL);
  530. while (URL) {
  531. controller = [self openURLAction:[TTURLAction actionWithURLPath:URL]];
  532. URL = va_arg(ap, id);
  533. }
  534. va_end(ap);
  535. return controller;
  536. }
  537. ///////////////////////////////////////////////////////////////////////////////////////////////////
  538. - (UIViewController*)viewControllerForURL:(NSString*)URL {
  539. return [self viewControllerForURL:URL query:nil pattern:nil];
  540. }
  541. ///////////////////////////////////////////////////////////////////////////////////////////////////
  542. - (UIViewController*)viewControllerForURL:(NSString*)URL query:(NSDictionary*)query {
  543. return [self viewControllerForURL:URL query:query pattern:nil];
  544. }
  545. ///////////////////////////////////////////////////////////////////////////////////////////////////
  546. - (UIViewController*)viewControllerForURL: (NSString*)URL
  547. query: (NSDictionary*)query
  548. pattern: (TTURLNavigatorPattern**)pattern {
  549. NSRange fragmentRange = [URL rangeOfString:@"#" options:NSBackwardsSearch];
  550. if (fragmentRange.location != NSNotFound) {
  551. NSString* baseURL = [URL substringToIndex:fragmentRange.location];
  552. if ([self.URL isEqualToString:baseURL]) {
  553. UIViewController* controller = self.visibleViewController;
  554. id result = [_URLMap dispatchURL:URL toTarget:controller query:query];
  555. if ([result isKindOfClass:[UIViewController class]]) {
  556. return result;
  557. } else {
  558. return controller;
  559. }
  560. } else {
  561. id object = [_URLMap objectForURL:baseURL query:nil pattern:pattern];
  562. if (object) {
  563. id result = [_URLMap dispatchURL:URL toTarget:object query:query];
  564. if ([result isKindOfClass:[UIViewController class]]) {
  565. return result;
  566. } else {
  567. return object;
  568. }
  569. } else {
  570. return nil;
  571. }
  572. }
  573. }
  574. id object = [_URLMap objectForURL:URL query:query pattern:pattern];
  575. if (object) {
  576. UIViewController* controller = object;
  577. controller.originalNavigatorURL = URL;
  578. if (_delayCount) {
  579. if (!_delayedControllers) {
  580. _delayedControllers = [[NSMutableArray alloc] initWithObjects:controller,nil];
  581. } else {
  582. [_delayedControllers addObject:controller];
  583. }
  584. }
  585. return controller;
  586. } else {
  587. return nil;
  588. }
  589. }
  590. ///////////////////////////////////////////////////////////////////////////////////////////////////
  591. - (BOOL)isDelayed {
  592. return _delayCount > 0;
  593. }
  594. ///////////////////////////////////////////////////////////////////////////////////////////////////
  595. - (void)beginDelay {
  596. ++_delayCount;
  597. }
  598. ///////////////////////////////////////////////////////////////////////////////////////////////////
  599. - (void)endDelay {
  600. if (_delayCount && !--_delayCount) {
  601. for (UIViewController* controller in _delayedControllers) {
  602. [controller delayDidEnd];
  603. }
  604. TT_RELEASE_SAFELY(_delayedControllers);
  605. }
  606. }
  607. ///////////////////////////////////////////////////////////////////////////////////////////////////
  608. - (void)cancelDelay {
  609. if (_delayCount && !--_delayCount) {
  610. TT_RELEASE_SAFELY(_delayedControllers);
  611. }
  612. }
  613. ///////////////////////////////////////////////////////////////////////////////////////////////////
  614. - (void)persistViewControllers {
  615. NSMutableArray* path = [NSMutableArray array];
  616. [self persistController:_rootViewController path:path];
  617. TTDCONDITIONLOG(TTDFLAG_NAVIGATOR, @"DEBUG PERSIST %@", path);
  618. // Check if any of the paths were "important", and therefore unable to expire
  619. BOOL important = NO;
  620. for (NSDictionary* state in path) {
  621. if ([state objectForKey:@"__important__"]) {
  622. important = YES;
  623. break;
  624. }
  625. }
  626. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  627. if (path.count) {
  628. NSDate* historyTime = [NSDate date];
  629. NSNumber* historyImportant = [NSNumber numberWithInt:important];
  630. if (TTIsStringWithAnyText(_persistenceKey)) {
  631. NSDictionary* persistedValues = [NSDictionary dictionaryWithObjectsAndKeys:
  632. path, kNavigatorHistoryKey,
  633. historyTime, kNavigatorHistoryTimeKey,
  634. historyImportant, kNavigatorHistoryImportantKey,
  635. nil];
  636. [defaults setObject:persistedValues forKey:_persistenceKey];
  637. } else {
  638. [defaults setObject:path forKey:kNavigatorHistoryKey];
  639. [defaults setObject:historyTime forKey:kNavigatorHistoryTimeKey];
  640. [defaults setObject:historyImportant forKey:kNavigatorHistoryImportantKey];
  641. }
  642. [defaults synchronize];
  643. } else {
  644. [self resetDefaults];
  645. }
  646. }
  647. ///////////////////////////////////////////////////////////////////////////////////////////////////
  648. - (UIViewController*)restoreViewControllers {
  649. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  650. NSDate* timestamp = nil;
  651. NSArray* path = nil;
  652. BOOL important = NO;
  653. if (TTIsStringWithAnyText(_persistenceKey)) {
  654. NSDictionary* persistedValues = [defaults objectForKey:_persistenceKey];
  655. timestamp = [persistedValues objectForKey:kNavigatorHistoryTimeKey];
  656. path = [persistedValues objectForKey:kNavigatorHistoryKey];
  657. important = [[persistedValues objectForKey:kNavigatorHistoryImportantKey] boolValue];
  658. } else {
  659. timestamp = [defaults objectForKey:kNavigatorHistoryTimeKey];
  660. path = [defaults objectForKey:kNavigatorHistoryKey];
  661. important = [[defaults objectForKey:kNavigatorHistoryImportantKey] boolValue];
  662. }
  663. TTDCONDITIONLOG(TTDFLAG_NAVIGATOR, @"DEBUG RESTORE %@ FROM %@",
  664. path, [timestamp formatRelativeTime]);
  665. BOOL expired = _persistenceExpirationAge
  666. && -timestamp.timeIntervalSinceNow > _persistenceExpirationAge;
  667. if (expired && !important) {
  668. return nil;
  669. }
  670. UIViewController* controller = nil;
  671. BOOL passedContainer = NO;
  672. for (NSDictionary* state in path) {
  673. NSString* URL = [state objectForKey:@"__navigatorURL__"];
  674. controller = [self openURLAction:[[TTURLAction actionWithURLPath: URL]
  675. applyState: state]];
  676. // Stop if we reach a model view controller whose model could not be synchronously loaded.
  677. // That is because the controller after it may depend on the data it could not load, so
  678. // we'd better not risk opening more controllers that may not be able to function.
  679. // if ([controller isKindOfClass:[TTModelViewController class]]) {
  680. // TTModelViewController* modelViewController = (TTModelViewController*)controller;
  681. // if (!modelViewController.model.isLoaded) {
  682. // break;
  683. // }
  684. // }
  685. // Stop after one controller if we are in "persist top" mode
  686. if (_persistenceMode == TTNavigatorPersistenceModeTop && passedContainer) {
  687. break;
  688. }
  689. passedContainer = [controller canContainControllers];
  690. }
  691. [self.window makeKeyAndVisible];
  692. return controller;
  693. }
  694. ///////////////////////////////////////////////////////////////////////////////////////////////////
  695. - (void)persistController:(UIViewController*)controller path:(NSMutableArray*)path {
  696. NSString* URL = controller.navigatorURL;
  697. if (URL) {
  698. // Let the controller persists its own arbitrary state
  699. NSMutableDictionary* state = [NSMutableDictionary dictionaryWithObject:URL
  700. forKey:@"__navigatorURL__"];
  701. if ([controller persistView:state]) {
  702. [path addObject:state];
  703. }
  704. }
  705. [controller persistNavigationPath:path];
  706. if (controller.modalViewController
  707. && controller.modalViewController.parentViewController == controller) {
  708. [self persistController:controller.modalViewController path:path];
  709. } else if (controller.popupViewController
  710. && controller.popupViewController.superController == controller) {
  711. [self persistController:controller.popupViewController path:path];
  712. }
  713. }
  714. ///////////////////////////////////////////////////////////////////////////////////////////////////
  715. - (void)removeAllViewControllers {
  716. [_rootViewController.view removeFromSuperview];
  717. TT_RELEASE_SAFELY(_rootViewController);
  718. [_URLMap removeAllObjects];
  719. }
  720. ///////////////////////////////////////////////////////////////////////////////////////////////////
  721. - (NSString*)pathForObject:(id)object {
  722. if ([object isKindOfClass:[UIViewController class]]) {
  723. NSMutableArray* paths = [NSMutableArray array];
  724. for (UIViewController* controller = object; controller; ) {
  725. UIViewController* superController = controller.superController;
  726. NSString* key = [superController keyForSubcontroller:controller];
  727. if (key) {
  728. [paths addObject:key];
  729. }
  730. controller = superController;
  731. }
  732. return [paths componentsJoinedByString:@"/"];
  733. } else {
  734. return nil;
  735. }
  736. }
  737. ///////////////////////////////////////////////////////////////////////////////////////////////////
  738. - (id)objectForPath:(NSString*)path {
  739. NSArray* keys = [path componentsSeparatedByString:@"/"];
  740. UIViewController* controller = _rootViewController;
  741. for (NSString* key in [keys reverseObjectEnumerator]) {
  742. controller = [controller subcontrollerForKey:key];
  743. }
  744. return controller;
  745. }
  746. ///////////////////////////////////////////////////////////////////////////////////////////////////
  747. - (void)resetDefaults {
  748. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  749. if (TTIsStringWithAnyText(_persistenceKey)) {
  750. [defaults removeObjectForKey:_persistenceKey];
  751. } else {
  752. [defaults removeObjectForKey:kNavigatorHistoryKey];
  753. [defaults removeObjectForKey:kNavigatorHistoryTimeKey];
  754. [defaults removeObjectForKey:kNavigatorHistoryImportantKey];
  755. }
  756. [defaults synchronize];
  757. }
  758. ///////////////////////////////////////////////////////////////////////////////////////////////////
  759. ///////////////////////////////////////////////////////////////////////////////////////////////////
  760. #pragma mark -
  761. #pragma mark UIPopoverControllerDelegate
  762. ///////////////////////////////////////////////////////////////////////////////////////////////////
  763. - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
  764. if (popoverController == _popoverController) {
  765. TT_RELEASE_SAFELY(_popoverController);
  766. }
  767. }
  768. @end
  769. ///////////////////////////////////////////////////////////////////////////////////////////////////
  770. ///////////////////////////////////////////////////////////////////////////////////////////////////
  771. ///////////////////////////////////////////////////////////////////////////////////////////////////
  772. @implementation TTBaseNavigator (TTInternal)
  773. ///////////////////////////////////////////////////////////////////////////////////////////////////
  774. /**
  775. * Present a view controller that strictly depends on the existence of the parent controller.
  776. */
  777. - (void)presentDependantController: (UIViewController*)controller
  778. parentController: (UIViewController*)parentController
  779. mode: (TTNavigationMode)mode
  780. action: (TTURLAction*)action {
  781. if (mode == TTNavigationModeModal) {
  782. [self presentModalController: controller
  783. parentController: parentController
  784. animated: action.animated
  785. transition: action.transition];
  786. } else if (mode == TTNavigationModePopover) {
  787. [self presentPopoverController: controller
  788. sourceButton: action.sourceButton
  789. sourceView: action.sourceView
  790. sourceRect: action.sourceRect
  791. animated: action.animated];
  792. } else {
  793. [parentController addSubcontroller: controller
  794. animated: action.animated
  795. transition: action.transition];
  796. }
  797. }
  798. ///////////////////////////////////////////////////////////////////////////////////////////////////
  799. - (UIViewController*)getVisibleChildController:(UIViewController*)controller {
  800. return controller.topSubcontroller;
  801. }
  802. ///////////////////////////////////////////////////////////////////////////////////////////////////
  803. - (Class)navigationControllerClass {
  804. return [TTBaseNavigationController class];
  805. }
  806. @end