Animating Bring to Front UIView Method - objective-c

Is there a way to animate the following:
UIView *exampleView = //something
[exampleView.superview bringToFront:exampleView];
In a for loop so that every view in a view hierarchy would be brought to the front of the hierarchy one at a time with a delay between each view change, because when I put the above line of code into an animation inside a for loop all of the views are brought to the front of the hierarchy at once since animations occur immediately in code and only change over time in the way they look on screen.

You can only animate the changing of animatable properties. You cannot animate the enactment of arbitrary methods. And only certain properties are animatable (they are explicitly listed as such). A property is animatable because there is some way to represent visually the intermediate stages between the initial and final values.
You might investigate the layer's zPosition property, which is expressly said to be animatable, but I do not know what the visual effect looks like.

You can also animate transitions using UIView's transition(from:to:duration:options:completion:) or transition(with:duration:options:animations:completion:) functions. Links to the Apple documentation to these functions are here and here.
Please keep in mind that Apple is discouraging use of these animation techniques as of iOS 13 and recommending to use UIViewPropertyAnimator class functions instead.
Let me give an example of the usage in Swift:
let containerView = UIView()
let transitionFromView = UIView()
containerView.addSubview(transitionFromView)
//add your layout constraints to the transitionFromView within the containerView
let transitionToView = UIView()
containerView.addSubview(transitionToView)
//add your layout constraints to the transitionToView within the containerView
...
func performTransition1() {
//since both transitionFromView and transitionToView have already been added
//to their parent's subviews, we can use showHideTransitionViews animation option
//in this example I am using transitionCrossDissolve animation; however,
//there are other options that can be used instead such as
//transitionFlipFromRight
UIView.transition(from: transitionFromView,
to: transitionToView,
duration: 0.5,
options: [.showHideTransitionViews, .transitionCrossDissolve],
completion: nil)
}
ps: full list of animation options can be found here

Related

UIView animations - When does it animate certain properties like scale and position?

I'm currently working with a UITableViewController which contains some UITableViewCells subclasses.
When layoutSubviews is called on these UITableViewCells, I change some of the cells' subviews' scales and positions depending on the width and height of the contentView. (The UITableViewCells have some subviews, and I change their scales and positions)
A good example is toggling edit mode, since it shortens the contentView by a bit.
When I do this, the scales and positions of my UITableViewCell's subview do animate
func toggleEdit() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.35)
self.tableView.beginUpdates()
self.tableView.editing = !self.tableView.editing
self.tableView.endUpdates()
UIView.commitAnimations()
}
When I do this, ONLY the positions animate, the scales change immediately which looks really ugly:
func toggleEdit() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.35)
self.tableView.editing = !self.tableView.editing
UIView.commitAnimations()
}
I kind of found this out by accident, so I'm wondering now what kind of magic begin/end tableViewUpdate does, and how I can control myself in any scenario which properties should animate and which shouldn't.
The docs state
When you call endUpdates, UITableView animates the operations simultaneously
You could try changing things that you don't want animated in a performWithoutAnimation(_:) block
UIView.performWithoutAnimation {
// things you don't want to animate
}

Apply CALayer changes to UIView

I have a UIView. I applied the animation to its CALayer.
[view.layer addAnimation:groupAnimation forKey:name];
I want the final state of the layer to be the state of the UIView after the animation. Let's say I rotated by 45degrees and moved to a new position using the layer; is it possible for my view to be in that state after the animation? Because right now, after the animation, it goes back to the original state of the UIView. I hope to receive some help with this. Thanks.
The thing to understand is that layer animation is just animation; it's merely a kind of temporary "movie" covering the screen. When the animation ends, the "movie" is removed, thus revealing the true situation. It is up to you to match that situation with the final frame of the movie.
UIView animation does this for you, to a great extent. But layer animation leaves it entirely up to you.
Thus, let's say you animate a position change; doing something so that the layer or view actually is where you animated to is completely up to you.
The usual thing is to perform the changes yourself as a separate set of commands. But be careful not to do anything that might trigger implicit layer animation, as this will conflict with the animation you are trying to perform explicitly.
Here's example code (not related to yours, but it does show the general form):
CAAnimationGroup* group = // ...
// ... configure the animation ...
[view.layer addAnimation:group forKey:nil];
// now we are ready to set up the view to look like the final "frame" of the animation
[CATransaction setDisableActions:YES]; // do not trigger implicit animation by mistake
view.layer.position = finalPosition; // assume we have worked this out
When animating a CALayer or using a CAAnimationGroup, the following properties must be set, e.g.:
groupAnimation.removedOnCompletion = NO;
groupAnimation.fillMode = kCAFillModeForwards;
See also: After Animation, View position resets
Also note that it may be helpful to apply animations directly to the view itself, rather than by accessing the view's layer. This is accomplished using animation blocks, which I have found to be very useful.
Block style animations can be customized in many ways, but here's a basic example, which could be invoked within a function when your view needs to animate:
- (void) animateMyView
{
CGRect newViewFrame = CGRectMake(x,y,w,h);
[UIView animateWithDuration:0.5
delay:0
options: (UIViewAnimationOptionCurveLinear )
animations:^{
self.myView = CGAffineTransformMakeRotation (45);
[self.myView setBounds:newViewFrame];
}
completion:NULL];
}
For more information, see Apple's documentation on View Animation.

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];
}

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.

