Using a view with a superlayer as a layer mask - macos

I have a view that I want to overlay with a transparent black layer whose edges match the view exactly. The view does not clip its bounds, so subviews may be hanging off.
The obvious solution is the mask property of CALayer, but the docs say that the mask layer "must not have a superlayer" or the behavior is undefined.
I was hoping that using the presentationLayer of that view would be an effective workaround, but I don't think I fully understand what presentation layers are, as that property returns nil.
Does anyone have tips on how I could mask my transparent black layer to match the shape of the view over which it will be drawn? Thanks.

There’s a less obvious solution to this that should work better than masking: a blend-mode filter, specifically source-atop. If you add your transparent layer as a sibling of the layers it’s supposed to dim, and assign it the appropriate CI compositing filter, it will dim those layers without affecting anything outside of their boundaries. As a bonus, you’ll get correct antialiasing at the edges, unlike the masked-overlay approach.
Here’s an example. For illustrative purposes I made the dimming layer only half the height of the area it covers, but you’d of course want to make it larger.
let container = CALayer()
container.backgroundColor = NSColor.systemBlue.cgColor
container.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
let dimmedRoot = CALayer()
let dimmedLayer1 = CALayer()
dimmedLayer1.frame = CGRect(x: 20, y: 20, width: 100, height: 100)
dimmedLayer1.backgroundColor = NSColor.systemGreen.cgColor
dimmedLayer1.transform = CATransform3DMakeRotation(0.3, 0, 0, 1)
let dimmedLayer2 = CALayer()
dimmedLayer2.frame = CGRect(x: 80, y: 80, width: 100, height: 100)
dimmedLayer2.backgroundColor = NSColor.systemPurple.cgColor
dimmedLayer2.transform = CATransform3DMakeRotation(-0.1, 0, 0, 1)
let dimmingLayer = CALayer()
dimmingLayer.frame = CGRect(x: 0, y: 50, width: 200, height: 100)
dimmingLayer.backgroundColor = NSColor(white: 0, alpha: 0.5).cgColor
dimmingLayer.compositingFilter = CIFilter(name: "CISourceAtopCompositing")
dimmedRoot.sublayers = [ dimmedLayer1, dimmedLayer2, dimmingLayer ]
container.addSublayer(dimmedRoot)

Related

Animating a view in a real time

I am trying to create animation that is connected to real time.
I have two points on my screen and an image moves between them. So for example I have an image that is at the Strat point at 12:00 and I want it to be at the end point at 12:10.
func animate_this_image(start: Date, end: Date, imageView: UIImageView, start_point: CGPoint, end_point: CGPoint) {
let duration = end.timeIntervalSince(start)
let frame = CGRect(x: end_point.x, y: end_point.y, width: 60, height: 60).offsetBy(dx: -30, dy: -30)
UIView.animate(withDuration: duration) {
imageView.frame = frame
}
}
My problem is that I want it to be connected to the actual clock. So for example if I close the view at 12:02 and open it at 12:09 I want the image to be at the correct place between the two point (roughly at the end). Instead at the moment the image starts again from the start point. That is happening because I just take the two dates and use the difference as the animation duration.
Anyone that has had similar problem or a suggestion on how I can solve this?
This is connected to a map. I have two points and a poly line between the points. I wanted an image to glide between the points. I could not figure anything easy with the map so I created a view on top of the map that has the image view on. I transfer the points from the map to the other view to get the start and stop points. Maybe there is a better way to do this by using the map?
Cheers,
Jonas
This was easier than I thought... this is my solution
func animate_this_image(start: Date, end: Date, imageView: UIImageView, start_point: CGPoint, end_point: CGPoint) {
// find the total duration and the time left until reaching next point
let duration = end.timeIntervalSince(start)
let time_left = end.timeIntervalSince(Date())
// the percentage is used to calculate how far is left on the line
let percentage = time_left / duration
//calculate the correct point at this time
let x_dist = (end_point.x - start_point.x) * CGFloat(percentage)
let y_dist = (end_point.y - start_point.y) * CGFloat(percentage)
imageView.frame = CGRect(x: end_point.x - x_dist, y: end_point.y - y_dist, width: 60, height: 60).offsetBy(dx: -30, dy: -30)
// define end point
let frame = CGRect(x: end_point.x, y: end_point.y, width: 60, height: 60).offsetBy(dx: -30, dy: -30)
// Then animate it
UIView.animate(withDuration: time_left) {
imageView.frame = frame
}
}

