How do I cast two shadows from an UIImageView with a SF Symbol as the image? - swift

I want to cast a shadow to the lower left AND the lower right of my Image. But I'm struggling to get the second shadow cast (layer2)...
I've created a subclass of UIImage view and use that in IB:
import UIKit
class ShadowImageView: UIImageView {
var layer2 = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
self.layer2 = CALayer(layer: self.layer)
self.clipsToBounds = false
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: -16, height: 16)
self.layer.shadowOpacity = 0.33
self.layer.shadowRadius = 3
self.layer.masksToBounds = false
self.layer2.shadowColor = UIColor.gray.cgColor
self.layer2.shadowOffset = CGSize(width: 16, height: 16)
self.layer2.shadowOpacity = 0.33
self.layer2.shadowRadius = 3
self.layer2.masksToBounds = false
self.layer.insertSublayer(self.layer2, at: 0)
print("\(self.layer)\n")
if let sublayers = self.layer.sublayers {
for layer in sublayers {
print("\(layer)\n")
}
}
}
}
Am I going in the right direction to copy the layer and apply a new shadow to it or should I look in a different direction?

Fixed the shadow problem. I was able to generate double shadows by adding 3 layers on my UIView. The furthest behind (the first sublayer of the view's layer) is the darker shadow, the one on top of that is the lighter shadow and on top of that I have one with the normal color.
I'm 'drawing' the images on the layers by applying a shadow path & color.
The image I'm drawing is coming from a method like listed below, but it's not being generated from a SFSymbols. I would like to generate it during runtime so I end up with a simple method of using symbols to place Neumorphic elements in our apps. The manual generation of these BezierPaths isn't really pleasant.
static var shieldFill: UIBezierPath = {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 58.94, y: 115.53))
bezierPath.addCurve(to: CGPoint(x: 62.84, y: 114.4), controlPoint1: CGPoint(x: 60.06, y: 115.53), controlPoint2: CGPoint(x: 61.57, y: 115.04))
bezierPath.addCurve(to: CGPoint(x: 101.76, y: 74.46), controlPoint1: CGPoint(x: 91.5, y: 100.49), controlPoint2: CGPoint(x: 101.76, y: 91.89))
bezierPath.addLine(to: CGPoint(x: 101.76, y: 39.94))
bezierPath.addCurve(to: CGPoint(x: 92.04, y: 25.05), controlPoint1: CGPoint(x: 101.76, y: 31.84), controlPoint2: CGPoint(x: 99.41, y: 28.12))
bezierPath.addCurve(to: CGPoint(x: 67.63, y: 16.85), controlPoint1: CGPoint(x: 87.89, y: 23.29), controlPoint2: CGPoint(x: 71.29, y: 17.97))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 15.53), controlPoint1: CGPoint(x: 64.94, y: 16.11), controlPoint2: CGPoint(x: 61.52, y: 15.53))
bezierPath.addCurve(to: CGPoint(x: 50.24, y: 16.85), controlPoint1: CGPoint(x: 56.35, y: 15.53), controlPoint2: CGPoint(x: 52.93, y: 16.11))
bezierPath.addCurve(to: CGPoint(x: 25.83, y: 25.05), controlPoint1: CGPoint(x: 46.53, y: 17.97), controlPoint2: CGPoint(x: 29.93, y: 23.29))
bezierPath.addCurve(to: CGPoint(x: 16.11, y: 39.94), controlPoint1: CGPoint(x: 18.46, y: 28.12), controlPoint2: CGPoint(x: 16.11, y: 31.84))
bezierPath.addLine(to: CGPoint(x: 16.11, y: 74.46))
bezierPath.addCurve(to: CGPoint(x: 55.03, y: 114.4), controlPoint1: CGPoint(x: 16.11, y: 91.89), controlPoint2: CGPoint(x: 26.46, y: 100.29))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 115.53), controlPoint1: CGPoint(x: 56.3, y: 115.04), controlPoint2: CGPoint(x: 57.76, y: 115.53))
bezierPath.close()
return bezierPath
}()

