Adding a ChildViewController causes strange behavior to the child viewcontroller - ios

By "strange", I am noticing the following behavior:
background color set by the ChildViewController does not appear
despite adding a basic tap gesture recognizer to the ChildViewController, there is no tap recognition
adding a UIButton to the ChildViewController results in the UIButton shown, but there is no response when tapping on the UIButton part
In my ParentViewController, I present the ChildViewController very generically, like so:
UIViewController *viewController;
viewController = (ChildViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"ChildViewController"];
[self addChildViewController:viewController];
viewController.view.frame = self.view.bounds;
viewController.view.translatesAutoresizingMaskIntoConstraints = NO;
viewController.view.center = self.view.center;
[self.view addSubview:viewController.view];
[self.view bringSubviewToFront:viewController.view];
[viewController didMoveToParentViewController:self];
And here are some very basic things I do in ChildViewController's viewDidLoad:
self.view.backgroundColor = [UIColor yellowColor];
UIButton *tapButton = [UIButton buttonWithType:UIButtonTypeSystem];
[tapButton addTarget:self action:#selector(tappedOnTap) forControlEvents:UIControlEventTouchUpInside];
//button view customization code omitted for brevity
tapButton.userInteractionEnabled = YES;
tapButton.enabled = YES;
[self.view addSubview:tapButton];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tappedOnView)];
[self.view addGestureRecognizer:tapGesture];
[self.view setUserInteractionEnabled:YES];
[self.view addGestureRecognizer:tapGesture];
I ensured that everything is hooked up correctly - however, there is no acknowledgment in tappedOnView and tappedOnTap when I tap on it, in both simulator or device.
Am I missing something basic about presenting a ChildViewController?
Edit
For those curious, this very basic app is on Github.

The containment calls are fine. But if you use the view debugger (), you'll see the frame is not correct. This is because you've turned off translatesAutoresizingMaskIntoConstraints, but you don't have any constraints. You can either turn that back on (and set autoresizingMask accordingly):
viewController.view.translatesAutoresizingMaskIntoConstraints = true;
Or you can define constraints rather than setting the frame (and redundantly setting the center):
//viewController.view.frame = self.view.bounds;
viewController.view.translatesAutoresizingMaskIntoConstraints = false;
//viewController.view.center = self.view.center;
[self.view addSubview:viewController.view];
[NSLayoutConstraint activateConstraints:#[
[viewController.view.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[viewController.view.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[viewController.view.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[viewController.view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor]
]];

Try this:
ChildViewController *viewController;
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildViewController"];
viewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleheight;
[self addChildViewController:viewController];
[self.view addSubview:viewController.view];
[viewController didMoveToParentViewController:self];
Usually you don't need to do anything when adding a ViewController to another except those basic methods. since the views will be adjusted automatically and you don't need to set all those properties like size and center etc...

Related

Is there a way to call the subview of a view controller's self.view in a function outside of viewDidLoad?

Essentially what I'm trying to do here is have a function which sets the alpha of a subview of self.view to 1 (it is hidden by default) which gets called upon pressing a button. i've run into this problem a few times because i declare subviews in viewDidLoad and i don't know of a way to interact with those in a different function (i've tried self.view.subviews[n] but xcode is not happy with that). the only other way i can think of to make this happen is to set self.view equal to the view i want, or maybe even create the view within the function i'm hoping to call with the button (the issue i see with that is potentially the view will not overlay the button, which i want to happen).
here's what i have so far:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView *firstBackground = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIView *blackOverlay = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
blackOverlay.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.0];
firstBackground.backgroundColor = [UIColor whiteColor];
UIButton *fakeNotifButton = [UIButton buttonWithType:UIButtonTypeCustom];
[fakeNotifButton addTarget:blackOverlay action:#selector(setupFakeNotifScreen:) forControlEvents:UIControlEventTouchUpInside];
//i haven't finished the button yet, that i don't need any help with
}
-(IBAction)setupFakeNotifScreen:(id)sender {
[UIView animateWithDuration:1.0
delay:0.5
options:UIViewAnimationOptionCurveEaseInOut
animations:^{view = 1.0;} //here is where i want to call blackOverlay
completion:^(BOOL finished){}];
}
#end
edit: i figured it out... didn't even need the blackOverlay view. here's the solution for any future google people... there are probably more efficient ways to do this so i would appreciate anybody who's better than me weighing in:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *fakeNotifButton = [UIButton buttonWithType:UIButtonTypeCustom];
[fakeNotifButton addTarget:self action:#selector(setupFakeNotifScreen:) forControlEvents:UIControlEventTouchUpInside];
fakeNotifButton.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width/2) - 80, 300, 160, 40);
[fakeNotifButton setTitle:#"test" forState:UIControlStateNormal];
[fakeNotifButton setBackgroundColor:[UIColor systemGreenColor]];
fakeNotifButton.layer.cornerCurve = #"continuous";
fakeNotifButton.layer.cornerRadius = 15;
[self.view addSubview:fakeNotifButton];
}
-(void)setupFakeNotifScreen:(UIButton *)sender {
[UIView animateWithDuration:0.2
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{self.view.backgroundColor = [UIColor blackColor]; sender.alpha = 0.0;}
completion:^(BOOL finished){}];
}
#end
When you declare
UIView *blackOverlay = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
...that reference is inside the method viewDidLoad, so its existence is confined to that method. It is visible only inside the method, and indeed the reference goes out of existence when the method ends. (The view itself also goes out of existence; you have not put it into the interface.)
If you want a persistent reference to this view, you need to declare a property (or at least an instance variable, aka iva), and set it to this view. You still won't be able to do anything visible with the view if you don't actually put it into the interface.

