i'm trying to learn how to draw shapes and animate it, i succeeded at both and i managed to draw shapes in the center of my screen and in alignment to each other, but when i change my simulator to any device other than my view as device they jump out of the center of the screen, is there any way to set constraints to my drawn shapes so that they are always in the center?
here is my code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// rect
let layer = CAShapeLayer()
let bounds = CGRect(x: 60, y: 200, width: 250, height: 250)
layer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 20, height: 20)).cgPath
layer.strokeColor = UIColor.white.cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineWidth = 4
view.layer.addSublayer(layer)
let rectAnimation = CABasicAnimation(keyPath: "strokeEnd")
rectAnimation.fromValue = 0
rectAnimation.toValue = 1
rectAnimation.duration = 2
layer.add(rectAnimation, forKey: "line")
}
A powerful tool to give constraint is Masonry, you will really appreciate it. It's super easy for setting constraints and for animating them;
Else you can set your x and y "bounds" coordinates to be at centerX and centerY too
Related
I have a tab bar that I'm trying to customize it's look. So far, I changed it's background color and added corner radius. I'm trying to add a shadow to it, but I can't get it to work.
I tried few solutions, but nothing worked for me. There is not errors or anything, but it just won't work. This is what I have so far:
private func setupTabBarAppearance() {
let CORNER_RADIUS: CGFloat = 20
let tabBar = self.tabBar
tabBar.barTintColor = UIColor.appWhite
tabBar.unselectedItemTintColor = .lightGray
tabBar.tintColor = UIColor.appBlue
tabBar.layer.masksToBounds = true
tabBar.layer.cornerRadius = CORNER_RADIUS
tabBar.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
tabBar.layer.shouldRasterize = true
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: tabBar.frame.height), byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: CORNER_RADIUS, height: CORNER_RADIUS)).cgPath
shapeLayer.path = path
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowOffset = CGSize(width: 2, height: 5)
shapeLayer.shadowOpacity = 1
shapeLayer.shadowRadius = 10
shapeLayer.shouldRasterize = true
shapeLayer.rasterizationScale = UIScreen.main.scale
tabBar.layer.rasterizationScale = UIScreen.main.scale
tabBar.layer.addSublayer(shapeLayer)
}
I have two problems with this that I don't find a solution to, which is:
I can't get the shapeLayer to be behind tabBar, it just covers it.
The shadow from shapeLayer is facing downwards so even if I would be able to get the shapeLayer to be behind the tab bar, the shadow still won't be displayed.
Let’s just tackle this part:
tabBar.layer.addSublayer(shapeLayer)
Obviously if you add a shape layer to the tab bar’s own layer, the shape layer must be in front. That is what it means to be a sublayer.
If you want to cast a shadow using a custom shape, you will need a completely separate view hidden behind your tab bar.
I am trying to make a design which have top left corner radius and bottom right corner radius with shadow. I am able to make shadow also corner radius around the view.But when i am trying to give corner radius only to side shadow or radius is not showing. Hare what i have done
extension UIview {
//corner radius function
func roundCornersForView(corners:UIRectCorner, radius: CGFloat) {
DispatchQueue.main.async {
let path = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
let maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}
}
func setShadow(){
self.layer.masksToBounds = false
// set the shadow properties
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 1.0)
self.layer.shadowOpacity = 0.2
self.layer.shadowRadius = 4.0
self.roundCornersForView(corners: [.topLeft, .bottomRight], radius: 20)
}
}
/// finally call it table cell
cell?.BackView.setShadow()
here is the design what i want to do
1) Why write a separate function to draw rect and then apply cornerRadius mask to your view? Replace the function's contents with:
yourView.layer.masksToBounds = true
yourView.layer.cornerRadius = 20
yourView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
2) Once you mask your view's corners and set its masksToBounds property to true, you can set shadow to the view's layer and it will automatically wrap around the rounded corners as you desire:-
yourView.layer.shadowOffset = CGSize(width: 0, height: -0.5)
yourView.layer.shadowRadius = 1
yourView.layer.shadowColor = UIColor(red:0.01, green:0.05, blue:0.09, alpha:0.18).cgColor
yourView.layer.shadowOpacity = 0.8
3) It's advised to play with your cell's UI attributes(a subview's cornerRadius and shadow in your case) in the cell class's awakeFromNib() or an init() method rather than in your delegate methods as they get called a lot of times and hence break the whole point of reusability
Have tested the code to apply shadow and it works fine for me. Hope this helps!
I wanted to recreate the AppStore's "today" cards with rounded corners and a light drop shadow.
I created a path, a maskLayer and a separate shadowLayer, which – according to several sources – is the way of doing it.
The problem, however, is that my lovely rounded rectangle with a shadow has got some gray strokes at it corners. How can I solve this? I tried different shadow opacities and different radii. It didn't solve my problem.
Here you can see my screenshots and my code below.
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
// create sample view and add to view hierarchy
let bigTeaser = UIView(frame: CGRect(x: 16, y: 200, width: 343, height: 267))
bigTeaser.backgroundColor = UIColor.white
view.addSubview(bigTeaser)
// create the path for the rounded corners and the shadow
let roundPath = UIBezierPath(roundedRect: bigTeaser.bounds, cornerRadius: 20)
// create maskLayer
let maskLayer = CAShapeLayer()
maskLayer.frame = bigTeaser.bounds
maskLayer.path = roundPath.cgPath
bigTeaser.layer.mask = maskLayer
// create shadowLayer
let shadowLayer = CAShapeLayer()
shadowLayer.path = roundPath.cgPath
shadowLayer.frame = bigTeaser.frame
shadowLayer.shadowOpacity = 0.3
shadowLayer.shadowRadius = 24
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOffset = CGSize(width: 0, height: 2)
// insert layers
bigTeaser.superview!.layer.insertSublayer(shadowLayer, below: bigTeaser.layer)
}
If you replace:
shadowLayer.path = roundPath.cgPath
to
shadowLayer.shadowPath = roundPath.cgPath
The ugly borders will magically disappear.
I created an arbitrary view
let middleView = UIView(
frame: CGRect(x: 0.0,
y: view.frame.height/4,
width: view.frame.width,
height: view.frame.height/4))
middleView.backgroundColor = UIColor.blue
view.addSubview(middleView)
Then I created a circle using UIBezierPath; however when I set the position to middleView.center, the circle is far off to the bottom of the view. Can you set the position in the center of a subview?
let shapeLayer = CAShapeLayer()
let circlePath = UIBezierPath(
arcCenter: .zero,
radius: 100,
startAngle: CGFloat(0).toRadians(),
endAngle: CGFloat(360).toRadians(),
clockwise: true)
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.purple.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.position = middleView.center
middleView.layer.addSublayer(shapeLayer)
How do I center this circle in that view?
You have two problems.
First, you are setting shapeLayer.position = middleView.center. The center of a view is is the superview's geometry. In other words, middleView.center is relative to view, not to middleView. But then you're adding shapeLayer as a sublayer of middleView.layer, which means shapeLayer needs a position that is in middleView's geometry, not in view's geometry. You need to set shapeLayer.position to the center of middleView.bounds:
shapeLayer.position = CGPoint(x: middleView.bounds.midX, y: middleView.bounds.midY)
Second, you didn't say where you're doing all this. My guess is you're doing it in viewDidLoad. But that is too early. In viewDidLoad, the views loaded from the storyboard still have the frames they were given in the storyboard, and haven't been laid out for the current device's screen size yet. So it's a bad idea to look at frame (or bounds or center) in viewDidLoad if you don't do something to make sure that things will be laid out correctly during the layout phase. Usually you do this by setting the autoresizingMask or creating constraints. Example:
let middleView = UIView(
frame: CGRect(x: 0.0,
y: view.frame.height/4,
width: view.frame.width,
height: view.frame.height/4))
middleView.backgroundColor = UIColor.blue
middleView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleTopMargin, .flexibleBottomMargin]
view.addSubview(middleView)
However, shapeLayer doesn't belong to a view, so it doesn't have an autoresizingMask and can't be constrained. You have to lay it out in code. You could do that, but it's better to just use a view to manage the shape layer. That way, you can use autoresizingMask or constraints to control the layout of the shape, and you can set it up in viewDidLoad.
let circleView = CircleView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
circleView.center = CGPoint(x: middleView.bounds.midX, y: middleView.bounds.midY)
circleView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
circleView.shapeLayer.strokeColor = UIColor.purple.cgColor
circleView.shapeLayer.fillColor = nil
middleView.addSubview(circleView)
...
class CircleView: UIView {
override class var layerClass: AnyClass { return CAShapeLayer.self }
var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer }
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
}
}
Result:
And after rotating to landscape:
This is what I'm getting with this code
private func setupBorders(){
let path = UIBezierPath(roundedRect: mainTableView.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 20, height: 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
mainTableView.layer.mask = maskLayer
mainTableView.layer.borderColor = UIColor.darkGray.cgColor
mainTableView.layer.borderWidth = 1.0
}
MainTableView is a uiview containing the notepad table and the table header. If I can get it to work for any UIView then it will work for this one. Much appreciation to anyone who can help!
Edit: In case its not clear, the problem is the border disappears on the rounded corners.
A mask layer is not enough to solve your requirement, because the layer border will not respect to the layer mask. Instead you should create a view for drawing the backgound and the border, and it should clip its contents along the border, too.
In storyboard drag a UIView to your ViewController, set constrains as you want, link it to NewView and try this,
import UIKit
import QuartzCore
class ViewController: UIViewController{
#IBOutlet var NewView :UIView!
override func viewDidLoad() {
super.viewDidLoad()
let screenSize: CGRect = UIScreen.mainScreen().bounds
let HeightFloat :CGFloat = screenSize.height - 60
let WidthFloat :CGFloat = screenSize.width - 50
let NewRect :CGRect = CGRectMake(10, 20, WidthFloat, HeightFloat)
let maskPath = UIBezierPath(roundedRect: NewRect,
byRoundingCorners: [.TopLeft, .TopRight],
cornerRadii: CGSize(width: 10, height: 10))
let shape = CAShapeLayer()
shape.path = maskPath.CGPath
shape.fillColor = UIColor.clearColor().CGColor
shape.strokeColor = UIColor.darkGrayColor().CGColor
shape.lineWidth = 2
shape.lineJoin = kCALineJoinRound
NewView.layer.mask = shape
}
}
You will get your output,