iOS: Setting text in nib subview from view in UITabBar/UINavigationController application - ios

I'm having a problem getting a UISearchDisplay's text value to be set programatically on load of the view by another view and my question is have I overcomplicated my problem and missed something or am I on the right track of thinking.
Here's the situation: I have a UITabBarController as my root view, there are 2 tabs, both have a UINavigationController setup so I can push views as needed.
Tab 1 has a UITableViewController which is populated with a list of categories.
Tab 2 has a MapView in it's main view but I have done a custom UINavigationItem view to put various buttons and a UISearchDisplay on the rightBarButtonitem area.
The mapview layout and custom navigation item are stored in the same nib as two separate view objects. In Tab 2's viewDidLoad(), I initialise the rightBarButtonItem programatically with:
UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithCustomView:buttonBar];
self.navigationItem.rightBarButtonItem = btnItem;
[btnItem release];
Everything fires up, buttonBar is wired up to an IBOutlet as searchWhat and I can talk to this object from within the mapview's controller class.
If the user is in Tab 1 and taps a cell, I want it to switch to Tab 2 and populate the searchWhat.text and then execute the search code as if someone had typed in the search themselves.
What i'm having trouble with is the order of load and populate on a view.
I can access the 2nd tab from the 1st without any problem and get it to appear with something like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Quick Category cell tapped at row %d", indexPath.row);
self.tabBarController.selectedIndex = 1; // change to the search view controller
//[self.tabBarController.selectedViewController viewDidAppear:YES];
UINavigationController *nav = (UINavigationController *)self.tabBarController.selectedViewController;
SearchViewController *srch = [nav.viewControllers objectAtIndex:0];
//NSLog(#"%#", [srch description]);
[srch queueSearchByType:kSearchTypeQuickCategories withData:[catList objectAtIndex:indexPath.row]];
[srch viewDidAppear:YES];
}
Don't worry about catList and SearchViewController, they exist and this bit works to switch tabs.
Here's the problem though, if the user starts the application and selects an item in tab 1, tab 2 appears but the values of the search display text don't get set - because viewDidLoad and viewDidAppear are called in another thread so the execution of queueSearchByType:withData: gets called while the view is still loading and setting up.
If the user selects tab 2 (therefore initialising the subview) and then selects tab 1 and an item, it can populate the search display text.
I can't just change the order of the tabs so that tab2 is first and therefore loads it's subviews to the navigation bar as the project specification is category search first.
Have I missed something very simple? What I need to do is wait for the second tab to fully appear before calling queueSearchByType:withData: - is there a way to do this?
At the moment, i've implemented a queue the search, check for a queue search approach, this seems to be a bit long winded.

Ok, I don't like answering my own question but it appears my fears were right, basically if you want a UINavigationItem that is a custom view (ie, to put a search bar and various other buttons up on the nav controller) and be able to switch to and populate them from another tab on a tab bar controller, then you need to put the subview in it's own class which is a subclass of UIViewController and then make delegation your friend (which it already is), i've provided an example in case anybody needs to repeat it which i've put on my blog HERE.
http://www.jamesrbrindle.com/developer/ios-developer/howto-add-a-custom-uinavigationitem-to-a-uinavigationcontroller-with-delegation.htm
If anyone disagrees and thinks this can be simpler, please let me know or rate this post

Related

Regarding the UITabBarController, is there a way to force a view to be fully released when a new tab is selected

I'm creating a tab bar application for the iPhone and I want whatever view is currently active to be fully unloaded/released if a new tab is selected. When any tab is reselected I want it to have to reload everything. The reason I seek this feature is because all views interact with a single database, and can modify the database. When the views are built they are built off the current database, which means that they can be out of date without a forced reload of the view.
To see what I'm referring to load the "Phone" app on your iPhone and you can type in a number on the keypad, switch tabs, switch back to the keypad and the number you typed remains there. Which is a desirable trait for the Phone app, but not so much for what I'm designing.
Is there a way to achieve this? Or, should I use another method to update my views when tabs are switched?
I think Neil Galiaskarov comment that you should not think about releasing the view. His idea to put your current reloading logics in -(void)viewWillAppear is sound:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppeared:animated];
// Any code you are currently triggering in your init / viewDidLoad
// to reload your database and display the result
}
This is triggered every time you return via the tab-bar and should give you the result you are after. There are few caveats however, depending on what you do next, that might not deliver the required functionality:
The screen will be updated when it is reached by popping the stack (which is probably OK from what you are describing).
If you start to navigate backwards in the stack with the swipe-gesture but abort the navigation the screen will also be updated. (As this triggers viewWillAppear and viewDidAppear).
If the latter is a problem one way to handle it through a BOOL _shouldTriggerReload;:
Wrap the viewWillAppear logic inside an if:
if (_shouldTriggerReload){
_shouldTriggerReload = NO; // Preparing for the next round
// Any code you are currently triggering in your init / viewDidLoad
// to reload your database and display the result
}
Then in your viewDidDisappear:
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
_shouldTriggerReload = YES;
}
And it will also have to be set inside your viewDidLoad for that first entry:
- (void)viewDidLoad{
_shouldTriggerReload = YES;
}
tabBarController has a viewControllers property.
You modify the viewControllers property to achieve such effect.
What i would do is,
For the view controller(say VC) which you want to reload its view, when user selects that tab.
add UITabBarControllerDelegate delegate to VC. and implement below method.
– tabBarController:shouldSelectViewController:
when the user selects this in bottom tab bar, tabBarController calls this method whether should it show this viewController or not.
Here, change the tabBarController.viewControllers property and return YES.
Lets say your tab bar has only two viewControllers attached to it, and user now selects tab 2 and you have to create new viewController and show it.
So,
UITabBarController *tabBarController;
NSMutableArray *array=[NSMutableArray arrayWithArray:tabBarController.viewControllers];
UIViewController *viewController2; //init your viewController type 2
[array replaceObjectAtIndex:1 withObject:viewController2];
tabBarController.viewControllers =[NSArray arrayWithArray:array];
Use delegate method
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
Add your piece of code which you can trigger each time whenever you select tabBarItem.