UIView doesn't respond to touches

I have simple view controller and I add this controller like subview for window using this code:
UIWindow *window = [UIApplication sharedApplication].keyWindow;
if (!window)
window = [[UIApplication sharedApplication].windows objectAtIndex:0];
self.view.alpha = 0.0;
self.view.userInteractionEnabled = YES;
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.view.alpha = 1.0;
}completion:nil];
But when I click at this view - nothing happens. Even event touchesBegan: are not called.
UPD:
Code above is in -(void)show method. I want to show one controller above all controllers.
In FirstViewController I create instance of CustonAlertViewController like that:
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
[alertVC show];
In CustomAlertViewController I have show method presented at the top and viewDidLoad method with:
self.view.backgrondColor = [UIColor greenColor];
Hidden views (and equivalently, those with alpha == 0.0) do not respond to touches. If you require a fully transparent view, leave alpha as > 0.0, and say...
self.view.backgroundColor = [UIColor clearColor];
Alternatively, assign a nonzero alpha.
EDIT yes, the CustomAlertViewController instance is being deallocated immediately after show is invoked. The view controller that does the allocating needs to have a strong property to keep the alert around,
#property(nonatomic,strong) CustomAlertViewController *alertVC;
and adding...
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
self.alertVC = alertVC;
[alertVC show];
This doesn't try to address some potential problems beyond the scope of this question (like rotation, or cleanly restoring when the alert is done).
// try like this , u need to give number of touches
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
gesture.numberOfTapsRequired = 1;
gesture.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:gesture];
You have been setting your UITapGestureRecognizer after you addSubview to your window. like:
You have been doing this
//...
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
//...
Try this
Set UITapGestureRecognizer before you addSubview. And remember to add the UIGestureRecognizerDelegate to your ViewController
self.view.alpha = 0.2;
self.view.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
tapGesture.numberOfTapsRequired = 1;
[tapGesture setDelegate:self];
[self.view addGestureRecognizer:tapGesture];
// After you add your self.view to window
[window addSubview:self.view];
I hope this can help you.

Adding a UISegmentedControl under NavigationBar

