UISearchController searchBar don't disappear when push viewcontroller - ios

I'm using a UISearchController inside ma UIViewcontroller that contains a UITableView, I do this in viewDidLoad:
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.delegate = self;
self.searchController.searchResultsUpdater = self;
self.searchController.searchBar.delegate = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;
self.definesPresentationContext = NO;
when I push a button in the navbar i do this:
self.tableView.contentOffset = CGPointMake(0, 0 - self.tableView.contentInset.top);
self.tableView.tableHeaderView = self.searchController.searchBar;
[self.searchController.searchBar becomeFirstResponder];
all works fine, but when I push a UIViewController from a row in the UITableView results the UISearchBar stay there and display also on the content of the other view, how can i dismiss when I push a view and present when I go back to see the result of the UITableView?
thanks
EDIT:
this is the code of the method didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
DetailListController *detailList = [[DetailListController alloc] init];
[self.navigationController pushViewController:detailList animated:YES];
}

Put this code in your viewDidLoad:
self.definesPresentationContext = YES;

You need to call this when you come back from DetailListController to your view controller (encapsulating in main thread for safety):
dispatch_async(dispatch_get_main_queue(), ^{
self.searchController.active = NO;
});
You can also call this in viewWillDisappear: of your current view controller.

Try this standard way suggested by apple:
Declare the properties:
#interface APLMainTableViewController () <UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating>
#property (nonatomic, strong) UISearchController *searchController;
// our secondary search results table view
#property (nonatomic, strong) APLResultsTableController *resultsTableController;
// for state restoration
#property BOOL searchControllerWasActive;
#property BOOL searchControllerSearchFieldWasFirstResponder;
#end
- (void)viewDidLoad {
[super viewDidLoad];
_resultsTableController = [[APLResultsTableController alloc] init];
_searchController = [[UISearchController alloc] initWithSearchResultsController:self.resultsTableController];
self.searchController.searchResultsUpdater = self;
[self.searchController.searchBar sizeToFit];
self.tableView.tableHeaderView = self.searchController.searchBar;
// we want to be the delegate for our filtered table so didSelectRowAtIndexPath is called for both tables
self.resultsTableController.tableView.delegate = self;
self.searchController.delegate = self;
self.searchController.dimsBackgroundDuringPresentation = NO; // default is YES
self.searchController.searchBar.delegate = self; // so we can monitor text changes + others
// Search is now just presenting a view controller. As such, normal view controller
// presentation semantics apply. Namely that presentation will walk up the view controller
// hierarchy until it finds the root view controller or one that defines a presentation context.
//
self.definesPresentationContext = YES; // know where you want UISearchController to be displayed
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// restore the searchController's active state
if (self.searchControllerWasActive) {
self.searchController.active = self.searchControllerWasActive;
_searchControllerWasActive = NO;
if (self.searchControllerSearchFieldWasFirstResponder) {
[self.searchController.searchBar becomeFirstResponder];
_searchControllerSearchFieldWasFirstResponder = NO;
}
}
}
#pragma mark - UISearchBarDelegate
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
}
#pragma mark - UISearchControllerDelegate
// Called after the search controller's search bar has agreed to begin editing or when
// 'active' is set to YES.
// If you choose not to present the controller yourself or do not implement this method,
// a default presentation is performed on your behalf.
//
// Implement this method if the default presentation is not adequate for your purposes.
//
- (void)presentSearchController:(UISearchController *)searchController {
}
- (void)willPresentSearchController:(UISearchController *)searchController {
// do something before the search controller is presented
}
- (void)didPresentSearchController:(UISearchController *)searchController {
// do something after the search controller is presented
}
- (void)willDismissSearchController:(UISearchController *)searchController {
// do something before the search controller is dismissed
}
- (void)didDismissSearchController:(UISearchController *)searchController {
// do something after the search controller is dismissed
}
and here comes the interesting part: Use the below code to restore the status when you comeback from the detail view
#pragma mark - UIStateRestoration
// we restore several items for state restoration:
// 1) Search controller's active state,
// 2) search text,
// 3) first responder
NSString *const ViewControllerTitleKey = #"ViewControllerTitleKey";
NSString *const SearchControllerIsActiveKey = #"SearchControllerIsActiveKey";
NSString *const SearchBarTextKey = #"SearchBarTextKey";
NSString *const SearchBarIsFirstResponderKey = #"SearchBarIsFirstResponderKey";
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
// encode the view state so it can be restored later
// encode the title
[coder encodeObject:self.title forKey:ViewControllerTitleKey];
UISearchController *searchController = self.searchController;
// encode the search controller's active state
BOOL searchDisplayControllerIsActive = searchController.isActive;
[coder encodeBool:searchDisplayControllerIsActive forKey:SearchControllerIsActiveKey];
// encode the first responser status
if (searchDisplayControllerIsActive) {
[coder encodeBool:[searchController.searchBar isFirstResponder] forKey:SearchBarIsFirstResponderKey];
}
// encode the search bar text
[coder encodeObject:searchController.searchBar.text forKey:SearchBarTextKey];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
// restore the title
self.title = [coder decodeObjectForKey:ViewControllerTitleKey];
// restore the active state:
// we can't make the searchController active here since it's not part of the view
// hierarchy yet, instead we do it in viewWillAppear
//
_searchControllerWasActive = [coder decodeBoolForKey:SearchControllerIsActiveKey];
// restore the first responder status:
// we can't make the searchController first responder here since it's not part of the view
// hierarchy yet, instead we do it in viewWillAppear
//
_searchControllerSearchFieldWasFirstResponder = [coder decodeBoolForKey:SearchBarIsFirstResponderKey];
// restore the text in the search field
self.searchController.searchBar.text = [coder decodeObjectForKey:SearchBarTextKey];
}
#end

