Custom view transition in OpenGL ES - iphone

I'm trying to create a custom transition, to serve as a replacement for a default transition you would get here, for example:
[self.navigationController pushViewController:someController animated:YES];
I have prepared an OpenGL-based view that performs an effect on some static texture mapped to a plane (let's say it's a copy of the flip effect in Core Animation). What I don't know how to do is:
grab current view content and make a texture out of it (I remember seeing a function that does just that, but can't find it)
how to do the same for the view that is currently offscreen and is going to replace current view
are there some APIs I can hook to in order to make my transition class as native as possible (make it a kind of Core Animation effect)?
Any thoughts or links are greatly appreciated!
UPDATE
Jeffrey Forbes's answer works great as a solution to capture the content of a view.
What I haven't figured out yet is how to capture the content of the view I want to transition to, which should be invisible until the transition is done.
Also, which method should I use to present the OpenGL view?
For demonstration purposes I used pushViewController. That affects the navbar, though, which I actually want to go one item back, with animation, check this vid for explanation:
http://vimeo.com/4649397.
Another option would be to go with presentViewController, but that shows fullscreen.
Do you think maybe creating another window (or view?) could be useful?

While I cannot completely answer your question without doing some more research of my own, I can help a bit:
-In order to get the view of a UINavigationController, you need to take a screenshot. The easiest way to do this is by grabbing it into a UIImage:
UIGraphicsBeginImageContext(self.view.frame.size);
[[self.view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage* test = UIGraphicsGetImageFromCurrentImageContext();
UIImageView* view = [[UIImageView alloc] initWithImage:test];
UIGraphicsEndImageContext();
I am not sure if you can render a GLContext (not familiar on the phone) into a CGImage, but I would do something like that (and init a UIImage from that). I would prerender every frame of the animation you are trying to do and slap it into an UIImageView using the animation stuff provided within. That is, if your animation is simple enough. Otherwise, it might come down to writing your own animation function :-/

I have just put together a transition class to implement your own transition animation in OpenGL ES.
Feel free to read about it here
There are two example transitions in the project, feel free to add you own to it.

I think the function you might be thinking of is http://www.opengl.org/sdk/docs/man/xhtml/glCopyTexImage2D.xml ... you set the viewport to the texture size and then draw as usual, then do glCopyTexImage2D to copy the scene onto a texture.
or you should look into FrameBuffer Objects. The default OpenGL template in XCode uses these. Just generate the example project to see how those work.

I recently write some transitioning animation betweeen view controllers like you. If you want to get any extra info from the invisible view, you can try delaying the transition like this :
- (void)animationFromModalView:(UIView *)modalView toMasterView:(UIView *)masterView
{
[masterView setNeedsLayout];
[masterView layoutIfNeeded];
[self performSelector:#selector(delayAnimationFromModalViewToMasterView) withObject:nil afterDelay:.1f];
}

Related

UIImage animations don't work in a view pushed without animation

I've got a view controller whose view contains a UIImageView that does animation:
//AnimationViewController::ViewDidLoad event:
var ctlAnimations = new UIImageView();
ctlAnimations.AnimationImages = list.ToArray(); //<--list contains the UIImages
ctlAnimations.AnimationDuration = 1.0 * list.Count;
ctlAnimations.StartAnimating();
this.Add(ctlAnimations);
This works perfectly: when I push AnimationViewController onto the navigation stack, it displays and animates the UIImage.
But now I need to show AnimationViewController with a custom animated transition:
var transition = CATransition.CreateAnimation ();
transition.Duration = 0.3f;
transition.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
transition.Type = CATransition.TransitionFade;
this.View.Layer.AddAnimation (transition, "fade");
//viewController is being pushed with animated=false, because we have a custom animation
base.PushViewController (viewController, false);
this.View.Layer.RemoveAnimation("fade");
This also works perfectly, in that the new View transitions into place using the specified custom animation.
But when I push AnimationViewController onto the stack using an animated transition, it displays but the animation doesn't run. Instead, it shows the first frame of the animation (the first image in the list), and doesn't run it.
So, something about the transition is breaking the ability to animate a UIImage in the new view controller, but I simply can't figure out what to do about it.
Update: I've noticed that if I tap the NavigationController's back button, but then move off of the back button and let go (so I don't actually go back), the animation starts playing!
PushViewController works like this: Over the current view controller the next view controller is placed you can say pushed onto the stack. From Apple docs its clear that either you need to push view controllers either with animation or without.
Work around:
Set the frame of the next view controller's view's x position beyond
the screen's right
Suppose width of the screen is 320, then set the x position of next
view as 320.
Add the next view as subview to the existing one.
Now do your custom animation.
Another work around:(a bit more overhead though)
Take a snapshot programmatically of current view.
Add the snapshot image as the initial view of next view controller.
Now push view controller without animation. (User will still see the old view)
In viewDidAppear of new view controller start your custom animation.
[I have to warn you that this method of taking snapshot might give you a small delay in older devices. Newer devices are pretty fast enough you wont see any lag]
Let me know if any issues in case you are implementing any of these solutions.
Try putting the animating bit in ViewDidAppear rather than ViewDidLoad. Also, try using breakpoints and NSLogs to follow what happens after the animation, starting with the ViewDidLoad and ViewDidAppear. Try having the animation repeat forever so you can see if it has ever been animating or not.
I'm very curious what the culprit is here as well. Why is the animation not displaying correctly in some cases?
My theory is that you have placed animation code in viewWillAppear rather than viewDidAppear. Animation code does not run properly when placed in WILL or SHOULD methods.
Can you please post back what caused the issue?
Suspicion #1
I am betting that your code is not being called because it is in ViewDidLoad. I believe you are creating a customized view stack, that means you need to be using the ChildViewController methods from Cocoa.
I am unfamiliar with MonoTouch (I only write pure CocoaTouch), so this might not be 100% correct
I would be consoling out your viewDidLoad and viewDidAppear methods and absolutely make sure they are being called. It is my suspicion that viewDidLoad IS NOT. And this is causing viewDidLoad to not be called on the UIImageView.
In your code you probably need the equivalent of (from objective-c):
[self addChildViewController:viewController];
// OR?
[base addChildViewController:viewController];
This tells the 'parent' viewController that the Child has been made visible, so call the viewDidLoad/Appear and Unload/Disappear methods when appropriate. This might not exist in MonoTouch, or the Push methods might not be fully implemented, so you might need to do some hacky (bad) stuff like manually calling the viewDidLoad method manually.
Suspicion #2
It could also be that your 'list' variable (the one holding the images) is nil. If that happened the animation would not run. OR maybe it has something to do with the duration of your animation, try to set it to whatever would make it repeat forever. Be sure it isn't running REAL FAST somehow and you are just missing it.
begin philosophical musing
Either that or start learning actual Cocoa development :) Not meant as a flame, but definitely meant seriously, you are going to run into problems trying to develop applications through translation layers (custom language-bridges meant to get around writing the base language of a framework/application/platform).
Titanium/MonoTouch/PhoneGap will never produce as robust or high-quality applications as real Objective-C. And besides that, once you learn Cocoa it will change how you write everything else, and I doubt you will want to go back. As the wonderful website of the same name says, 'Cocoa is my girlfriend'
Let me tell the something about UI in IOS. In IOS access to the UI Elements is limited to a single thread.
The single thread would always be the mainThread except in the case when you are running an animation.
Hence when u are performing number of animation at the same instance you have to use
beginAnimation.
setFrame (or) some methods that changes state of UI element.
Repeat step2 for all those objects u are scheduling to animate.
comitAnimations to perform all animations at once. (using comit animations ensure all the animations are performed on same thread)
So, I guess here is what happening in ur case.
Viewcontroller started an animation to push the view controller into stack.
Image view started another animation before finishing first animation.
Look at the links to get a clear idea link1 and link2.
Well Let's get into the solution
Add an ivar and retained property named ctlAnimations to your class
In ViewDidLoad (or) ViewDidAppear
self.ctlAnimations = new UIImageView();
ctlAnimations.image=(UIImage*)[list.toArray() objectAtIndex:0];
this.Add(ctlAnimations);
[self performSelector:#selector(startAnimatingImage) afterDelay:0.1];
Create a private method named startAnimatingImage with below code
self.ctlAnimations.AnimationImages = list.ToArray();
ctlAnimations.AnimationDuration = 1.0 * list.Count;
ctlAnimations.StartAnimating();
On a brief we just shown first image for a moment when the firstanimation is taken place and then we delayed animation start for 1 second so that it performs after first animation and then starts animating the image.
Go ahead and give it a try
There is the concept for the Main thread. So UIAnimation are actually works on Main Thread and at the same time may be this was happen that another task is performing on the same Main Thread.So that was a case like IOS will give preference according to processes id of each process at a time and for the solution you need to make background thread for the same operations.
Well, I never figured out what the problem was here, but it also turned out that there was some sort of memory leak in UIImage such that on some phones my code was crashing.
So I rewrote it to do the animation manually. Now I set a timer and keep a frame index variable, and every time the timer rings I change the image myself. This amounts to the same thing, and it turns out it fixes this problem.

How do I “redraw layers” in Core Graphics?

I remember that when I was reading the Apple documentation, it would mention that when you call a function such as addSubview, you are adding a “layer of paint,” so to speak, and every time it is called, another layer is overlaid.
This should be an easy question to answer, but I had a hard time thinking of keywords to google for, so please excuse the asking of such a simple question.
How do I clear the “layers” of a custom UIView?
My situation, as it may be relevant: I have these “user cards” that are displayed on the screen. They are initialized with some user images. The cards stay the same, but I call a method in my custom UIView (the card UIView) to redraw the images when I want to display a different user. The problem is that some elements of this custom UIView are transparent, and redrawing these images each time builds on that transparency (an obvious problem).
In Core Graphics, what you draw is what gets shown. The painter’s analogy only refers to a single frame. So if you’re using drawRect, you just don’t cache the previous drawing.
But I suspect you’re talking about some UIKit stuff where you’ve added subviews or sublayers. This will remove those leftover views if you just want to clear everything:
for (UIView *view in customView) {
[view removeFromSuperview];
}
for (CALayer *layer in customView.layer) {
[layer removeFromSuperlayer];
}
Ryan's code for clearing UIViews is more or less correct, but if you came here from Google looking for how to clear CLLayers from a view, I was getting a crash when I attempted to fast enumerate customView.layer like Ryan has in his example.
When I switched it to regular enumeration, it worked for me. Here's a code snippet:
for (int i = 0; i > yourView.layer.sublayers.count; i++) {
CALayer *layer = [self.yourView.layer.sublayers objectAtIndex:i];
[layer removeFromSuperlayer];
}

Fixed Background for iPhone app using Storyboard

Can't seem to find a way to fix a graphic - a light graphic that would remain static as the user navigates from scene to scene. Have been combing forums for a few days, but most answers point to MainWindow.xib, which isn't generated for a storyboard project. Would appreciate any advice.
Here’s what I was able to cobble together thanks to advice from #Max. Like I said, very new to this, so if this code needs tuning please leave a comment so I don’t lead others astray.
Add the image to the app’s main window in the AppDelegate’s didFinishLaunchingWithOptions method :
UIImageView *myGraphic = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"myGraphic.png"]];
[self.window.rootViewController.view addSubview: myGraphic];
[self.window.rootViewController.view sendSubviewToBack: myGraphic];
Then, to make your views transparent so the background shows through - in your viewController class/es, add to viewDidLoad method:
self.view.backgroundColor = [UIColor clearColor];
(And note if you try to decrease the transparency through the alpha property for the view via the Attributes Inspector, it will make everything transparent on that view -buttons, etc - that’s why it needs to be done programmatically.)
Sure, it’s not thoroughly tested. But for now it’s working and it’s a thing of beauty. Thanks all.
You can try to manually add UIImageView to your window and then set 0 background transparency
for all other views.
One way to do it -- it may not be the best -- can be that you subclass a UIView, and set the UIView instance in all of your viewControllers in the storyboard an instance of your cutomized UIView class, which contains your graphic as a background.
If you're going to tackle this programmatically, within each view controller, set:
[[self view] setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"background.png"]]];
in viewDidLoad
I'm sure some tweaking would allow you to apply this principle throughout the program from the app delegate, or by using a singleton.
Using this solution, though, you're still going to have to make some method call within each view controller where you want this style applied.

layoutSubviews during an animation?

I have a UIView with a bunch of subviews, all positioned using layoutSubviews. When the view is resized, the relative positions all change. I'd like these re-calculations to happen during an animated resize (using +[UIView beginAnimations:] calls). This doesn't seem to be happening. Any ideas?
Assumption: You want to have multiple animation steps (i.e. position doesn't change linearly with frame size).
This isn't possible with a single "standard" UIView animations. Why? The frame/bounds is only set once.
Core Animation has three "layer trees":
The model tree is where your app thinks things are.
The presentation tree is approximately what's being displayed on screen.
The render tree is approximately what Core Animation is compositing.
UIView is a (somewhat thin) wrapper around the model layer. During a UIView animation, Core Animation updates the presentation/render tree — the model tree represents the endpoint of animations. The upshot is that your code can (for the most part) treat animations as instantaneous — moving a view from A to B instantly moves it to B; the change just happens to be animated to the user.
There are more complicated things you can do with CALayer/CAAnimation directly, but I haven't investigated this much.
You could chain multiple animations together using -[UIView setAnimationDidStopSelector:]. (You could also try using multiple animations together with setAnimationDelay:, but I'm not sure what happens with multiple animations on the same property; you might have luck with setAnimationBeginsFromCurrentState:.)
If you want really fine-grained control, CADisplayLink (OS 3.1+) is a timer that fires after each screen refresh. A fallback option (for 3.0 support) is to use an NSTimer at 30/60 Hz or so.
I know this is an old question, but this code works for me very well (suited for your example of changing frame).
-(void)layoutSubviews{
[super layoutSubviews];
// layout your subviews here, or whatever
}
-(void)someMethod{
double duration=...;
[UIView animateWithDuration:duration animations:^{
self.frame = ...;
[self layoutIfNeeded];
}];
}
Of course you can call this method from another object. The "trick" is to call layoutIfNeeded (or layoutSubviews directly - same thing, if You change the frame the setNeedsLayout is called).
As tc. nicely explained the "layer trees", You just force the presentation layer to display the final stage of model layer with animation.
The advantage of this method is in possibility to control when the frame/bounds change is animated and when it's instant.
Hope this helps someone:).
Completing #GrizzlyNetch's anwer, you can set the UIViewAnimationOptionLayoutSubviews animation option, so you don't need to call layoutIfNeeded:
-(void)someMethod{
double duration = ...;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{
self.frame = ...;
} completion:nil];
}
Posting for completeness. Thanks to tc. for explaining that what I want to do, exactly, is not supported by Core Animation.
I eventually came up with a reasonable solution. Rather then layout my subviews in -layoutSubviews, I do so in -setBounds:. Then, when I wrap a -setBounds: call in a UIView +beginAnimations: block, those positioning calls are also animated, and the end result is everything properly animating to where it should god.

CAShaperLayer -renderInContext Doesn't Work?

I am able to create a UIImage from a Core Animation layer using the following code:
- (UIImage*)contentsImage;
{
UIGraphicsBeginImageContext([self bounds].size);
[self renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
This code is in my CALayer derived class. The issue I am running into is that I have two CAShapeLayers that are child layers of my layer that do not get rendered to the resulting image. If I add standard CALayers as children they get rendered fine. The Apple docs say:
Renders the receiver and its sublayers
into the specified context.
It also says that it's been available since iPhone OS 2.0. Wondering if there is something I'm missing or if I should file a radar.
Any ideas what might keep the child CAShapeLayers from getting drawn to the image?
Thanks.
The CALayer machinery calls renderInContext to create its bitmapped contents property. But in a CAShapeLayer, the path property is not actually rendered to its contents as seen by this note in the header:
The shape as a whole is composited
between the layer's contents and its
first sublayer.
It stands to reason that renderInContext won't actually render the CAShapeLayer path onto your context. I haven't actually tried this out for myself however.
Don't know if its relevant to you but there is a note in the CALayer documentation for renderInContext that says :
**Important**: The Mac OS X v10.5 implementation of this method does not
support the entire Core Animation composition model. QCCompositionLayer,
CAOpenGLLayer, and QTMovieLayer layers are not rendered. Additionally,
layers that use 3D transforms are not rendered, nor are layers that specify
backgroundFilters, filters, compositingFilter, or a mask values.
Future versions of Mac OS X may add support for rendering these layers
and properties.
Anyways, I ran into a similar problem when using the UIView drawRect function in conjunction with drawing in an image context. The overall UIView that contained subviews would not draw its subviews if I called drawRect (which makes sense now actually since it says in the documentation if you call drawRect you are responsible for filling that entire area regardless of super and subview implementations). I solved my problem by just called drawRect on all my subviews, passing them their own frames.
So I would suggest maybe switching away from renderInContext and use CALayer's drawInContext instead? You'll need to override the method since it doesn't do anything by default. Your subclasses will also need to move the contexts to their appropriate frames. Also to be safe you might want to check that none of the code you add affects normal rendering of these layers.
I filed a radar on this. I can't see any reason in the docs that it shouldn't work.I will respond back here if/when Apple replies to the radar.