Animating a view & its subview separately - cocoa-touch

I have a view interface which contains a subview touchWheel. On clicking a minimise button on interface I'd like to perform a transform & send the view to the bottom-right corner of my screen. The thing is that I want to "detach" the touchWheel view & send it to the corner separately with the parent-view, interface, following behind (& in fact fading out).
when I try this I encounter a problem. My animation works fine initially & touchWheel is sent towards the bottom corner, as desired. When touchWheel is about half-way to its destination I animate interface so that it follows touchWheel.
Once interface starts animating it appears to control touchWheel & touchWheel changes course.
It seems that interface still retains control of touchWheel since it's its parent view.
- (void)hideInterfaceButtonClicked : (id) sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"RemoveGrey" object:self];
//Remove Views & transform
[touchWheel removeViews];
interfaceHidden = YES;
//Send Interface to corner
[UIView animateWithDuration:1.25
delay:0.0
options:UIViewAnimationCurveEaseIn
animations:^{
// Move to the right
CGAffineTransform translateInterface = CGAffineTransformMakeTranslation(437,200);
// Scale
CGAffineTransform scale = CGAffineTransformMakeScale(0.135,0.135);
// Apply them to the view
self.transform = CGAffineTransformConcat(scale, translateInterface);
self.alpha = 0.0;
} completion:^(BOOL finished) {
NSLog(#"Animation Has Stopped");
[[NSNotificationCenter defaultCenter] postNotificationName:#"hiddenInterfaceViewNeeded" object:self]; //after MoveView finishes
}];
}
Making touchWheel subview of something other than interface creates other problems which would complicate things further.
Any ideas how one might get around this issue? any tips greatly appreciated :)

You could do the following:
Move touchWheel to the interface's superview, recalculating its frame using convertRect:toView: so that touchWheel appears to have the same window coordinates.
Animate both the views to their final locations.
In the completion block of the animation do the opposite of step 1: make touchWheel a subview of interface, recalculating its frame again.
It doesn't seem too complicated, but there's a caveat: you may want to temporarily disable the maximize button (or whatever the appropriate control is) to prevent the maximize action during the animation. Otherwise, this approach will cause unpredictable results.

Related

UIButton inside a UIView not working after UIView is animated