Get title of backBarButtonItem

I'm currently writing an app with multiple users.
I would like to use the same "Profile" view in storyboard to display info for all the users.
The plan was to set the title of the "Profile" view and then push it.
- (void) pushGeneralProfileViewFrom:(UIViewController *)target usernameAsTitleOfView:(NSString *)title {
UIViewController *myView = [target.storyboard instantiateViewControllerWithIdentifier:#"GeneralProfileView"]; // created in storyboard, Feed View
myView.title = title;
[target.navigationController pushViewController:myView animated:YES];
}
This worked great. However, the "profile" view also has buttons that lead it to other views in the storyboard. Those views also display specific info for the relevant user.
I was planning to use the name of the backBarButtonItem to know the user I need to display the info for. While I can see the back button with the username in the simulator, I'm unable to get it's self.navigationController.navigationItem.backBarButtonItem.title or self.navigationItem.backBarButtonItem.title. Both return null.
What am I doing wrong?
Also, I have a feeling this is not the best practice to handle those kind of things. I've searched the web and so far haven't seen a better way to pass values to a view I'm pushing. Suggestions?
Thank you.
The backBarButtonItem that you see when your view controller is visible in the navigation controller interface is not the backBarButtonItem of this view controller. It is the backBarButtonItem of the second view controller, the one behind this one in the stack.
So what you want is this:
UIInteger c = [self.navigationController.viewControllers count];
UIViewController* vc2 = [self.navigationController.viewControllers objectAtIndex:c-2];
Now get the backBarButtonItem of the navigationItem of that view controller. Or just get its title if they are the same.

Custom TabBarController functionality

I'm attempting to create a custom tab bar controller with the following options:
Feed
Map
New
Camera
Search
The feed, map, camera, and search will each pull up their individual VC's, while new is supposed to be more of a functionality button. When the new tab item is pressed, the map view should be displayed, and the map should begin recording the user's location. I'm trying to use a UITabBarController to manage these views, but I cannot seem to figure out how to implement the functionality that I would like with the "New" tab. It seems as if I would need to implement a separate view controller for the "New" record and stop functionality but that doesn't seem right.
The functionality of record/stop should be similar to snapchat's big red button that takes a picture when you press it.
While I agree with Scott's comment that this is a bad UX, if you technically wanted to do it, you could subclass UITabBarController and in viewDidLoad you could add a UIButton subview to the the tab bar controller's tabBar:
[self.tabBar addSubview:yourCustomButton];
Thus, this button could have it's own action and selector to do whatever you want with.
Take a look at idevrecipes for an example.
I think you have to implement the container view controller yourself. I think you can't do that with UITabBarController.
I was going to dig up the idevrecipes example that shawnwall pointed out, but there's another possibly answer, assuming you want the New button to match the standard UITabBarButton appearance. I agree it may not be the best UI, but it's workable.
For starters, you would create a dummy view controller for the New item. I'm not saying you should duplicate the Maps controller or anything, I'm just saying create an empty view controller and stash to it (or it's location) in a property. Assuming you're creating your tab bar in application:didFinishLaunchingWithOptions:, it'd look something like this.
self.newViewController = [[UIViewController alloc] init];
self.newViewController.title = NSLocalizedString(#"New", nil);
self.newViewController.image = [UIImage imageNamed:"new.png"];
Drop that view controller at the appropriate location in the tab bar controller's viewControllers property.
Save a reference to the Maps controller the same way you saved one for the dummy New controller.
If you haven't already done do, set the delegate of your tabBarController to your app delegate. You may need to declare that your app delegate conforms to UITabBarControllerDelegate.
UITabBarDelegate gives you a few hooks for tracking changes to the tab bar. tabBarController:shouldSelectViewController: looks to be the appropriate place for us to hook in for your desired behavior.
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if ([viewController isEqual:self.newViewController]) {
self.tabBarController.selectedViewController = self.mapViewController;
// Whatever logic you need to start the recording
return NO;
}
return YES;
}
The look of the user interaction's a little odd - the user tabs the middle item (New), but the item to the left (Map) gets selected. If you want the "tab bar triggers behavior" action, I'd go the idevrecipes route and make the button visually distinct. If you're married to the tab bar item look, though, I believe this is how you'd accomplish it.

Dismissing/Delegate second view in a modal stack in storyboard

I have a TableView which describes a book with sections which represents the chapters and rows representing the verses.
A the top of this TableView I have a button in a navigation bar to allow "navigation".
The goal of this navigation button is to allow the user to easily jump to a given chapter/verse without scrolling manually (which can be very long).
When the button is pressed a tableview controller is called displaying all the available chapters of the book and when a chapter is selected another table view is called displaying a list of the available verses in the current chapter. Finally when the line is chosen the tablew view displaying the book should scroll to the given index/row.
So the idea : from the tableview representing the book I call the chapters view as modal and the verses as a push over the chapters view.
My problem is that I don't get the point of managing the delegate and dismissing from the 2nd modal view.
With 1 modal view I do things like that.
In the displayed VC (View Controller) I added the protocol and the delegate
#protocol ChapitresTableViewControllerDelegate <NSObject>
- (void)didDismissPresentedViewController;
#end
#interface ChapitresTableViewController : UITableViewController
#property (nonatomic, weak) id <ChapitresTableViewControllerDelegate> delegate;
#end
I have in the didSelectRow
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.delegate didDismissPresentedViewController];
}
in the displaying VC I add the following line
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ChapitresTableViewController *chapitresTableViewController = segue.destinationViewController;
chapitresTableViewController.delegate = self;
}
and of course
-(void)didDismissPresentedViewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
this would just work fine if I wanted to close after the first modal VC, but what I want is to have the second one being dismissed after I click in the second. Ok I can write the protocol and stuff in the second modal VC but how do I manage to have the delegate being send to the second VC.
Hope my question is clear enough it is not so easy to explain it.
Anyone understand me and can help me ?
NOTE : I know for now I don't pass any parameters back to the delegate, which I will do later to do the scroll. For now I just want to be able to close the second view, then I will add the required stuff to pass the parameters back to the delegate
I'm sure you can do this, but rather than modal view controllers with a navigation bar, wouldn't it be easier to use a navigation controller? Then you can use popToViewController to go back as many levels as you want to a particular view controller. You can either pass the UIViewController* of the various controllers you might want to pop to, or do so programmatically: e.g. How to pop back to specify viewController from navigationController(viewControllers/stack)?
In this scenario previous views controllers are retained. The ones you pop off are released (just like the modal ones you dismiss are released), but the ones that you pushed from are retained (just like the ones you presented from in a modal world are retained).
If the book is large, though, you'll have to be sensitive to memory usage. Thus, you will probably want to handle didReceiveMemoryWarning to release the model data for the previous views in either your modal sequence or push sequence, in which case, on viewDidAppear, you'll want to see if your app had to release the memory in response to didReceiveMemoryWarning and reload it in that case. But that's the desired behavior, either way, gracefully release the pages if needed (and reload them when the particular view reappears), but keep it in memory if you can.
Finally, you might also want to contemplate using UIPageViewController. Given what you've described, I'd like consider UIPageViewController first, UINavigationController and push segues second, and the use of modal segues third.

