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.
Related
I have a UIViewController that pops up a custom UIView that needs to be able to present another UIViewController (BarcodeScanViewController).
When the user is done with the BarcodeScanViewController, data is passed back to the UIView to update a label.
How can I present the BarcodeScanViewController from the UIView with the a navigation bar so I close it if necessary?
The below code kinda works. It present the BarcodeScanViewController, but it doesn't actually do anything. Its just a black view.
Inside Custom UIView
- (void) startScan {
BarcodeScanViewController * bsvc = [[BarcodeScanViewController alloc] init];
bsvc.delegate = self;
UIViewController *currentTopVC = [self currentTopViewController];
[currentTopVC presentViewController:bsvc animated:YES completion:nil];
}
I also receive an warning
Assigning to
'id < BarcodeScanViewControllerDelegate > ' from incompatible type 'CustomView * const__strong'
when I try to assign the delegate.
I have never called UIViewController from a UIView before. I know that only a VC can call another VC. So I tried to create VC to use a presenter.
What am I doing wrong?
Okay, there were a bunch of smaller things. I forked your repo and fixed it so that you can see how it works. See here.
In short: You did present the scanner VC, but as I said you can't simply put it "over" the presenting view controller (your ViewController class). As soon as any presentation transitions (i.e. animations) are done, the view of that ViewController is removed from the view hierarchy. So what you saw as a black screen was just the window background (which is black). The scanner's view was transparent. Furthermore, the scanner expected you to provide a view to render the camera into (and buttons) via an IBOutlet. Since you didn't instantiate the controller from a storyboard, you didn't set any, i.e. the view used for that was nil and thus the frame the scanner wanted to render the video in had a size of (0, 0).
I fixed that for now, hopefully you can now see how presenting a viewcontroller is supposed to work. To achieve an actual popup (with "underlying views of previous view controllers") is a bit harder on iOS, but possible in several ways. I can elaborate further later if you want.
I've been struggling with this for a couple of days now and haven't been able to pinpoint the cause of my problem, as the title says, a UIButton on a subview is not firing the IBAction outlet when "clicked".
Let me elaborate, I'm working on a "tinder-like" app, where I'm using this third party library. I'm implementing customized views which have buttons and other controllers in them. The buttons in these "Cards" are not firing their IBAction outlets.
As far as I can tell, the library does the following:
Stacks 3 "DraggableViews" on top of each other, each draggable view has 2 child views, one is the content view, where my custom view lives, and the other is an overlay view, which has an image view on top. The draggable views use two gesture recognizer to do its thing, the first one is a tap gesture that calls a delegate method to handle tap events in the card (in their example its used to show a browser view). The second gesture is a pan gesture used to implement the swipe functionality of the card.
I've done my homework and have tried a few different solutions but I can't get the IBAction to fire. I've tried the following:
Use the parent view of the DraggableViews to disable the gesture recognizer when the touch event is triggered on a button. - This works (gesture is disabled) but the IBAction is not fired. on the case of the pan gesture, I am no longer able to "swipe" the card when touching the button and the tap event does not hit a breakpoint in the delegate method mentioned above. In the UI, my button reacts to the touch as it animates, but its outlet in the view controller fails to hit a breakpoint. I've disabled this as so:
//- Called when each DraggableView is visible (not called for view at index 0, but works alright for debugging in the mean time)
func koloda(koloda: KolodaView, didShowCardAtIndex index: UInt) {
let subviews = koloda.subviews
for subview in subviews {
if let recognizers = subview.gestureRecognizers {
for gesture in recognizers {
if gesture is UIPanGestureRecognizer || gesture is UITapGestureRecognizer{
//- Also tried setting this to false but nothing.
gesture.cancelsTouchesInView = false
gesture.delegate = self
}
}
}
}
//- On the gestureRecognizerDelegate
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view is UIButton {
return false
} else {
return true
}
}
Implemented a tap gesture on the button programmatically, On viewDidLoad I add the action to the tap gesture but this does not trigger the outlet either.
Programmatically set the userInteractionEnabled property of the ImageView to True hoping that this will allow the touch event to go through the responder chain (although I read in another post that this would have the opposite effect?).
I've also checked in the storyboard that all the relevant views and controllers in my custom view have the userInteractionEnabled option enabled.
I don't know if its relevant, but my custom view lives in a xib file, when I pass the view to the library I do it by instantiating the view controller and passing passing over its view, as so:
if let vc = spiViewConntroller {
return vc.view
}
Any help or suggestions would be greatly appreciated!
Cheers!
EDIT:
Continuing with my search for truth, I've completely removed the overlay view from the library, inspecting my custom views on the UI debugger i can see that the overlay view and its ImageView are no longer there. The button still does not fire its outlet so I can assume that the overlay view is not causing this issue.
I've forked the Koloda library and created a branch with a demo example, the branch name is "StackOverflowDemo". I've added a custom view with one button, I've created two outlets in its view controller where I'm changing the title of the button (which works) on view did load. I've also disabled the two gestures on the button to replicate what i've currently got in my app. If you do clone this down you'll need to swipe the first card off as the card at index 0 wont have the gestures disabled.
I'll keep digging, hopefully someone can pinpoint what I'm doing wrong!
Cheers.
Daniel.
Your issue is that you don't add your view controller as child to view controller which operates with Koloda. In the result your vc.view is shown, because Koloda retains it, but nobody retains your view controller, so you are losing important lifecycle methods and it gets deallocated.
The approach your trying to use is called Container View Controller. Apple has suggestion about its implementation here: https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
Simple implementation here:
- (void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
}
OK, I found my answer. After digging some more bit i was able to solve this. However my solution feels dirty and I'm unsure if it breaks the MVC pattern.
I think my issue is that my custom View Controller for my xib file is somehow lost or unable to respond to these events. When creating the views to be displayed as the content of the draggable card, I was creating an instance of my view controller and returning its view. What's weird to me is that the ViewDidLoad method of the view controller was doing its job when the view was loaded (i.e. changing values of outlets).
Solution:
I removed the file owner from the lib, created a custom class that inherits from UIView, say MyCustomUIView and moving the view controller logic, its outlets and actions to that class. Then on the xib file, I linked the content view to MyCustomUIView. However this solution feels dirty as I'd expect the view's view controller to handle all the logic. Might need to do a bit more reading on nib and reusable views.
I can suggest a way, that can help you to detect the problem. Use the debugger view in xcode when the simulator is running. It will show you the hierarchy of views in 3-D mode, which can help you in finding out the issue.
How to use debuggin view:
https://developer.apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ExaminingtheViewHierarchy.html
I'm attempting define a series of container view controllers using Interface Builder (probably my first mistake). In the storyboard, I created the top view controller, added 3 container views, which automatically added each child view controller to the storyboard. I added an outlet for each container view, and am able to successfully maneuver through the child views by hiding/showing the container views. There's really not much code to speak of, it's just:
-(IBAction) button1Pushed:(id)sender
{
containerView2.hidden = true;
containerView1.hidden = false;
}
While that works, I need to update the content on each showing. viewWillAppear (and related functions) fire for the Child View Controllers only on the initial creation, not when hiding/showing the containers. I supposed I could add something like:
[childVC1 updateContent];
containerView1.hidden = false;
but I was hoping I could rely on viewWillAppear (and related functions). I've tried a few things with limited success, and have several questions:
I understand the example in Apple's Programming Guide for manually creating containers:
[self addChildViewController:content]; // 1
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self]; // 3
but how does that apply when using IB? There's no mention of IB in that Programming Guide. IB must be calling addChildViewController, because I can find all the the container VCs using [self childViewControllers]. But since viewWillAppear only happens on creation, does that mean IB didn't go on to add the views as well?
So question 1 is: when adding container views via Interface Builder, how much of the example code does IB handle and how much do I have to implement?
As an experiment, I added this code in the parent's viewDidLoad:
for ( UIViewController *vc in [self childViewControllers])
{
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
}
That caused each of the child views to all appear at once on top of each other at the top left of the screen. It no longer respects the IB layout, and unsurprisingly, no longer respects showing/hiding the containerView, either. I can control them with
vc.view.hidden = true;
but they're moved to some default position. I could manually reset their coordinates, but one of the reasons for using IB in the first place was to avoid doing screen positioning via code.
question #2 is: when adding container views in IB, how am I supposed to manage displaying the children while still honoring the IB layout? By manipulating the container view's outlet? by manipulating the child view controller (found via [self childViewControllers])? Or something else?
At the end of the day, my desired state is:
Define the screen layouts and positions in IB for the parent and children
Hide/show children based on buttons selected by the user
viewWillAppear fires each time a child is displayed, allowing me to update the content
Any advice on reaching this state is appreciated, thanks!
Answer 1: When you add child view controllers via the storyboard (called container views in this case) then the children are added via segues. No it's not a segue in the normal sense where you push or present a view controller, but the method that's called in your 'super' view controller is prepareForSegue. In interface builder you can name the connection between the two controllers just like a normal segue and grab a reference to the view controller in this instance. This takes the places of the whole dance of addChildViewController, didMoveToParent and so forth.
Answer two: I think it makes sense to address your desired state. I looks like you have already solved #1 and #2. As for getting viewWillAppear to fire again you would have to get your parents viewWillAppear to fire again by doing something like pushing and popping a new VC or presenting and dismissing one. Just because you set it to hidden and then unhide it won't make it fire again (hidden basically tells the drawing system to not render it).
The recommended approach (from Apple, forgive me I don't know the link) is to do all of your updates in your view subclass via updateViewContraints or layoutSubviews. In the view controller world the similar method is ViewDidLayoutSubviews. You can signal to your view that it needs to be laid out again by calling [yourViewController.view setsNeedsLayout]. For reference check out the doc on UIView in regards to needsLayout.
Likely what you'll want to do when your button is pressed to unhide your viewController is something like:
1: set any properties or call methods on your viewController which update its content
2: call setsNeedsLayout on your viewControllers view
3: unhide it.
viewWillAppear will not fire for the children each time they are shown. Adjust your thinking.
When you create a container view and a child view in IB, it sets up an embed segue to link to the child view controllers.
What I suggest you do is to add unique segue identifiers for each of your child view controllers, and then in prepareForSegue, use an if/else if/else if statement to match each segue ID. When you find a segue Id, save that child view controller in a property of the parent:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString: #"firstChildID")
self.firstChildVC = (FirstChildVC *) segue.destinationViewController;
else if ([segue.identifier isEqualToString: #"secondChildID")
self.secondChildVC = (SecondChildVC *) segue.destinationViewController;
else if ([segue.identifier isEqualToString: #"thirdChildID")
self.thirdChildVC = (ThirdChildVC *) segue.destinationViewController;
}
Then define a protocol that you use to communicate with your child view controllers to tell them that they are being shown:
#protocol ChildVCProtocol
- (void) getReadyToShowView;
//whatever other methods
#end
Then make each of your child VCs conform to that protocol. Put your code to get ready to be shown in the getReadyToShowView method. Call that method every time you make a child VC visible.
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.
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.