Multiple Drop Shadows on Single View iOS - ios

I'm looking to add multiple drop shadows with different opacities to a view. The specs for the shadows are as follows:
Y-offset of 4 with blur radius of 1
Y-offset of 10 with blur radius of 10
Y-offset of 2 with blur radius of 4
Blur radius of 1, spread of 1 (no offsets, will probably have to be 4 different shadows)
I can get all this working just fine using CALayers. Here's the code I have working for that (please note that I haven't bothered to set shadowPath yet, and won't until I get the multiple shadows thing working):
layer.cornerRadius = 4
layer.masksToBounds = false
layer.shouldRasterize = true
let layer2 = CALayer(layer: layer), layer3 = CALayer(layer: layer), layer4 = CALayer(layer: layer)
layer.shadowOffset = CGSizeMake(0, 4)
layer.shadowRadius = 1
layer2.shadowOffset = CGSizeMake(0, 10)
layer2.shadowRadius = 10
layer2.shadowColor = UIColor.blackColor().CGColor
layer2.shouldRasterize = true //Evidently not copied during initialization from self.layer
layer3.shadowOffset = CGSizeMake(0, 2)
layer3.shadowRadius = 4
layer3.shouldRasterize = true
layer4.shadowOffset = CGSizeMake(0, 1)
layer4.shadowRadius = 1
layer4.shadowOpacity = 0.1
layer4.shouldRasterize = true
layer.addSublayer(layer2)
layer.addSublayer(layer3)
layer.addSublayer(layer4)
(While this code is in Swift, I trust that it looks familiar enough to most Cocoa/Objective-C developers for it to make sense. Just know that layer is equivalent to self.layer in this context.)
The problem, however, arises when I attempt to use different opacities for each shadow. The shadowOpacity property of layer ends up being applied to all of its sublayers. This is a problem, as I need all of them to have their own shadow opacity. I have tried setting each layer's shadow opacity to its correct value (0.04, 0.12, etc.), but then the opacity of 0.04 of layer is applied to all sublayers. So I tried to set layer.shadowOpacity to 1.0, but this made all the shadows solid black. I also tried to be clever and do layer2.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.12).CGColor, but it was just changed to full black with no transparency.
I suppose it makes some sort of sense that the layers should all have the same shadow opacity. But what's a way to get this working, varying opacities and all (doesn't have to utilize CALayer if it's easier another way)?
Please don't answer with "just use an image": no matter how sane that may be, I'm trying to avoid it. Just humor me.
Thanks.
EDIT: As per request, here's what I'm after: .

The key thing that needs to be added is setting the layers' shadowPath. By default, Core Graphics draws a shadow around the layer's visible content, but in your code neither backgroundColor nor bounds are set for the layers, so the layers are actually empty.
Assuming you have a UIView subclass, you can make it work by adding something like this:
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.forEach { (sublayer) in
sublayer.shadowPath = UIBezierPath(rect: bounds).cgPath
}
}
I tested this approach on a view with multiple shadows and it worked as expected, as soon as the shadowPath is defined for the shadow layers. Different shadow colors and opacities worked as well, but you have to keep in mind that upper layers in the hierarchy will overlap the layers behind them, so if the front layer has a thick shadow, the other shadows can get hidden by it.

What about adding the alpha to the shadow color instead of the layer shadow opacity?
i.e. instead of
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.5
do
layer.shadowColor = UIColor.black.withAlphaComponent(0.5).cgColor
for each layer.

Related

Optimization Opportunities: UISwitch mask - custom offTintColor

While debugging the View Hierarchy, I came across this optimization warning about a UISwitch control with a mask view. Though I have no clear picture what would be the benefits of addressing this warning (insights please), I tried to fix it with no success.
Optimization Opportunities: The layer is using a simple layer with background color set as a mask. Instead, use a container layer of the same frame and cornerRadius as the mask, but with masksToBounds set to YES.
let maskedSwitch: UISwitch = UISwitch()
maskedSwitch.translatesAutoresizingMaskIntoConstraints = false
maskedSwitch.tintColor = .black
maskedSwitch.onTintColor = .green
maskedSwitch.backgroundColor = .black
let maskView = UIView(frame: maskedSwitch.frame)
maskView.backgroundColor = .black
maskView.layer.cornerRadius = maskedSwitch.frame.height / 2
maskView.layer.masksToBounds = true
maskView.clipsToBounds = true
maskedSwitch.mask = maskView
let scale: CGFloat = 2.5 / 3
maskedSwitch.transform = CGAffineTransform(scaleX: scale, y: scale)
As suggested, I tried removing the maskView and directly applying the cornerRadius and offTintColor on switch. But it ended up messing the scale transformation, and it is kind of important to retain the UI.
How can I further optimise the code and make sure all these conditions addressed: 1. set offTintColor to black, 2. scale the switch and 3. Avoid the optimization opportunity warning?
Note: Please do share if there is an Apple doc to study more on the topic Optimization Opportunities. Thanks!

