Automatically passing events from ChildViewController to parentViewController - ios

I can't figure out how enable iOS to pass events from a childViewController down to its parentviewcontroller.
My childviewcontroller is like an overlay it's root view is transparent and it's opaque views cover a dynamic area - potentially the the entire parentviewcontroller (thus the root view of the childviewcontroller has the same frame as the parent).
Though only the opaque part of the childViewController's views should receive events. All other events I would like to get forwarded in the responderChain down to the parentviewcontrollers views. Just as if all views were sharing one viewcontroller.

I have made an instance of this class:
#interface SilentView : UIView
#end
#implementation SilentView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self) {
return nil;
}
return hitView;
}
#end
the rootview of my childViewController. That works for me.

Related

Pass an event on the whole view hierarchy

I have created a "classic" category on ui controller to present in my way an other controller.
#interface UIViewController (QZPopup)
- (void)presentPopupViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion;
- (void)dismissPopupViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion;
#end
The category adds:
a blurred view on top of the calling controller view
an invisible view on top of all the screen. this invisible view must catch touch event to dismiss the popup and after having dismissed the popup, I also want to transmit this event to do anything required.
For my usage, I have a UIController embedded in a UINavigationBarController. I want to have the part below the navigation bar blurred and the navigation bar visible. A touch on the blurred view just dismiss and a touch on the navigation bar must dismiss AND do anything required by touched items in the navigationbar.
Visual is ok on my side, but I have trouble to pass the event and reach the navigationbar. For the moment I have subclassed UIView in QZPopupDismiss like below:
#implementation QZPopupDismiss {
touchCallback _touchCompletion;
}
- (instancetype)initWithFrame:(CGRect)frame andTouchCompletion:(touchCallback)completion {
if (self = [self initWithFrame:frame]) {
_touchCompletion = completion;
self.userInteractionEnabled = YES;
}
return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// first do the touch completion
_touchCompletion();
// pass the event to the view hierarchy
[super touchesEnded:touches withEvent:event];
}
#end
But the fact is that, in touchsEnded, super does not reach the navigation bar. How can I pass my event to the whole view hierarchy to reach the navigation bar items ?

ignore touch over a barButton