I’m adding a UISegmentedControl right under the NavigationBar in a UITableViewController. This is the code.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationBar = self.navigationController.navigationBar;
UIView *segmentView=[[UIView alloc] initWithFrame:CGRectMake(0, self.navigationBar.frame.size.height, self.navigationBar.frame.size.width, 50)];
[segmentView setBackgroundColor:[UIColor whiteColor]];
segmentView.alpha = 0.95;
self.tabSegmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"Favourites", #"All", nil]];
self.tabSegmentedControl.frame = CGRectMake(20, 10, self.navigationBar.frame.size.width - 40, 30);
[self.tabSegmentedControl addTarget:self action:#selector(tabChanged:) forControlEvents:UIControlEventValueChanged];
[segmentView addSubview:self.tabSegmentedControl];
[self.navigationBar addSubview:segmentView];
[self.tabSegmentedControl setSelectedSegmentIndex:1];
}
The view and the SegmentedControl appear on the screen well, but they are not clickable. The selector doesn’t get executed when tapped on the SegmentControl; it doesn’t even switch tabs! In fact, the stuff that is underneath the segmentView (items in the TableView) get clicked when you tap on it. I have tried but failed to understand why this is happening! Any suggestions would be helpful!
You are adding a view below the bounds of its super view. You may see the view however you cannot click it because it is out of bounds. If you set the property of the navigation bar clipsToBounds to YES you should see that the view disappears. What you need to do is add the segment controller to the table view. Here is an example:
- (void)viewDidLoad
{
[super viewDidLoad];
...
[self.view addSubview: self.segmentView]; // need to keep a pointer to segmentView
self.tableView.contentInset = UIEdgeInset(self.segmentView.frame.size.height, 0,0,0);
}
-(void) scrollViewDidScroll:(UIScrollView*) scrollView{
CGRect rect = self.segmentView.frame;
rect.origin = self.tableView.contentOffset;
self.segmentView.frame = rect;
}

How to interrupt viewWillAppear hierarchy

I have a rootViewController, in it's viewDidLoad method, I initialized another two ViewController2* object and their views as subview of rootViewController.view, then I set first ViewController2* controller.view.hidden = YES.
Then, on v1 has a button handler, when touch it, it present a UINavigationController, after that touch 'dismiss' button call dismissViewControllerAnimated on v1.
The question is: when dismiss complete, the two of ViewController2* fire viewWillAppear. How to make it only fire the viewWillAppear on the visible one, but not on the hidden one?
the rootViewController's implementation:
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.v1 = [[ViewController2 alloc] init];
self.v1.title = #"v1";
[self.view addSubview:self.v1.view];
self.v1.view.hidden = YES;
self.v2 = [[ViewController2 alloc] init];
self.v2.title = #"v2";
[self.view addSubview:self.v2.view];
UIButton * btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setTitle:#"POP" forState:UIControlStateNormal];
[btn sizeToFit];
[btn addTarget:self action:#selector(touchHandler:) forControlEvents:UIControlEventTouchDown];
[self.view addSubview:btn];
}
- (void)touchHandler:(id)sender {
UINavigationController * nc= [[UINavigationController alloc] initWithRootViewController:[[UIViewController alloc] initWithNibName:nil bundle:nil]];
((UIViewController *)[nc.viewControllers objectAtIndex:0]).navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"dismiss" style:UIBarButtonItemStyleBordered target:self action:#selector(dismissHandler:)];
[self presentViewController:nc animated:YES completion:nil];
}
- (void) dismissHandler:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
ViewController2:
#implementation ViewController2
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"%#",self.title);
}
#end
viewWillAppear will fire on your UIViewController's, even if the view controllers view is set hidden=YES.
You can surely test if (self.view.hidden == YES) in your viewWillAppear delegate method if you want to prevent some expensive operation from occurring, but beware that if you later make that view un-hidden, that viewWillAppear won't fire then.
Simple, the reason why those methods are called is because the viewController's view is part of the main window's view hierarchy. This means that it has a superview that has a superview that has a superview and so on until that superview is the main window.
Instead of hiding and unhiding the viewController views, you should instead add and remove them from their superview. Also, to make sure that viewWillAppear and viewDidAppear are called correctly at the correct times, take a look at ViewController Containment:
http://www.cocoanetics.com/2012/04/containing-viewcontrollers/