After trying to deal with the same problem for a few days, realized that the proper way of doing this is to have a navigation controller that contains everything in the search hierarchy
like this :
Navigation 1 (no nav bar)
(.... tab bar, anything else ...)
Navigation 2 (has nav bar, this nav bar is replaced by the search bar
Table controller
when you push details controller, you push it into navigation 1, showing its navigation bar at the same time.
This leaves search stack untouched and ready to work when you hit "back" on the detail page

Related

IOS/Objective-C: Programatically Change Text of Title for Standard UITabBar item

When a user segues to a second VC from within a starting VC that is embedded in a UITabBarController, I change the title of the UITabbarItem with some code placed in the viewWillAppear method of the second view controller.
//Second VC, View WIll Appear
UITabBarItem *selectedItem = self.tabBarController.tabBar.selectedItem;
if (selectedItem) {
selectedItem.title = #"VoiceMail";
}
This works fine.
When the user returns to the starting view controller, I want to switch the title back.
I tried to do this by placing similar code in the view will appear method of the starting view controller.
Starting VC: ViewWIllAppear
UITabBarItem *selectedItem = self.tabBarController.tabBar.selectedItem;
if (selectedItem) {
selectedItem.title = #"Phone";
}
But it is having no effect, leaving the title as Voicemail.
WOuld appreciate any suggestions on how to change back to initial value.
Thanks for any suggestions.
Trying to change the "2nd" tab title by referencing .selectedItem in your "Starting VC" won't work, because at that point .selectedItem is StartingVC.
One approach would be to save a reference to the index of SecondVC... then, inside that VC, on viewWillDisappear you can reset its tab's title:
This is all in SecondVC:
#import "ChangeSecondViewController.h"
#interface ChangeSecondViewController ()
#property (assign, readwrite) NSInteger myTabIndex;
#end
#implementation ChangeSecondViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UITabBarItem *selectedItem = self.tabBarController.tabBar.selectedItem;
if (selectedItem) {
selectedItem.title = #"VoiceMail";
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
_myTabIndex = self.tabBarController.selectedIndex;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
UITabBarItem *myTabItem = [[self.tabBarController.tabBar items] objectAtIndex:_myTabIndex];
if (myTabItem) {
myTabItem.title = #"Phone";
}
}
#end

Enable back back swipe gesture when using custom navigation controller

I've got a custom navigation controller declared as below. My problem is that once I implement this, the back swipe gesture to go back to previous stack (interactivepopgesturerecognizer) is not working. How can I enable it back? I've got a lot of view controller in my app. Thank You.
#import "NavController.h"
#interface NavController ()
{
BOOL shouldIgnorePushingViewControllers;
}
#end
#implementation NavController
-(instancetype)init {
self = [super init];
self.delegate=self;
return self;
}
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (!shouldIgnorePushingViewControllers)
{
[super pushViewController:viewController animated:animated];
}
shouldIgnorePushingViewControllers = YES;
}
- (void)didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
shouldIgnorePushingViewControllers = NO;
}
#end
Try to enable property
self.interactivePopGestureRecognizer.enabled = YES;
to init method