I was able to get this very easily:
That seems to be the sort of thing you're after.
The double shadow stems from the fact that in a graphics context, shadow drawing is a state applied to any future drawing. So if you set the context's shadow to be down-and-to-the-left and draw the symbol image, and then set the context's shadow to be down-and-to-the-right and draw the symbol image, you get the double shadow.
It is true that the symbol image itself is drawn twice but that does not necessarily change the way it appears; if I hadn't told you I'd drawn it twice, I don't think you'd have known.

Related

How to give corner radius in one side to Imageview in swift?

How can I give corner radius in one side to Imageview like zig zag?
like in image
In this image I want like a large image witch is highlighted in Blue line.
If anyone know about this Please help me.
And I apologize if I could not explane my problem properly.
Solve the problem using bezier path
Study the bezier path principle and find the appropriate control point
Here is the sample code
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height/3))
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
path.addLine(to: CGPoint(x: self.view.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.view.frame.width, y: self.view.frame.size.height/3))
path.addCurve(to: CGPoint(x: 0, y: self.view.frame.size.height/3 - 50),
controlPoint1: CGPoint(x: 200, y: self.view.frame.size.height/3 - 20),
controlPoint2: CGPoint(x: self.view.frame.width/2, y: self.view.frame.size.height/3 - 100))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
imageView.layer.mask = shapeLayer
imageView.image = UIImage(named: "spider.jpg")
self.view.addSubview(imageView)

Need to create custom shape for UIView Swift

I need to create this custom UIView for my project.I need to achieve this
I was doing trial and error by creating, My own custom UIView and then drawing on it.
I'm working on this
My Question is, if I'm able to draw my custom shape, How will I clip-off the remaining white space of the UIView
func drawCustomShape() {
let viewSize = self.frame.size
let buttonSlotRadius = CGFloat(30)
let effectiveViewHeight = viewSize.height - buttonSlotRadius
let path = UIBezierPath()
path.move(to: CGPoint(x: 30, y: 0))
path.addLine(to: CGPoint(x: viewSize.width - 30, y: 0))
path.addArc(withCenter: CGPoint(x: viewSize.width - 30,
y: 30),
radius: 30,
startAngle: .pi * 3 / 2,
endAngle: 0,
clockwise: true)
path.addLine(to: CGPoint(x: viewSize.width, y: effectiveViewHeight))
path.addArc(withCenter: CGPoint(x: viewSize.width - 30,
y: effectiveViewHeight - 30),
radius: 30,
startAngle: 0,
endAngle: .pi / 2,
clockwise: true)
path.addLine(to: CGPoint(x: viewSize.width / 4, y: effectiveViewHeight))
// close path join to origin
path.close()
UIColor.secondarySystemFill.setFill()
path.fill()
path.lineWidth = 6.0
path.stroke()
}
You can use UIView with clear background color, and use CAShapeLayer with desired path like this:
extension UIView {
func drawCustomShape() {
let cornerRadius: CGFloat = 18.0
let shapeOffset = self.frame.size.height * 0.2
//create shape layer
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.lineWidth = 1.0
shapeLayer.fillColor = UIColor.white.cgColor
self.layer.addSublayer(shapeLayer)
//create path
let path = UIBezierPath()
//top left point
path.move(to: CGPoint(x: 0, y: cornerRadius))
//top left corner
path.addQuadCurve(to: CGPoint(x: cornerRadius, y: 0),
controlPoint: CGPoint(x: 0, y: 0))
//top right point
path.addLine(to: CGPoint(x: self.frame.size.width - cornerRadius, y: 0))
//top right corner
path.addQuadCurve(to: CGPoint(x: self.frame.size.width, y: cornerRadius),
controlPoint: CGPoint(x: self.frame.size.width, y: 0))
//bottom right point
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height - shapeOffset - cornerRadius))
//bottom right corner
path.addQuadCurve(to: CGPoint(x: self.frame.size.width - cornerRadius, y: self.frame.size.height - shapeOffset),
controlPoint: CGPoint(x: self.frame.size.width, y: self.frame.size.height - shapeOffset))
//bottom left
path.addLine(to: CGPoint(x: cornerRadius, y: self.frame.size.height))
//bottom left corner
path.addQuadCurve(to: CGPoint(x: 0, y: self.frame.size.height - cornerRadius),
controlPoint: CGPoint(x: 0, y: self.frame.size.height))
path.close()
shapeLayer.path = path.cgPath
}
Usage:
myView.drawCustomShape()
Result:

