- (void)createBarButtons
{
UIBarButtonItem *myCheckButton = [[UIBarButtonItem alloc] initWithTitle:#"Check Records" style:UIBarButtonItemStylePlain target:self action:#selector(checkRecordsAction)];
UIBarButtonItem *mySaveButton = [[UIBarButtonItem alloc] initWithTitle:#"Save" style:UIBarButtonItemStylePlain target:self action:#selector(saveAction)];
[mySaveButton setTintColor:[UIColor colorWithRed:34.0/255.0 green:97.0/255.0 blue:221.0/255.0 alpha:1]];
NSArray *myButtonArray = [[NSArray alloc]initWithObjects:mySaveButton, myCheckButton,nil];
self.navigationItem.rightBarButtonItems = myButtonArray;
}
I dont know if this question falls under too localized category. But help me out here.So like you see i have created two bar button items. Save is just saving the data onto CoreData,works just fine. But the check records should launch a new UITableviewcontroller.
- (void)checkRecordsAction
{
NSLog(#"the new stack action");
ITMSyncRecordsTVC *syncRecords = [[ITMSyncRecordsTVC alloc]init];
// [self presentViewController:syncRecords animated:YES completion:^{
// self.navigationController.view.superview.bounds = CGRectMake(0, 0, 250, 250);}];
[self.navigationController pushViewController:syncRecords animated:YES];
}
ITMSyncRecordsTVC is a TableViewController with a "Back" button on it.So when i click the check records it launches a tableview controller but no values in it and it does not show the "Back" bar button i put on it. Until now i have been using segues and storyboards just fine. But how do i launch a new view controller without them i dont know. My first leap into ios is ios6. I am missing something I dont know. So let me know how to call/launch a new TableViewController. In android we had intents that did the trick. Please let me know if you need more information. Thanks...
EDIT: So i edited my checkRecordsAction code.
EDIT :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ITMOrdersVC *ordersVC = [[ITMOrdersVC alloc]init];
NSLog(#"at line 188 %d",indexPath.row);
if(indexPath.row < self.salesOrdersArray.count)
{
ordersVC.salesOrder = [self.salesOrdersArray objectAtIndex:indexPath.row];
NSLog(#"the sales purchase order number is %#",ordersVC.salesOrder.purchaseOrderNumber);
NSLog(#"done - 140");
[self.navigationController pushViewController:ordersVC animated:YES];
}
}
So on selecting a row on ITMSyncRecordsTVC table view controller it does the above. I get a new ITMOrdersVC screen with "Back" bar button at the left and 2 dynamically generated bar buttons.I get the 2 dynamically generated bar buttons but not the back. I thought once i click the row it will "go back" to previous screen to which i am passing the salesOrder object. My next step was to check if i get the salesorder object from the TVC then load it. So basically 2 screens only. First screen (save,check sync records). Second screen click a.) back(go to first screen..do nothing) or b.)click a row in second screen and populate first screen without the bar button.If it is not clear please ask me.
To launch a new table view controller using storyboards, you want to:
Have your main scene embedded in a navigation controller.
You want to have a push segue from the main controller to the second one. So control-drag from the view controller icon (in the bar below the main scene) to the next scene:
It should then look like (note the appearance of the navigation bar in the destination table view):
Then select the segue and give it a "storyboard identifier":
And now, your code to transition to that scene would look like:
The checkRecordsAction:
- (void)checkRecordsAction
{
NSLog(#"the new stack action");
[self performSegueWithIdentifier:#"PushSegue" sender:self];
}
Update:
By the way, in the interest of full disclosure, there's an alternative to the push segue. If you give that next scene, itself, a "storyboard id", you can use the following code (obviously replacing "insertNextScenesStoryboardIdHere" with the identifier you give your next scene:
- (void)checkRecordsAction
{
NSLog(#"the new stack action");
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"insertNextScenesStoryboardIdHere"];
[self.navigationController pushViewController:controller animated:YES];
}
I personally don't like this as much, as your storyboard now has a scene floating out there without any segue, your code now has dictated the nature of the transition between view controllers vs having everything in the storyboard, etc., but it is another approach.
Related
My iOS application is based on John-Lluch's SWRevealViewController to get sidebar navigation. I have ran into a navigation problem that I don't know how to solve.
I want one of the menu items to lead to a page that doesn't contain a sidebar but a back button instead. (The other menu items leads to pages where the sidebar can be opened).
To get a back button on a view I need to use a Show Detail Segue, but I also need a navigation controller "somewhere" for the back button to show up. (I don't mean that I need a navigation controller AFTER the segue - I have that already - but I need it somewhere before).
Have someone used SWRevealViewController in this way or know how to achieve this? "Where" should I place a navigation controller?
Thanks!
The only working solution I came up with was to use a Category on UIView, that could do my Detail Segues.
#interface UIViewController (SidebarView)
(void)setupSidebarWithGestureRecognizer:(BOOL)useGestureRecognizer;
(void)openSettingsPage;
(void)openAboutPage;
#end
and the .m-file's code for openAboutPage:
/* This method is called by the sidebar. If the sidebar opens the settings page from itself, the back button
* doesn't know where it should point. This method opens the about menu when the sidebar tells it to, and the back button
* is pointing to the home page. */
- (void)openAboutPage {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *about = [storyboard instantiateViewControllerWithIdentifier:#"About"];
[self.revealViewController rightRevealToggleAnimated:YES];
[self.navigationController pushViewController:about animated:NO];
self.navigationController.navigationItem.title = #"About";
}
and the setupSideBarWithGestureRecognizer... method
- (void)setupSidebarWithGestureRecognizer:(BOOL)useGestureRecognizer {
SWRevealViewController *revealViewController = self.revealViewController;
if (revealViewController) {
UIBarButtonItem *sidebarButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"reveal"] landscapeImagePhone:[UIImage imageNamed:#"reveal"] style:UIBarButtonItemStylePlain target:self.revealViewController action:#selector(rightRevealToggle:)];
self.navigationItem.rightBarButtonItem = sidebarButton;
if(useGestureRecognizer) {
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
}
}
Then for each page I want to show a sidebar, I just call [self setupSidebar:YES]; in the viewWillAppear method of that view controller.
I've looked at all other answers for this topic on Stackoverflow but don't get really further. I've set up my Tabbar controller in Storyboard. I've defined the icons for the tabbar items also in Storyboard, the titles however I've set via code in their respective view controllers since my app offers multi-language support.
Now I want one single tabbar button which doesn't segue to another view but just calls an actionsheet. No matter on which other tabbar I am. So my questions are:
Where do I add this tab bar button? Because all other buttons I can only define after creating the segue to the new view controller
Where do I place the code for the action sheet?!
I'm not very clued up on the tabbar, however i would do the following:
Assuming you are using a TabBarController View or a Central View With a TabBar in it.
In the -viewDidLoad
NSMutableArray *tmp = [[NSMutableArray alloc] initWithArray:self.tabBarController.viewControllers];
UIViewController *sheet= [[UIViewContoller alloc] init];
sheet.title = #"Sheet";
sheet.tabBarItem.image = [UIImage....];
[tmp addObject:sheet];
[self.tabBarController setviewControllers:tmp];
self.tabBarController.delegate = self;
Then place the following
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if([viewController.Title isEqualToString:#"Sheet"])
{
//ActionSheet create code here
retrun NO;
}
else
{
return YES;
}
}
I'm trying to create a transition between two scenes, this is a dumbed down version of what I have in my production code :
Both are ViewController, the left one has a TableView inside it and when clicked it should transition to the right hand scene, passing along data from whatever cell was clicked.
Currently, with a modal segue I can tap the cell and it transitions correctly, however, I can't figure out how to place a back button onto the nav bar.
I'm transitioning from the cell to the 2nd view controller like so :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"toSecond" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toSecond"])
{
NSLog(#"Preparing segue for toSecond, setting some data on target scene");
NSIndexPath *path = [self.theTableView indexPathForSelectedRow];
MyData * myData = [myDataArray objectAtIndex:path.row];
// Obtain handles on the current and destination controllers
FirstController * startingViewController;
SecondController * destinationController;
startingViewController = (FirstController * ) segue.sourceViewController;
destinationController = (SecondController * ) segue.destinationViewController;
destinationController.someData = myData;
}
}
On the SecondController, I've tried amending the viewDidLoad method to programatically include a back button item as suggested in this previous SO question:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIBarButtonItem * back = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
[self.navigationItem setBackBarButtonItem:back];
}
So my question is, how can I get a back button onto that nav bar? Something like this :
Thanks
One (easy) way, using the storyboard, is to embed a navigation controller into your view that has the table. Make sure the correct view, the one with the table, is highlighted and then go to Editor > Embed In > Navigation Controller.
The back button will automatically be there when you click a row of your table. Like Dan said, make sure it is a push segue between the 2 views in your picture.
You will not be able to add a back looking button to a modally presented ViewController easily
If you just want to add a normal button to the left side of the bar, do the following
UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonSystemItemDone target:nil action:nil];
UINavigationItem *item = [[UINavigationItem alloc] initWithTitle:#"Title"];
item.leftBarButtonItem = rightButton;
item.hidesBackButton = YES;
[self.yourNavigationBar pushNavigationItem:item animated:NO];
If you insist of presenting your view modally and still want a back button style you could use
Three20
In place of where you have the view with the table, us a UINavigationController don't delete the view with the table, just in place of where you segue to it, or if it was the root controller, use the nav con. Make the view with the table the root view controller of the navigation controller, and then simply use a push segue instead of a modal segue, and you should automatically get the back button
The best way is to just use a UINavigationController as the parent of your table view controller, and use a push segue instead of a modal segue.
You can create two instances of UINavigationItem and tell the UINavigationBar about them by setting the bar's items property. Instance 0 represents the table view controller.
You can create a UIButton with type 101 (the undocumented back button type), and wrap it in a UIBarButtonItem using initWithCustomView:.
My app is based on a tab bar architecture. In order to have an easy internationalisation mechanic, I do not write any string in XIB files. viewdidload permits to change programmaticaly the strings in the views.
When my app launches, I can see the 4 tabs. But in fact only the first one loads its view controller. The others wait for user click to be loaded.
Tabs title can be changed using [self setTitle:#"Mouhahaha"]; in viewDidLoad of loaded view controller.
If I want to keep my internationalisation mechanic available, I do not set in my XIB the name of tabbar items. But, as at start all tab' view controllers are not loaded, I have blank titles for some tabs. The right title is set only when the user click on the tab.
I am looking for a way to set this title programaticaly for each tabbaritem. Do you have hints ?
Thanks a lot.
kheraud
my preferred method of doing this programmatically together with the storyboard is to make a subclass of UITabBarController, have my tab bar controller scene in my storyboard use the new subclass (with 3 UIViewController relationships from the tab bar controller to the desired view controller in the case below), and then override viewWillAppear:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSArray* titleKeys = [NSArray arrayWithObjects:#"top places",
#"localizablekey1",
#"localizablekey2",
#"localizablekey3",
nil];
[super viewWillAppear:animated];
int count = 0; for (UIViewController* viewController in self.viewControllers)
viewController.tabBarItem.title = NSLocalizedString([titleKeys objectAtIndex:count++], nil);
}
All you need to do is make an instance of UITabBarController, then alloc any views you want in it, then set the UITabBarController views. Make sure that your TabBarController is the one that is visible. Also make sure that any viewControllers you want in your tab bar are being imported with #import.
UITabBarController *c = [self tabBarController];
SecondViewController *s = [[SecondViewController alloc] init];
[s setTitle:#"Whatever"];
c.viewControllers = [NSArray arrayWithObjects:self, s, nil];
[s release];
Put this in the viewDidLoad method of the first controller allocated.
Alternatively you could just change the way your ApplicationDelegate sets up the TabBarController, but i won't go into that.
Hope this helps
What does this error indicate:
"Popovers cannot be presented from a view which does not have a window."
the thing that saved my life:
if (self.view.window != nil)
[popoverController presentPopoverFromRect:CGRectMake(44, yCoord, 111, 111) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
by adding if condition it doesn´t crash anymore. I don´t really get it because the presentPopoverFromRect function is ALWAYS called. There is no situation where window would be nil but anyway it did the trick.
edit: I have this code in viewDidAppear.
Nevertheless in most cases it's enough to move presentPopoverFromRect to viewDidAppear or didMoveToWindow but in my case for some reason the if condition was necessary.
the view you're adding the popover to has to already have been added to a window with the "addSubview:" method.
Try waiting until
- (void) didMoveToWindow
is called for the view and then load the popover
I got this problem.
I had a UITabBarController as the detail view, and I set the barButtonItem as the leftBarButtonItem on all three navigation controllers in the tab bar.
vcChart.navigationItem.leftBarButtonItem = barButtonItem;
vcAnalysis.navigationItem.leftBarButtonItem = barButtonItem;
vcTechnicals.navigationItem.leftBarButtonItem = barButtonItem;
Turns out only the last one added is valid, and the previous two would throw the exception when tapped on.
To fix it, I only set the leftBarButtonItem for the visible view controller, and just switched the barButtonItem to the visible view controller every time the user switched tabs.
Just encountered this issue. Turned out that the inView: parameter was using an IBOutlet that wasn't connected in IB. Thus, an attempt was made to launch the popover in nil. That doesn't work.
So, make sure you are using a valid view.
There are many ways to get to this error. Basically you need to wait to call the presentPopover command until your calling view is added to a window. I did it this way.
- (void)viewDidAppear:(BOOL)animated
{
[self methodThatDisplaysPopOver];
}
My presentPopoverFromRect call is inside my methodThatDisplaysPopOver function.
You could protect every presentPopover call like MobiMaciek suggests with this.
if (self.view.window != nil)
[popoverController presentPopoverFromRect:CGRectMake(10, 10, 100, 100) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
However, I think it would be better to understand when self.view.window gets assigned and make sure that you present you popover after the view has a window.
I received the same error message when assigning the same UIBarButtonItem to multiple navigation items as did Lewis. My example was slightly more complicated as I was using a UISplitViewController.
In my RootViewController I have an array of arrays to accomplish multiple sections within my table. Each time that the user clicks a row in the table, a new "detail" view controller is placed in the right pane of my splitViewController. Prior to setting the leftBarButtonItem = nil, I would receive a segfault after 3-4 clicks of the "Menu" button with the same error as a111. I updated my code to actually retrieve the previous detail view controller and set the leftBarButtonItem item to nil.
allData is my NSMutableArray that contains several other NSMutableArrays as objects.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Retrieve the new detail view controller
UIViewController *detailViewController = [[self.allData objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
// Add the detail view controller to a navigation controller and set the bar style
UINavigationController *detailNavigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
detailNavigationController.navigationBar.barStyle = [[NSUserDefaults standardUserDefaults] integerForKey:#"UIBarStyle"];
// Retrieve previous detail view controller and remove the leftBarButtonItem
UINavigationController *previousDetailNavigationController = [splitViewController.viewControllers objectAtIndex:1];
UIViewController *previousDetailViewController = [[previousDetailNavigationController viewControllers] lastObject];
previousDetailViewController.navigationItem.leftBarButtonItem = nil;
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailNavigationController, nil];
splitViewController.viewControllers = viewControllers;
[detailNavigationController release];
[viewControllers release];
// Dismiss the popover if it's present.
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
// This sets the left bar to nil when in landscape and equal to "Menu" when in portrait.
// We need to remove rootPopoverButtonItem from the previous viewController...
detailViewController.navigationItem.leftBarButtonItem = rootPopoverButtonItem;
}
The error message was slightly deceiving at first but the answers above helped me out. I wonder why I could click the "Menu" button up to 3-4 different times before the segfault... I'll investigate further.
This error also occurred when the inView: Parameter is incorrect - to test try self.view
yes, you are right but still we can add subview from parent class in it. so it can be represented from a view which have a window:
[popoverController.contentViewController.view addSubview:mySubView];
I had the same error message as the OP, in a very similar situation to that reported by TPoschel, except I had a split view controller with an embedded tab bar controller in the detail pane, and a navigation controller within this. The bar button item is added as the navigation bar leftBarButtonItem.
Only on iOS5.0 (not 5.1) does it seem to require you invalidate the bar button item on the tab bar you are leaving by setting it to nil. Before then adding the bar button to the navigation bar on the tab you are going to.
If I don't do that, from debugging my own code, the window property of the bar button item stays set to nil, and causes the exception, on returning to a screen you'd previously been to. I'm guessing as a side effect of setting the leftBarButtonItem in the navigation item, it goes off and sets the frame. But it doesn't seem to bother unless the button is different from what is currently set there. Hence, the need to set it to nil when leaving a tab, even though it is technically the same button that's being passed around.
I would upvote TPoschel's answer, except SO won't let me.
I had a problem like this. Received this message when clicking a customized UIBarButton item that invoked a selector method with did performSeque.
The problem was my segue was still attached to the UIBarButton item. It should have been attached to the main view of of the view controller. Changed this and worked fine.
P.S., all this got started because I wanted to add and "info" button to my UIToolBar. This isn't one in the system provided list and should be.
There will be a view from which you asks to display your popover.The reason for this error is because you didn't made this view as a subview of the window.
[self.view addSubview:displayPopOverVC];
where displayPopOverVC is the view controller from which the popOver appears
i had the same problem, after adding PresentPopOver in viewDidAppear this was solved
- (void) viewDidAppear:(BOOL)animated{
CGRect popoverRect = screenBounds;
popoverRect.size.width = MIN(popoverRect.size.width,0) ;
popoverRect.origin.x = screenBounds.origin.x;
[popoverController
presentPopoverFromRect:popoverRect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
this was happening as inView:self.view should be called after viewDidLoad as suggested by #hey68You and MobiMaciek..
I replaced
[actionSheet showFromBarButtonItem:self.navigationController.navigationItem.leftBarButtonItem animated:YES];
with
[actionSheet showInView:self.view];