UIView Draw method called multiple times on animating the sublayer - animation

I have sub classed the UIView and added the sublayer(inherited from CAShape layer) to this class and I am animating the sublayer in the draw method of UIView.
While animating before the animation gets completed, draw method called multiple times and this results in flicker. This issue occurs while navigating the page and occurs only in iOS12. I am using CADisplaylink for animation.
Inside the draw method I am using the following code to perform animation
if (myCAShapeLayer == null)
myCAShapeLayer = new AnimationLayer(this); // animation layer inherited from CAShapeLayer.
this.Layer.AddSublayer(myCAShapeLayer);
Animate(); //Animation logics.
myCAShapeLayer.StrokeColor = RangeColor.CGColor; //set to some color
myCAShapeLayer.FillColor = RangeColor.CGColor; // set to some color

Related

Subview loaded inside UIView does not constrain to UIView bounds

I have created the following scene:
Application scene
The scene contains a stackView with all other views contained inside of it. The grey area is a UIView which gets three subviews added to it during viewDidLoad. I want each of these views to fill the UIView and be constrained to its bounds. When one of the three buttons is clicked, the corresponding view is brought to the front of for display. The issue I am facing is that the subviews are not being constrained to the UIView. I've tried quite a few different options but cannot seem to get the effect I am looking for. The red in the following image is the background of one of the loaded subviews.
iPhone 5
On larger devices, the subviews overextend the boundary on the right side as well. How do I constrain the subViews to my UIView boundary?
Set the frame of the 3 views you want to be constrained with the bounds of the grey superview. This can be done in layoutSubviews of the grey view or in viewDidLayoutSubviews of the viewController.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
subview0.frame = greyView.bounds
subview1.frame = greyView.bounds
subview2.frame = greyView.bounds
}
or within the custom class for the grey view (if you have defined a custom class)
override func layoutSubviews() {
super.layoutSubviews()
subview0.frame = bounds
subview1.frame = bounds
subview2.frame = bounds
}
Can also be done by using constraints to constrain the subviews to their superview.

Why is an empty drawRect interfering with a simple animation?

