I have a table view cell with a button that, when pressed, needs to call the method of a different view controller. I''ve followed this tutorial, which shows one way to get at another view controller in story boards, but it involves hardcoding the position of the view controllers. Anytime I'd change the order of my view controllers, I'd need to update my code, which I know I will mess up.
Here is their method:
UITabBarController *tabBarController = (UITabBarController *)
self.window.rootViewController;
UINavigationController *navigationController =
[[tabBarController viewControllers] objectAtIndex:0];
PlayersViewController *playersViewController =
[[navigationController viewControllers] objectAtIndex:0];
playersViewController.players = players;
Yikes, what is that?! We want to assign the players array to the
players property of PlayersViewController so it can use this array for
its data source. But the app delegate doesn’t know anything about
PlayersViewController yet, so it will have to dig through the
storyboard to find it. This is one of the limitations of storyboards
that I find annoying. With Interface Builder you always had a
reference to the App Delegate in your MainWindow.xib and you could
make connections from your top-level view controllers to outlets on
the App Delegate. That is currently not possible with storyboards. You
cannot make references to the app delegate from your top-level view
controllers. That’s unfortunate, but we can always get those
references programmatically.
Does anybody know a cleaner way?
I tried creating an IBOutlet in my table view cell to the other view controller, but I can't ctrl-click and drag to the other view controller for some reason.
I also tried setting the IBAction of my button in my other view controller, but I need to know what row was clicked, and the only information i can get from -(IBAction) addButtonClicked:(id)seder is the sender information, which is just RectButton.
Thanks!
As a workaround, you could set the tag property of the sender in InterfaceBuilder to something, and then just compare [sender tag] in the addButtonClicked: method. I believe tags are ints.
Related
One of the things I don't like about WYSIWYG/visual programming, is when you get to a point where you need to step outside of the box, you're left scratching your head. I'm at such a point right now along my iOS learning curve.
I have a custom UICollectionView thing. In two places (create and edit points), I need to present a list to the user to enable/disable (select/deselect) items from a list. So I go to the storyboard and whip up something like this:
In the past, following tutorials, I would control-drag a link from some control to the NavigationController show in the middle, I would tell it was a modal segue, tune a couple of methods, and get an arrow connecting the two for my efforts.
But in this case, I don't have obvious points to start that action from. I do have a + button. But it needs to do some other things first, and if all is well, then programmatically initiate the open, and somehow get notified of the state when it returns. Same for the individual cells, they might want to configure my table controller, and then open it, and be notified when it closes.
So I'm looking for a recipe of how one does this, what the key methods I should be looking for are. Apple's docs are great for reference, but I find figuring out how to do something from scratch difficult.
After creating a segue in your storyboard, you can initiate a segue any time programmatically by calling
[self performSegueWithIdentifier:#"segueID" sender:person];
Where "segueID" is a string you set for your segue in interface builder in the Identifier field in the identity inspector (right menu panel, 4th tab).
The segue does not need to be created from a control, you can just create one directly from one view controller to another. I usually do this on the right side menu by right-clicking on one view controller object and dragging to another one. This way, it acts as a segue that you can initiate programmatically any time you want.
As for getting notified when you come back to a view controller, (unless I'm misunderstanding your question) you can use either:
(void)viewWillAppear:(BOOL)animated
(void)viewDidAppear:(BOOL)animated
Create a UINavigationController programmatically with a desired view controller set as a root view controller. Here is an example of what you could put in a method invoked when user taps the plus button:
UIViewController *vc = [[UIStoryboard storyboardWithName:#"YourStoryboardName" bundle:nil] instantiateViewControllerWithIdentifier:#"YourViewControllerID"];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentViewController:nc
animated:YES completion:nil];
To get a state, or information about the selected items you can use Delegation and declare a protocol. Example:
#protocol YourSampleDelegate <NSObject>
- (void)didSelectItem:(NSObject *)item;
#end
Then, your view controller (the one with the plus sign) should implement this protocol:
#interface ViewController : UIViewController<YourSampleDelegate>
...
#end
#implementation ViewController
...
#pragma mark - YourSampleDelegate conformance
- (void)didSelectItem:(NSObject *)item;
{
// Do something with the item.
}
#end
You also have to create a delegate property in a view controller with collection view and set the view controller with a plus as a delegate. There are tons of examples on the Internet. I hope this shows you the right direction.
I'm trying to update the interface contents of a ContainerView on iOS (UIViewController embedded in a UIView) from the UIViewController that it's being displayed in. However, the ContainerView just won't update its content.
The ContainerView and the ViewController are associated with different classes. I can pass data between the two View Controllers by using a few methods like these:
- (void)displayStringInContainer:(NSString *)string
The string gets successfully passed to the ContainerView from the ViewController, however when I try to display that string in an interface element - nothing happens (even though the code is getting called):
self.buttonName.titleLabel.text = string;
I've even tried calling setNeedsDisplay on the button, but nothing happens. Note that this is happening with all interface items.
Here's how I call the method on the ContainerView from my ViewController:
ContainerViewController *cvc = [[ContainerViewController alloc] init];
[cvc displayStringInContainer:#"Text"];
I've done quite a bit of searching, but haven't found anything (also tried to look on the Apple Dev Site, but it's been down for the past three days :P). Does anyone know how to update the content of a ContainerViewController from another ViewController? Why isn't this working? I've been scratching my head on this for a while now.
Alloc init'ing cvc is not the right way to get your reference -- that's a new instance, not the same instance as the one embedded in your view. You can access that instance in code from the parent controller with self.childViewControllers[0] (assuming you have only one container view). You can also get the reference by implementing prepareForSegue and use segue.destinationController (that will be your embedded controller).
What you seem to be missing in your understanding, is that the controller you get when you use a container view in the storyboard is a child view controller. It's the same as if you had called [self addChildViewController:whatever] in code and then added the child's view as a subview of your view.
I have a TableViewController that can be transitioned to from two other view controllers; one transitions by segue, and the other programmatically. Everything works well when transitioning via segue, but when the TableViewController appears after a programmatic transition, the UIBarButtonItem is missing and the cell segue doesn't work (it should take the user to a new screen).
I'm guessing that because the UIBarButtonItem and cell segue were setup in the Storyboard, they aren't loaded when the TableViewController is loaded programmatically. So, I'm assuming that I need to load them manually somewhere. My question is where the appropriate place to do this is?
Thanks for reading.
EDIT:
Here's the code for the transition:
OtherTVC *otherTVC=[[OtherTVC alloc] init];
[self.navigationController pushViewController:otherTVC animated:YES];
This is linked to the callout accessory of a map pin.
To get an instance of the controller as it was created in the storyboard, try giving it an identifier and using instantiateViewControllerWithIdentifier: rather than the alloc/init route. If your current controller where the code is running came from the same storyboard, you can get a reference to it through the controller's storyboard property in order to make that call.
(Designed in storyboard , screenshot below) I have two subviews on my rootviewcontroller's view
In my code i want to assign a separate view controller to each subview. i.e Assign a tableViewController to the TableView.
I tried to do this in awakeFromNib (or ViewDidLoad) method but to no avail. The delegate method in my tableview controller are never called. I think storyboard does the job of loading the subviews here even before the tableviewcontroller i assign can do something.
self.myTableViewController = (TodoListViewController *)[[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
self.myTableView.delegate = self.myTableViewController;
self.myTableView.dataSource = self.myTableViewController;
self.myTableViewController.tableView = self.myTableView;
I am not sure if this is allowed when having views like this in storyboard or i am doing anything wrong ?
I came to this site as I had a similar problem. Actually I am doing the exact same thing: I have a viewcontroller with two subviews (all defined in a storyboard with lots of constraints).
in the containerviewcontroller in viewDidLoad I am doing the same calls as you do (but I defined the view first):
self.myTableViewController = (TodoListViewController *)[[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
self.myTableViewController.tableView = self.myTableView;
self.myTableView.delegate = self.myTableViewController;
self.myTableView.dataSource = self.myTableViewController;
That works for me (I guess, here is your problem: You don't have a navigation controller around it.... and be sure that the outlets of your tableview are really connected in the storyboard.).
But the real problem comes after the correct wiring. If you tab into a cell, you probably want to give some cellID to the nextView. As you have defined your tableviewcontroller manually, only your delegate method didSelectRowAtIndexPath gets called, but not the prepareForSegue.
I have played around with instantiating the viewcontroller from the storyboard
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"aStoryboard" bundle:nil];
self.myTableViewController = [storyboard instantiateViewControllerWithIdentifier:#"myTableViewID"];
but the segue did not get called. calling it directly (performSegueWithIdentifier:...) did not work as this tableviewcontroller is not managed by a navigation controller.
For me it ended up that the prepareForSegue method gets called in the containerViewController which can delegate the calls to its subviewcontrollers.
EDIT: Actually the real solution is DON'T. There is a "Container View" object which can be added to a view and defines a region of a view controller that can include a child view controller. This is what I really wanted to do.
Try again with your viewDidLoad method, that is the simplest answer. If the method is not loading you may have to look into the other things inside you method because if the application is large as they often are using storyboards you may have conflicting methods.
I would also look at this:
http://blog.waynehartman.com/archive/2012/01/07/uistoryboard-on-ios-5-the-good-the-bad-and-the.aspx
It shows the most common accidents people make when using any storyboard function programatically
Hope that helps!
Sounds like you want to write yourself a custom container controller, e.g. similar to UISplitViewController. Here's apple's brief docs on doing this in the UIViewController class reference. You could for example instantiate the children controllers programmatically in your container controller's viewDidLoad: or viewWillAppear: methods. I don't think you can get IB to instantiate the children for you, though, in the same way you can wire up say a tab bar or navigation controller's relationships to their children. (If there is a way, I'd like to know!)
It's typically easiest to set your classes and delegates all in the storyboard (as shown in numerous totorials including this one).
If you're really trying to put a scroll view and table view into the same view, then you'll need to look into UIViewController instantiateViewControllerWithIdentifier:, you can reasonably easily pull multiple view controllers (ideally with their proper classes, delegates and sources set in the storyboard) in and add their views to your outer wrapper view. I will say that I've done this and you can do cool things with it reasonably easily, but it usually isn't the cleanest way to do things.
I have relatively complex ui appearing in a popover (complex enough that doing all the layout and relationships from code would be a pain), but the button that calls it is created and placed into the parent view (which exists in the storyboard) from code.
I've tried making a popover segue from the parent view's viewcontroller object to the popover content vc, then triggering this with performSegueWithIdentifier. This almost works, but I can't figure out how to set the popOver's Anchor from code so it appears at the wrong place (squished at the bottom of the screen).
Is there a way to set the popOver segue's Anchor dynamically?
or
Can i create a UIPopOverController object and get the view i've put together in the storyboard into it?
or
Is there some other obvious way to do this that I'm not seeing?
please be gentle, I'm new here.
iPad iOS5.1 XCode4.3.2
Alex,
I'm not completely sure I understand what you're trying to do, but let me take a stab at it from what I think I understand.
For the same reason you cite (view complexity, etc.), I often build out my views in the storyboard and then load them from some action. What you can do is instantiate the view controller by identifier with something like this:
FancyViewController *controller = [[self storyboard]
instantiateViewControllerWithIdentifier:#"FancyViewController"];
This assumes you have created a UIViewController subclass called FancyViewController and have set the type for your view controller in the storyboard.
Now, you can display the view controller in a popover controller or you can push it onto a navigation stack. You just need to make sure you've set the identifier for your view controller in the storyboard.
Also, you'll probably want to instantiate your view controller once if you use a popover controller and just update the view controllers properties each time the action gets triggered. So, if it's tapping a button that triggers the popover, your code might look like this:
- (IBAction)didTapButtonToShowFancyViewController:(id)sender
{
if (![self fancyViewController])
{
// fancyViewContrller is a property of type FancyViewController *
fancyViewController = [[[self storyboard]
instantiateViewControllerWithIdentifier:#"FancyViewController"];
// fancyViewPopoverController is also a property
fancyViewPopoverController = [[UIPopoverController alloc]
initWithContentViewController:fancyViewController];
}
// Perform setup on the fancy controller you want to do every
// time the action gets triggered here. Do initialization in the if
// block above.
// Now display the popover from the sender's frame. I'm assuming the
// sender is a UIButton.
[fancyViewPopoverController presentPopoverFromRect:[sender valueForKey:#"frame"]
inView:[self view]
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
The only way to set the popover's "anchor" dynamically is to use an explicit action that calls presentPopoverFromRect:permittedArrowDirections:animated: instead of a segue.
I hope that helps. Let me know if I've misunderstood what you're trying to do.
Best regards.