Displaying UIViewController inside a UIPopoverController - ios

I am trying to present a UIViewController inside a UIPopoverController. I've designed a view controller in Interface Builder, gave it a custom class (let's say MyAppTestViewController) and I'm trying to create a popover this way:
MyAppTestViewController *fxViewController = [MyAppTestViewController new];
self.fxPopover = [[UIPopoverController alloc] initWithContentViewController:fxViewController];
self.fxPopover.popoverContentSize = CGSizeMake(1024, 120);
[self.fxPopover presentPopoverFromBarButtonItem:_fxButton permittedArrowDirections:UIPopoverArrowDirectionDown animated:NO];
When I press the button, a popover is displayed at the correct place with the correct size, but it is empty. I've verified that the MyAppTestViewController's viewDidAppear method is being called (by NSLog), but the Popover's inside is empty:
Am I missing something?

I see that you mentioned in a comment that you're using a storyboard. So why not use a popover segue to your MyAppTestViewController? You could wire the segue directly to the Effects button on your toolbar. (Or, alternatively, call performSegueWithIdentifier: from your presenting view controller.) You might do a quick test by just throwing a UILabel into MyAppTestViewController right on the storyboard and seeing if it displays properly.

I think the problem is here:
MyAppTestViewController *fxViewController = [MyAppTestViewController new];
Generally you would use [[MyViewControllerClass alloc] initWithNibName:nil bundle:nil] (assuming you have a xib file with a matching name). I don't believe I have ever seen a view controller initialized with new. Everything in Objective-C is alloc-init.
Apple Docs: UIViewController Class Reference
Quote:
To initialize your view controller object using a nib, you use the
initWithNibName:bundle: method to specify the nib file used by the
view controller. Then, when the view controller needs to load its
views, it automatically creates and configures the views using the
information stored in the nib file.
EDIT:
Fascinating, well okay. It looks like you are right about the use of the new keyword, here is bit of an explanation of this.
So fine, that's not the problem. Have you tried breaking on the viewDidAppear method and using the debugger to print out the view properties, check its frame, check its superview, and so on, try to understand the problem better? You may already know how to do this, but the commands would be po self.view and so on.
In any case, I also found this, although it only goes into the mechanics of popover presentation and not content view assignment, which you seem to have down.

Related

iOS UIButton in programmatically instantiated subview not firing

I have a Nib in my storyboard which is not connected to anything but I'm instantiating it as a subview thusly:
-(void)LoadCameraOverlayView
{
CameraOverlayViewController *cameraVC = [self.storyboard instantiateViewControllerWithIdentifier:#"CameraOverlayNib"];
cameraVC.view.userInteractionEnabled = YES;
[self.view addSubview:cameraVC.view];
}
The UIViewController has a camera with feedback which is working fine and a button which (when I segue to the view controller also works fine.)
The button is linked to a Touch Down event and Touch Up Inside event.
When I click the button I can see it is changing visually from its default state to its focused or highlighted state however none of the code seems to be executing. If I put an NSLog in the ViewDidLoad I can see that in the console but not for the linked method. However if I segue to the view it does work.
What's going on?
I looked at other solutions but I don't think it could be that there is a view in the way or that the CGRect isn't calibrated correctly or anything since I can see the visual change when clicking the button.
This code violates Rule One of how to use view controllers:
CameraOverlayViewController *cameraVC = [self.storyboard instantiateViewControllerWithIdentifier:#"CameraOverlayNib"];
[self.view addSubview:cameraVC.view];
You cannot simply instantiate a view controller and then add its view manually to the view hierarchy like that. There are very strict rules about this; there is an elaborate "dance" that you must do in order to add a view controller's view to your interface manually, and you are not doing the dance.
The result is that your cameraVC comes into existence, is given no place in the view controller hierarchy, and vanishes in a puff of smoke. There is thus no object for your button to talk to, and so when the button is tapped, nothing happens.
My suggestion would be: use a .xib file, not a storyboard scene, for this view; and after you have loaded it and put it into the interface, use code to configure the button so that it talks to self, your current view controller.

Code in viewDidLoad runs every time it is called

Hi all I am doing a course in Udemy, and the code calls for placing code in the viewDidLoad function as shown below:
override func viewDidLoad() {
super.viewDidLoad()
placesArray.append(["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"])
}
The array append should only run once, however, when I segue to another viewController and come back, it runs the code to append again. So I now have an array with 2 rows, both of which are Taj Mahal.
I thought that the viewDidLoad function only runs code once?
Is there a way around this?
Thanks.
Addendum:
I am using Swift, so I don't see any alloc and init while creating and launching the viewController. And weird as it sounds, the video tutorial has it working in the viewDidLoad and the trainer is using the storyboard to segue from the initial table view controller to a map view on a view controller and just has a back button on the map view that segue's back to the table view controller via the storyboard as well. - Could be because I have the latest version of the Swift language and the trainer was using an earlier version, (cause I noticed some slight differences in coding earlier) but you never know. Either way whenever he touches the back button it does not run the append code anymore.
I am trying to get in contact with the trainer as some of the suggestions here, though they are good don't seem to work.
I will put the solution in here once I get in contact with the trainer.
The viewDidLoad method is called when your view controller's view finishes loading. The view will load when a view controller's view property is nil and something attempts to access it.
UIViewController *myVC = [[UIViewController alloc] init];
UIView *aView = myVC.view; // this loads myVC's view; viewDidLoad is called when it completes loading.
If the view has unloaded (usually due to memory limitations), it will be called when the view property is next accessed.
Manipulation of data sets should generally not be done within view methods. Consider moving this to the init of the view controller (or to a different "datasource" class).
I suppose you are trying to do data initialisation in viewDidLoad. If there is no other operation on placesArray before viewDidLoad, then instead of append, what about setting the placesArray directly:
placesArray = ["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"]
Then even if your view is unloaded for some reasons. Taj Mahal will still be added once only.
viewDidLoad is called whenever the view controller's view property is set. When does this happen? It depends on how the view controller is contained:
UINavigationController
- View Controller views are loaded as they are added to the navigation stack and "unloaded" (although the viewDidUnload method is deprecated) as they are removed.
UITabBarController
- View Controller views are loaded as they are added to the tab bar regardless of whether they are on screen or not. They stay loaded as you change from tab to tab.
Depending on your needs and use case, you can create your own view controller container that does what you need. Checkout the Apple docs on the proper way to do this:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html

Issues updating iOS ContainerView interface objects

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.

how to set view controller programmatically for subview in storyboard?

(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.

lay out a view in Storyboard then load it in a PopOver from code?

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.

Resources