I am using a UINavigationController and pushing/popping UIViewControllers onto it. In some instances I am attempting to pop to the root view controller and then push a view controller after a short delay (0.1f).
My push code for the Message View Controller is as follows. My app fires two notifications. The first to select a tab and the second to push the correct view controller onto the stack of that tab.
//user taps a button and the app needs to switch tab and push the correct viewController
//onto the tab. I have tried setting pop == NO to avoid a 'double pop' but I still get
//overlapped titles
-(IBAction)messages:(id)sender {
NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:[NSNumber numberWithInt:4], [NSNumber numberWithBool:YES] , nil] forKeys:[NSArray arrayWithObjects:#"tab",#"pop", nil]];
[[NSNotificationCenter defaultCenter] postNotificationName:kAutoSelectTab object:dictionary];
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:kMessages afterDelay:0.1f];
}
//responds to the first notification
-(void)autoSelectTab:(NSNotification*)notification {
NSDictionary* dictionary = (NSDictionary*)[notification object];
int tab = [[dictionary objectForKey:#"tab"] intValue];
BOOL pop = [[dictionary objectForKey:#"pop"] boolValue];
[self.tabBarController setSelectedIndex:tab];
UIViewController* vc = [[self.tabBarController childViewControllers] objectAtIndex:tab];
PSLogDebug(#"Selecting tab:%#",[vc class]);
[self tabBarController:self.tabBarController didSelectViewController:vc];
if (pop == YES) {
if ([vc isKindOfClass:[UINavigationController class]]) {
[(UINavigationController*)vc popToRootViewControllerAnimated:YES];
}
}
}
//responds to the second notification
-(IBAction)messages:(id)sender {
[self.navigationController popToRootViewControllerAnimated:NO];
MessagesViewController* vc = [[MessagesViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
[vc release];
}
Functionally the views appear to pop and push correctly BUT the titles do not pop and each new title is overlaid atop the old one.
I set the titles for each of the view controllers in viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.navigationItem.title = #"More";
}
When I don't attempt the pop to root followed by the delay followed by the push - the titles and views behave as expected with no overlapping occurring.
Example Images from screenshots
I've had a good dig around stack overflow but I can't see any questions which describe the same issue as the one I am having.
Qn.1: Is there something fundamentally incorrect with the popToRoot, Delay, push View approach?
Qn.2: If anyone out there has seen this kind of behaviour before, how did you resolve it?
Increasing the delay from 0.1f to 0.5f fixed the problem
Change
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:kMessages afterDelay:0.1f];
to
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:kMessages afterDelay:0.5f];
Related
I'm new to IOS7 and i am struggling to call pushViewController.
First i have UINavigationController as a root view controller i have UICollectionViewController.
In this UICollectionViewController i have also register UICollectionViewCell class
[self.collectionView registerClass:[MYProductCell class]
forCellWithReuseIdentifier:#"product"];
I am trying to pushViewController width detail view based on user action in the cell.
I have NO problems by using
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *product = self.products[indexPath.row];
TXDetailProductController *detailView = [[TXDetailProductController alloc] init];
detailView.product = product;
[self.navigationController pushViewController:detailView animated:YES];
}
But I want to call pushViewController:detailView from UICollectionViewCell class based on User action.
Can someone give me directions?
Thanks in advance!
You could send a message.
In your viewDidLoad you could set up a message listener:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(pushDetailView:) name:#"pushDetailView" object:nil];
Then add a method:
-(void) pushDetailView:(id)sender
{
// do your pushViewController
}
Then in your UICollectionViewCell when you need to push the view do:
NSNotification* notification = [NSNotification notificationWithName:#"pushDetailView" object:self];
[[NSNotificationCenter defaultCenter] postNotification:notification];
The listener should receive that notification and call the pushDetailView which will push the view. You might need some error checking in there also.
You might need to pass information to the method so you know what to push. You can put the information in an object and send it with the message. Something like:
NSNumber *indexPathRow = [NSNumber numberWithInt: indexPath.row];
NSNotification* notification = [NSNotification notificationWithName:#"pushDetailView" indexPathRow object:indexPathRow];
[[NSNotificationCenter defaultCenter] postNotification:notification];
Then on the receiver class you pull that info out of the notification's object:
-(void) pushDetailView:(NSNotification*)note
{
NSNumber indexPathRow = [note object];
NSDictionary *product = self.products[[indexPathRow intValue]];
// and on with your pushViewController code
}
Again error checking in there also.
I have a UINavigationController hierarchy, VC1 --> VC2.
VC1 has a table view that I need to reload when VC2 is done with its work, so VC1 has this code:
-(void)viewWillAppear:(BOOL)animated
{
[[self tableView] reloadData];
}
VC2 is essentially working with the server to create a new table row in VC1. When the done button in VC2 is pressed, I call [navController popViewControllerAnimated:YES]. So here's what happens from the user's perspective:
Visit VC2, use it to create a new row for the table in VC1. Press done.
The hierarchy successfully navigates back to VC1, but the tableview does not reload and display the new row.
However, if I then nav forward to VC2, and immediately hit the navController back button, the table does reload and show the new row.
So why does [tableview reload] work on 3 but not 2? Thanks so much.
==
More code in response to answer mentioned below:
In App delegate:
CWLandingVC *lvc = [[CWLandingVC alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:lvc];
[[self window] setRootViewController:navController];
In VC0:
-(void)toSessionMgmtViewController
{
TSessionMgmtViewController *tsmvc = [[TSessionMgmtViewController alloc] init];
[[self navigationController] pushViewController:tsmvc animated:YES];
}
In VC1:
- (IBAction)toCreateSessionView:(id)sender
{
TCreateSession *cs = [[TCreateSession alloc] init];
[[self navigationController] pushViewController:cs animated:YES];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[self tableView] reloadData];
}
In VC2:
Finishes working with server...
UINavigationController *navControler = [self navigationController];
[navControler popViewControllerAnimated:YES];
Also, when VC2 is done working with the server, it updates a data store of TSessions called SessionListStore:
- (TSession *)addSession:(NSString *)code withName:(NSString *) name qs:(int)qs
{
TSession *s = [[TSession alloc] initWithName:name code:code numberQuestions:qs];
[_sessions setObject:s forKey:code];
return s;
}
where sessions is a NSNutatbleDictionary in SessionListStore.
Thanks so much in advance.
EDIT: The solution was to trigger the reloadData call from the completion block of Server call.
Please check this answer,
Popping ViewController doesn't call viewWillAppear when going back
Have you added navigation controller to your view controller or view controllers to your navigation controller?
Also, you can set the desired view controller as the delegate of your navigation controller and implement this method.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
I have been trying everything to work this out. I get a notification when the app is closed with 2 custom items, a type and an id. The type is supposed to tell me which view to load, and the id is supposed to tell the app which row to get from the database. I am going through hell trying to figure this out.
I need to click on the notification and have it take me to the relevant record. So far I have been almost successful with two different methods that I'll outline below.
I should also point out that I know the payload is working correctly from APNS as I've debugged it to death :)
The first thing I tried was as follows:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSString *itemType = [[userInfo objectForKey:#"T"] description];
NSString *itemId = [[userInfo objectForKey:#"ID"] description];
self.window=[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// type 1 = call, type 2 = contact
if ([itemType isEqual: #"1"]) {
Leads_CallsDetailViewController *callView = [[Leads_CallsDetailViewController alloc] init];
[callView displayItem:itemId];
[self.window addSubview:callView.view];
[self.window makeKeyAndVisible];
} else if([itemType isEqual: #"2"]) {
Leads_ContactsDetailViewController *contactView = [[Leads_ContactsDetailViewController alloc] init];
[contactView displayItem:itemId];
[self.window addSubview:contactView.view];
[self.window makeKeyAndVisible];
}
}
With this one, I have a method on the detail views called displayItem that I was going to use to get the data from the api and then display it. This did something, but it looked like the view never really loaded. I have a scrollview and various buttons on the page, but all that ever got loaded from addSubview was a background image. Nothing ever really happened to fully load the view. I wasn't sure how to handle that.
The second thing I tried was to go directly to the view like this:
NSString *storyboardId = #"Leads_Calls_SB";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *initViewController = [storyboard instantiateViewControllerWithIdentifier:storyboardId];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = initViewController;
[self.window makeKeyAndVisible];
This one seems to load the view functioning and nice looking with two major caveats. 1. I'm not sure how to pass data to it, and 2. It didn't like it when I tried to pop back and it also got angry when I tried to segue pushes from there, almost as if there was no navigation controller for the view, even though the entire application is embedded in a navigation controller.
Thanks so much for your help. If anyone can help me figure this out I'll be indebted to you.
Normally for this requirement I would do this..
Use NSNotificationCenter and post a notification from didReceiveRemoteNotification.
[[NSNotificationCenter defaultCenter] postNotificationName:#"notificationReceived" object:self userInfo:userInfo];
Subscribe to it from the VC from where you can open your details view to show the message.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationReceived:) name:#"notificationReceived" object:nil];
If you are instantiating the VC yourself and not using segue. you can do this..
UIStoryboard* storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
detailVC = [storyBoard instantiateViewControllerWithIdentifier:#"detailVC"];
detailVC.delegate = self;
detailVC.userInfo = #"YOUR DATA";
[self presentViewController:detailVC animated:YES completion:nil];
To return you can do this in your detail VC..
[self dismissViewControllerAnimated:YES completion:nil];
I am testing an application in IOS5 using OCUnit. Before posting my problem I read all the relevant posts here as much I could. It really helped. I am now facing the problem below ,which I tried to narrate as clearly as possible.
My application has a login screen which is nothing but a view controller say: initialController. The root controller of my app delegate is the Navigation controller. The property initWithRootViewController of the Navigation controller is set with this initialController.
Now after logging in , the application loads another controller and I want to do unit testing in that controller without having to go through this login process.
Post login, another controller say PostloginController gets loaded, this controller's Navigation item is customized with 2 button's say: button1 and button2.
These buttons are added as the subviews of UIBarButtonItem and this UIBarButtonItem is set as the rightBarButtonItem of the PostloginController.navigationItem.rightBarButtonItem.(pseudo code)
finally PostLoginController is pushed in the Navigation controller
navigationcontroller pushviewcontroller:postLoginController.(pseudo code)
I have to write unit test code which on button1's UIControlEventTouchUpInside should launch another controller say :Newcontroller.
Loading this new controller on button1's event complete one test case.
My problem is that I don't get the subview of UIBarButtonItem which should be 2 buttons. The log show me there is one controller in the Navigation Controller and only one subview. My code is as follows:
- (void)setUp
{
[super setUp];
// Set-up code here.
self.appDelegate = (MYAppDelegate*)[[UIApplication sharedApplication] delegate ];
self.navigationController = (UINavigationController *)self.appDelegate.window.rootViewController;
}
- (void)testAppDelegate
{
STAssertNotNil(self.appDelegate, #"Cannot find the application delegate");
NSArray *tempArray = [self.navigationController viewControllers];
NSLog(#"Number of controllers in navigationController = %i", [tempArray count]);
}
- (void) testButton1
{
//self.myController = (PostLoginController*)self.navigationController.topViewController;
UIBarButtonItem *barButtonItem = self.navigationController.topViewController.navigationItem.rightBarButtonItem;
UIView *customView = barButtonItem.customView;
NSArray *subviews1 = customView.subviews;
NSLog (# "Got subviews");
NSLog(#"Number of subviews = %i", [subviews1 count]);
if ([subviews1 count] == 0)
{
NSLog (# "no subviews");
}
for (UIView* view in subviews1)
{
NSLog(#"%#", view);
if([view isKindOfClass:[UIButton class]])
{
UIButton *btn = (UIButton*)view;
NSLog (# " UIButton parsed");
// Check if Button1 clicked.
NSData *data1=UIImagePNGRepresentation([btn backgroundImageForState:UIControlStateNormal]);
NSData *data2=UIImagePNGRepresentation([UIImage imageNamed:#"BUTTON1.png"]);
if([data1 isEqualToData:data2]) // CHECKING IF BUTTON1 OR BUTTON2 PRESSED BY IMAGE SINCE THEY DONT HAVE ANY TAGS SET.
{
[btn sendActionsForControlEvents:(UIControlEventTouchUpInside)];
STAssertTrue([self.navigationController.visibleViewController isMemberOfClass: [NewController class]], #" NewController failed to load!"); //BUTTON1 CLICK SHOULD LAUNCH NEWCONTROLLER - COMPLETES ONE TEST CASE.
}
}
}
}
I have a strange issue on ios 4.3.I have one of my screen in landscape mode, a button click presents a popover.My popover has a search bar.Whenever keyboard appears it automatically pushes my popover bit up.When I resign the keyboard , popover reduces in height.This is the issue only on ios 4.3.While in rest of the ios , my popover doesnot reduces in height after keyboard dismissal.
None of the answers above worked for me. Apparently the keyboard scales the view and restores this scaling after the UIKeyboardDidHideNotification notification, making the presentPopoverFromRect method useless when applied handling this notification.
The way I solved it was by delaying the latter call as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
popup = nil; //my ViewController with UITextField
popover = nil; //my UIPopoverController
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(resizePopup:)
name:UIKeyboardDidHideNotification
object:nil];
}
- (void)doDelayedResize
{
[popover presentPopoverFromRect:myButton.bounds inView:myButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
- (void)resizePopup:(NSNotification*)note
{
[self performSelector:#selector(doDelayedResize) withObject:nil afterDelay:0.01];
}
I answered a very similar question here: UIPopoverController's view controller gets resized after keyboard disappears
The way I got around it was to observe the keyboard disappearing in the controller which controls the UIPopoverController:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(presentSearchPopover) name:UIKeyboardDidHideNotification object:nil];
And then in -presentSearchPopover, present the UIPopoverController again (it's quite a seamless transition):
- (void)presentSearchPopover
{
self.searchPopoverController.popoverContentSize = CGSizeMake(width, height));
[self.searchPopoverController presentPopoverFromRect:someRect) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
Don't forget to remove the observer in -dealloc or similar too:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
[super dealloc];
}
I found an answer for this.It was a bug with the top arrow of popover.If I use the left arrow direction for popover, everything works fine.
I ran into this issue as well - specifically, the popover wasn't growing back to its pre-keyboard size after tapping away from the popover. (The popover would grow back if the user dismissed the keyboard directly or the popover's view controller resigned first responder).
Unfortunately, I have to use the top arrow direction for the popover due to the UI's layout. To solve this, the view controller responsible for the popover implements - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController. For example:
#interface MyController : UIViewController <UIPopoverControllerDelegate>
{
// ...
}
//...
#end
Set that controller as the popover's delegate:
MyPopoverViewController *popoverVC = [[MyPopoverViewController alloc] init];
UIPopoverController *myPopover = [[UIPopoverController alloc] initWithContentViewController:popoverVC];
myPopover.delegate = self;
// Hang on to popoverVC, myPopover or release them as desired...
In addition, my popover's view controller sets its contentSizeForViewInPopover property to the desired size:
#implementation MyPopoverViewController
- (id)init
{
self = [super init];
if (self)
{
// ...
self.contentSizeForViewInPopover = CGSizeMake(320, 400); // desired size
}
return self;
}
When the keyboard causes the popover to shrink, it affects the popover's popoverContentSize and not its view controller's contentSizeForViewInPopover. Therefore, reset popoverContentSize in MyController's delegate method:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
// Check if popoverController is valid, the popover you want, etc
popoverController.popoverContentSize = popoverController.contentViewController.contentSizeForViewInPopover;
}
Here is my solution:
1. Register for keyboard Notifications (UIKeyboardWillShowNotification, UIKeyboardWillHideNotification)
2. Create local variables:
CGSize _currentPopoverContentSize; //if you want to have custom size for popover
UIView *_currentPopoverSender; //to remember from wich view you will present popover
BOOL _keyboardIsShown; //enable in keyboardWillShow, and disable in keyboardWillHide
3. In my presentPopover method:
- (void)presentPopoverControllerWithSize:(CGSize)size fromView:(UIView *)sender{
MyController *controller = [[[MyController alloc] init] autorelease];
if (self.popover)
{
[_popover release];
_popover = nil;
}
_popover = [[UIPopoverController alloc] initWithContentViewController:controller];
_popover.popoverContentSize = size;
_popover.delegate = self;
//checking if keyboard is shown - if NO, than present popover, if YES - just `resignFirstResponder` for your _`activeTextField`(you can set it in -textFieldDidBeginEditing: and nullify in -textFieldDidEndEditing:)
if (!_keyboardIsShown)
{
[_popover presentPopoverFromRect:[sender bounds]
inView:sender
permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
}
else
{
[_activeTextField resignFirstResponder];
}
_currentPopoverContentSize = size;
_currentPopoverSender = sender;
}
4. Than:
- (void)keyboardWillBeHidden:(NSNotification*)aNotification{
[UIView animateWithDuration:0.3
animations:^{
//do some stuff
[self.scrollView setContentSize:_scrollViewContentSize];
} completion:^(BOOL finished) {
if (_popover && _currentPopoverSender)
{
[_popover presentPopoverFromRect:[_currentPopoverSender bounds]
inView:_currentPopoverSender
permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
}
}];
_keyboardIsShown = NO;
}
Hi After going through the forum, I don't think it's a bug after playing with frame sizes a lot, working on IOS 4,5,6,7 it's the same behaviour.
The solution for me was to:
1) Go into the designer by
2) Opening the XIB ViewController that is causing the problem (i.e. the PopOver one).
3) Click to select it's VIEW.
4) Uncheck "AutoResizeSubviews"
5) When loading the PopOver in code, make sure you do:
6) Your_Popup_Window.popoverContentSize = Your_ViewController.view.bounds.size;
I hope this helps.
Kind Regards
Heider Sati