I have the following scenario:
A UIViewController is embedded inside a UINavigationViewController. The UIViewController has a UINavigationItem attached to it and has a UIContainerView as a subview. The UIContainerView has a embed segue to a UITableViewController with some static rows.
How can I make it so that when the user presses the Advanced row's
little arrow image on the right, the UIViewContainer's embed segue get
changed to reference another UITableViewController and have a Back
button on the UINavigationController so that the user can navigate
back to the original UITableViewController(with animations)?
Basically I want the user to be able to switch between Settings and Advanced Settings.
Things I have tried:
I tried having the container view have an embed segue to an UINavigationController. The first UITableViewController was set up to have a root-view controller segue relationship with the UINavigationController. I then made a show segue between the arrow (button) and another UITableViewController. This way I was able to transition between settings -> advanced settings -> settings, but I had double navigation bars. One is the bar I want, the white one on top, and the other is a gray one that appears below and where the back button is displayed. I want it to be a single navigation bar (the white one).
I have no problem using code to achieve what I want (Objective-C or Swift).
In the end I did it by code by adding the back button and dismissing the "Advanced Settings" view controller programmatically. I achieved this by created a show segue to the "Advanced Settings" UITableView. I then subclassed the UITableViewController:
#implementation AdvancedSettingsController
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(goBack)];
self.navigationController = (UINavigationController*)self.parentViewController;
self.parentViewController.parentViewController.navigationItem.leftBarButtonItem = backButton;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Incomplete implementation, return the number of sections
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete implementation, return the number of rows
return 0;
}
-(IBAction)goBack
{
[self.navigationController popViewControllerAnimated:YES];
}
#end
I just wanted to have it be done using the Xcode builder to automate the process a bit, because I do not like the self.parentViewController.parentViewController thing, because if I change my controllers this will stop working and is a little hard to read.
End result (webm) - https://webmshare.com/m8Kne
Related
I have a UIViewController on the More... tab. In viewDidLoad, I set toolbarHidden to NO, and the toolbar appears. However, if I select another tab from More..., then when I return to my original UIViewController, the toolbar is hidden (toolbarHidden is YES). I've checked to be sure it's the same view controller object.
My workaround is to set toolBarHidden to NO in viewWillAppear.
Is this documented behavior that I'm missing or is it a bug?
UPDATE
Here is some fairly trivial sample code. It turns on the toolbar in viewDidLoad and offers a button to toggle the toolbar on and off.
// Test_VC.m
#import "Test_VC.h"
#implementation Test_VC
- (void)viewDidLoad {
[super viewDidLoad];
// for testing to turn the toolbar on and off
UIBarButtonItem *sampleButton = [[UIBarButtonItem alloc]
initWithTitle:#"Toggle TB"
style:UIBarButtonItemStylePlain
target:self action:#selector(toggleToolbar:)];
self.navigationItem.rightBarButtonItem = sampleButton;
// the toolbar should re-appear whenver this view controller is selected
self.navigationController.toolbarHidden = NO;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// if on More..., this line will keep toolbar visible
//[self.navigationController setToolbarHidden:NO animated:NO];
}
- (void)toggleToolbar:(UIBarButtonItem *)sender
{
[self.navigationController setToolbarHidden:!self.navigationController.toolbarHidden animated:YES];
}
#end
To test:
Create a project with numerous tabs, each of which is an instance of this view controller, Test_VC. Have enough tabs so that at least two of these controllers are on the More tab.
Run the app.
Go to the More tab.
Select a "Test_VC" on More (I refer to this as "Test_1"). Notice that the toolbar appears.
Select another Test_VC from More. (I refer to this as "Test_2"). Notice that the toolbar also appears.
Touch "Toggle TB". Notice that the toolbar goes away on Test_2.
Touch More.
Reselect the first Test VC ("Test_1"). Notice that the toolbar is no longer there.
Repeat these tests using Main tab view controllers. Notice that there is no interactions with any others.
In step 4, the toolbar for Test_1 was present. In step 6, the Test_2 toolbar is removed. However, in step 8, the Test_1 toolbar is gone, too, but no change was made to the Test_1 VC.
This demo turns the toolbar on and off via the Toggle TB button. In my app, some of my view controllers simply hide the toolbar in viewDidLoad and some don't. This demo code could be modified to make two view controller versions: some that show the toolbar in viewDidLoad and some that hide the toolbar in viewDidLoad (no need for the Toggle button).
If these view controllers are main tabs (step 9), then each operates independently of the others, as expected.
What I have found: Toolbar visibility on all view controllers launched from More is set by the most recent More view controller that executes viewDidLoad. Thus, viewDidLoad is strange for More tab view controllers.
It's as if there's only one toolbar managed by More. I do not understand this behavior.
I want to design an app with UI like this:
At bottom, I know it is UITabbarController. In each tab, it has many view controllers like BEAT, TOP, FUN... Each tab has different view controllers.
When scroll horizontal, can change from BEAT to TOP to FUN...
How can I design like that? What view controller should I use? It seems like UIPageController in UITabController, but with UIPageController, I don't know how to replace dots at bottom (.) by BEAT, TOP, FUN... at top.
Thanks for your help.
Alternative solution is to add UISwipeGesture(Left/Right) on view and upon swipe action you can push or pop view controllers.
In ViewController.h
#property (retain, nonatomic) UISwipeGestureRecognizer * leftSwipe;
#property (retain, nonatomic) UISwipeGestureRecognizer * rightSwipe;
In ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupSwipeGestures];
}
-(void)setupSwipeGestures
{
_leftSwipe=[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(next)];
[_leftSwipe setDirection:UISwipeGestureRecognizerDirectionLeft];
_rightSwipe =[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(previous)];
[_rightSwipe setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:_leftSwipe];
[self.view addGestureRecognizer:_rightSwipe];
}
- (void) previous
{
// perform pop to get previous viewController
// i.e [self.navigationController popViewControllerAnimated:YES];
}
- (void) next
{
// perform push to get next viewController
// i.e [self.navigationController pushViewController:viewController animated:YES];
}
If you want to use above code in every Controller, then you may define your own viewController and paste above code in it, after that inherit all of your viewControllers where you need above functionality.
You can use XLPagerTabStrip library in each tab of your tab controller. Awesome library, even gives you the swiping feature similar to android which can enable you to swipe through individual page sections as well as keeping tab property.
I have a ViewController (with a container view) embedded in a Navigation controller. The container contains a pageViewController with one of the 'pages' being a TableViewController (with UITableView outlet: 'aTableView'). I want to trigger the edit mode in the tableViewController when tapping a custom editButton in the navigation bar. When I create a custom editutton in the tableViewController the edit mode works as expected, but when I use the custom editButton in the navigation bar the setEditing bool value remains zero even when I setEditing to YES in the editButton selector. Here's the code:
ViewController.m
-(void)viewDidLoad {
self.editBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[self.editBtn setFrame:CGRectMake(0, 0, 40, 22)];
[self.editBtn addTarget:self action:#selector(goToToggleEdit:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *editButton=[[UIBarButtonItem alloc]initWithCustomView:self.editBtn];
self.navigationItem.rightBarButtonItem = editButton;
}
-(void)goToToggleEdit:(id)sender
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
TableViewController *tvc = [storyboard instantiateViewControllerWithIdentifier:#"aTableViewController"];
if(something==foo){
[tvc toggleEdit];
}
}
aTableViewController.h
#interface aTableViewController : UITableViewController <UITextFieldDelegate> {
IBOutlet UITableView *aTableView;
}
-(void) toggleEdit ;
#end
aTableViewController.m
-(void)toggleEdit {
[aTableView setEditing:YES animated:YES];
NSLog(aTableView.editing ? #"Yes" : #"No"); // --> logss 'No'.
if (aTableView.editing){
//do something
}
else {
//do something else
}
}
How can I efficiently trigger the edit mode in the tableViewController this way?
Edit
#Bagrat Kirakosian pointed out to me that my view hierarchy (Navigation Controller > View Controller (with containter) > Page View Controller (in container) > Table View Controller) might be the problem. I just want to create a Navigation Bar (with an edit button) that is fixed, therefore I can't embed the Table View Controller directly in a Navigation Controller.
Thanks.
UPDATE: Solution
I have accepted #sebastien's solution although both #sebastien's and #Bagrat's solution work great. #Bagrat's answer includes direct access to the Table View Controllers, while #sebastien's solution calls edit mode in the pageViewController. I think, considering the tricky hierarchy, the latter is a bit more secure.
Here is the code for my View Controller that totally work fine. be sure you configure your bar button in the right method of View Controller lifecycle. Also be sure that your #selector is properly implemented in your code.
In the same View Controller put these two blocks of code
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UIBarButtonItem *rightBarButton = [[UIBarButtonItem alloc] initWithImage:[[UIImage imageNamed:#"edit_icon.png" ] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] style:UIBarButtonItemStylePlain target:self action:#selector(edit:)];
[rightBarButton setTintColor:[UIColor whiteColor]];
self.navigationItem.rightBarButtonItem = rightBarButton;
}
Afterwards you need also to put your edit selector
-(void)edit:(UIButton *)sender {
// Toggle edit by inverting current edit state
// Also in this block change your right bar button text or image
[self.tableView setEditing:!self.tableView.editing animated:YES];
}
UPDATE 1
After your comment we got to whole another question. You problem is not in the part where you try to call toggle edit. Your problem is the wrong hierarchy of controllers (Navigation Controller > View Controller > Page View Controller > Table View Controller). This might cause a problem. Try to change your controllers like this;
UINavigationController > UIPageViewController > UIViewController(s)
Also it's a good practice to have a UITableView in UIViewController rather than using really dead UITableViewController. Don't forget to connect your tableView IBOutlet (by the way you didn't need it in UITableViewController), also connect datasource and delegate to Files owner. In your MyTableViewVC.h file add this line
#interface MyTableViewVC : UIViewController <UITableViewDataSource, UITableViewDelegate>
After that all your calls will work fine.
UPDATE 2
After analyzing your entire structure I found a mistake that you do every time on the button click.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
tvc = [[TodolistTableViewController alloc] init];
When you call storyboard every time it's ok but when you do [[TodolistTableViewController alloc] init] you are RE-MAKING the same table view controller every time but not even adding it to your main view. As I told you, your tricky hierarchy might cause difficulties but it has a solution.
In your PageViewController.m make tv1 and tv2 properties in .h file, like so.
#property (strong, nonatomic) UITableViewController *tv1;
#property (strong, nonatomic) UITableViewController *tv2;
Then in the view controller file do this
-(void)toggleEdit:(id)sender
{
PageViewController *current = (PageViewController *)[[self childViewControllers] firstObject];
if ([current isKindOfClass:[PageViewController class]])
{
[((TodolistTableViewController *)[current tv1]) toggleEdit];
}
}
Answer includes all security checks and direct access to your table view controllers, because you might need to change other properties/call functions later.
Now in -(void)toggleEdit:(id)sender you don't re-create your views every time but you catch the ones you already have in your current View Controller.
Good Luck!
Ok, your issue here is that you are trying to access an embedded controller in a wrong way.
You are actually managing 2 differents PageViewController:
The one you generated through your storyboard
The other one you are initiating in your code further
That's why you can't reach the expected result.
First of all, add a new method to your PageViewController:
PageViewController.h:
- (void)editTableAtIndex:(int)index;
PageViewController.m:
- (void)editTableAtIndex:(int)index {
[[self viewControllerAtIndex:index] setEditing:YES];
}
Now, in your main ViewController, access the PageViewController by using childViewControllers:
-(void)toggleEdit:(id)sender
{
PageViewController *pvc = self.childViewControllers[0];
[pvc editTableAtIndex:0];
}
It should be editing your TodoListTableView:
(Please notice that I used [pvc editTableAtIndex:0];, instead you should be calling something like [pvc editTableAtIndex:_PageViewController_current_index_];)
I have a table view controller embedded in a navigation controller. The table cells are all static and selecting any of them will segue to another table view. When segue happened, the navigation bar shows 'Cancel' button for the new view, instead of 'Back' button.
I could add a back button in code like
- (void)viewDidLoad
{
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonSystemItemCancel
target:nil
action:nil];
self.navigationItem.leftBarButtonItem = self.navigationItem.backBarButtonItem;
}
But then the back button would be a rectangle shape, not the default back button shape which has an angle on the left edge. How to simply change the cancel button to a system back button?
Here is my code for segue from table cell to the next table view
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0:
[self performSegueWithIdentifier:#"goToSecondTable" sender:self.tableView];
break;
/* and perform segue for other rows */
default:
break;
}
}
And there is nothing to do inside prepareForSegue.
Here is what the connections inspector showed
And Here is the connections for the 'Bar Button Item - Back'
The system provided back button should be the left bar button item by default without having to do anything (in code or in IB).
Remove the connection to the backBarButton in the Connections inspector. Remove the back bar button from the nav bar in IB. Remove the outlet to the back bar button in your code. Run your app, you should see a back bar button provided for you for free.
Why are you performing a segue in didselectrow instead of just pushing the viewcontroller?
try [self.navigationController pushViewController:YOURVIEWCONTROLLER];
that will make sure you have a back button. Also you must use a segue like that, make sure you are using a push segue to your next controller. It also looks like you created your own back button in the second viewController. You do not need to create one, as one will be created for you with the title of the previous viewcontroller. You can make sure it says back by changing self.title = #"Back" in the previous viewcontroller right before you push it.
Codes:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0:
self.title = #"Back";
[self.navigationController pushViewController:SECONDVC];
break;
/* and perform segue for other rows */
default:
break;
}
}
and in viewWillAppear:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.title = #"Whatever you want";
}
Customize the code if don't have default back button not working
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:myBackImage style: UIBarButtonItemStylePlain target:self action:someAction];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
- (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.