I need to pop up a quick dialog for the user to select one option in a UITableView from a list of roughly 2-5 items. Dialog will be modal and only take up about 1/2 of screen. I go back and forth between how to handle this. Should I subclass UIView and make it a UITableViewDelegate & DataSource?
I'd also prefer to lay out this view in IB. So to display I'd do something like this from my view controller (assume I have a property in my view controller for DialogView *myDialog;)
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:myDialog options:nil];
myDialog = [nibViews objectAtIndex:0];
[self.view addSubview:myDialog];
problem is i'm trying to pass owner:myDialog which is nil as it hasn't been instantiated...i could pass owner:self but that would make my view controller the File's Owner and that's not how that dialog view is wired in IB.
So that leads me to think this dialog wants to be another full blown UIViewController... But, from all I've read you should only have ONE UIViewController per screen so this confuses me because I could benefit from viewDidLoad, etc. that come along with view controllers...
Can someone please straighten this out for me?
There is no such thing as a view controller being on the screen; its view is on the screen. With that said, you can present as many views as you want on the screen at once.
I would create a new view and view controller. You would not make a UIView be a UITableViewDelegate, you make a UIViewController be a UITableViewDelegate. But instead of doing that manually, instead make your new view controller a subclass of UITableViewController, if you're using iPhone OS 3.x+. You can then present this view controller modally.
You probably want to give the user a chance to cancel out of the selection. A good way to do that is to wrap your new dialog view controller in a UINavigationController and then put a "Cancel" button in the nav bar. Then use the delegate pattern to inform the parent view controller that the user has made their choice so you can pop the stack.
Here's what the code will look like inside your parent view controller, when you want to present this option dialog:
- (void)showOptionView
{
OptionViewController* optionViewController = [[OptionViewController alloc] initWithNibName:#"OptionView" bundle:nil];
optionViewController.delegate = self;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:optionViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[optionViewController release];
}
Your OptionViewController .h will look like this:
#protocol OptionViewControllerDelegate;
#interface OptionViewController : UITableViewController
{
id<OptionViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<OptionViewControllerDelegate> delegate;
#end
#protocol OptionViewControllerDelegate <NSObject>
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSString*)selection;
// or maybe
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection;
// etc.
#end
Your OptionViewController.m will have something like this:
- (void)madeSelection:(NSUInteger)selection
{
[delegate OptionViewController:self didFinishWithSelection:selection];
}
Which has a matching method back in your original view controller like:
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection
{
// Do something with selection here
[self.navigationController dismissModalViewControllerAnimated:YES];
}
There are plenty of examples throughout Apple's sample source code that follow this general pattern.
Related
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_];)
Need to implement a popover (or a modal view), and based on selection in the popover, may go to another view once the popover is closed
Yes, you can. Add delegate to popover, and trigger an action after the select. Then you can do whatever you want based on results of popover. If you need some more help please provide some code.
I will try to help you without your code:
Create protocol for the method you want to trigger:
#protocol someMethodDelegate
- (void)functionYouWantToTrigger;
#end
In your view controller(MyViewController) from which you call your popover .h file:
#interface MyViewController<someMethodDelegate>
In your .m file for MyViewController you have declaration like:
MyPopOver *popover = [[MyPopOver alloc] init];
popover.delegate = self;
Later on, in your .m file:
- (void)functionYouWantToTrigger{
//do some wild things here
}
And lastely, in your popover on some event:
[self.deleagate functionYouWantToTrigger];
You can expend this method with attributes as you wish.
I have an app where you can customize products to varying degrees. In some cases the options are split to two views, while in some other cases the first step isn't necessary.
What I would like is to treat all products the same and push the first customization step view controller to the navigation controller stack, let that view controller decide whether or not this step is necessary. If it is not necessary I want it to apply some default options to the product and immediately skip (before the transition animation) to step 2 while not allowing the user to back up to the first step.
The normal UINavigationController.viewControllers stack may look like this when at step 2:
[ListView (root)] -> [CustomizeStep1] -> [CustomizeStep2]
But I want it to apply the default values to the product and amend the view controller stack so that:
[ListView (root)] -> [CustomizeStep1]
----- becomes -----
[ListView (root)] -> [CustomizeStep2]
What I've tried is to use code like this in the CustomizeStep1 view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (shouldSkipToStep2) {
UINavigationController *navController = self.navigationController;
// Move directly to step 2
UIStoryboard *storyboardLoader = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *customizeStep2VC = [storyboardLoader instantiateViewControllerWithIdentifier:#"customizeStep2"];
// Replace current view contoller
NSMutableArray *viewHierarchy = [NSMutableArray arrayWithArray:navController.viewControllers];
[viewHierarchy removeObject:self];
[viewHierarchy addObject:customizeVC];
// Apply new viewController stack
[navController setViewControllers:viewHierarchy animated:NO];
}
}
If I take a look at the navigation controller's viewControllers array after this has been set, everything looks as expected.
What happens in iOS 7
When doing this, the entire functionality of the UINavigationController breaks. The CustomizeStep1 view controller still animates in but is nonfunctional. Tapping the back button still shows CustomizeStep1. Trying to interact with the view controller crashes the app. (It works as expected if the view controller is displayed without the sliding transition, though.)
What happens in iOS 8
The CustomizeStep1 view controller still animates in, but immediately after the transition ends it snaps over to show CustomizeStep2. Other than that it works as intended.
So, my question is if there is a better place to add the code to amend the view controller stack on the navigation controller?
I obviously need to wait until the view controller has been added to the navigation controller, otherwise I can't replace the view controller in the stack. However, I need to be able to cancel the transition animation so that I can animate in CustomizeStep2 instead.
I appreciate if this is impossible, just wanted to check if anyone knows a good way around this.
Edit:
How I would like it to ideally appear to the user
Instead of viewWillAppear:, use viewDidAppear: which is called after the animation finishes.
You could have a boolean on your view controller denoting whether it is filled in or not:
#interface ViewControllerOne : UIViewController
#property (nonatomic, assign, getter = isInitiallyFilledIn) BOOL initiallyFilledIn;
#end
Then, when it is initially filled in, just denote this boolean value.
ViewControllerOne *viewController = [[ViewControllerOne alloc] init];
[viewController setInitiallyFilledIn:YES];
Now, in viewDidAppear:, check this boolean value and check whether that method has been launched before. If it hasn't been launched before (to allow editing) and it is initially filled in, push the next controller!
#interface ViewControllerOne
#property (nonatomic, assign) BOOL hasCheckedFillInStatusBefore;
#end
#implementation ViewControllerOne
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([self isInitiallyFilledIn] && ![self hasCheckedFillInStatusBefore]) {
// push the next view controller
}
[self setHasCheckedFillInStatusBefore:YES];
}
#end
Alternatively, if you want to display the two view controllers at the same time, you could alter the navigation stack:
// create instances of ViewControllerOne and ViewControllerTwo
NSMutableArray *viewControllers = [[[self navigationController] viewControllers] mutableCopy];
[viewControllers addObjectsFromArray:#[viewControllerOne, viewControllerTwo]];
[[self navigationController] setViewControllers:viewControllers animated:YES];
Note, the ViewControllerOne will not have viewDidLoad called so if you do any setup in that method (such as a back button title or the view controller title), you will either have to manually invoke that method before setting the view controllers or move that setup to the initializer.
I have a UITableViewController within a UIViewController. While this table viewcontroller was the only one involved, it was pushing views just fine when the user would tap a row. However, ever since I moved it to be one of two contained within the UIViewController, the taps of rows suddenly do nothing.
I've tried searching around and I'm not the first to run into this problem, but none of the answers fit my circumstances or the questions have no working answers. That link was the closest I found, but I'm not using storyboards -- I'm using separate XIBs.
So how do I push a new view from a viewcontroller within a viewcontroller?
To recap:
Here is what I had, and it worked fine in taking users to a new screen!
// Normal table behavior, as illustrated by [another question][2].
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SomeView *detailViewController = [[SomeView alloc] initWithNibName:#"SomeView" bundle:nil];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
}
Now I have the viewcontroller as a property in a view -- and the above code, which is in the file for the tableviewcontroller and not at the "main" view, doesn't cause a new screen to appear anymore!
Thanks for the comments! Here's some code to clarify my scenario.
The controllers within a controller. This is a file from a test project I've been using to test the concept out. In this case, I have a tableview controller within a tableview controller.
#interface SimpleTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
// This is the controller within the controller
#property IBOutlet SecondTableViewController *secondTableController;
#property IBOutlet UITableView *secondTable;
My SecondTableViewController has this fun bit.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"SimpleNonTableViewController" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[manualViewControllerParent.navigationController pushViewController:detailViewController animated:YES];
}
The view that the user interacts with is hooked up to SimpleTableViewController. In this way, SecondTableViewController is "within" SimpleTableViewController. Feel free to comment if you'd like more details!
I've put my test/concept project on github. https://github.com/hyliandanny/TableViewCeption
You need to use a custom container controller to do what you want. It would be easiest if you used a storyboard, but you can do it in code with xibs as well. The outer controller should be a UIViewController, not a table view controller. You can do something like this (in the outer controller):
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"SimpleNonTableViewController" bundle:nil];
[self addChildViewController:detailViewController];
detailViewController.view.frame = set the frame to what you want;
[self.view addSubview:detailViewController.view];
[detailViewController didMoveToParentViewController:self];
}
You should read up on Apple's documentation for custom container controllers.
What you need to make sure:
Your UITableView delegate is hooked up to your controller. Otherwise it wouldn't call didSelectRow. You can do this in xib or in viewDidLoad method.
Your self.navigationController is not nil
Your detailViewController is not nil
I also think that what you mean is you have UITableView inside your UIViewController. UITableView is only the view, whereas UITableViewController is a controller. You can't have a controller inside another controller.
Ok so I am trying to pass a string from one view controller to another via the AppDelegate. I want to stay on the current view while this happens.
This is the main body of the code I am currently using to do this:
AppDelegate *dataCenter = (AppDelegate *)[[UIApplication sharedApplication] delegate];
MyMealViewController *vc = [[MyMealViewController alloc] initWithNibName:nil bundle:nil];
dataCenter.selectedMenuItem = recipeLabel.text;
[self presentViewController:vc animated:YES completion:NULL];
When I run the program I am able to confirm that the string is correctly passed. However, then the view on the simulator just turns black. I assume that this is because initWithNibName is set to nil.
So my question is: how should I change my code so that the string will still be passed, but the current view will continue to be displayed on the iphone. Is there a line of code that I could write that would just reload the current view?
Thanks for your help with this issue. I am new to xcode so I may be making a very basic error. Please let me know if any additional information would be helpful in answering this question.
Edit: It looks like you want to show a list of food items in the first view. Tapping an items opens a detail view. From that detail view, the user can press a button to add it to the meal. Eventually, they can tap a button on the first view to open the meal view, which should contain all of the items that they selected.
If this is the case, keep an array on the first view controller, and make sure the detail (second) view controller has a reference to the first view controller when it is presented. This will let us use that array. Note that there are better ways to architect this, but this will work for now:
#interface FoodListViewController : UIViewController
#property (strong, nonatomic) NSMutableArray *foodItems
#end
#implementation FoodListViewController
- (void)showFoodItem
{
FoodItemDetailViewController *detailViewController = [[FoodItemDetailViewController alloc] initWithNibName:nil bundle:nil];
detailViewController.foodListController = self;
[self presentModalViewController:detailViewController animated:YES];
}
#end
Once the detail view is presented, tapping the 'add to meal' button should add the current 'mealItem' to the array. In your example, you were using strings - if you would rather keep an array of strings for some reason, I'll leave that to you.
#interface FoodItemDetailViewController : UIViewController
#property (nonatomic, weak) FoodItemsViewController *foodListController;
#end
#implementation FoodItemDetailViewController
- (IBAction)buttonTapped:(id)sender
{
[self.foodListController.foodItems addObject:self.mealItem];
// Update the UI to let the user know that the item was added to the meal
}
#end
Finally, when it comes time to present the MealDetailsViewController, just pass it the array that you have been building:
#interface MealDetailsViewController : UIViewController
#property (nonatomic, strong) NSArray *foodItems;
#end
#implementation MealDetailsViewController
// Set foodItems before this view controller is presented, then use it to drive the
// UITableView data source, or find some other way of displaying it.
#end
As you can see, both the second and third view controllers are presented by the first. View controllers (nearly) always form a hierarchy - so keeping your data at the top of that hierarchy (by storing it in FoodListViewController) lets you neatly pass it down the hierarchy as you present other view controllers.