Can I make shadow that can look through transparent object with scenekit and arkit?

I made transparent object with scenekit and linked with arkit.
I made a shadow with lightning material but can't see the shadow look through the transparent object.
I made a plane and placed the object on it.
And give the light to a transparent object.
the shadow appears behind the object but can not see through the object.
Here's code that making the shadow.
let light = SCNLight()
light.type = .directional
light.castsShadow = true
light.shadowRadius = 200
light.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
light.shadowMode = .deferred
let constraint = SCNLookAtConstraint(target: model)
lightNode = SCNNode()
lightNode!.light = light
lightNode!.position = SCNVector3(model.position.x + 10, model.position.y + 30, model.position.z+30)
lightNode!.eulerAngles = SCNVector3(45.0, 0, 0)
lightNode!.constraints = [constraint]
sceneView.scene.rootNode.addChildNode(lightNode!)
And the below code is for making a floor under the bottle.
let floor = SCNFloor()
floor.reflectivity = 0
let material = SCNMaterial()
material.diffuse.contents = UIColor.white
material.colorBufferWriteMask = SCNColorMask(rawValue:0)
floor.materials = [material]
self.floorNode = SCNNode(geometry: floor)
self.floorNode!.position = SCNVector3(x, y, z)
self.sceneView.scene.rootNode.addChildNode(self.floorNode!)
I think it can be solved with simple property but I can't figure out.
How can I solve the problem?
A known issue with deferred shading is that it doesn’t work with transparency so you may have to remove that line and use the default forward shading again. That said, the “simple property” you are looking for is the .renderingOrder property on the SCNNode. Set it to 99 for example. Normally the rendering order doesn’t matter because the z buffer is used to determine what pixel is in front of others. For the shadow to show up through the transparant part of the object you need to make sure the object is rendered last.
On a different note, assuming you used some of the material settings I posted on your other question, try setting the shininess value to something like 0.4.
Note that this will still create a shadow as if the object was not transparent at all, so it won’t create a darker shadow for the label and cap. For additional realism you could opt to fake the shadow entirely, as in using a texture for the shadow and drop that on a plane which you rotate and skew as needed. For even more realism, you could fake the caustics that way too.
You may also want to add a reflection map to the reflective property of the material. Almost the same as texture map but in gray scale, where the label and cap are dark gray (not very reflective) and a lighter gray for the glass portion (else it will look like the label is on the inside of the glass). Last tip: use a Shell modifier (that’s what it’s called in 3Ds max anyway) to give the glass model some thickness.

Remove line drawn on view

I am new to CoreGraphics . I am trying to create view which contains two UIImageview added in scrollview programatically. After adding it i want to connect both center with line. I have used bezier path as well as CAShapelayer. But line is drawn on UIImageview also so i want to remove line above UIImageview or send line to back to UIImageview. I have done below code.
let path: UIBezierPath = UIBezierPath()
path.moveToPoint(CGPointMake(personalProfile.center.x, personalProfile.center.y))
path.addLineToPoint(CGPointMake(vwTwo.center.x, vwTwo.center.y))
let shapeLayer: CAShapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = UIColor.blueColor().CGColor
shapeLayer.lineWidth = 3.0
shapeLayer.fillColor = UIColor.clearColor().CGColor
self.scrollView.layer.addSublayer(shapeLayer)
Please also check screenshot, i want to remove red marked portion of blue line .
You can do this simply by reducing the zPosition of your shapeLayer
This will allow the layer to be drawn underneath your two views (and far easier than trying to calculate a new start and end point of your line). If you look at the documentation for zPosition:
The default value of this property is 0. Changing the value of this property changes the the front-to-back ordering of layers onscreen. Higher values place this layer visually closer to the viewer than layers with lower values. This can affect the visibility of layers whose frame rectangles overlap.
Therefore, as it defaults to 0, and UIViews are just wrappers for CALayers, you can use a value of -1 on your shapeLayer in order to have it drawn behind your other views.
For example:
shapeLayer.zPosition = -1
Side Note
Most of the time in Swift, you don't need to explicitly supply a type when defining a variable. You can just let Swift infer it. For example:
let path = UIBezierPath()
I would see 2 options, an easy and a harder option.
Move the UIImageView to the front after drawing the line, effectively hiding the line behind the UIImageView.
Calculate the points at which you want the line to start and end and draw a line from these points instead of the centers.