How to add iPad subview controller to act like a Tab Bar

I have an app that I just got setup with a split view controller to display blogs on the ipad version of the app. The current setup is master controller is a table view to show the different articles off the blog, and the detail controller is a view controller with a webview inside used to show the content of the article. The issue is that I have a few other features in the app, and on the iPhone version, I use a tab bar controller to navigate. What would be some options to add buttons to the detail controller that would allow me to navigate to the other sections of the app? I know I can't get a Tab Bar Controller within the Split View Controller so I just need some guidance.
I know that the Engadget app is setup so that when you open in portrait mode, it shows the table view of apps, along with a controller at bottom to go to different things like photos, and when in landscape the table view is on the left and the text of the articles is on the right. I just want it set up so there is no blank page if you open in portrait mode, and have a feature to view other pages, besides just adding buttons to the navigation bar.
you CAN add a tabBarController to your SplitViewController's detail view. Simply create a UIViewController that responds to <UITabBarControllerDelegate> and in the xib file for that controller add a UITabBarController object and link it to your UIViewController. In your viewDidLoad add the Tab bar controller to the view:
-(void)viewDidLoad {
...
[self addChildViewController:myTabBarController];
//add the tabBarController view
[self.view addSubview:myTabBarController.view];
}
This UIViewController will be assigned to your detail view. this works and I've tried it before. However, whether apple would allow it? i don't know!
EDIT:
first of you should never call viewDidLoad yourself.
Also, i don't think you need rootipad and detailipad as long as the controllers are linked to the SplitViewController in the xib file.
in your RootViewiPad (master) add the following inside its viewDidLoad :
-(void)viewDidLoad {
...
if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
//select the first row to load
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] animated:NO scrollPosition:UITableViewScrollPositionNone];
//NOTE, I'm assuming that selecting the cell will load the detail view.
//if that is not the case, you need to do what ever you need to load the
//detail view.
}
}
if you want this behavior to happen every time (not only on first load) then add the code to the -(void)viewWillAppear method instead.

Resources