Flipping a view only works when hiding, but not when showing - objective-c

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.

Related

Animate the presenting controller view while animating presented controller view

I have a view controller that on certain action presents another view controller that covers bottom half of the screen.
When I animate the second view controller (presented view controller) from the bottom to cover the bottom half of the screen, I also want animate the presenting view to top half of the screen. See the code below -
- (void)animatePresentationWithTransitioningContext:(id<UIViewControllerContextTransitioning>) transitioningContext
{
UIViewController *presentedController = [transitioningContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *presentedControllerView = [transitioningContext viewForKey:UITransitionContextToViewKey];
UIView *containerView = [transitioningContext containerView];
presentedControllerView.frame = [transitioningContext finalFrameForViewController:presentedController];
CGPoint center = presentedControllerView.center;
center.y = containerView.bounds.size.height;
presentedControllerView.center = center;
[containerView addSubview:presentedControllerView];
// This returns nil
UIView *presentingControllerView = [transitioningContext viewForKey:UITransitionContextFromViewKey];
[UIView animateWithDuration:[self transitionDuration:transitioningContext] delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
CGPoint presentedViewCenter = presentedControllerView.center;
presentedViewCenter.y -= containerView.bounds.size.height/2.0;
presentedControllerView.center = presentedViewCenter;
// This is where I want to move the presenting view controller.
// of course it does not work.
CGPoint presentingViewCenter = presentingControllerView.center;
presentingViewCenter.y -= containerView.bounds.size.height/2.0;
presentingControllerView.center = presentingViewCenter;
} completion:^(BOOL finished) {
[transitioningContext completeTransition:finished];
}];
}
The viewForKey:UITransitionContextFromViewKey returns nil. I overrode shouldRemovePresentersView to return NO in my custom UIPresentationController but I still get nil. I am using UIModalPresentationCustom as the modal presentation style for the view that I am presenting.
EDIT:
I realized that I can achieve the same affect by animating the presenting view by overriding the presentationTransitionWillBegin method in my custom UIPresentationController. But I would like to get an answer on accessing the presenting view controller.
EDIT:
Since I have found a solution, clarifying my question: (1) Is this the expected behavior i.e. the presentingViewController is going to be nil in the animator object?
I am using UIModalPresentationCustom as the modal presentation style for the view that I am presenting.
Well, there's your problem. In that case, the From view will be nil. It is nil unless you are using the FullScreen presentation style.
I'm not saying you are wrong to make the presentation style Custom. You need to do that, if you want to leave the presenting view controller's view in place, and if you want to supply your own presentation controller. And you do! But in that case, the work is divided: it is the presentation controller that becomes responsible for the final position of the view.
By the way, it occurs to me (thinking about it some more) that you are doing something you should probably not be doing. You are not expected to move the presenting view controller's view. It might be safer to take a snapshot view of the presenting view controller's view and animate that, behind your presented view controller's view.

Possible to manually rotate UIPopoverController's view?