Subtract UIView from another UIView in Swift

I'm sure this is a very simple thing to do, but I can't seem to wrap my head around the logic.
I have two UIViews. One black, semi-transparent and "full-screen" ("overlayView"), another one on top, smaller and resizeable ("cropView"). It's pretty much a crop-view setup, where I want to "dim" out the areas of an underlying image that are not being cropped.
My question is: How do I go about this? I'm sure my approach should be with CALayers and masks, but no matter what I try, I can't get behind the logic.
This is what I have right now:
This is what I would want it to look like:
How do I achieve this result in Swift?
Although you won't find a method such as subtract(...), you can easily build a screen with an overlay and a transparent cut with the following code:
Swift 4.2
private func addOverlayView() {
let overlayView = UIView(frame: self.bounds)
let targetMaskLayer = CAShapeLayer()
let squareSide = frame.width / 1.6
let squareSize = CGSize(width: squareSide, height: squareSide)
let squareOrigin = CGPoint(x: CGFloat(center.x) - (squareSide / 2),
y: CGFloat(center.y) - (squareSide / 2))
let square = UIBezierPath(roundedRect: CGRect(origin: squareOrigin, size: squareSize), cornerRadius: 16)
let path = UIBezierPath(rect: self.bounds)
path.append(square)
targetMaskLayer.path = path.cgPath
// Exclude intersected paths
targetMaskLayer.fillRule = CAShapeLayerFillRule.evenOdd
overlayView.layer.mask = targetMaskLayer
overlayView.clipsToBounds = true
overlayView.alpha = 0.6
overlayView.backgroundColor = UIColor.black
addSubview(overlayView)
}
Just call this method inside your custom view's constructor or inside your ViewController's viewDidLoad().
Walkthrough
First I create a raw overlayView, then a CAShapeLayer which I called "targetMaskLayer". The ultimate goal is to draw a square with the help of UIBezierPath inside that overlayView. After defining the square's dimensions, I set its cgPath as the targetMaskLayer's path.
Now comes an important part:
targetMaskLayer.fillRule = CAShapeLayerFillRule.evenOdd
Here I basically configure the fill rule to exclude the intersection.
Finally, I provide some styling to the overlayView and add it as a subview.
ps.: don't forget to import UIKit
There might be another drawing solution but basically you have 4 areas that need to be handled. Take the square area above and below the space with full width and add the right and left side between them with constraints to eachother.

CATransform3D breaks layer order?

Basically I want to build the scrollView like the one in iOS7 safari tab switcher. I use CATransform3D to get the feeling of oblique. However when I employ transform the layers just don't display in proper order.(They are in correct order before transform, seems like a total reversal in order.Back layer jumps to the front). How can I fix this thorny problem?
By the way in my case superView.bringSubViewToFront doesn't work.
My code:
//Create imageView
...
scroller.addSubview(imageView)
let layer = imageView.layer
//Set shadow
layer.shadowOpacity = 0.2
layer.shadowRadius = 2
layer.shadowOffset = CGSizeMake(6, -10)
//Transform, if without following code the layer order is correct
var transform = CATransform3DIdentity
transform.m34 = 0.0009
let radiants = 0.11*M_PI
transform = CATransform3DRotate(transform, CGFloat(radiants), 1.0, 0.0, 0.0)
scroller.bringSubviewToFront(imageView)//It doesn't work.
I just found that if you set the z axis to 1.0 in CATransform3DTranslate the layer become in front of other layers, so just make it 0.

Resources