UIView transitionFromView: toView: animation not working.

It's my first post on stackoverflow. I'm a iOS developer newbie and I'm not a native English speaker, so I will do my best to explain my problem.
Problem:
I have added two views to my AppDelegate window and I want to flip from one to the other using:
UIView transitionFromView:toView:
The first view (MainScreenView) has its own ViewController. On the MainScreenView .xib file I have a button with an action that calls the method "goShow" implemented in my AppDelegate. In that method I use UIView transitionFromView:toView: to transition to the second view. So far everything is working fine.
My second view (a scrollview) is declared programmatically in my AppDelegate and has a bunch of pictures inside it (picturesViewController) and on top of those, has a UIPinchGestureRecognizer.
I'm using a gesture recognizer to flip back to my MainScreenView. That is where the problem is. When I do a pinch gesture on the scrollview the MainScreenView.view appears immediately, before the animation, so the flip animation looks wrong.
The code I'm using is:
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
mainScreen = [[MainScreenViewController alloc] initWithNibName:#"MainScreenViewController" bundle: [NSBundle mainBundle]];
CGRect frame = self.window.bounds;
int pageCount = 10;
scrollView = [[UIScrollView alloc] initWithFrame:frame];
scrollView.contentSize = CGSizeMake(320*pageCount, 480);
scrollView.pagingEnabled = YES;
scrollView.showsHorizontalScrollIndicator = FALSE;
scrollView.showsVerticalScrollIndicator = FALSE;
scrollView.delegate = self;
[...] 'While' adding pictures to de scrollView
UIPinchGestureRecognizer *twoFingerPinch = [[[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(goBackToMain)] autorelease];
[scrollView addGestureRecognizer:twoFingerPinch];
[self.window addSubview: scrollView];
[scrollView setHidden:TRUE];
[self.window addSubview: mainScreen.view];
[self.window makeKeyAndVisible];
return YES;
}
-(void) goShow{
[UIView transitionFromView:mainScreen.view
toView:scrollView
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
[UIView commitAnimations];
}
-(void) goBackToMain {
[UIView transitionFromView:scrollView
toView:mainScreen.view
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
[UIView commitAnimations];
}
I'm using show/hide views instead of addSubview/removeFromSuperView because I tried the add and remove and got an app crash in the pinch gesture, exactly in same step that is failing the animation. Probably it is the same error, but I'm unable to find the reason for this. Any help would be appreciated.
Thanks.
Ok. With Adrian's help, here's the UIPinchGesture code that solved my problem:
[...]
UIPinchGestureRecognizer *twoFingerPinch = [[[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(goBackToMain:)] autorelease];
[scrollView addGestureRecognizer:twoFingerPinch];
-(void)goBackToMain:(UIPinchGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded)
{
[UIView transitionFromView:scrollView
toView:mainScreen.view
duration:0.4
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:nil];
[UIView commitAnimations];
}
First, you cannot mix the old method beginAnimation commitAnimation combination with the new block method transitionFromView.
Second, when using block method animation, make sure you use a container (probably a UIView) that will be the parent of the two views you want to switch. Without the container you will be animating the whole view instead. Make sure the container have the same size as the subviews that will switch.
Example:
[container addSubView:frontView];
[container addSubView:backView];
[self.view addSubView:container];
[UIView transitionFromView:backView toView:frontView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:nil];
Read more about animations in iOS.
In your example you forgot [UIView beginAnimations].

Resources