I'm trying to put together my own take on Cocoa's NSPathControl.
In a bid to figure out the best way to handle the expand-contract animation that you get when you mouse over a component in the control, I put together a very simple sample app. Initially things were going well - mouse into or out of the view containing the path components and you'd get a simple animation using the following code:
// This code belongs to PathView (not PathComponentView)
override func mouseEntered(theEvent: NSEvent) {
NSAnimationContext.runAnimationGroup({ (ctx) -> Void in
self.animateToProportions([CGFloat](arrayLiteral: 0.05, 0.45, 0.45, 0.05))
}, completionHandler: { () -> Void in
//
})
}
// Animating subviews
func animateToProportions(proportions: [CGFloat]) {
let height = self.bounds.height
let width = self.bounds.width
var xOffset: CGFloat = 0
for (i, proportion) in enumerate(proportions) {
let subview = self.subviews[i] as! NSView
let newFrame = CGRect(x: xOffset, y: 0, width: width * proportion, height: height)
subview.animator().frame = newFrame
xOffset = subview.frame.maxX
}
}
As the next step in the control's development, instead of using NSView instances as my path components I started using my own NSView subclass:
class PathComponentView: NSView {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
}
}
Although essentially identical to NSView, when I use this class in my animation, the animation no longer does what it's supposed to - the frames I set for the subviews are pretty much ignored, and the whole effect is ugly. If I comment out the drawRect: method of my subclass things return to normal. Can anyone explain what's going wrong? Why is the presence of drawRect: interfering with the animation?
I've put a demo project on my GitHub page.
You've broken some rules here, and when you break the rules, behavior becomes undefined.
In a layer backed view, you must not modify the layer directly. See the documentation for wantsLayer (which is how you specify that it's layer-backed):
In a layer-backed view, any drawing done by the view is cached to the underlying layer object. This cached content can then be manipulated in ways that are more performant than redrawing the view contents explicitly. AppKit automatically creates the underlying layer object (using the makeBackingLayer method) and handles the caching of the view’s content. If the wantsUpdateLayer method returns NO, you should not interact with the underlying layer object directly. Instead, use the methods of this class to make any changes to the view and its layer. If wantsUpdateLayer returns YES, it is acceptable (and appropriate) to modify the layer in the view’s updateLayer method.
You make this call:
subview.layer?.backgroundColor = color.CGColor
That's "interact[ing] with the underlying layer object directly." You're not allowed to do that. When there's no drawRect, AppKit skips trying to redraw the view and just uses Core Animation to animate what's there, which gives you what you expect (you're just getting lucky there; it's not promised this will work).
But when it sees you actually implemented drawRect (it knows you did, but it doesn't know what's inside), then it has to call it to perform custom drawing. It's your responsibility to make sure that drawRect fills in every pixel of the rectangle. You can't rely on the backing layer to do that for you (that's just a cache). You draw nothing, so what you're seeing is the default background gray intermixed with cached color. There's no promise that all of the pixels will be updated correctly once we've gone down this road.
If you want to manipulate the layer, you want a "layer-hosting view" not a "layer-backed view." You can do that by replacing AppKit's cache layer with your own custom layer.
let subview = showMeTheProblem ? PathComponentView() : NSView()
addSubview(subview)
subview.layer = CALayer() // Use our layer, not AppKit's caching layer.
subview.layer?.backgroundColor = color.CGColor
While it probably doesn't matter in this case, keep in mind that a layer-backed view and a layer-hosting view have different performance characteristics. In a layer-backed view, AppKit is caching your drawRect results into its caching layer, and then applying animations on that fairly automatically (this allows existing AppKit code that was written before CALayer to get some nice opt-in performance improvements without changing much). In a layer-hosting view, the system is more like iOS. You don't get any magical caching. There's just a layer, and you can use Core Animation on it.

How to control drawing of NSTableView in NSScrollView?

How can I get NSTableView to always show the same columns regardless of the horizontal scroller position? In the rightmost visible column I have custom cell views. I want the horizontal scroller to control what is being drawn in these custom views. The vertical scrolling should work normally.
I have tried several approaches without much success. For example, I can control the knob proportion of the horizontal scroller by making the table view wider, or by making the scroll view think its document view is actually wider than it is. One way is subclassing NSClipView and overriding -documentRect as follows:
-(NSRect)documentRect {
NSRect rect = [super documentRect];
rect.size.width += [[NSApp delegate] hiddenRangeWidth];
return rect;
}
However, while the scroller knob looks as it should and I can drag it right without moving the table view, when I start scrolling in another direction, the knob returns to the left edge. I also have the problem that I can't get the horizontal scroller to appear automatically. This happens with the original classes as well, not just with my custom clip view. Could these problems be related?
I have also tried replacing the document view with a custom view that acts as a proxy between the clip view and the table view. Its -drawRect: calls the table view's -drawRect:. However, nothing is drawn. I guess this is because the table view now has no superview. If the table view were added to this proxy view as a subview, it would move with it. How would I make it stationary in horizontal axis?
So, to reiterate:
What is the best way to make a table view scrollable, while always showing the same columns regardless of the horizontal scroller position?
What is the best way to get the scroller position and knob proportion? Should I add an observer for the NSViewBoundsDidChangeNotification from NSClipView?
I finally managed to solve the problem by letting the scroll view and table view behave normally, and adding an NSScroller. In order to make hiding the scroller easier, I decided to use Auto Layout and add it in Interface Builder. (The Object library doesn't include a scroller, but you can add a custom view and set its class to NSScroller.) I set the height of the scroller as a constraint, and bound the scroller and the constraint to outlets in code:
#property (nonatomic, retain) IBOutlet NSScroller *scroller;
#property (nonatomic, unsafe_unretained) IBOutlet NSLayoutConstraint *scrollerHeightConstraint;
Now I can make the scroller visible or hide it when necessary:
if (_zoomedIn) {
_scrollerHeightConstraint.constant = [NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleOverlay];
[_scroller setKnobProportion:(_visibleRange / _maxVisibleRange)];
[_scroller setDoubleValue:_visibleRangePosition];
[_scroller setEnabled:YES];
} else {
_scrollerHeightConstraint.constant = 0.0;
}
Here the properties visibleRange, maxVisibleRange and visibleRangePosition are the length of the visible range (represented by the scroller knob), the total range (represented by the scroller slot), and the start of the visible range (the knob position), respectively. These can be read by binding the scroller's sent action to the following method in Interface Builder:
- (IBAction)scrollAction:(id)sender {
switch (self.scroller.hitPart) {
case NSScrollerNoPart:
break;
case NSScrollerDecrementPage:
_visibleRangePosition = MAX(_visibleRangePosition - _visibleRange / _maxVisibleRange, 0.0);
self.scroller.doubleValue = _visibleRangePosition;
break;
case NSScrollerIncrementPage:
_visibleRangePosition = MIN(_visibleRangePosition + _visibleRange / _maxVisibleRange, 1.0);
self.scroller.doubleValue = _visibleRangePosition;
break;
case NSScrollerKnob:
case NSScrollerKnobSlot:
_visibleRangePosition = self.scroller.doubleValue;
break;
default:
NSLog(#"unsupported scroller part code %lu", (unsigned long)self.scroller.hitPart);
}
// Make the custom cell views draw themselves here.
}
In order to get the scrolling work with gestures, we need to implement -scrollWheel: in the custom cell view class:
- (void)scrollWheel:(NSEvent *)event {
if (event.deltaX != 0.0) {
NSScroller *scroller = appDelegate.scroller;
if (scroller.isEnabled) {
double delta = event.deltaX / (NSWidth(scroller.bounds) * (1.0 - scroller.knobProportion));
scroller.doubleValue = MIN(MAX(scroller.doubleValue - delta, 0.0), 1.0);
}
}
if (event.deltaY != 0.0) {
[self.nextResponder scrollWheel:event];
}
}
I thought I could've just passed the event to the scroller, but apparently it doesn't handle the event. The above code doesn't seem to handle bounce back, and momentum scrolling doesn't always work. Sometimes the knob just halts in the middle of the motion. I believe this has to do with the scroller style being NSScrollerStyleLegacy by default. Setting it to NSScrollerStyleOverlay would require changes to the layout, so I haven't tried it yet.
Another problem is that the scrollers don't blend into each other in the corner like they do in a scroll view (see below). Maybe NSScrollerStyleOverlay would fix this, too.

App get crash on animation of 2d objects in Unity 4.3

Getting bit issue on playing animation when first object collide to second object its OnCollisionEnter2D method I am playing animation. I attached animation with second object and when I run code it gets crash. Here is what I am doing. declare string on top and call on collision.
string blastAnim = "BlastAnim";
void OnCollisionEnter2D(Collision2D col){ Debug.Log("Collision Enter............."); animation.Play(blastAnim);
}
You can not use 3d animation methods in 2d. There are different way to implement 2d animations in unity.
you need to create animation controller for sprite.
Create animation clip for the same.
Declare variable for Animator as
Animator anim;
void Start()
{
anim = gameObject.GetComponent<Animator>();
Animator anim = gameObject.GetComponent<Animator>();
anim.SetBool("Jump",isJump);
}

Can I use CALayer to speed up view rendering?

I am making a custom NSView object that has some content that changes often, and some that changes much more infrequently. As it would turn out, the parts that change less often take the most time to draw. What I would like to do is render these two parts in different layers, so that I can update one or the other separately, thus sparing my user a sluggish user interface.
How might I go about doing this? I have not found many good tutorials on this sort of thing, and none that talk about rendering NSBezierPaths on a CALayer. Ideas anyone?
Your hunch is right, this is actually an excellent way to optimise drawing. I've done it myself where I had some large static backgrounds that I wanted to avoid redrawing when elements moved on top.
All you need to do is add CALayer objects for each of the content items in your view. To draw the layers, you should set your view as the delegate for each layer and then implement the drawLayer:inContext: method.
In that method you just draw the content of each layer:
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx
{
if(layer == yourBackgroundLayer)
{
//draw your background content in the context
//you can either use Quartz drawing directly in the CGContextRef,
//or if you want to use the Cocoa drawing objects you can do this:
NSGraphicsContext* drawingContext = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES];
NSGraphicsContext* previousContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext:drawingContext];
[NSGraphicsContext saveGraphicsState];
//draw some stuff with NSBezierPath etc
[NSGraphicsContext restoreGraphicsState];
[NSGraphicsContext setCurrentContext:previousContext];
}
else if (layer == someOtherLayer)
{
//draw other layer
}
//etc etc
}
When you want to update the content of one of the layers, just call [yourLayer setNeedsDisplay]. This will then call the delegate method above to provide the updated content of the layer.
Note that by default, when you change the layer content, Core Animation provides a nice fade transition for the new content. However, if you're handling the drawing yourself you probably don't want this, so in order to prevent the default fade in animation when the layer content changes, you also have to implement the actionForLayer:forKey: delegate method and prevent the animation by returning a null action:
- (id<CAAction>)actionForLayer:(CALayer*)layer forKey:(NSString*)key
{
if(layer == someLayer)
{
//we don't want to animate new content in and out
if([key isEqualToString:#"contents"])
{
return (id<CAAction>)[NSNull null];
}
}
//the default action for everything else
return nil;
}

Resources