The view system in my app is highly customized and uses a number of views that are manually rotated from portrait to landscape based on user interactions (the rotation is done by applying an affine transform to the view/layer).
I want to present a popover inside one of these rotated views, but the orientation of the popover always appears relative to the orientation of the device (i.e., not relative to the view). I'm guessing the answer is no, but just in case someone has a clever idea: is there any way to manually rotate the view that is presented by UIPopoverController?
Sean, I just tested it for kicks, yes it works.
It has to be done (in my case at least) in viewDidAppear (if done in viewWillAppear, it gets knocked back to the original setting.)
This worked just fine (just tested now) to have a popover at a 90 degree angle. i.e in my case my main view is in portrait mode and the popover is turned 90 deg.
self.navigationController.view.superview.superview.transform = CGAffineTransformMakeRotation (M_PI/2.0);
Are you trying to rotate the popover or just the content shown in the popover? You can control some of the former by setting which arrow orientations are possible. I'm interested in the latter, and it seems to work just by grabbing the content view controller. E.g.:
aPopoverController.contentViewController.view.transform = CGAffineTransformMakeRotation(M_PI);
DISCLAIMER: If you're at all interested in trying to get your app into the store, this code is almost certainly grounds for rejection. It dives into UIKit's private API's which is a big no-no as far as apple is concerned.
#RunningPink had the right idea. Depending on how the view hierarchy is set up, the popover may be back up farther than two superviews. The popover itself it an instance of the (private) class _UIPopover (at least in iOS 5). You can find this view by doing:
UIView *possiblePopover = popoverController.contentViewController.view;
while (possiblePopover != nil) {
// Climb up the view hierarchy
possiblePopover = possiblePopover.superview;
if ( [NSStringFromClass([possiblePopover class]) isEqualToString:#"_UIPopoverView"] ) {
// We found the popover, break out of the loop
break;
}
}
if (nil != possiblePopover) {
// Do whatever you want with the popover
}
In doing this, I found that transforming the view often ended up making the popover look blurry. I found the reason was that the popover's superview was an instance of another private class called UIDimmingView which is responsible for accepting touches outside of the popover and causing the popover to dismiss. Performing the rotation on the dimming view removed the blurriness I was seeing in the popover.
However, transforming the dimming can result in weirdness where certain parts of the window are not "covered" by the dimming view so the popover will not dismiss if these parts of the window are tapped. To get around this, I applied the rotation to the dimming view, reset the dimming view's frame to cover the screen, and then translated the popover view into place.
if (nil != possiblePopover) {
// Found the popover view
CGAffineTransform rotation = CGAffineTransformMakeRotation(-M_PI_2);
CGAffineTransform translation = // Whatever translation in necessary here
// Rotate the UIDimming View and reset its frame
[possiblePopover.superview setTransform:rotation];
[possiblePopover.superview setFrame:CGRectMake(0, 0, possiblePopover.superview.frame.size.height, possiblePopover.superview.frame.size.width)];
// Translate the popover view
[possiblePopover setTransform:translation];
}

Subviews not displaying when using replaceSubview:with:

I'm writing a dynamic wizard application using cocoa/objective c on osx 10.6. The application sequences through a series of views gathering user input along the way. Each view that is displayed is provided by a loadable bundle. When the app starts up, a set of bundles are loaded and as the controller sequences through them, it asks each bundle for its view to display. I use the following to animate the transition between views
[[myContentView animator] replaceSubview:[oldView retain] with:newView];
This works fine most of the time. Every once in a while, a view is displayed and some of the subviews are not displayed. It may be a static text field, a checkbox, or even the entire set of subviews. If, for example, a checkbox is not displayed, I can still click where it should be and it then gets displayed.
I thought it might have something to do with the animation so I tried it like this
[myContentView replaceSubview:[oldView retain] with:newView];
with the same result. Any ideas on what's going on here? Thanks for any assistance.
I don't think is good to use this [oldView retain]. This retain do not make sense. The function replaceSubview will retain it if it is necessary.
Because it work "most of the time" I think it is a memory problem. You try to use a released thing. Test without it and see what happens.
I got the same problem, replaceSubview didn't work.
Finally i found something wrong with my code. so here are the rules :
Both subviews should be in MyContentview's subviews array.
OldView should be the topmost subview on MyContentView or replacesubview will not take place.
here is a simple function to perform replacesubview
- (void) replaceSubView:(NSView *)parentView:(NSView *)oldView:(NSView *)newView {
//Make sure every input pointer is not nill and oldView != newView
if (parentView && oldView && newView && oldView!=newView) {
//if newview is not present in parentview's subview array
//then add it to parentview's subview
if ([[parentView subviews] indexOfObject:newView] == NSNotFound) {
[parentView addSubview:newView];
}
//Note : Sometimes you should make sure that the oldview is the top
//subview in parentview. If not then the view won't change.
//you should change oldview with the top most view on parentview
//replace sub view here
[parentView replaceSubview:oldView with:newView];
}
}

Why is this UIImageView autocentering itself?

I have a UIImageView in a UIScrollView in another UIScrollView (based on Apple's
PhotoScroller sample code). When the UIScrollView calls back to its controller to dismiss itself, it calls this method:
- (void)dismiss {
[scrollView removeFromSuperview];
ImageScrollView *isv = [self currentImageScrollView];
UIImage *image = isv.imageView;
image.frame = [self.view convertRect:image.frame fromView:isv];
[self.view insertSubview:image belowSubview:captionView];
[(NSObject *)delegate performSelector:#selector(scrollViewDidClose:)
withObject:self
afterDelay:2.0];
}
Now here's the weird part: the image view jumps to a different position right after this method executes, but before the scollViewDidClose method gets called on the delegate. If the image is larger than its new super view, it jumps so that its left edge is aligned with the left edge of its super view. If it's smaller than its new super view, it jumps to the very center of the view. There is no animation to this change.
So my question is, how do I prevent it from doing that? I've tweaked both the super view (self.view) class and the image view class to see what methods might be called. Neither the frame nor the center is set on the image view after this method is called, and while the layoutSubviews method is called on the super view, that is not what jumps the image to the center or left side of the superview. I've also tried turning off autoResizesSubviews in the super view, and setting the autoresizingMask of the image view to UIViewAutoresizingNone, with no change in behavior.
So where is this happening, and why? And more importantly, how do I make it stop?
I've been beating my head on this for days. Any pointers or guidance would be greatly appreciated.
ImageScrollView is the one centering your UIImageView. Set a breakpoint in ImageScrollView layoutSubviews and you'll see how your UIImageView is being centered.
You're taking ImageScrollView's internal imageView and placing it into another view. That's not going to work because ImageScrollView still retains ownership of that UIImageView instance and is still managing its layout.
You'll either need to copy the image into another UIImageView instance, or you'll need to change ImageScrollView to allow it to relinquish ownership of its imageView.
You're not setting up the frame of the 'image' view when you insert it as a subview. You probably want to do that explicitly if you want the view to appear at a particular position in the scroll view.

Draw an NSView into an NSGraphicsContext?

I have a CGContext, which I can turn into an NSGraphicsContext.
I have an NSWindow with a clipRect for the context.
I want to put a scrollview into the context and then some other view into the scrollview so I can put an image into it... However, I can't figure out how to attach the scrollview into the context.
Eventually the view will probably be coming from a nib, but I don't see how that would matter.
I've seen this thread, (http://lists.apple.com/archives/quartz-dev/2006/Nov/msg00010.html) But they seem to leave off the step of how to attach the view into the context, unless there's something obvious I'm missing.
EDIT:
The reason I'm in this situation is that I'm writing a Mozilla Plugin. The browser gives me a CGContext (Quartz) and a WindowRef (QuickDraw). I can turn the CGContext into an NSGraphicsContext, and I can turn the windowRef into an NSWindow. From another data structure I also have the clipping rectangle...
I'm trying to draw an image into that context, with scrollbars as needed, and buttons and other UI elements... so I need (want) an NSView...
You can't put a view into a graphics context. A view goes either into another view, or as the content view of a window.
You can draw a view into a context by setting that context as the current context and telling the view to draw. You might do this as a means of rendering the view to an image, but otherwise, I can't think of a reason to do it. (Edit: OK, being a Netscape plug-in is probably a good reason.)
Normally, a view gets its own graphics context in NSView's implementation of the lockFocus method, which is called for you by display, which is called for you by displayIfNeeded (only if the view needs display, obviously), which is called for you as part of the event loop.
You don't need to create a context for a view except in very rare circumstances, such as the export-to-an-image case I mentioned. Normally, you let the view take care of that itself.
A partial solution?
What I have done currently is create a nib with a button in an IKImageView inside an NSScrollView. I load this in my plugin.
Then, since I have the NSWindow, I can get the contentView of the window. Then, I add the scrollview as subview of contentView.
It appears, but there seems to be some coordinate confusion about where the origin is. (top vs bottom) and since I'm mucking with the contentview of the WHOLE WINDOW, I'm doing some stuff very globally that perhaps I should be doing more locally. Like, the view never disappears, even when you close the tab, or go to another tab. (it does close when you close the window of course)
So, does this sound like a reasonable way of doing this? it feels a bit ... kludgy...
For future generations (and me when I forget how I did this and Google leads me back to my own question) Here's how I'm doing this:
I have a NIB with all my views, I load this on start-up.
on SetWindow, I set the clip rect and actually do the attaching:
NP_CGContext* npContext = (NP_CGContext*) window->window;
NSWindow* browserWindow = [[[NSWindow alloc] initWithWindowRef:npContext->window] autorelease];
NSView* cView = [browserWindow contentView];
NSView* hitView = [cView hitTest:NSMakePoint(window->x + 1, clip.origin.y + 1)];
if (hitView == nil || ![[hitView className] isEqualToString:#"ChildView"])
{
return;
}
superView = [hitView retain];
[superView addSubview: topView];
[superView setNextResponder: topView];
[topView setNextResponder: nil];
[browserWindow makeFirstResponder: topView];
To make sure I only addSubView once, I have a flag...
And then in handleEvent, I actually draw, Because I'm using an IKImageView, I can use the undocumented method: [imageView setImage: image]; which takes an NSImage.
So far this seems to be working for me. Hopefully this helps someone else.