UIPageViewController check when swiped back or forward - iphone

I want to keep track of the index using the UIPageViewController. Whenever I swipe I need to index++ or index--. This delegate method gets called whenever you swipe back or further:
- (void)pageViewController:(UIPageViewController *)pvc didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
// If the page did not turn
if (!completed)
{
// You do nothing because whatever page you thought
// the book was on before the gesture started is still the correct page
return;
}
// I want to check here whenever the page was swiped back or further
}
How do I check in this method if the user swiped back or further? I know there are the 2 DataSource methods "viewControllerAfterViewController" and "viewControllerBeforeViewController" but I cannot check if the page transition has completed (and I can do this in the above method) any idea how I could know if the user swiped back or further in the above method?

use protocol:
MyClass : UIViewController <UIPageViewControllerDataSource,UIPageViewControllerDelegate
declare a atributes:
#property(nonatomic) NSInteger currentIndex;
#property(nonatomic) NSInteger nextIndex;
and in the methods:
-(void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{
NewsTableViewController *controller = [pendingViewControllers firstObject];
self.nextIndex = [self.arrViews indexOfObject:controller];
}
-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if (completed) {
self.currentIndex = self.nextIndex;
}
self.nextIndex = 0;
}
there you will have the current page.
Thanks to Corey Floyd in enter link description here

According to the documentation there does not appear to be a way to tell whether the user has swiped the page forward or backward. The boolean 'finished' will tell you whether or not the user has completed the page turn.
A workaround:
Create an int variable and using the viewControllerAfterViewController and viewControllerBeforeViewController methods either increase or decrease the value of the variable. Use that to test whether or not they moved forward or backward.
Edit: You could use presentationIndexForPageViewController from the documentation
Edit 2: Check this link here There is a method named setViewControllers:direction:animated:completion: the direction will be either UIPageViewControllerNavigationDirectionForward or UIPageViewControllerNavigationDirectionReverse
Edit 3: Code - This is assuming you know which view controller will be called by either going forward or backward:
Create a variable on your appDelegate and a setter method:
int indexVar;
- (void)setIndex:(int)indexVar;
Then on your view controllers (forward or backward) either increase the value or decrease the value (viewDidLoad):
(AppDelegate *)[[UIApplication sharedApplication] delegate] setIndex:<whatever>];
Something along those lines. This won't be an exact way to accomplish your goal, but hopefully it will get you headed in the right direction.