I have seen many questions in this forum which gives answer to this topic "UIButton inside a UIView, when animated doesn't work", but after having tried out the answers like
a) UIViewAnimationOptionAllowUserInteraction to the options
b) subView.setUserInteractionEnabled = YES
c) [button addTarget:self action:#selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
none of them is working for me :-(
Here is the scenario. App is in landscape mode and a UIView called menuView is placed at x=480,y=0. Its height=200 and width=150. So, when I tap the button on the top right corner, the following code is executed
- (IBAction)showMenu {
[menuView setUserInteractionEnabled:YES];
[optionsButton setUserInteractionEnabled:YES];
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationCurveEaseOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.view.frame = CGRectOffset(self.view.frame, 0, 200);
}
completion:NULL];
}
Output?: View is appearing, but optionsButton inside the menuView is not clickable. I hooked up to an event, did an NSLog(#"Options button clicked"), but nothing happens :-(
Please tell me what I am doing wrong?
Update: When I place the subview that is "menuView" inside the self.view, then upon running and click the optionsButton, I get the NSLog message. Only if I specify the origin.x of menuView to be 480 and above, it doesn't work.
If you can see the UIButton, its userInteractionEnabled (YES by default!) is set, and its superview's userInteractionEnabled in the whole hierarchy is YES - you should be able to interact with it.
An animation will never change your userInteractionEnabled property! So what you are doing there is simply unnecessary. If you try to enable interaction during animation - that's a different story and is just an option passed to the animation message.
So if that's not your problem (and is probably not), then I guess one of the superview's frame is cropping the UIButton!
Now you see, if a UIButton (or any UIView) is outside a UIView's frame, it can still be visible, unless clipsToBounds is set on the superview.
And the outcome of that situation is: You can see me, but you can't touch me.
In my case missing a constraint on the height of the containing view caused the view height to be 0. Buttons were visible, below the view, but untouchable.
For me it was all about an overlooked mistake in the animation code itself. Where my new height was being called I was actually calling my new width, and where my new width was being called I was actually calling my new height, therefor causing the UIView to be super long yet super narrow, thus pushing all content within the center of view, outside of the view reach.
Change your UIViews background color to Red so that you can actually see where it animates to.
If your UIView is within another view, turn on clipSubviews so that you only see what is actually enabled within the subView.
Run your project.
If your UIView is not its normal height and width, more than likely you have overlooked some code in your animation call.
Make sure that your x, y, width, height are being shown in the correct orders within the animation code.
In my case, it seems like my animated transitioning is not yet completed. I forgot to to put completeTransition:Bool at the end of my animateTransition:context method.

Flipping a view only works when hiding, but not when showing

I'm using the iCarousel library to show a coverflow-like UI. Animating the iCarousel subviews directly tends to blow up, so I create a wrapper for my animation and stick a subview inside of it which looks like the tapped cover. Then I hide the actual iCarousel view, and animate the fake cover on top of it.
I'm using UIView's transitionWithView:duration:options:animations:completion: method, but I'm running into some trouble. When animating another view onscreen for the first time, the view appears without any animation. When animating out, the view correctly flips and hides.
My view hierarchy is as follows:
main view, loaded from a nib
wrapper view
view to transition from
view to transition to
The view I'm transitioning to is a UINavigationController's view which contains a UITableViewController subclass. Instead of the initial animation, the UINavigationController appears and then the view grows upward a little, as if it's taking over the space otherwise occupied by a status bar.
Any idea why the table view might be animating like this? (I suspect containment APIs and/or wantsFullscreen, although I'm not explicitly using them. I simply install the views into the wrapper via addSubview:.)
Here's my "flip in" code, that animates every time but the first:
- (void) flipInWithCompletion:(MBTransitionCompletion)completion {
BOOL displayingPrimary = [self isDisplayingPrimaryView];
UIView *frontView = [self frontView];
UIView *backView = [self backView];
UIView *wrapperView = [self wrapperView];
[wrapperView addSubview:frontView];
[UIView transitionWithView:wrapperView
duration:0.8
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[wrapperView addSubview:backView];
[frontView removeFromSuperview];
}
completion:^(BOOL finished) {
[self setIsDisplayingPrimaryView:!displayingPrimary];
if (completion) {
completion();
}
}
];
}
What might cause the table view to grow instead of allowing the wrapper to flip?
Edit:
I've made a video demoing the exact problem.
Sounds like the view isn't properly loaded before it starts to animate in. I think I remember having a similar problem before. Try adding it to your view first, then remove it, and try to animate it into place to see if that fixes it.
That's not really a solution though, but it should give you a clue as to what might be wrong.

Rotating UIViews become unresponsive

I have a UIViewController subclass that changes its frame when the UI rotates (for example, most commonly, the frame is the same size and centered in both landscape and portrait.) When the view rotates, the view visually changes its frame as expected. But the controls (buttons, scroll views, etc.) that end up in the area of the new frame do not respond to touches. The only controls that respond are the ones that have remained in the original frame (so, if the view moves down and to the left, controls or parts of controls in the upper right corner remain responsive.) Any idea what's going on here?
Here's the code that changes the frame:
- (void) setLandscape:(bool)bLandscape {
if( bLandscape )
[self setFrame:m_landscapeFrame];
else
[self setFrame:m_portraitFrame];
}
And in the bigger view controller that handles rotation directly:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
bool bToLandscape= UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
[UIView beginAnimations:NULL context:nil];
[UIView setAnimationDuration:duration];
[m_pCurrentWindowViewController setLandscape:bToLandscape];
// (Some unrelated stuff happens here...)
[UIView commitAnimations];
}
To me this seems like a bug or at least poor design on Apple's part - there should never be a time when a view is in one position visually and another position in terms of control. But I'm just looking for a way to fix this behavior.
The simplest workaround I found was to update the frame in didRotateFromInterfaceOrientation after animating in willRotateToInterfaceOrientation.

UIView animation does not allow center setting

