I have set up my class with UITabBarDelegate and implemented its method didSelectItem to detect when a certain tabBar item is pressed. Works great. In each tabBar item I have one containerView that can show a "you have to login"-page if the user is not logged in, and another containerView that present viewControllers that are embedded in a navigationController.
I would like to keep track of the viewController that is presented in the current tab item, and/or the root viewController of that tab.
I have tried a number of different approaches, but most of them return nil or I can't get it to work. I think the whole container situation makes it harder to handle.
It looks something like this:
#interface MyTabBarController () <UITabBarDelegate>
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
NSUInteger indexOfTab = [[tabBar items] indexOfObject:item];
switch (indexOfTab) {
case 0: {
NSLog(#"🐳PRESSIIING %lu", (unsigned long)[[tabBar items] indexOfObject:item]);
break;
}
case 1: {
NSLog(#"🐳PRESSIIING %lu", (unsigned long)[[tabBar items] indexOfObject:item]);
break;
}
case 2: {
NSLog(#"🐳PRESSIIING %lu", (unsigned long)[[tabBar items] indexOfObject:item]);
//These return nil
NSLog(#"🐳AAAAAA %#", ((UINavigationController*)_appD.window.rootViewController).visibleViewController);
NSLog(#"🐳AAAAAA %#", ((UITabBarController*)_appD.window.rootViewController).selectedViewController);
NSLog(#"🐳AAAAAA %#", self.navigationController.topViewController);
NSLog(#"🐳AAAAAA %#", self.navigationController.visibleViewController);
//This returns with a value, but can't get it to work with conditionals, that is, when I'm in root, the else is triggered
NSLog(#"🐳AAAAAA %#", self.tabBar.window.rootViewController);
if(!self.tabBar.window.rootViewController) {
NSLog(#"🐳🐳🐳THIS IS NOT ROOT🐳🐳🐳");
}else {
NSLog(#"🐳🐳🐳this is ROOT🐳🐳🐳");
}
// This returns nil
((UINavigationController*)_appD.window.rootViewController).visibleViewController;
((UITabBarController*)_appD.window.rootViewController).selectedViewController;
//Doesn't work
if([self.navigationController.viewControllers[0] isKindOfClass:[ExperiencesListViewController class]]) {
NSLog(#"🐳IS KIND OF CLASS LIST");
}
if([self.navigationController.viewControllers[0].childViewControllers isKindOfClass:[ExperiencesContainerViewController class]]) {
NSLog(#"🐳IS KIND OF CLASS CONTAINER");
}
break;
}
case 3: {
NSLog(#"🐳PRESSIIING %lu", (unsigned long)[[tabBar items] indexOfObject:item]);
break;
}
case 4: {
NSLog(#"🐳PRESSIIING %lu", (unsigned long)[[tabBar items] indexOfObject:item]);
break;
}
default:
break;
}
}
So, what else can I try? Seems like I have to use `self.tabBar.window.rootViewController` in some way, no?
***EDIT***
Oh, and I have tried the `tabBarController` delegate but that doesn't trigger. Also, the `tabBar` is constructed programmatically if that helps.
Sorry to have not read your question correctly. Here's what I suggest you do.
All of these view controllers that you're interested in keeping track of: you should have them send a custom notification from within their -viewDidAppear: (or -viewWillAppear:) method. Then let your ApolloTabBarController object register for that notification. When it gets the notification, you could then store a reference to the view controller. That reference will always point to the active view controller.
In your individual view controllers, do something like the following:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:#"XYZViewControllerDidBecomeActiveNotification"
object:self];
}
Of course, you would want to use some kind of constant for the notification name.
In your ApolloTabBarController class, register for XYZViewControllerDidBecomeActiveNotification and implement something like:
- (void)viewControllerDidBecomeActive:(NSNotification *)notification
{
self.activeViewController = [notification object];
}
I hope that helps!
When you're setting up each view controller for each of the tabs, set the tag property of the UITabBarItem to correspond to the index of the view controller in the tab bar's viewControllers array.
UIViewController* myFirstVC = [[UIViewController alloc] init];
UIViewController* mySecondVC = [[UIViewController alloc] init];
// "self" is your ApolloTabBarController.
[self setViewControllers:#[myFirstVC, mySecondVC]];
myFirstVC.tabBarItem =
[[UITabBarItem alloc] initWithTitle:#"First" image:nil tag:0];
mySecondVC.tabBarItem =
[[UITabBarItem alloc] initWithTitle:#"Second" image:nil tag:1];
Then, you'll be able to grab a reference to view controller.
// In your example, your ApolloTabBarController acts as its own delegate.
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
UIViewController* activeVC =
[[self viewControllers] objectAtIndex:[item tag]];
}
Related
I want to include some UIKeyCommands in my app. My app consists of one UISplitViewController that forces the master to be always visible on iPad full screen. On smaller screen it works like it normally would.
Now, I've implemented some UIKeyCommands in the MasterViewController and some in the DetailViewController. However, the app will only show those in DetailViewController. So I put all of them in the RootSplitViewController, but that will show all of them, even when the MasterViewController is hidden in iOS 9's splitview.
What I want though, is for it to show all when the app is fullscreen on iPad and thus the MasterViewController is forced on screen together with the DetailViewController. And when the view is small (ie 50-50) and the MasterViewController is hidden, I want it to only show those of the window that's on screen.
Any ideas on how to achieve this?
In the end I managed to do this - although in a not-so-pretty way.
The UIKeyCommands are added to the RootSplitViewController.
- (NSArray *)keyCommands {
if (self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
return #[
[UIKeyCommand keyCommandWithInput:#"r" modifierFlags:UIKeyModifierCommand action:#selector(changeRestaurant:) discoverabilityTitle:#"Change restaurant"],
[UIKeyCommand keyCommandWithInput:#"t" modifierFlags:UIKeyModifierCommand action:#selector(changeTable:) discoverabilityTitle:#"Change table"]
];
} else {
if (self.masterIsVisible == YES) {
return #[
[UIKeyCommand keyCommandWithInput:#"t" modifierFlags:UIKeyModifierCommand action:#selector(changeRestaurant:) discoverabilityTitle:#"Change restaurant"]
];
} else {
return #[
[UIKeyCommand keyCommandWithInput:#"t" modifierFlags:UIKeyModifierCommand action:#selector(changeTable:) discoverabilityTitle:#"Change table"]
];
}
}
}
Those methods call the actual methods in the specific UIViewController.
- (void)changeRestaurant:(id)sender {
UINavigationController *nav = (UINavigationController *)[self.viewControllers objectAtIndex:0];
RestaurantController *master = [nav.viewControllers objectAtIndex:0];
[master changeRestaurant];
}
- (void)changeTable:(id)sender {
UINavigationController *nav = (UINavigationController *)[self.viewControllers objectAtIndex:1];
TableController *detail = [nav.viewControllers objectAtIndex:0];
[detail changeTable:sender];
}
In order for this to work I added a BOOL to the UISplitViewController.
#interface RootSplitViewController : UISplitViewController
#property (nonatomic) BOOL masterIsVisible;
#end
Which is then called in the MasterViewController.
- (void)viewDidDisappear:(BOOL)animated {
RootSplitViewController *rootView = (RootSplitViewController *)self.splitViewController;
rootView.masterIsVisible = NO;
}
- (void)viewDidAppear:(BOOL)animated {
RootSplitViewController *rootView = (RootSplitViewController *)self.splitViewController;
rootView.masterIsVisible = YES;
}
I know this might not be the pretties method, but it works. If anyone knows a better way to do it, I'd love to hear your feedback.
I am new to iPad development. I know how to use images for Page view controller. My problem is i have 3 charts which i have done in three view controllers.. How can i combine all the view controllers in a single page view controller. I have for now kept three view controllers.
I have tried lot of tutorials but none explains me how to use three view controllers
I have done like this now but this is wrong
-(IBAction)handleSwipeLeft:(UISwipeGestureRecognizer*)sender {
LastLearningSessionViewController *last=[[LastLearningSessionViewController alloc]init];
[self presentViewController:last animated:YES completion:nil];
}
From what I can see it appears you may have done this slightly wrong.
First you need to create a controller for the UIPageViewController, that is a datasource and delegate.
Please note all code has been written directly to the answer and has not been tested.
MyUIPageViewController.h
#interface
MyUIPageViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
{
NSNumber *currentIndex //Using NSNumber to handle 32bit/64bit easier.
}
#property (nonatomic, strong) UIPageViewController *pageViewController
#property (nonatomic, strong) NSArray *controllersArray //Used to help navigate between controllers
#end
MyUIPageViewController.m
#import MyUIPageViewController.h
#implementation MyUIPageViewController
- (instancetype)initWithNibName:(NSString *)nibName
bundle:(NSBundle *)nibBundle
{
if(self = [super initWithNibName:nibName bundle:nibBundle])
{
//Create ChartViewController1 (UIViewController *ChartViewController1 = [[ChartViewController1Class alloc] init];)
//Create ChartViewController2
//Create ChartViewController3
//Now we have created all 3 chartViewControllers, create our controllers Array with the controller objects.
self.controllersArray = [[NSArray alloc] initWithObjects:ChartViewController1, ChartViewController2, ChartViewController3];
//Currently setting to 0. A proper way of handling with Multi-tasking is to store the index value from before, but not dealing with that right now.
currentIndex = [NSNumber numberWithInt:0];
//Create our PageViewController. Currently set to PageCurl and all pages will go from left to right.
//These options can be changed, if so desired (Scroll Effect like iBooks Textbooks and a page change from bottom to top like a flip book.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
//Set ourselves as the datasource and delegate to handle the pages etc.
self.pageViewController.datasource = self;
self.pageViewController.delegate = self;
//We need to set the viewControllers for the PageViewController, because this is the initial load, we will not animate the change.
[self.pageViewController setViewControllers:self.controllersArray direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^ (BOOL finished) {
//No animation is being done so no need to worry.
}];
//Set our view to be the pagecontroller's view, so we can see it all.
self.view = self.pageViewController.view;
}
return self;
}
//DataSource Methods:
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
//As this method looks for the previous view controller. If our current index is 0, there is no previous VC. But using the objectAtIndex method on the array would throw a outOfRange exception
if([self.currentIndex intValue] <= 0)
{
return nil;
}
else
{
return [self.controllersArray objectAtIndex:([self.currentIndex intValue] - 1)];
}
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
//As this method looks for the next view controller. If our current index is the maximum value the array count and be (2), there isn't a new VC to push. But using the objectAtIndex method on the array would throw a outOfRange exception
if([self.currentIndex intValue] >= self.controllersArray.count)
{
return nil;
}
else
{
return [self.controllersArray objectAtIndex:([self.currentIndex intValue] + 1)];
}
}
//Delegate Methods
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if(transitionCompleted)
{
//We will update our currentIndex, only if the transition has happened.
switch (previousViewControllers)
{
case 0:
//Something went wrong :S
break;
case 1:
//We are either in Vertical Orientation of the first viewController is only being shown.
if([pageViewController.viewControllers contains:[self.controllersArray objectAtIndex:([currentIndex intValue]+ 1)]])
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] + 1)];
}
else
{
if([currentIndex intValue] == 0)
{
//Saftey Net.
}
else
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] - 1)];
}
}
break;
case 2:
//We are in horizontal Orientation.
//With 3 View Controllers the only ViewController that will be in both arrays is the ViewController at index 1. We just need to see if the pageViewControllers viewcontrollers array contains the ViewController at index 0 or index 1.
if([pageViewController.viewControllers contains:[self.controllersArray objectAtIndex:([currentIndex intValue]+ 1)]])
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] + 1)];
}
else
{
if([currentIndex intValue] == 0)
{
//Saftey Net.
}
else
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] - 1)];
}
}
break;
default:
//Should never reach here.
break;
}
}
}
#end
It is also handy to look at the reference documentation:
Class Reference - Apple Docs
I have two UIViewControllers, one is a UIPickerViewController, the Other a UITableViewController. Ideally the Picker should get a request from the user to add x amount of some item to the tableView. The Picker gets user inputs and assigns them to variables val1, val2, val3, where val1 is the number of items (number of rows) and val2 is the name or label for the item.
PickerViewController.m
- (IBAction)add:(id)sender
{
TableViewController *tvc = [[TableViewController alloc] init];
[tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:tvc animated:YES completion:nil];
}
TableViewController.m
-(void)setValues:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
self.val1 = newVal1;
self.val2 = newVal2;
self.val3 = newVal3;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"UITableViewCell"];
// This is just a header which holds my "Add" button
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];
[self addNew:self.val1 :self.val2 :self.val3];
}
- (void)addNew:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
if(!self.numberOfRows){
NSLog(#"Initially no of rows = %d", self.numberOfRows);
self.numberOfRows = [self.val1 intValue];
NSLog(#"Then no of rows = %d", self.numberOfRows);
}
else
{
self.numberOfRows = self.numberOfRows + [newVal1 intValue];
NSLog(#"New no rows = %d", self.numberOfRows);
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows inSection:0];
// Only run when called again .. not initially
if(self.run != 0){
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
self.run ++;
[self.tableView endUpdates];
}
}
// "ADD" button which should go back to the picker and get new items to add to the table
- (IBAction)testAdd:(id)sender
{
PickerViewController *pvc = [[PickerViewController alloc] init];
[self presentViewController:pvc animated:YES completion:nil];
}
Now, I realize every time I call the next view controller I am creating a new instance of it, but I don't know how else to do it, I figure this is the main problem. As of right now, I expect when I leave the tableview for the picker view and return the console should log "New no of rows = x" but that doesn't happen.
I know val3 isn't used and my addNew: may not be the best, but I just need it to handle the basic logging mentioned above and I should be able to take it from there.
Been stuck on this for days
Create a property for TableViewController, and only create it the first time you present it,
- (IBAction)add:(id)sender {
if (! self.tvc) {
self.tvc = [[TableViewController alloc] init];
}
[self.tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:self.tvc animated:YES completion:nil];
}
It's not entirely clear from you question, whether it's this presentation or the one you have in the table view class that you're talking about. It also looks like you're doing something wrong in terms of presentation -- you're presenting the picker view from the table view controller, and also presenting the table view controller from the picker. That's not correct, you should present which ever controller you want to appear second, and that controller should use dismissViewControllerAnimated to go back, not present another controller.
In testAdd you don't need to create a new instance and present it. If you want to go back to the presentingViewController, just use dismissViewControllerAnimated .
And you will go one controller up in the stack.
My question is based on the following question, but my case is reversed.
Example for login screen modally based on storyboard
I would like to have a few tabs private for users only so required login, my storyboard is below:
Below is my code in custom tabBarController
#interface PNTabBarController ()
#end
#implementation PNTabBarController
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSUInteger indexOfTab = [tabBar.items indexOfObject:item];
NSLog(#"%lu", (unsigned long)indexOfTab);
if (indexOfTab == 2 || indexOfTab == 3) {
PFUser *currentUser = [PFUser currentUser];
NSLog(#"%#", currentUser.username);
if(currentUser == nil){
PNLoginViewController *obj = [[PNLoginViewController alloc]init];
[self presentViewController:obj animated:YES completion:NULL];
}
else{
NSLog(#"%#", currentUser.username);
}
}
}
But what happens is a dark screen when I click the 2 or 3 tab. What should be the correct way to achieve this?
I want to reset my UISearch when app is entering background, or entering foreground again. It would be enough when the tableview from UISearch gets hidden.
But when I try to hide it from AppDelegate.m it doesn't work. I have also logged the UIElements, there are also (null).
Here is how I try to access it:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
XLog(#"");
/*
Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
*/
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
XLog(#"");
searchViewController = [[SearchViewController alloc] initWithNibName:#"SearchViewController_iPad" bundle:[NSBundle mainBundle]];
} else {
XLog(#"");
searchViewController = [[SearchViewController alloc] initWithNibName:#"SearchViewController_iPhone" bundle:[NSBundle mainBundle]];
}
XLog(#"searchViewController.searchBar.text: %#", searchViewController.searchBar.text);
searchViewController.tableViewSearch.hidden = YES; // is (null)
XLog(#"searchViewController.tableViewSearch: %#", searchViewController.tableViewSearch); // is (null)
}
How can I access that? Do I something wrong here, or is it not allowed to access elements from other classes thru appdelegate.m?
NSArray *ary_navigationControllerViews = [[NSArray alloc] initWithArray:[self.navigationController viewControllers]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self.class.description == %#", [[SearchViewController class] description]];
NSArray *ary_viewController = [[NSArray alloc] initWithArray:[ary_navigationControllerViews filteredArrayUsingPredicate:predicate]];
if ([ary_viewController count] > 0)
{
SearchViewController *sVw = (SearchViewController*) [ary_viewController objectAtIndex:0] ;
sVw.tableViewSearch.hidden = YES;
}
You are initializing new instance of your view controller, instead you need to get existing instance of your view controller and hide your view. Hope this helps.