navigationBar display deranged

I create a subclass of UINavigationController, like this
#interface CustomNav : UINavigationController
#property(assign , nonatomic) BOOL canBack;
#end
#implementation CustomNav
- (void)viewDidLoad {
[super viewDidLoad];
self.canBack = YES;
// Do any additional setup after loading the view.
id target = self.interactivePopGestureRecognizer.delegate;
UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc]
initWithTarget:target action:#selector(handleNavigationTransition:)];
pan.delegate = self;
[self.view addGestureRecognizer:pan];
self.interactivePopGestureRecognizer.enabled = NO;
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
// NSLog(#"self.childViewControllers.count = %li",self.childViewControllers.count);
if (self.childViewControllers.count==1||!self.canBack) {
return NO;
}
return YES;
}
#end
but when i use it to push viewController , the navigationBar display deranged ,for example, it has A viewController, then it push another B viewController,but sometimes, it doesn't display the navigationItem of B , it just display the navigationItem of A , and then I push C to the CustomNav , it also display the navigationItem of A. i don't know why ,i found it seems the CustomNav display the navigationItem of B(or C) ,and then the navigationBar covered by the navigationItem of A . Could everyOne help me ? Thank you ever so much.

UITableView does not refresh after returning from its child view

I'm reading the Big Nerd Ranch's book and I have written a little app using UITableView. Let the UITableView shows the items in the inventory. When didSelectRowAtIndexPath:, it enters a subview and you can edit the properties of item here, which is detailViewController. Afterwards you can go back to UITableView via the navigation controller's back button.
However it doesn't refresh after pressing back button at first. You have to go to a detailViewController and come back to see the change, it's not important whether it is same item.
Navigation goes like: ItemsViewController > DetailViewController
ItemsViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:#"UITableViewCell"];
UIView * header = self.headerView;
[self.tableView setTableHeaderView:header];
self.tableView.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
DetailViewController.m
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
// Clear first responder
[self.view endEditing:YES];
// Save "changes" to the item
BNRItem * item = self.item;
item.itemName = self.nameField.text;
item.serialNumber = self.serialNumberField.text;
item.valueInDollars = [self.valueField.text intValue];
}
Thoughts?
You should change the values in viewWillDisappear: method in DetailViewController. viewWillAppear: method of ItemsViewController executes before viewDidDisappear: of DetailViewController, therefore your BNRItem changes after the table is refreshed.

iOS state restoration discarding navigation stack