I made a custom split view controller, and I have tried to mimic the standard one as much as possible.
One feature I have is if the device is in portrait and the master view is displayed, if you tap on the details view, the master view will hide.
I use this code in the details view to accomplish this.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// Detect touches on the details View and notify the Split View controller
if(point.y != self.touchLocation.y)
{
if (_delegate && [_delegate respondsToSelector:#selector(detailsViewDidTapDetails:)])
[_delegate detailsViewDidTapDetails:self];
}
return [super pointInside:point withEvent:event];
}
The problem I am having, is if the detailsView is filled with a view that has its own navigation bar, then any buttons pressed in the navigation bar trigger the 'details view tapped' methods displayed above. This conflicts with adding the 'Show Details' button that you place inside a nav bar. Essentially what happens, is the methods to hide/show the master View is called twice back to back.
I need a way to ignore any taps on top of my 'hide/show details' button. I have a reference to the button, I just need to ignore taps on it within my hitTest / pointsInside methods.

Overriding loadView causes viewDidLoad and loadView to fire everytime a VC appears

My view heirarchy sits on several custom "root" UIViewController subclasses. I'm trying to set a custom self.view for one of my root VC subclasses. There, I am doing:
MyRootViewController_With_Scroll.h
// Import lowest level root VC
#import "MyRootViewController.h"
// my custom scroll view I want to use as self.view
#class MyScrollView;
#interface MyRootViewController_With_Scroll : MyRootViewController {
}
#property (strong) MyScrollView *view;
#end
MyRootViewController_With_Scroll.m
#import MyRootViewController_With_Scroll.h;
#interface MyRootViewController_With_Scroll ()
#end
#implementation MyRootViewController_With_Scroll
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)loadView
{
NSLog(#"loading view");
CGRect windowSize = [UIScreen mainScreen].applicationFrame;
MyScrollView *rootScrollView = [MyScrollView scrollerWithSize:windowSize.size];
self.view = rootScrollView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// Getter and setter for self.view
- (MyScrollView *)view
{
return (MyScrollView *)[super view];
}
- (void)setView:(MyScrollView *)view
{
[super setView:view];
}
According to the iOS6 docs, viewDidLoad in only suppose to fire once per view controller for the entire lifecycle of the app.
What am I doing wrong here? What is causing my view controllers to repeatedly call loadView/viewDidLoad? Strangely, my apps "home screen" VC loads the view just once, but all its subsequent views in the navigation heirachy fires loadView everytime they appear. What is going on?
edit I've changed the property to strong. Same issue happens.
edit 2 I've stopped overriding loadView and its still happening. Now I'm really confused.
This is expected behaviour. If you're popping view controllers off a navigation stack, and nothing else has a reference to them, then they're going to get deallocated. Therefore when it appears again, it will be a new instance, so it has to perform loadView and so on all over again. Include self in your logging, you should see that it is a different object each time.
You've also redefined the view controller's view property as weak - if you are re-using the view controller objects, then this will be nilled out as soon as the view has no superview.
Prior to iOS 6, a view controller that was mid-way in your navigation stack would get its view unloaded under memory pressure:
root --> VC1 --> VC2
VC2 is on screen, a memory warning is received. VC1 would unload its view. When you pop VC2 off the stack, VC1 would call loadView again. This no longer happens.
However, if you've popped back to VC1, and nothing has a strong reference to VC2, then VC2 is deallocated. When you push another VC2 onto the stack, this is a new instance and loadView will be called again. Presumably you are creating these new instances of VC2 in code, so you should be able to tell that you are creating a new instance.
Thats because you have weak view property. So it get realloced all the time. Also, I don't think that you need to override view property at all.

iOS: UIViewController doesn't display another UIViewController using storyboards

I am tryin to display multiple UIViewController objects inside a single view. For the time being I want to display a single UIViewController object when the app loads. But the app screen appears blank, while it should be displaying a label inside the child view controller.
Here is what I did:
ParentViewController.h
#import <UIKit/UIKit.h>
#interface ParentViewController : UIViewController
{
UIViewController *child1Controller;
UIViewController *child2Controller;
}
#end
ParentViewController.m
#import "ParentViewController.h"
#import "Child1Controller.h"
#import "Child2Controller.h"
#interface ParentViewController ()
#end
#implementation ParentViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { ... }
- (void)viewDidLoad
{
child2Controller = [[Child2Controller alloc] init];
[self.view addSubview:child2Controller.view];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewDidUnload { ... }
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { ... }
#end
Then in the storyboard in interface builder
add 3 view controllers
assigned a class to each one of them ParentViewController, Child1Controller & Child2Controller
in Child2Controller object, added a UILabel inside View.
in Child2Controller.h defined the IBOutlet for UILabel and added a synthesize statement for the same in Child2Controller.m
finally in project-Info.plist set the main storyboard file
Did I miss something over here?
Starting from iOS 5 it's possible to take advantage of View Controller Containment. This is a new methodology that allows you to create a custom controller container like UINavigationController or UITabBarController.
In your case, this could be very useful. In fact, in your storyboard you could create the parent controller and the two child controllers. The parent could be linked to another scene while the two children are not linked. They are independent scenes that you can use within your parent controller.
For example in viewDidLoad method of your parent controller you could do the following:
- (void)viewDidLoad
{
[super viewDidLoad];
UIStoryboard *storyboard = [self storyboard];
FirstChildController *firstChildScene = [storyboard instantiateViewControllerWithIdentifier:#"FirstChildScene"];
[self addChildViewController:firstChildScene];
[firstChildScene didMoveToParentViewController:self];
}
Then in your FirstChildController override didMoveToParentViewController
- (void)didMoveToParentViewController:(UIViewController *)parent
{
// Add the view to the parent view and position it if you want
[[parent view] addSubview:[self view]];
CGRect newFrame = CGRectMake(0, 0, 350, 400);
[[self view] setFrame:newFrame];
}
And voilĂ ! You have a controller that contains one view that is managed by a child controller.
For further info see how-does-view-controller-containment-work-in-ios-5.
Hope it helps.

iPad 'dismiss keyboard' button doesn't dismiss keyboard

I have a UITextField in a Landscape view, and when I press the 'dismiss keyboard' button in the lower right of the UIKeyboard view, the keyboard does NOT disappear. Is there a way to programmatically listen for when this key was pressed? Or is there a connection I am not seeing that will make this keyboard go away?
This is iOS 4 and XCode 4. Thanks.
I had same problem today and I wondered, wy it works in Apple's KeyboardAccessory Sample Code.
So I did reverse engineering. The ViewController was not the mistake I made in my case.
In the implementation of UIApplicationDelegate there is the entry point of application, where root viewcontroller and window will be setup - (void) applicationDidFinishLaunching:(UIApplication *)application. If you forgot to add root viewcontrollers view to window as subview, the dismiss-keyboard-button wouldn't work in any view of your app.
#class ViewController;
#interface KeyboardAccessoryAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
ViewController *viewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet ViewController *viewController;
#end
...
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
Please don't forget to setup the outlets in the main xib file.
I dont know why this is related to keyboards behavior. But my theory is, that the responder chain is not linked to window, but it needs.
To dismiss the keyboard using the dismiss keyboard button you need to implement the delegate method
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
//Check if your text field is first responder, and if it is, have it resign
}
Alternatively, if you want to dismiss the keyboard by tapping outside of it, use
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UIView* view in self.view.subviews)
{
if ([view isKindOfClass:[UITextField class]]) {
[view resignFirstResponder];
}
if ([view isKindOfClass:[UITextView class]]) {
[view resignFirstResponder];
}
}
}
you need to tell the text field that is accepting the keyboard input to no longer be the first responder.
[UITextField resignFirstRepsonder];

Resources