Xcode Swift, Keyframe animation has unwanted delay between animations loops on a bezier curve

Im attempting to animate a UIView along an oval using a bezier curve. The code works except that there is a delay between each loop of the animation. I'm at a loss as to what is causing this.
let myView = UIView()
myView.backgroundColor = UIColor.blue
myView.frame = CGRect(x: 0, y:0, width: 30, height: 30)
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.repeatCount = 1000
animation.duration = 1
myView.layer.add(animation, forKey: "animate along path")
Found a solution. The pause between animation loops only takes place when making a path with the UIBezierPath(ovalIn:) constructor. There's no delay when manually making your own path. So I made my own oval.
Replaced this
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
with this
let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.maxY), controlPoint: CGPoint(x: rect.maxX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.midY), controlPoint: CGPoint(x: rect.minX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY), controlPoint: CGPoint(x: rect.minX, y: rect.minY))
path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.midY), controlPoint: CGPoint(x: rect.maxX, y: rect.minY))

How to make a CAshapeLayer fit into a button?

I tried to create a function inside a custom UIButton class to add a shape to an existing button.
func drawStartButton(){
let shape = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 500, y: 0))
path.addLine(to: CGPoint(x: 500, y: -100))
path.addLine(to: CGPoint(x: 250, y: -50))
path.addLine(to: CGPoint(x: 0, y: -100))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
shape.path = path.cgPath
shape.fillColor = redColor.cgColor
shape.frame = self.bounds
self.layer.addSublayer(shape)
}
So far no problem... BUT when i add the layer to the button, the layer is to big of course! How can i "autoresize" the layer to its button? I did expect something like
shape.frame = self.bounds
... but the path still keeps the same size as without the self.bounds.
Your best bet is to subclass the button and then layout your layer in layoutSubviews which gets called when your surrounding frame changes.
final class StartButton: UIButton {
private let shapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.frame = bounds
}
}
But I'm noticing that you are dictating a giant shape in terms of your CGPoints. You might have to subclass the layer, too, and redraw based on your bounds in layoutSublayers.
class CustomShapeCAshapeLayer: CAShapeLayer {
func shapeButton(width: CGFloat, height: CGFloat){
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: width, y: 0))
path.addLine(to: CGPoint(x: width, y: height + 30))
path.addLine(to: CGPoint(x: width/2, y: height))
path.addLine(to: CGPoint(x: 0, y: height + 30))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
self.fillColor = redColor.cgColor
self.path = path.cgPath
}
}
main:
let layer = CustomShapeCAshapeLayer()
layer.shapeButton(width: startGameButton.frame.width, height: startGameButton.frame.height)
startGameButton.layer.addSublayer(layer)

SKShapeNode Strokes show gaps when drawn with CGPath

I am trying to draw a shape using CGPath and SKShapeNode. However, whatever the lineJoin or lineCap combinations I try to use, the lines have gaps.
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
super.didMove(to: view)
let path = CGMutablePath()
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
path.addCurve(to: NSPoint(x: 66.585, y: -20.3150000000001), control1: NSPoint(x: -60.585, y: -41.765), control2: NSPoint(x: -3.39500000000001, y: -1.88499999999999))
path.addCurve(to: NSPoint(x: 56.545, y: 18.235), control1: NSPoint(x: 66.125, y: -7.09500000000003), control2: NSPoint(x: 62.695, y: 6.16499999999996))
path.addCurve(to: NSPoint(x: -66.585, y: 1.125), control1: NSPoint(x: 56.055, y: 19.1849999999999), control2: NSPoint(x: -15.415, y: 41.765))
path.closeSubpath()
let shapeNode = SKShapeNode(path: path)
shapeNode.fillColor = .red
shapeNode.lineWidth = 10
shapeNode.lineJoin = .round
shapeNode.strokeColor = .white
addChild(shapeNode)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
What am I doing wrong here?
This looks odd to me:
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
You move to (-66.58., 1.125). Then you add a curve where the first point is (-60.585, -41.765), and the first control point is the same as the original point you moved to. It seems like that will cause some odd issues. I'd think you'd want to have the point you move to be the first point of the curve, not its control point.