tl;dr State restoration process appears to be happening, but the stack (non-root) view controllers are not ending up in the app after restoration completes.
I'm trying to implement state restoration in an app, which doesn't use any .nibs or storyboards. It's a fairly basic structure: the window's rootViewController is a UINavigationController whose own rootViewController and all other child view controllers are UITableViewControllers (eg, Window > Nav Ctrl > Table View Ctrl > Table View Ctrl > etc). None of the view controllers are ever repeated (ie each item on the stack is a distinct UITableViewController subclass)
As I have no view controllers being created with storyboards, I have the designated initializer for each view controller class setting the restorationIdentifier and restorationClass.
When the app is being restored, I am seeing decoding happening for each view controller that was present when the app went into the background (eg, the nav controller, the podcast list controller, and the podcast detail controller), but the end result of the restoration is always the navigation controller showing up with it's normal root view controller (the podcast list controller).
The problem seems very similar to this question, but I am definitely calling super in the encode- and decodeRestorableStateWithCoder: methods on my view controllers (when present), so that solution doesn't help me.
I have watched the WWDC videos and gone through many tutorials, and while I seem to be hitting all the requirements, something is not working the way it should be.
The only thing I can think is happening is the restoration is happening, but my default initialization code is replacing the restored navigation view controller stack with a "fresh" one that only includes the root. According to the WWDC video, the window and root view controller should be set up normally prior to state restoration, and that shouldn't impact the final app state after restoration.
I guess the one question mark for me is what should actually be happening in the viewControllerWithRestorationIdentifierPath: method of my UINavigationController. Should the rootViewController be set as I am doing? And if not, what else would happen? I couldn't actually find any working example code where a navigation controller was being restored and it wasn't created from a nib or storyboard. Other than that I'm stumped.
Implementation code
FLAppDelegate.m
# pragma mark - UIApplicationDelegate
# pragma mark Monitoring App State Changes
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Window and root VC set up in window getter
[self.window makeKeyAndVisible];
return YES;
}
# pragma mark Managing App State Restoration
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder {
return YES;
}
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder {
return YES;
}
#pragma mark Providing a Window for Storyboarding
- (UIWindow *)window {
if (!_window) {
_window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
_window.rootViewController = self.navigationController;
}
return _window;
}
- (FLNavigationController *)navigationController {
if (!_navigationController) {
FLPodcastTableViewController* podcastViewController = [[FLPodcastTableViewController alloc] initWithStyle:UITableViewStylePlain];
_navigationController = [[FLNavigationController alloc] initWithRootViewController:podcastViewController];
}
return _navigationController;
}
FLNavigationController.m
- (id)initWithRootViewController:(UIViewController *)rootViewController {
self = [super initWithRootViewController:rootViewController];
if (self) {
self.restorationIdentifier = #"FLNavigationController";
self.restorationClass = self.class;
}
return self;
}
#pragma mark - UIViewControllerRestoration
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
FLPodcastTableViewController* podcastViewController = [[FLPodcastTableViewController alloc] initWithStyle:UITableViewStylePlain];
return [[self alloc] initWithRootViewController:podcastViewController];
}
FLPodcastTableViewController.m
- (id)initWithStyle:(UITableViewStyle)style {
self = [super initWithStyle:style];
if (self) {
[self.tableView registerClass:FLPodcastTableViewCell.class forCellReuseIdentifier:FLPodcastTableViewCellIdentifier];
self.restorationIdentifier = #"FLPodcastTableViewController";
self.restorationClass = self.class;
}
return self;
}
#pragma mark - UIViewControllerRestoration
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
return [[self alloc] initWithStyle:UITableViewStylePlain];
}
FLPodcastEpisodeTableViewController.m
- (id)initWithPodcast:(FLPodcast *)podcast {
self = [self initWithStyle:UITableViewStylePlain];
if (self) {
self.podcast = podcast;
self.restorationIdentifier = #"FLPodcastEpisodeTableViewController";
self.restorationClass = self.class;
[self.tableView registerClass:FLPodcastEpisodeTableViewCell.class forCellReuseIdentifier:FLPodcastEpisodeTableViewCellIdentifier];
}
return self;
}
#pragma mark - UIViewControllerRestoration
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
FLPodcastEpisodeTableViewController* viewController = nil;
NSString* podcastURI = [coder decodeObjectForKey:kPodcastURLKey];
NSURL* podcastURL = [NSURL URLWithString:podcastURI];
FLPodcast* podcast = [FLPodcast podcastWithURL:podcastURL];
if (podcast) {
viewController = [[self alloc] initWithPodcast:podcast];
}
return viewController;
}
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[coder encodeObject:self.podcast.feedURL.absoluteString forKey:#kPodcastURLKey];
[super encodeRestorableStateWithCoder:coder];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
}

Resources