How to give corner radius in one side to Imageview in swift? - 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)

Related

Create ellipse shape ImageView with straight edges

Im trying to create a custom shaped ImageView that looks like this.
My initial though was to round corners using the following extensions like so:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
extension UIBezierPath {
static func rectWithRoundCorners(
size: CGSize,
topLeft: CGFloat = 0,
bottomLeft: CGFloat = 0,
bottomRight: CGFloat = 0,
topRight: CGFloat = 0)
-> UIBezierPath
{
let path = UIBezierPath()
path.move(to: CGPoint(x: size.width - topRight, y: 0))
path.addLine(to: CGPoint(x: topLeft, y: 0))
path.addQuadCurve(to: CGPoint(x: 0, y: topLeft), controlPoint: .zero)
path.addLine(to: CGPoint(x: 0, y: size.height - bottomLeft))
path.addQuadCurve(to: CGPoint(x: bottomLeft, y: size.height), controlPoint: CGPoint(x: 0, y: size.height))
path.addLine(to: CGPoint(x: size.width - bottomRight, y: size.height))
path.addQuadCurve(
to: CGPoint(x: size.width, y: size.height - bottomRight),
controlPoint: CGPoint(x: size.width, y: size.height))
path.addLine(to: CGPoint(x: size.width, y: topRight))
path.addQuadCurve(to: CGPoint(x: size.width - topRight, y: 0), controlPoint: CGPoint(x: size.width, y: 0))
return path
}
}
extension UIView {
func roundCorners(topLeft: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0, topRight: CGFloat = 0) {
let path = UIBezierPath.rectWithRoundCorners(
size: bounds.size,
topLeft: topLeft,
bottomLeft: bottomLeft,
bottomRight: bottomRight,
topRight: topRight)
let shape = CAShapeLayer()
shape.path = path.cgPath
layer.mask = shape
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 114, height: 114)
label.backgroundColor = .red
label.text = "Hello World!"
label.textColor = .black
label.roundCorners(topLeft: 100, bottomLeft: 50, bottomRight: 0, topRight: 50)
view.addSubview(label)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
but I get the following result:
Is there a better approach using rounded rect and UIBezierPath? I have no experience using UIBezierPath so not sure how it will do round corners and straight edges...
Well, your shape only has two straight edges, so there should be two addLine calls (or, as below, one addLine and one close).
There might be more mathematically rigorous solutions, but I use tools like Adobe Photoshop or Illustrator to approximate what Bézier curves I need.
The overall shape (bounded by 117×103 rectangle) can be reasonably well approximated with one big cubic Bézier (shown below), one addLine for the bottom edge, one quad Bézier (or arc) for the rounded, lower-right corner, and then just close the path.
To get those control points, I used a pen tool in Adobe Illustrator to create a cubic bezier of the appropriate shape, and I then exported the path as a SVG, and deciphered the file to come up with the appropriate coordinates for the various control points:
let rect = CGRect(origin: .zero, size: CGSize(width: 117, height: 103))
let radius: CGFloat = 10
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.maxX, y: 27.62))
path.addCurve(to: CGPoint(x: 11.77, y: rect.maxY),
controlPoint1: CGPoint(x: 77, y: -46.21),
controlPoint2: CGPoint(x: -36.11, y: 44.91))
path.addLine(to: CGPoint(x: rect.maxX - radius, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.maxY - radius),
controlPoint: CGPoint(x: rect.maxX, y: rect.maxY))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.path = path.cgPath
someView.layer.addSublayer(shapeLayer)
Tweak the coordinates as you see fit, but hopefully this illustrates (no pun intended) how to approximate the Bézier curves in a drawing and translate that to Swift code.
This is the resulting image:

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)

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

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.

How to cut of corners of UIButton, not round them?

I would like to cut the corners off of a UIButton in swift.
I know how to set a corner radius but I'd like to cut the corners.
// what I DON'T want, rounded corners
myButton.layer.cornerRadius = myButton.frame.height / 2
Cut off corners are for me:
Create a CAShapeLayer with the desired path like this
let button = UIButton()
button.backgroundColor = .red
button.frame = CGRect(x: 100, y: 100, width: 200, height: 40)
view.addSubview(button)
let layer = CAShapeLayer()
layer.fillColor = UIColor.yellow.cgColor
let cutSize:CGFloat = 20
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: button.bounds.midY))
path.addLine(to: CGPoint(x: cutSize, y: 0))
path.addLine(to: CGPoint(x: button.bounds.maxX-cutSize, y: 0))
path.addLine(to: CGPoint(x: button.bounds.maxX, y: button.bounds.midY))
path.addLine(to: CGPoint(x: button.bounds.maxX-cutSize, y: button.bounds.maxY))
path.addLine(to: CGPoint(x: cutSize, y: button.bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: button.bounds.midY))
layer.path = path.cgPath
button.layer.insertSublayer(layer, at: 0)
You can achieve by following:
sampleButton.layer.cornerRadius = 15
sampleButton.clipsToBounds = true