jCanvas method drawRect() position issue

I have a problem with jCanvas which is a framework drawing canvas and jquery.
When I set x = 0 , y = 0 The .drawRect output should be same as the pure javascript result.
I tried the document and google but I did not find anything to solve this.
Screen shoot:
Pure js result
jCanvas result
Please help me.
Many thanks.
// Pure javascript
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext("2d");
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);
// jCanvas
$('canvas').drawRect({
fillStyle: '#000',
x: 0, y: 0,
width: 100,
height: 100
});
To run this code, please copy and pass to this sanbox.
https://projects.calebevans.me/jcanvas/sandbox/
From jCanvas documentation:
The [.drawRect's] fromCenter property determines if a rectangle’s x and y properties lie at its center (as opposed to its top-left corner). This property is true by default.
So yeah. Your X and Y with jCanvas, refer to the center of the rectangle, unlike canvas' fillRect X and Y, which refer to the top left corner of the rectangle.

Xcode SceneKit Making a glowing light box

I have been trying to figure out if there a way to make a "glowing" SCNBox in SceneKit. Unfortunately, I didn't figure it out by myself.
Don't know if the solution is so simple it didn't come into my mind.
Ideas are welcome
Thanks
Make an omni light in the same position as your SCNNode. And set the value of the emission of the SCNNode the same colour as your light.
let box = SCNBox.init(width: 1, height: 1, length: 1, chamferRadius: 0.3)
box.materials.first?.diffuse.contents = UIColor.blue
box.materials.first?.emission.contents = UIColor.white
box.materials.first?.emission.intensity = 1.0
let boxNode = SCNNode.init(geometry: box)
boxNode.position = SCNVector3(x: 0, y: 0, z: -10)
self.sceneView.scene?.rootNode.addChildNode(boxNode)
let omniLight = SCNLight()
omniLight.type = .omni
omniLight.color = UIColor.yellow
boxNode.light = omniLight

How to make two lines in UIBezierPath to animate at different speed

Say the following scenario:
I have drawn a quadrilateral shape, which is a mask for a UIView. I denote the shape layer as maskLayer. maskLayer crops the bottom of the UIView asymmetrically.
But then I want to fully reveal my UIView in an animation. The animation should be left side of maskLayer drops down to the bottom of UIView, and .2 sec later my right side of maskLayer also drops down to the bottom of UIView, thus fully reveal the entity of UIView.
My approach is to drop down left line first, then right one as the following code:
//this quadrilateral will put down left corner to the bottom of screen
var path2 = UIBezierPath()
path2.moveToPoint(CGPointZero)
path2.addLineToPoint(CGPoint(x: 0, y: frame.height))
path2.addLineToPoint(CGPoint(x: frame.width, y: frame.height / goldRatio / goldRatio))
path2.addLineToPoint(CGPoint(x: frame.width, y: 0))
path2.closePath()
//this rectangle path will put down both corner to the bottom of screen
//thus fix the view to its original shape
var path3 = UIBezierPath()
path3.moveToPoint(CGPointZero)
path3.addLineToPoint(CGPoint(x: 0, y: frame.height))
path3.addLineToPoint(CGPoint(x: frame.width, y: frame.height))
path3.addLineToPoint(CGPoint(x: frame.width, y: 0))
path3.closePath()
I have spent 2 hours trying to figure it out to no avail. May you please give me some instructions about how to achieve just that.
The initial state is like the following:
The end state is like the following:
I truly appreciate your help!
Easiest way to do this... Cheat.
Don't try to animate the path of the shape it really doesn't need it.
What you should do is something like this.
Create your final state view. Rectangular, at the bottom of the screen with the UI on it etc...
This final state view will not move. It will always be here.
Now create another view and insert it as a sub view underneath the final state view. On this you can add a shape layer with the angular corner cut off.
Now all you need to do is animate the position of this angular view downward until it is completely below the final state view.
If they are the same colour the this will give the effect of animating the path of the shape.
To get the different speeds you could have a rectangluar shape layer rotated to 45 degrees. Then animate it to 0 degrees as the view slides down?
In fact, you could do this with a single shape layer that is rotated and moved.
To do this sort of animation, you would generally use a CADisplayLink (sort of like a timer, but linked to updates of the display rather than some arbitrary interval) to repeatedly change the path associated with desired shape. I think this is easiest if you use a CAShapeLayer and just change the path property of this shape.
To make this work, you need a function that represents the path at a given point of time (or easier, a path at an instant a certain percentageComplete along the animation duration). Since you have a regular shape (constantly the same number of points), you can simply interpolate between some array of startPoints and endPoints.
So, create the shape layer, capture the start time, start the display link, and then for every "tick" of the display link, calculate what percentage of the total animationDuration has passed, and update the shape layer's path accordingly:
class ViewController: UIViewController {
let animationDuration = 2.0
var displayLink: CADisplayLink!
var startTime: CFAbsoluteTime!
var shapeLayer: CAShapeLayer!
let goldRatio: CGFloat = 1.6180339887
var startPoints:[CGPoint]!
var endPoints:[CGPoint]!
override func viewDidLoad() {
super.viewDidLoad()
self.startAnimation()
}
func startAnimation() {
startPoints = [
CGPoint(x: 0, y: view.bounds.size.height),
CGPoint(x: 0, y: view.bounds.size.height - view.bounds.size.height / goldRatio),
CGPoint(x: view.bounds.size.width, y: 0),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
]
endPoints = [
CGPoint(x: 0, y: view.bounds.size.height),
CGPoint(x: 0, y: view.bounds.size.height * 0.75),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height * 0.75),
CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
]
assert(startPoints.count == endPoints.count, "Point counts don't match")
createShape()
startDisplayLink()
}
func startDisplayLink() {
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
startTime = CFAbsoluteTimeGetCurrent()
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
func stopDisplayLink() {
displayLink.invalidate()
displayLink = nil
}
func handleDisplayLink(displayLink: CADisplayLink) {
var percent = CGFloat((CFAbsoluteTimeGetCurrent() - startTime) / animationDuration)
if percent >= 1.0 {
percent = 1.0
stopDisplayLink()
}
updatePathBasedUponPercentComplete(percent)
}
func createShape() {
shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.blueColor().CGColor
shapeLayer.strokeColor = UIColor.clearColor().CGColor
updatePathBasedUponPercentComplete(0.0)
view.layer.addSublayer(shapeLayer)
}
func updatePathBasedUponPercentComplete(percentComplete: CGFloat) {
shapeLayer.path = pathBasedUponPercentComplete(percentComplete, frame: view.frame).CGPath
}
func pathBasedUponPercentComplete(percentComplete: CGFloat, frame: CGRect) -> UIBezierPath {
var path = UIBezierPath()
for i in 0 ..< startPoints.count {
let point = CGPoint(
x: startPoints[i].x + (endPoints[i].x - startPoints[i].x) * percentComplete,
y: startPoints[i].y + (endPoints[i].y - startPoints[i].y) * percentComplete
)
if i == 0 {
path.moveToPoint(point)
} else {
path.addLineToPoint(point)
}
}
path.closePath()
return path
}
}

using cache in a complex structure

I'm using easeljs to build a certain structure.
Inside that structure, there are many containers and shapes.
I ran across a problem where I needed to change the color of a certain element when the user hovered it with his mouse. I managed to do it However there is a considerable delay until the color is drawn and return to its original color because the stage redraws itself.
I saw that I could use the cache for this purpose so I follow the example in the docs like this:
myShape.cache(150, 150, 100, 100, 1); however nothings happens and I don't see the shape.
I have to say that the shape resides inside a container which is added to the stage.
Here's the relevant code:
enter code here
var g = curShape.graphics.clone().clear();
g.beginFill("#2aa4eb");
g.drawRoundRect(0, 0, curShape.width, curShape.height, 1.5);
//g.drawRect(0, 0, curShape.width + 2, curShape.height + 2);
g.endFill();
g.endStroke();
var newShape= new createjs.Shape(g);
newShape.cache(150, 150, 100, 100, 2);
Any help would be appreciated
You are caching at x:150 and y:150, but you are drawing your shapes at 0,0. If your shape is smaller than 150x150, then it will be caching nothing. Change your cache to 0,0, and it should be fine.
Additionally, you are not providing the 5th parameter (corner radius) to the drawRoundRect call, which will make it fail. Here is a quick sample with a modified version of your code.
http://jsfiddle.net/LNXVg/
var stage = new createjs.Stage("canvas");
var g = new createjs.Graphics();
g.beginFill("#2aa4eb");
g.drawRoundRect(0, 0, 300, 200, 5);
var newShape = new createjs.Shape(g);
//newShape.cache(150, 150, 100, 100, 2);
newShape.cache(0, 0, 100, 100, 2);
stage.addChild(newShape);
stage.update();

Resources