I am having some trouble with tapping the selected tab to make the tableview go to the top. I am using a custom TabBarViewController and inside there I am using the following function:
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
static UIViewController *previousController = nil;
if (previousController == viewController) {
// the same tab was tapped a second time
if ([viewController respondsToSelector:#selector(scrollToTop)]) {
[viewController scrollToTop];
}
}
previousController = viewController;
}
I am also getting the following Error:
No visible #interface for 'UIViewController' declares the selector 'scrollToTop'
I am wondering how I go about fixing this? If you guys need anymore information, please let me know. This has been bugging me for like a week!
Thanks all!!
Did your view controllers of tab bar controller is kind of UINavigationController?Maybe you should do like this:
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
static UIViewController *previousController = nil;
if ([viewController isKindOfClass:[UINavigationController class]]) {
if (previousController == ((UINavigationController *)viewController).viewControllers.firstObject) {
// the same tab was tapped a second time
if ([viewController respondsToSelector:#selector(scrollToTop)]) {
[viewController scrollToTop];
}
}
previousController = ((UINavigationController *)viewController).viewControllers.firstObject;
} else {
previousController = viewController;
}
}
Related
I am debugging legacy code which is always fun. The old code tried to mock the splitView delegate methods, causing all sorts of issues - mainly crashing: on a Plus device in Portrait, rotating to landscape caused the crash - if there was no detail view set, old code attempted to create one in a dodgy hack and it was just useless...
My app is UISplitViewController based, where I have a navigation stack in both master and detail sides of the splitView.
By reading though SO and using this example and was able to implement UISplitViewController delegate methods and everything is working correctly in regards to rotation, and showing the correct master/detail views when appropriate. Here is my implementation: (apologies for wall of code snippets)
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
if ([secondaryViewController isKindOfClass:[UINavigationController class]]
&& [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[AECourseHTMLTableViewController class]]
&& ([(AECourseHTMLTableViewController *)[(UINavigationController *)secondaryViewController topViewController] htmlContentEntry] == nil)) {
// If the detail controller doesn't have an item, display the primary view controller instead
return YES;
}
return NO;
}
And the other splitView delegate method - see comments in code for where I'm stuck.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:courseViewController.currentIndexPath];
}
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
if (!self.splitViewController.isCollapsed) {
UINavigationController *navController = self.splitViewController.viewControllers.firstObject;
AEContentMenuTableViewController *contentMenuVC = navController.viewControllers.firstObject; // This controller needs to be master in Landscape
NSMutableArray<UIViewController *> *controllers = [navController.viewControllers mutableCopy]; // Contains 3 controllers, first needs removed
NSMutableArray *toDelete = [NSMutableArray new];
for (UIViewController *viewController in controllers)
if ([viewController isKindOfClass:[contentMenuVC class]] || [viewController isKindOfClass:[AECourseHTMLTableViewController class]]) {
[toDelete addObject:viewController]; // Remove first VC, so master should become AEContentMenuVC?
break;
}
// Remove the object
[controllers removeObjectsInArray:toDelete];
// Set viewControllers
navController.viewControllers = controllers;
}
return navController;
}
AECourseHTMLTableViewController has next/prev buttons to select the next row in the tableview of the tableview menu class class (AEContentMenuTableViewController). I have a delegate function which can tell me the current indexPath in which AECourseHTML... is using from AEContentMenu..., and when calling it, it selects the menu tableview row and instantiates a new AECourseHTML... and pushes it.
This is where I'm stuck. In Portrait, pressing next/prev is fine, it selects the correct row and works as expected. But once I rotate the device, both master and detail views show the detail view. I can press "Back" on the master view, and it takes me to the correct AEContentMenu... class. As noted in the code snippet comments, I need to remove a ViewController from the master stack (the first object actually), and AEContentMenu... should become the first object of that stack - so when rotating, that should be the master view.
Apologies for such a long post, I've been banging my head with this for weeks now and I want to include as much info as possible in this question. Thanks in advance.
I found a solution which works well for my use cases. It may not be the cleanest code, but I'm happy with what I've got.
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
remains unchanged. I have updated my splitViewController:separateSecondaryViewControllerFromPrimaryViewController: delegate method with the solution. Any feedback is welcome.
- (UIViewController *)splitViewController:(UISplitViewController *)splitViewController separateSecondaryViewControllerFromPrimaryViewController:(UIViewController *)primaryViewController {
// If detail view already exists
if ([primaryViewController isKindOfClass:[UINavigationController class]]) {
for (UIViewController *controller in [(UINavigationController *)primaryViewController viewControllers]) {
if ([controller isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)controller visibleViewController] isKindOfClass:[AECourseHTMLTableViewController class]]) {
return controller;
}
}
}
// Return CourseVC
UINavigationController *navController = splitViewController.viewControllers.firstObject;
UIViewController *viewController;
for (viewController in navController.viewControllers) {
if ([navController.viewControllers.lastObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
return viewController;
} else {
// Create detail view
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"CourseHTMLNav"];
if ([navController.viewControllers.firstObject isKindOfClass:[AECourseHTMLTableViewController class]]) {
// Enable back button
UIViewController *controller = [navController visibleViewController];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
AECourseHTMLTableViewController *courseViewController = navController.viewControllers.firstObject;
// If next/prev has been tapped, configure current ContentHTML
if (self.currentContentHTML) {
[self configureViewController:courseViewController entry:self.currentContentHTML indexPath:courseViewController.currentIndexPath];
} else {
// Create new ContentHTML from first row of AEContentMenuVC
[self configureViewController:courseViewController entry:self.contentSection.sections[0] indexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
}
return navController;
}
}
}
return navController;
}
Your top if statement should return nil. Since you were returning the nested navigation controller you were missing out on the default behaviour of popping the master navigation's top controller which is required so it can then be placed on the right.
The default behaviour will find that nested nav controller and pop it. However the reason you still need to search for it yourself is if it isn't there then you need to load the detail nav from the storyboard as you have done.
I have view controller say A i am going from A to B.Then on B by pressing back button i am coming to A.Now i want to know from which view controller i am coming back.Please tell me how can i do this.I know i can do it by using viewWillAppear method but don't want to use this.
Please tell which is the best way of doing it?
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"view will appear called");
}
You have been push to B-VC from A-VC right.You can come back by popping.
self.navigationController!.popViewControllerAnimated(true)--Write this code in B-VC
VC-ViewController.
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
NSLog(#"from VC class %#", [fromVC class]);
if ([fromVC isKindOfClass:[ControllerYouJustPopped class]])
{
NSLog(#"Returning from popped controller");
}
return nil;
}
This really saved me .
you can pass id of controller before pop A.h #property NSInteger childVC; A.m
-(void)viewWillAppear:(BOOL)animated
{
if (_childVC == 3){
//todo
}
}
B.m
-(void) viewWillDisappear:(BOOL)animated {
A *parent = (A *)self.navigationController.viewControllers[self.navigationController.viewControllers.count - 2];
parent.childVC = 3;
[super viewWillDisappear:animated];
}
I do not see other ways
You could use a navigation controller delegate and implement the following UINavigationControllerDelegate method:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
UIViewController *fromViewController = [navigationController.transitionCoordinator viewControllerForKey:UITransitionContextFromViewControllerKey];
NSLog(#"%#", fromViewController.title);
}
Basically, I have a Table View Controller and a normal View Controller, I want to make it so that I can swipe between the view controllers like on the home screen of app (obvious same type of view controller).
But the thing I am finding is that the UIpageviewcontroller is usaully used when you have multiple views of the same type of viewcontroller for example swiping between pictures, you could just set an index in the class and an array of images relating to the index and set it up that way.
But I'm trying to get different view controllers in this set up. Right now it's two but will definitely go up to about 4.
Is UIPageViewController the right way to set it up or something else.
EDIT: Code added below
//
// ViewController.m
// testb
//
#import "MainPostController.h"
#import "MainTableViewController.h"
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize pageViewController;
#synthesize indexValue = _indexValue;
- (void)viewDidLoad
{
[super viewDidLoad];
self.indexValue= 1;
NSLog(#"main intitated");
// Do any additional setup after loading the view, typically from a nib.
self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageViewController"];
self.pageViewController.dataSource = self;
MainTableViewController *tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainTableViewController"];
MainPostController *pvc;
NSArray *viewcontrollers =[NSArray arrayWithObjects:tvc, pvc, nil];
[self.pageViewController setViewControllers:viewcontrollers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 30);
[self addChildViewController:pageViewController];
[self.view addSubview:pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
if (self.indexValue == 1) {
MainPostController *pvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainPostController"];
self.indexValue = 0 ;
return pvc;
}
else return nil;
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
if (self.indexValue == 0) {
MainTableViewController *tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainTableViewController"];
self.indexValue = 1;
return tvc;
}
else return nil;
}
#end
I want the intial view to be the table view controller(tvc) and i want the Post view controller(pvc) to be to its left. I can load it up and swipe between the two easily but sometime i can swipe further than the boundaries so basically if i start on the tvc-->pvc-->tvc then i can go further by swiping right to the same tvc then the boundary is reached or go the other way tvc-->pvc-->tvc-->pvc-->pvc by swiping left for the last transition. The boundaries are reached in the end. Once the app intially launches at tvc i cannot go forward a page, (which is what should happen) and if i go tvc-->pvc and try to go to a page to the left it wont let me (which is what should happen)
Thanks
Finally done it, didn't have to add any properties to the different classes. Just tested the class. Heres the code:
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
if ([viewController isKindOfClass:[MainTableViewController class]]) {
MainPostController *pvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainPostController"];
return pvc;
}
else return nil;
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
if ([viewController isKindOfClass:[MainPostController class]]) {
MainTableViewController *tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainTableViewController"];
return tvc;
}
else return nil;
}
Should be easy to add more view controllers as i just need to add else if statements to test them. The main problem solver was the method isKindOfClass:
Thanks for your reply guys!
I had the same issue, here's what i did and working perfectly.
.h
#interface ViewController : UIViewController <UIPageViewControllerDataSource>
#property (strong, nonatomic) UIPageViewController *pageViewController;
#property (nonatomic) NSArray *viewControllerIDArr;
#end
.m
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupDefaults];
}
- (void)setupDefaults
{
self.viewControllerIDArr = #[#"FirstViewController", #"SecondViewController"];
self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageViewController"];
self.pageViewController.dataSource = self;
UIViewController *startingViewController = [self.storyboard instantiateViewControllerWithIdentifier:self.viewControllerIDArr[0]];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 45);
[self addChildViewController:_pageViewController];
[self.view addSubview:_pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
}
- (UIViewController *)getViewControllerFromClass:(Class)class isNext:(BOOL)next
{
NSInteger index = [self.viewControllerIDArr indexOfObject:[NSString stringWithFormat:#"%#", class]];
if (next)
index+=1;
else
index-=1;
if (index < 0 || index >= self.viewControllerIDArr.count)
return nil;
else
return [self.storyboard instantiateViewControllerWithIdentifier:self.viewControllerIDArr[index]];
}
#pragma mark - Page View Controller Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
return [self getViewControllerFromClass:viewController.class isNext:NO];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
return [self getViewControllerFromClass:viewController.class isNext:YES];
}
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
return 0;
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return 0;
}
Note:
self.viewControllerIDArr containers Storyboard ID's of the viewControllers.
It looks like this in storyboard:
This is an old question but since I bumped into this problem I'll post my solution.
The first time I did this I added my code
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { }
Which received problems when you continuously scroll the views using two fingers and didFinishAnimating is not called unless scrolling stops.
So I just used this delegate method instead.
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) {
let currentView = pendingViewControllers[0]
if currentView is UINavigationController && currentView.restorationIdentifier == "Settings" {
index = 0
} else if currentView is UINavigationController && currentView.restorationIdentifier == "Main" {
index = 1
} else if currentView is UINavigationController && currentView.restorationIdentifier == "Message" {
index = 2
}
}
Where currentView is the currentUIViewController (or UINavigationController like what I am using, using a restorationIdentifier to identify my navigation controller as well), and index is a global value to know which UIViewController in the array to instantiate.
You could use a UIViewPageViewController, with some custom logic.
As you say, the normal way to handle this is to have the page view controller's data source simply create the same type of view controller for each page and fill it with data from your model.
In your case, you could add a pageNumber property to all the view controllers that you're going to host in your page view controller. Then, in the data source methods, use the page number of the old view controller to figure out the new page number. Then have a switch statement based on page number. Each case in the switch statement would create a different type of view controller.
I have 3 TabBarItems in UITabBarController:
<UINavigationController: 0xc76a680>
<SplitViewController: 0xc76a170>
<UINavigationController: 0xca5e6f0>
And I have the method in AppDelegate:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
NSLog(#"tab selected index %#",viewController);
if (viewController == nil ) // I NEED TO IMPLEMENT A CHECk HERE
{
//show popup
return NO; //does not change the tab
}
return YES; //does change the tab
}
So how to check that view controller which should be selected is the second Navigation Controller? Thx
try this code
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
BOOL result;
if (viewController == [self.tabBarController.viewControllers objectAtIndex:2]) //assuming the index of uinavigationcontroller is 2
{
NSLog(#"Write your code based on condition");
result = NO;
}
else {
result = YES;
}
return result;
}
You could check for viewcontroller.title property and then make the decision. Assuming that title for both viewcontrollers are different.
I may have some misunderstanding regarding the use of the UINavigationControllerDelegate protocol. Here is my situation:
I have a ViewController, let's call it, BViewController that may display a PopoverViewController. BViewController is the second ViewController in a NavigationContoller's stack, after AViewController. I need to dismiss the PopoverViewController when the user hits a button in BViewController and the app takes us back to the previous view--AViewController.
To do that, I have implemented the following in BViewController
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"BViewController will disappear");
// Check whether the popoverViewController is visible
if (self.popoverController.popoverVisible==YES) {
[self.popoverController dismissPopoverAnimated:NO];
}
}
However, that is not being called directly by the framework as BViewController is inside a NavigationController. Hence, I register a UINavigationControllerDelegate with my NavigationController and implement the following two methods:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Pass the message on to the viewController in question
[viewController viewWillAppear:animated];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Pass the message on to the viewController in question
[viewController viewWillDisappear:animated];
}
However, it seems that the passed in viewController parameter in both methods is the one that is about to be shown. I would have expected that the second method gives me access to the one that is about to disappear. So, when the user hits aforementioned button viewWillDisappear gets called on AViewController (which is about to be shown) and not on BViewController (which is about to disappear). Does that sound right? The apple documentation refers in both cases to
The view controller whose view and navigation item properties are being shown.
...which is not quite clear, I think. Thank you for some help, guys.
The two delegate method are both called for the same action (showing a view controller). The navigationController: willShowViewController:animated: is called before the new view controller is visible in the gui. The navigationController:navigationController didShowViewController:animated: is called after the new view controller is shown.
You will find this pattern in a lot of delegate protocols from apple. Unfortunately you do not have a delegate method in the NavigationViewController which tells you if the action was a pop or push.
I hook in my own protocol, which will know about the TO and FROM sides:
NavigationControllerDelegate.h:
#protocol NavigationControllerDelegate <NSObject>
#required
-(void) navigationController: (UINavigationController*) navController
willMoveFromViewController: (UIViewController*) from
toViewController: (UIViewController*) to;
#end
Instead of the regular UINavigationViewController, I then use a little helper class which keeps track of the view controllers:
NavigationHandler.h:
#interface NavigationHandler : NSObject <UINavigationControllerDelegate> {
NSMutableArray* m_viewControllers;
}
In my app delegate, I create one of these objects and set it as the delegate of the navigation controller:
...
m_navigationHandler = [[NavigationHandler alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController: mainMenuViewController];
navigationController.delegate = m_navigationHandler;
...
And from then on its a simple case of comparing my own list of view controllers with what the navigation controller has:
NavigationHandler.m
#import "NavigationHandler.h"
#import "NavigationControllerDelegate.h"
#implementation NavigationHandler
-(id) init {
if ((self = [super init])) {
m_viewControllers = [[NSMutableArray alloc] init];
}
return self;
}
-(void) dealloc {
[m_viewControllers release];
[super dealloc];
}
- (void)navigationController:(UINavigationController *)navController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated {
// Find out which viewControllers are disappearing and appearing
UIViewController* appearingViewController = nil;
UIViewController* disappearingViewController = nil;
if ([m_viewControllers count] < [navController.viewControllers count]) {
// pushing
if ([m_viewControllers count] > 0) {
disappearingViewController = [m_viewControllers lastObject];
}
appearingViewController = viewController;
[m_viewControllers addObject: viewController];
} else if ([m_viewControllers count] > [navController.viewControllers count]) {
// popping
disappearingViewController = [m_viewControllers lastObject];
appearingViewController = viewController;
[m_viewControllers removeLastObject];
} else {
return;
}
// Tell the view that will disappear
if (disappearingViewController != nil) {
if ([disappearingViewController conformsToProtocol: #protocol(NavigationControllerDelegate)]) {
if ([disappearingViewController respondsToSelector: #selector(navigationController:willMoveFromViewController:toViewController:)]) {
UIViewController<NavigationControllerDelegate>* vcDelegate = (UIViewController<NavigationControllerDelegate>*)disappearingViewController;
[vcDelegate navigationController: navController willMoveFromViewController: disappearingViewController toViewController: appearingViewController];
}
}
}
// Tell the view that will appear
if ([appearingViewController conformsToProtocol: #protocol(NavigationControllerDelegate)]) {
if ([appearingViewController respondsToSelector:#selector(navigationController:willMoveFromViewController:toViewController:)]) {
UIViewController<NavigationControllerDelegate>* vcDelegate = (UIViewController<NavigationControllerDelegate>*)appearingViewController;
[vcDelegate navigationController: navController willMoveFromViewController: disappearingViewController toViewController: appearingViewController];
}
}
}
#end