I did it by creating protocols in each of my ViewController classes, with the protocol method called in the viewWillAppear method. Then in the PageViewController whenever I instantiate one of the view controllers I set its delegate to be the PageViewController.
This is the 3rd ViewController in my project(Note that I've done this in each of my view controllers)
#class ViewController3;
#protocol ViewControllerPageNumber <NSObject>
-(void)viewWillAppearMyPageNumber:(int)myPageNumber;
#end
#interface ViewController3 : UIViewController
#property (nonatomic, weak) id <ViewControllerPageNumber> delegate;
#end
and in the .m file in the viewWillAppear method
-(void)viewWillAppear:(BOOL)animated{
[self.delegate viewWillAppearMyPageNumber:3];//The 3 will be different for each ViewController
}
Next, in the PageViewController.m, whenever I instantiate a view controller I set its delegate to be self( or PageViewController). viewCons is just an array of strings with my viewControllers names.
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index {
id vc = [[NSClassFromString([viewCons objectAtIndex:index]) alloc] init];
if([vc isMemberOfClass:[NSClassFromString(#"ViewController3") class]]){
ViewController3 *vc3=(ViewController3 *) vc;
vc3.delegate=self;
}else if([vc isMemberOfClass:[NSClassFromString(#"ViewController2") class]]){
ViewController2 *vc2=(ViewController2 *) vc;
vc2.delegate=self;
}else if([vc isMemberOfClass:[NSClassFromString(#"ViewController") class]]){
ViewController *vc1=(ViewController *) vc;
vc1.delegate=self;
}
return vc;
}
Finally, I'm implementing my custom delegate method, which in my case is refreshing labels' text I have set on top of the PageViewController.
-(void)viewWillAppearMyPageNumber:(int)myPageNumber{
[self refreshLabelsOnCurrentPageWithIndex:myPageNumber];
}

I think the easiest solution is to to create an own ViewController class with a property that keeps track of the currently shown index. In most of the cases I need a custom ViewController for my PageViewController anyways. In my example this is the following class.
#interface PageZoomViewController : UIViewController
#property (nonatomic) int pageIndex;
#end
Then in the viewControllerAfter/Before methods you can pass the index to the new page.
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
int nextPageIndex = ((PageZoomViewController *)viewController).pageIndex-1;
PageZoomViewController *controller = [[PageZoomViewController alloc] initWithPageViewControlParent:self andFrame:[self frameForPagingScrollView] andPageIndex:nextPageIndex];
return controller;
}
When the animation for the next page finished you can easily set the current index like this.
-(void)pageViewController:(UIPageViewController *)thePageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if(completed) {
index = ((PageZoomViewController *)thePageViewController.viewControllers[0]).pageIndex;
}
}
Hope this helps!

Related

it possible to Pass Data with popViewControllerAnimated?

I came across an interesting problem, i have main ViewController let's call him MainVC with navigationController and i am doing performSegueWithIdentifier from him to Mine second ViewController let's call him SecVC. so when i am trying to do the popViewControllerAnimated i want to pass some data from the SecVC to the MainVc.. i know i can do it with appDelegate Param or with singletons class but my question is : can i do it with more Elegant solution? like i use prepareForSegue and use local parmeters..
Thank you...
The best way to do it is by using a delegate.
//SecVCDelegate.h
#import <Foundation/Foundation.h>
#protocol SecVSDelegate <NSObject>
#optional
- (void)secVCDidDismisWithData:(NSObject*)data;
#end
//SecVC.h
#import <UIKit/UIKit.h>
#import "SecVSDelegate.h"
#interface SecVC : UIViewController
/** Returns the delegate */
#property (nonatomic, assign) id<SecVSDelegate> delegate;
#end
//SecVC.M
...
- (void) dealloc
{
...
delegate = nil
...
}
When ever you popViewControllerAnimated, right after it (or before it) you do this
if(_delegate && [_delegate respondsToSelector:#selector(secVCDidDismisWithData:)])
{
[_delegate secVCDidDismisWithData:myDataObject];
}
And in the MainVC you must be certain that you implement the delegate function
//MainVC.m
- (void)secVCDidDismisWithData
{
//do whatever you want with the data
}
To avoid any warnings you must tell that the MainVC class implements the delegate like this:
//MainVC.h
#import "SecVCDelegate.h"
...
#interface MainVC : UIViewController <SecVCDelegate>
...
secVCInstance.delegate = self;
[self.navigationController pushViewController:secVCInstance];
...
While I agree that the best option is to use Delegate,
but still if any one is looking for something different, he can use NSNotificationCenter as an alternative.
In viewDidLoad of MainVC:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(recvData:)
name:#"SecVCPopped"
object:nil];
}
And add method recvData in MainVC.m
- (void) recvData:(NSNotification *) notification
{
NSDictionary* userInfo = notification.userInfo;
int messageTotal = [[userInfo objectForKey:#"total"] intValue];
NSLog (#"Successfully received data from notification! %i", messageTotal);
}
Now in SecVC, before popping, add this line
NSMutableDictionary* userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:[NSNumber numberWithInt:messageTotal] forKey:#"total"];
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:#"SecVCPopped" object:self userInfo:userInfo];
I would do it in one of the following ways, but I'm not sure if it's elegant enough...
In SecVC, add an #property MainVC *mainVC; Use [self.mainVC setSomeValue:...]; before calling [self.navigationController popViewControllerAnimated:...];
Use [self.navigationController viewControllers]; to find out the MainVC *mainVC, and call [mainVC setSomeValue:...]; before the line of code that pop the ViewController.
Is this what you want?
I simply set up a protocol in the view being dismissed (example in Swift):
protocol ExampleTableViewControllerDismissDelegate {
func didDismiss(withData: Any)
}
var delegate: SearchableTableViewControllerDismissDelegate?
You can then call this when you dismiss/pop your view like this
self.navigationController?.popViewController(animated: true)
delegate?.didDismiss(withData: Any)
Then in the view being dismissed to (the parent in the hierarchy), we can conform to the delegate and essentially get a callback with the data after the view has been dismissed.
//MARK: ExampleTableViewControllerDismissDelegate
func didDismiss(withData: Any) {
//do some funky stuff
}
And don't forget to subscribe to the delegate in the parent view by using
viewController.delegate = self
There is another way to pass data between views including popViewControllerAnimated and it's with a global var instance, so If you modify that Var in your detail view and after do the popViewControllerAnimated, you can call the new data in the viewWillAppear method.
The first step is declare the Global var in main.h
NSMutableArray * layerList;
And now you have to call it in detail view.
SecondView.m
extern NSString *layerList;
SecondView.h
-(void)back{
layerList = #"Value to send";
[self.navigationController popViewControllerAnimated:YES];
}
Now you can use the information in the Master View after detect the pop action.
FirstView.m
extern NSString *layerList;
FirstView.h
-(void)viewWillAppear:(BOOL)animated{
NSLog(#"This is what I received: %#",layerList);
}

Object is nil when called from another class

I want to change properties of another object, when a method is called in another class.
The code to change the properties of this object sits in a method of the first class, and works when calling it from it's own class, but when called from the other class the object in the method returns nil.
Here is the code:
ViewController.h
#interface ViewController : UIViewController {
UIView *menuView; //the object
}
#property (nonatomic, retain) IBOutlet UIView *menuView;
-(void)closeMenu; //the method
#end
ViewController.m
#implementation ViewController
#synthesize menuView;
-(void)closeMenu{
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
NSLog(#"%f", menuView.frame.size.height); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
SDNestedTableViewController.h (nothing too important, but might help?)
#interface SDMenuViewController : SDNestedTableViewController{
}
SDNestedTableViewController.m
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
What you posted looks like:
-(void)closeMenu{
// menuView is never initialized, == nil
[nil setFrame:CGRectMake(0, -0, 0, 0)];
NSLog(#"%f", 0); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
So you are doing NSLog(#"%f", 0);.
If you do load the view by accessing the view property, the menuView will be initialized by IB rules.
For the details of viewController view loading/unloading see the reference docs.
I think this may help you.
At Your AppDelegate class, you have to declare an object of ViewController class. Make it as a property of the YourAppDelegate class. like below. (This would import ViewController class and creates a shared object of YourAppDelegate class so that you can access the members of YourAppDelegate class globally by simply importing the YourAppDelegate.h).
#import "ViewController.h"
#define UIAppDelegate ((YourAppDelegate *)[UIApplication sharedApplication].delegate)
#interface YourAppDelegate : NSObject <UIApplicationDelegate>
{
ViewController *objViewController;
}
#property (nonatomic, retain) ViewController *objViewController;
#end
And synthesize the property at YourAppDelegate.m file.
#implementation YourAppDelegate
#synthesize objViewController;
#end
Then the tricky part is, you have to backup the object of ViewController class in the YourAppDelegate class at the time you are loading the ViewController class.
For that first import the YourAppDelegate.h in the ViewController.h class and at the ViewController.m implement viewWillAppear: delegate as follows.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIAppDelegate.objViewController = self;
}
Then at SDNestedTableViewController.m,
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = (ViewController *)UIAppDelegate.objViewController;
if(firstViewController && [firstViewController isKindOfClass:[ViewController class]])
{
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
}
Try this way. I am not saying this as the right way but, this should works. Glad if this helps you.
EDIT 2:
Well, you shipped your code over to me, so now I can no longer say that I don't have enough information to solve your problem.
Let's see.
Now I see that your ViewController is the rootViewController of your app, like so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
Good, now how does the ViewController relate to your SDNestedTableViewController?
You have this in your ViewController's viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
SDMenuViewController *mvc = [[[SDMenuViewController alloc] initWithNibName:#"SDNestedTableView" bundle:nil] autorelease];
[self addChildViewController:mvc];
[mvc didMoveToParentViewController:self];
[menuView addSubview:mvc.view];
// Some other stuff with gesture recognizers I'm omitting...
[self openMenu];
}
Alright, so it looks like SDMenuViewController is the child of ViewController. Now, you have a method in SDMenuViewController called item:subItemDidChange:
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
ViewController *firstViewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
//close the menuView
[firstViewController closeMenu];
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
So, you want the reference back to the existing ViewController object, right? Because right there you're making another one. So, you can do this:
ViewController *firstViewController = self.parentViewController;
That gets you a reference to SDMenuViewController's parent, which is the instance of ViewController. This property is set when you do your addChildViewController: call.
Okay, this is confusing though:
In your post, you say that your item:subItemDidChange: method is in SDNestedTableViewController, but in the code you sent me it's in the SDMenuViewController.
In the SDNestedTableViewController, I found this method:
- (void) mainItem:(SDGroupCell *)item subItemDidChange: (SDSelectableCell *)subItem forTap:(BOOL)tapped
{
if(delegate != nil && [delegate respondsToSelector:#selector(item:subItemDidChange:)] )
{
[delegate performSelector:#selector(item:subItemDidChange:) withObject:item withObject:subItem];
}
}
So it looks like you're not using the same code as in the original post, but close enough, whatever.
Now, if you want to get a reference to the ViewController instance from anywhere in the app, not just your SDMenuViewController (which happens to be the child of the ViewController instance) you should use #Mathew Varghese's answer.
Here's a restatement of this method:
Add the line + (AppDelegate *)instance; to your AppDelegate.h file.
Add the following method to your AppDelegate.m file.
Like so:
+ (AppDelegate *)instance
{
AppDelegate *dg = [UIApplication sharedApplication].delegate;
return dg;
}
Then, in whatever object you want that reference, you #import AppDelegate.h and say ViewController *vc = AppDelegate.instance.firstViewController;
Anyway, it's just another way of saying what Mathew mentioned earlier.
the problem is:
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem {
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
...
[firstViewController closeMenu];
}
When you call closeMenu from there, it is never initialized, because not enough time has passed to initialize view of view controller, viewDidLoad method of your firstViewController is not called at this point either. menuView is not created from nib either, so this is the reason why it is nil.
Maybe for some reason there might be a delay long enough so menuView is created, but this is not how you should do things in iOS.
So, if you don't want to show your menuView, just add some boolean value to your firstViewController and instead of closeMenu do:
firstViewController.shouldCloseMenu = YES;
Then in your ViewController in viewDidLoad method do something like:
if (self.shouldCloseMenu ) {
[self closeMenu];
}
Maybe this is not the best way to do it, but now you have an idea how it suppose to work.
I believe your problem is the related to the way you have initialized the viewController.
Instead of
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
use
ViewController *firstViewController = [[[ViewController alloc] initWithNibName:#"yourNibName" bundle:nil] autorelease];
I'm assuming you have a nib because you are using an IBOutlet. But I believe the IBOutlet is never setup because you have not loaded the nib file.
Also double check your IBOutlet connection with interface builder and use "self.menuView"
I would suggest you to solve this problem in the following steps.
Do not use any instance or variable of firstViewController in the SDMenuViewController.
In the case check block, post a message to the NSNotificationCenter
In the ViewController register the message with the same message Id, use the closeMenu method as its handler.
For me, use the message center to dispatch the handling can decouple the relationship between controllers. This is a better way that you would concern less about the lifecycle of the controller within another one.
Hope it would be helpful.
There is a difference between alloc-init'ing a ViewController and alloc-init'ing that view controller's properties.
Regarding your second example (calling from another class). Your current code indicates that you alloc-init firstViewController, but then don't do anything with it. Assuming you have not overriden your ViewController's init method, its properties and iVars should be nil (or undefined at worst). You need to alloc-init your firstViewController.menuView first. I.e:
firstViewController.menuView = [[UIView alloc] initWithFrame]; // Don't do this.
The problem with this approach is that you're setting up firstViewController's properties form another class, and that's generally fairly average design practice. This required setup would usually happen in viewDidLoad but because you haven't done anything with firstViewController yet, it never gets called.
In contrast, when you call closeMenu from its own View Controller, the odds are you are actually doing something with the view and viewDidLoad (or wherever menuView = [[UIView alloc] init];is found) is called first, thus initialising your menuView object.
You need to ensure that your menuView object is initialised first before you try and do anything with it, just initialising the View Controller that contains it is not enough.
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
// why are we allocating this object here, if it is only required in case Checked :
ViewController *firstViewController = [[[ViewController alloc] init] autorelease];
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
[firstViewController closeMenu]; //called from other class
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
Change it to
#import "SDMenuViewController.h"
#import "ViewController.h"
- (void) item:(SDGroupCell *)item subItemDidChange:(SDSelectableCell *)subItem
{
// why are we allocating this object here, if it is only required in case Checked :
SelectableCellState state = subItem.selectableCellState;
NSIndexPath *indexPath = [item.subTable indexPathForCell:subItem];
switch (state) {
case Checked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Checked\"", indexPath);
// here no need to put object in autorelease mode.
ViewController *firstViewController = [[ViewController alloc] init];
[firstViewController closeMenu]; //called from other class
[firstViewController release];
break;
case Unchecked:
NSLog(#"Changed Sub Item at indexPath:%# to state \"Unchecked\"", indexPath);
break;
default:
break;
}
}
try to remove UIView *menuView; //the object from the interface file
#interface ViewController : UIViewController {
// try to remove this line
UIView *menuView; //the object
}
and update this method
-(void)closeMenu{
[self.menuView setFrame:CGRectMake(self.menuView.frame.origin.x, -self.menuView.frame.size.height, self.menuView.frame.size.width, self.menuView.frame.size.height)];
NSLog(#"%f", self.menuView.frame.size.height);
}
Everything is correct, change the -(void)closeMenu method like...
-(void)closeMenu
{
menuView=[[UIView alloc]initWithFrame:CGRectMake(50.0,50.0,200.0,200.0)]
NSLog(#"%f", menuView.frame.size.height); //returns height when method is called from it's own class. But returns 0 (nil) when called from the other class.
}
Try this and let me know.
I suggest you use this:
if(menuView) {
[menuView setFrame:CGRectMake(menuView.frame.origin.x, -menuView.frame.size.height, menuView.frame.size.width, menuView.frame.size.height)];
} else {
NSLog(#"menuView is nil");
}

iOS didSelectTabBarItem knowing what item was selected prior

I have an IOS app with a UITabBar and have its delegate set to my class.. the didSelectTabBarItem properly fires and all is right with the world. However I do have some conditional code that has to occur when the UITabBarItem selected is after one particular UITabBarItem IE.. if the user clicks on tab bar item 3, and they were currently on tab bar item 2 I have to do a little extra code, that I would not have to do if the user selected tab bar item 3 and was previously on tab bar item 1.
So, is there anyway programmatically (other than keeping direct track via my program via a state variable, to know what was the previously selected item was on a tab bar when a new tab bar item is selected?
Yes it is possible, through key-value-observing (KVO).
note This answer is in regard to a UITabBar not a UITabBarController. Tab bar controller delegates have methods you are looking for (as mentioned by rdelmar).
To start, observe your tab bar like so:
- (void)viewDidLoad{
[super viewDidLoad];
[self.tabBar addObserver:self forKeyPath:#"selectedItem" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
}
I think you can already see where I'm going based on my using both options old & new. Then simply observe the change instead of using the delegate method, like so:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"selectedItem"] && [object isKindOfClass:[UITabBar class]]){
UITabBar *bar = (UITabBar *)object; // The object will be the bar we're observing.
// The change dictionary will contain the previous tabBarItem for the "old" key.
UITabBarItem *wasItem = [change objectForKey:NSKeyValueChangeOldKey];
NSUInteger was = [bar.items indexOfObject:wasItem];
// The same is true for the new tabBarItem but it will be under the "new" key.
UITabBarItem *isItem = [change objectForKey:NSKeyValueChangeNewKey];
NSUInteger is = [bar.items indexOfObject:isItem];
NSLog(#"was tab %i",was);
NSLog(#"is tab %i",is);
}
// handle other observings.
}
Remember to remove yourself as observer in both viewDidUnload and dealloc, since viewDidUnload may never be called.
I don't know if this can be done in a way other than what you suggested (a state variable), if you're not using a UITabBarController. If you are using a tab bar controller, then you can do this in the delegate of the tab bar controller:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
if (viewController == [self.tabBarController.viewControllers objectAtIndex:2 && self.tabBarController.selectedIndex == 1]) {
NSLog(#"Do special programming");
}
return YES;
}
This method is called before the switch is made (unlike the UITabBar method didSelectTabBarItem), so the selected index will be the index of the tab that was active before you touched the new tab.
There may be better ideas but one way to do is by by creating a NSString object in your AppDelegate to store the name of the class of the current view controller so that you can read the string from your next view controller and check the previously selected item.
In you AppDelegate.h declare a string and synthesize it.
#property (strong, nonatomic) NSString * preSelectedViewController;
And in all your UIViewControllers which are set as items for your UITabViewController do this
in .h files
#import "AppDelegate.h"
in .m files include this in your viewWillAppear: method
AppDelegate * delegate1 =(AppDelegate *) [[UIApplication sharedApplication] delegate];
if (delegate1.preSelectedViewController ==nil)
{
delegate1.preSelectedViewController=NSStringFromClass( [self class]);
}
NSLog(#"previous %#",delegate1.preSelectedViewController);
//include 2nd_viewcontroller.h file and this if statement in your 3rd_viewcontroller(i.e. where you want to check and do your other programming)
if ([delegate1.preSelectedViewController isEqualToString:NSStringFromClass([2nd_ViewController class]) ]) {
//do your little extra code
}
delegate1.preSelectedViewController=NSStringFromClass( [self class]);
NSLog(#"present %#",delegate1.preSelectedViewController);
Guess this will work for you
Why not storing the lastSelectedIndex in an iVar and in
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
you have both values on your hands.
You might even (never tried it) use
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
so you have the currently selected view controller index view selectedIndex and then via an additional method you can find the index of the to be selected index of the viewController.
I found that this works with ReactiveCocoa:
#import <ReactiveCocoa/ReactiveCocoa.h>
// ...
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
#weakify(self);
[RACObserve(appDelegate, tabBarController.tabBar.selectedItem) subscribeNext:^(UITabBarItem *selectedTab) {
#strongify(self);
NSUInteger selectedIndex = [appDelegate.tabBarController.tabBar.items indexOfObject:selectedTab];
NSLog(#"selected index: %lu", (unsigned long)selectedIndex);
}];

Delegate not being called

I've a viewcontroller "ResultsViewController" with a button called emailbutton. when this button is pressed, i want a function to be called from another view called "Illusions_AppViewController" (both these viewcontrollers are not linked).
Therefore i defined a protocol in the "ResultsViewController.h":
#protocol ResultsViewDelegate <NSObject>
#optional
- (void) resultspage;
#end
#interface ResultsViewController : UIViewController
{
id<ResultsViewDelegate> mydelegate;
UIButton *emailButton;
}
#property(nonatomic,retain) IBOutlet UIButton *emailButton;
#property (nonatomic,assign) id<ResultsViewDelegate> mydelegate;
#end
In the ResultsViewController.m :
-(IBAction)emailButtonPressed:(id)sender
{
NSLog(#"entered emailbuttonpressed");// the app enters this method and gets hanged
if ([mydelegate respondsToSelector:#selector(resultspage)]) {
NSLog(#"entered respondstoselector");// this is never displayed in the log-showing that the delegates doesnt respond to selector
[mydelegate resultspage];
}
}
In my other view, "Illusions_AppViewController.m":
- (void)resultspage{
NSLog(#"Entered results page");
ResultsPageController *resultspagecontroller = [[ResultsPageController alloc] initWithNibName:#"ResultsPageController" bundle:nil];
resultspagecontroller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:resultspagecontroller animated:YES];
}
Would appreciate if anyone can help me with this. I've no clue of why the delegate is not called. the app gets hanged as soon as i press the emailbutton. Thanks!
The implementation/use of delegates is wrong. Please refer to this tutorial.
Hope this helps.
Thanks,
Madhup
or is there any other way to get this done. i just need the results page function to be called whenever the email button is pressed. i tried using this way:
ResultsViewController.m
-(IBAction)emailButtonPressed:(id)sender
{
NSLog(#"entered emailbuttonpressed");
illusions_AppViewController *illusionsview = [[illusions_AppViewController alloc]init];
[illusionsview performSelector:#selector(resultspage)];
}
Now the results page function gets called, but the resultspagecontroller that it needs to display as a modalviewcontroller never appears.the app hangs, and no errors in the console either.
To answer your second question, you are on the right track. Simply create an instance of your Illusions_AppViewController and call the illusionsView method in it instead using:
- (IBAction)emailButtonPressed {
illusions_AppViewController *illusionsview = [[illusions_AppViewController alloc]init];
[illusionsview resultspage];
[illusionsview release];
}

frame size does not change in landscape mode

I have a UINavigationController that can rotate to landscape mode and that it initializes a new UIViewController and pushes it on the stack. On the UIViewController's viewDidLoad I just print the self.view.frame.size.
If the UINavigationController is in Portrait mode, the View Controller will print {320, 460}. However, when the Navigation Controller is in Landscape, the frame size is exactly the same. (To clarify, in the second case the UIViewController is initialized while the phone is already in landscape mode.)
Shouldn't the frame size of the view be rotated? And if not how I can I find the correct frame size without hardcoding the numbers?
You can't rely on the frame in landscape mode; you have to use a combination of bounds and center; frame is generated using a combination of those, and when there's a non-identity transform applied (as there is in landscape), it gets a little weird.
first you need to set your view to resize automatically with a proper autoresizingMask.
with this your view will adapt to the size of the controller itself.
you can check this yourself with an NSLog. But don't put it in loadView, this is too early. Put this in viewWillAppear.
If you set the autoresizingMask of your view with Interface Builder you should turn off the Simulated Interface Elements in the Attributes inspector. If any of these is on you can't change the autoresizingMask in the Size inspector.
This is a pain and it's still true in iOS 4.2 for iPads. The way I solved this is to subclass the UIView associated with the UIViewController. I did this in interface builder but I suppose one could also do this in code somehow. In interface builder select the UIView in the UIViewController then hit the (i) icon in the upper right of the Inspector window. Under class identity hit the popup and chose the UIView subclass below.
The approach is that this UIView subclass overrides the layoutSubviews method, finds the next UIViewController, determines if it implements a reactToLayout method (which is a method that you have to implement in the UIViewController subclass for the view controller of this view). If the reactToLayout method exists in the first UIViewController found, it is invoked.
The reactToLayout method in the view controller then does whatever one needs to do which it will be able to do successfully since the view's frame is set up properly by this time (unlike in ViewDidLoad, viewWillAppear, or even viewDidAppear). I have a method that I call anytime the orientation or frame changes. It's a pain but I store the last frame laid out and last orientation laid out in internal variables of the view controller. The internal layout for new orientation or frame change method compares these to the view's current frame and requested or current orientation so that it doesn't unnecessarily layout stuff over and over.
Here's the code:
UILayoutSubviewsView.h
#import <UIKit/UIKit.h>
#interface UILayoutSubviewsView : UIView {
}
#end
UILayoutSubviewsView.m
#import "UILayoutSubviewsView.h"
// Create this to avoid a warning that this method does not exist for UIViewControllers
// this is OK since we check to see that it does exist before invoking it
#interface UIViewController(UndocumentedMethodForUIViewController)
-(void) reactToLayout;
#end
#implementation UILayoutSubviewsView
// Pass this up to our view controller if it supports the reactToLayout method
// (this is the whole reason for the class)
-(void) layoutSubviews {
[super layoutSubviews];
// Look for the first next responder that is a UIViewController
UIViewController *ourViewController = nil;
id myNextResponder = [self nextResponder];
while (myNextResponder != nil && ourViewController == nil) {
if ([myNextResponder isKindOfClass:[UIViewController class]]) {
ourViewController = myNextResponder;
}
else {
myNextResponder = [myNextResponder nextResponder];
}
}
// If we got a view controller, then see if it supports the reactToLayout method
if (ourViewController != nil) {
if ([ourViewController respondsToSelector:#selector(reactToLayout)]) {
// Invoke the view controller's reactToLayout method
[ourViewController reactToLayout];
}
}
}
#end
YourViewController.h
#import <UIKit/UIKit.h>
#interface YourViewController : UIViewController {
CGRect lastLayedOutFrame;
UIInterfaceOrientation lastLayedOutOrientation;
}
#pragma mark -
#pragma mark Instance Methods
-(id) init;
-(void) reactToLayout;
#end
YourViewController.m
#import "YourViewController.m"
#pragma mark Private Interface Category
#interface YourViewController()
-(void) setViewForCurrentFrameAndRequestedOrientation:(UIInterfaceOrientation) interfaceOrientation;
#end
#implementation YourPadViewController
-(id) init {
// First our super then set ourselves up
if (self = [super initWithNibName:#"YourViewController" bundle:nil]) {
// Initialize some basic stuff
lastLayedOutFrame = CGRectZero;
lastLayedOutOrientation = UIDeviceOrientationUnknown;
}
return self;
}
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
-(void) viewDidAppear:(BOOL) animated {
[super viewDidAppear:animated];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
-(void) reactToLayout {
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
#pragma mark -
#pragma mark Rotation Support
-(BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation {
return YES;
}
// This is called right before the actual rotation
-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation duration:(NSTimeInterval) duration {
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:interfaceOrientation];
}
// Make the necessary adjustments for the different view orientations
-(void) setViewForCurrentFrameAndRequestedOrientation:(UIInterfaceOrientation) interfaceOrientation {
// Set up the requested orientation (need this to handle the Unknown case)
UIInterfaceOrientation requestedOrientation;
if (interfaceOrientation != UIDeviceOrientationUnknown) {
requestedOrientation = interfaceOrientation;
}
else {
requestedOrientation = [[UIDevice currentDevice] orientation];
}
// See if we have anything to do
if (!(CGRectEqualToRect(self.view.frame, lastLayedOutFrame) && lastLayedOutOrientation == requestedOrientation)) {
// Do whatever needs to be done
// Record our last layed out frame and orientation
lastLayedOutFrame = self.view.frame;
lastLayedOutOrientation = requestedOrientation;
}
}
Hi every one
I think there is a simple solution that do the job for me
you can use currentSize instead of self.view.frame.size
in yourClass.h
#interface yourClass : UIViewController {
CGSize currentSize;
}
#property (nonatomic, readwrite)CGSize currentSize;
#end
in yourClass.m
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (toInterfaceOrientation != self.interfaceOrientation) {
CGSize newSize;
// 20 is the status bar height
newSize.width = self.view.bounds.size.height + 20;
newSize.height = self.view.bounds.size.width - 20;
currentSize = newSize;
//any other necessary code
}
}