I try to move an ImageView (say, view1) so that its center matches another ImageView's center (view2), while resizing it. On completion this first view is removed, but is image is copied into the view2, so its image seems to remain.
For that purpose I tried many combinations but with any, even creating a new ImageView3 that I can insert into view2, as soon as I try to set the center of view3 to match the center of view2, nothing happens and my image disappears, never being inserted in view2. Without setting the center, everything works. Damn, Why? Did I miss something obvious or am I stupid?
Or should I definitely use coreAnimation?
[UIView animateWithDuration:0.5 animations:^{
view1.center = view2.center;
view3.center = view2.center;
} completion:^(BOOL finished){
[view2 addSubview:view3];
}];
In the completion block, you add view3 as a subview to view2. This is probably not what you want, at least not without further adjusting its position. Since you have reset its center in the animation, the coordinates are based on the parent view. When you add it to view2, the apparent position shifts based on the relative position of view2.

Why doesn't UIView.exclusiveTouch work?

In one of my iPhone projects, I have three views that you can move around by touching and dragging. However, I want to stop the user from moving two views at the same time, by using two fingers. I have therefore tried to experiment with UIView.exclusiveTouch, without any success.
To understand how the property works, I created a brand new project, with the following code in the view controller:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
UIButton* a = [UIButton buttonWithType:UIButtonTypeInfoDark];
[a addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
a.center = CGPointMake(50, 50);
a.multipleTouchEnabled = YES;
UIButton* b = [UIButton buttonWithType:UIButtonTypeInfoDark];
[b addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
b.center = CGPointMake(200, 50);
b.multipleTouchEnabled = YES;
a.exclusiveTouch = YES;
[self.view addSubview:a];
[self.view addSubview:b];
}
- (void)hej:(id)sender
{
NSLog(#"hej: %#", sender);
}
When running this, hej: gets called, with different senders, when pressing any of the buttons - even though one of them has exclusiveTouch set to YES. I've tried commenting the multipleTouchEnabled-lines, to no avail. Can somebody explain to me what I'm missing here?
Thanks,
Eli
From The iPhone OS Programming Guide:
Restricting event delivery to a single view:
By default, a view’s exclusiveTouch property is set to NO. If you set
the property to YES, you mark the view so that, if it is tracking
touches, it is the only view in the window that is tracking touches.
Other views in the window cannot receive those touches. However, a
view that is marked “exclusive touch” does not receive touches that
are associated with other views in the same window. If a finger
contacts an exclusive-touch view, then that touch is delivered only if
that view is the only view tracking a finger in that window. If a
finger touches a non-exclusive view, then that touch is delivered only
if there is not another finger tracking in an exclusive-touch view.
It states that the exclusive touch property does NOT affect touches outside the frame of the view.
To handle this in the past, I use the main view to track ALL TOUCHES on screen instead of letting each subview track touches. The best way is to do:
if(CGRectContainsPoint(thesubviewIcareAbout.frame, theLocationOfTheTouch)){
//the subview has been touched, do what you want
}
I was encountering an issue like this where taps on my UIButtons were getting passed through to a tap gesture recognizer that I had attached to self.view, even though I was setting isExclusiveTouch to true on my UIButtons. Upon reviewing the materials here so far, I decided to put some code in my tap gesture code that checks if the tap location is contained in any UIButton frame and if that frame is also visible on the screen at the same time. If both of those conditions are true, then the UIButton will already have handled the tap, and the event triggered in my gesture recognizer can then be ignored as a pass through of the event. My logic allows me to loop over all subviews, checking if they are of type UIButton, and then checking if the tap was in that view and the view is visible.
#objc func singleTapped(tap: UITapGestureRecognizer)
{
anyControlsBreakpoint()
let tapPoint = tap.location(in: self.view)
// Prevent taps inside of buttons from passing through to singleTapped logic
for subview in self.view.subviews
{
if subview is UIButton {
if pointIsInFrameAndThatFrameIsVisible(view: subview, point: tapPoint)
{
return // Completely ignores pass through events that were already handled by a UIButton
}
}
}
Below is the code that checks if point was inside a visible button. Note that I hide my buttons by setting their alpha to zero. If you are using the isHidden property, your logic might need to look for that.
func pointIsInFrameAndThatFrameIsVisible(view : UIView, point : CGPoint) -> Bool
{
return view.frame.contains(point) && view.alpha == 1
}