UIView subclass draws background despite completely empty drawRect: - why?

So, I have a custom UIView subclass which enables drawing of rounded edges. The thing draws perfectly, however the background always fills the whole bounds, despite clipping to a path first. The border also draws above the rectangular background, despite the fact that I draw the border in drawRect: before the background. So I removed the whole content of drawRect:, which is now virtually empty - nevertheless the background gets drawn!
Anybody an explanation for this? I set the backgroundColor in Interface Builder. Thanks!
Sorry for this monologue. :)
The thing is that the UIView's layer apparently draws the background, which is independent from drawRect:. This is why you can't get rid of the background by overriding drawRect:.
You can either override - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx and make the layer draw whatever you want, or you override - (void) setBackgroundColor:(UIColor *)newColor and don't assign newColor to backgroundColor, but to your own ivar, like myBackgroundColor. You can then use myBackgroundColor in drawRect; to draw the background however you like.
Overriding setBackgroundColor:
Define an instance variable to hold your background color, e.g. myBackgroundColor. In your init methods, set the real background color to be the clearColor:
- (id) initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super init...])) {
[super setBackgroundColor:[UIColor clearColor]];
}
return self;
}
Override:
- (void) setBackgroundColor:(UIColor *)newColor
{
if (newColor != myBackgroundColor) {
[myBackgroundColor release];
myBackgroundColor = [newColor retain];
}
}
Then use myBackgroundColor in your drawRect: method. This way you can use the color assigned from Interface Builder (or Xcode4) in your code.
Here's an easier fix:
self.backgroundColor = [UIColor clearColor];
I hesitate to suggest this because I don't want to offend you, but have you set opaque to NO?
This is easily reproducible:
Create a new View-Based iPhone Application. Create an UIView subclass and leave the drawRect: method completely empty. In Interface Builder, drag a UIView into the main view, assign it a background color and set the class of the view to your subclass. Save, build and run, and see, the view shows the background color.
I have circumvented this behaviour by overriding setBackgroundColor: and assigning the color to my own ivar, always leaving the backgroundColor property nil. This works, however I'm still wondering why this works this way.
You should set
clearsContextBeforeDrawing = NO
It's that simple.
From the UIView Reference Docs
Oops. I misunderstood the question. The OP (original poster) wants their custom control to support the standard "backgroundColor" property (eg, set by the gui designer), but does NOT want the system to paint that color. The OP wants to paint the bg himself so that he can round the corners.
In that case, the post about overriding the layer level drawing is correct.
The solution I posted will prevent the UI system from clearing your buffer before drawing. When you have no bg defined, if clearsContextBeforeDrawing is set, the iOS will clear your view's buffer, setting all pixels to transparent black. Doing this for a full-screen view takes about 5ms in an iPad3, so it's not free (pushing that 2048x1536 pixels never is). For comparison drawing a full-screen bitmap (using kCGBlendModeCopy to force blitting) takes ~25ms (using quartz, not GPU).
While it is possible to override the code that copies the background colour into the view's layer, I'm not a fan of this approach. I believe it can cause issues with views intended to be opaque. My suggestion is to create a custom colour variable like this:
#IBInspectable var foregroundColor: UIColor = .black {
didSet { setNeedsDisplay() }
}
Now, set your view's backgroundColor to clear and use foregroundColor in your drawing code in its place. The code above takes care of updating it when the value changes, and also exposes it to Interface Builder if required.
Another alternative is to use your view's tintColor instead, and to ensure that it updates correctly when tint colour changes. You may be reserving tint colour for other uses though, so this is not necessarily ideal. If you use tintColor, don't forget to include the following:
override func tintColorDidChange() {
setNeedsDisplay()
}