I want to centre a rectangle in a view, i try using midX or midY but the view still not centre
this is how i setup the rectangle
the overlayView have width = 414 and height = 688
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let midX = overlayView.bounds.midX
let midY = overlayView.bounds.midY
let center = CGPoint(x: midX, y: midY)
let size: CGFloat = 312
// Create the initial layer from the view bounds.
let maskLayer = CAShapeLayer()
maskLayer.frame = overlayView.bounds
// Create the path.
let rect = CGRect(x: center.x, y: center.y, width: size, height: size)
let path = UIBezierPath(rect: overlayView.bounds)
maskLayer.fillRule = .evenOdd
// Append the overlay image to the path so that it is subtracted.
path.append(UIBezierPath(roundedRect: rect, cornerRadius: 20))
maskLayer.path = path.cgPath
// Set the mask of the view.
overlayView.layer.mask = maskLayer
}
I already try to calculate the center but still can't get the rect in the centre.
You have to subtract half-height and width from the center, as you have set start x and y position from the center point
so change your rect code.
let rect = CGRect(x: center.x - size/2, y: center.y - size/2, width: size, height: size)
Related
I'm making a loading spinner animation that pushes a view out from the middle, and then rotates all the way around the center view back to it's original location. This is what I am trying to achieve:
The inner arrow moves the view away from the center. I've already achieved this, the part I am stuck on is then rotating the view around the center view. I've read various other StackOverflow posts but have not been close to achieving this.
Code so far:
UIView.animate(withDuration: 0.5) {
self.topView.transform = CGAffineTransform(translationX: 20, y: -20)
} completion: { _ in
self.topView.setAnchorPoint(self.centerView.center)
// Rotate
}
}
Here is how I am setting the anchor point of the view. I'm using this as the view disappears when setting its anchor point otherwise.
func setAnchorPoint(_ point: CGPoint) {
var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y);
newPoint = newPoint.applying(transform)
oldPoint = oldPoint.applying(transform)
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.position = position
layer.anchorPoint = point
}
Once the full 360 rotation is complete I would then need to move the view back in towards the center, completing the animation.
For the part when the loading view rotates around the circle view, you can use a UIBezierPath and create a CAKeyframeAnimation based on its path.
Take a look at this implementation. Hope it helps.
class LoadingViewController: UIViewController {
var circlePath: UIBezierPath!
lazy var loader = makeLoader()
lazy var centerView = makeCenterView()
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func makeLoader() -> UIButton {
let padding: CGFloat = 100
let width = self.view.frame.width - (2 * padding)
let b = UIButton(frame: CGRect(x: padding, y: 200, width: width, height: 50))
b.addTarget(self, action: #selector(didTap), for: .touchUpInside)
b.backgroundColor = .blue
return b
}
func makeCenterView() -> UIView {
let width: CGFloat = 20
let height: CGFloat = 20
let x = self.view.center.x - width/2
let y = self.view.center.y - height/2
let view = UIView(frame: CGRect(x: x, y: y, width: width, height: height))
view.backgroundColor = .green
return view
}
func setup() {
//create a UIBezierPath with a center that is at the center of the green view and a radius that has a length as the distance between the green view and the blue view.
let arcCenterX = centerView.center.x
let arcCenterY = centerView.center.y
let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
let radius = arcCenterY - loader.center.y
let startAngle = -CGFloat.pi/2
let endAngle = CGFloat.pi*(1.5)
let arcPath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
self.circlePath = arcPath
self.view.addSubview(loader)
self.view.addSubview(centerView)
}
#objc func didTap() {
//create a CAKeyframeAnimation with a path that matches the UIBezierPath above.
let loadAnimation = CAKeyframeAnimation(keyPath: "position")
loadAnimation.path = self.circlePath.cgPath
loadAnimation.calculationMode = .paced
loadAnimation.duration = 2.0
loadAnimation.rotationMode = .rotateAuto
loadAnimation.repeatCount = Float(CGFloat.greatestFiniteMagnitude)
loader.layer.add(loadAnimation, forKey: "circleAnimation")
}
}
I created a DrawView where it's possible to draw. I created an instance of UIBezierPath for drawing.
// This function is called when the user has finished drawing.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let shapeLayer = CAShapeLayer()
//The CAShapeLayer has the same path of the drawing (currentPath is the instance of UIBezierPath).
shapeLayer.path = currentPath?.cgPath
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 10.0
//Here I set the shapeLayer on the drawingView (the blue one that you can see in the image)
drawingView.layer.addSublayer(shapeLayer)
shapeLayer.position = CGPoint(x: drawingView.layer.bounds.midX, y: drawingView.layer.bounds.midY)
shapeLayer.backgroundColor = UIColor.blue.cgColor
shapeLayer.frame = drawingView.layer.bounds
}
The problem is that the path (e.g number 3 in the image) is not centered on its shapeLayer.
shapeLayer.path?.boundingBoxOfPath = (289.5, 349.5, 525.0, 129.0)
shapeLayer.frame = (0.0, 0.0, 200.0, 200.0)
shapeLayer.position = (100.0, 100.0)
drawingView.frame = (0.0, 912.0, 200.0, 200.0)
Any hint? thank you
Forget the drawing view and just think about how to center a path in a shape layer of known size. Here's a deliberate failure:
let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 50, height: 50))
let lay = CAShapeLayer()
lay.frame = CGRect(x: 40, y: 40, width: 200, height: 200)
lay.backgroundColor = UIColor.red.cgColor
lay.path = path.cgPath
self.view.layer.addSublayer(lay)
We get this:
The filled circle is not centered in the red shape layer. Okay, we know why, because we know the bezier path that created the filled circle. But let's say we don't know that. We can still center the shape in the shape layer, like this:
let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 50, height: 50))
let lay = CAShapeLayer()
lay.frame = CGRect(x: 40, y: 40, width: 200, height: 200)
lay.backgroundColor = UIColor.red.cgColor
let cgpath = path.cgPath
let box = cgpath.boundingBoxOfPath
let xtarget = (lay.bounds.width - box.width)/2
let ytarget = (lay.bounds.height - box.height)/2
let xoffset = xtarget - box.minX
let yoffset = ytarget - box.minY
var transform = CGAffineTransform(translationX: xoffset, y: yoffset)
let cgpath2 = cgpath.copy(using: &transform)
lay.path = cgpath2
self.view.layer.addSublayer(lay)
Result:
So, given a CGPath, and given a shape layer whose final bounds are known, you can use that technique to center the path in the shape layer. And those are circumstances that apply perfectly to your use case.
I am trying to add rounded edges via this extension , however I am getting proper rounded UIView in iPhone X but when I try to run this same code in iPhone 6s , its like not getting curved , tried to increase the desiredCurve still not able to see the curve.
extension UIView {
/* Usage Example
* bgView.addBottomRoundedEdge(desiredCurve: 1.5)
*/
func addBottomRoundedEdge(desiredCurve: CGFloat?) {
let offset: CGFloat = self.frame.width / desiredCurve!
let bounds: CGRect = self.bounds
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width, height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
// Create the shape layer and set its path
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
// Set the newly created shape layer as the mask for the view's layer
self.layer.mask = maskLayer
}
}
Call the addBottomRoundedEdge extension method to add the bottom curve on a UIView
extension UIView {
func dropShadow(scale: Bool = true) {
layer.masksToBounds = true
layer.shadowColor = UIColor.lightGray.cgColor
layer.shadowOpacity = 0.8
layer.shadowOffset = CGSize(width: 0, height: 5)
layer.shadowRadius = 2
layer.borderWidth = 1
layer.shouldRasterize = true
layer.rasterizationScale = scale ? UIScreen.main.scale : 1
layer.borderColor = UIColor(red:222/255, green:225/255, blue:227/255, alpha: 1).cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 5).cgPath
}
func addBottomRoundedEdge() {
let offset: CGFloat = (self.frame.width * 1.5)
let bounds: CGRect = self.bounds
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width , height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset , height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
}
You can use Layer.corner.Radius on the orange view.
First put that view width bigger than the super view:
#IBOutlet weak var orangeView: UIView!
self.orangeView.frame. width = self.view.frame + 50
self.orangeView.layer.corner.Radius = self.orangeView.frame.height /2
I want to give curve top to the view. I have used following code but it is not working properly. I am using following code but it is not giving desirable output. Please help me.
Thanks.
func roundedTop(targetView:UIView?, desiredCurve:CGFloat?){
let offset:CGFloat = targetView!.frame.width/desiredCurve!
let bounds: CGRect = targetView!.bounds
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y+bounds.size.height / 2, width: bounds.size.width, height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
let ovalBounds: CGRect = CGRect(x:bounds.origin.x - offset / 2,y: bounds.origin.y, width : bounds.size.width + offset, height :bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
// Create the shape layer and set its path
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
[![// Set the newly created shape layer as the mask for the view's layer][1]][1]
targetView!.layer.mask = maskLayer
}
Top curve result image
Bottom curve result image
Code in swift5
//TopRoundedCornerToView and BottomRoundedCornerToView
extension UIView {
func addTopRoundedCornerToView(targetView:UIView?, desiredCurve:CGFloat?)
{
let offset:CGFloat = targetView!.frame.width/desiredCurve!
let bounds: CGRect = targetView!.bounds
//Top side curve
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y+bounds.size.height / 2, width: bounds.size.width, height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
//Top side curve
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
// Create the shape layer and set its path
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
// Set the newly created shape layer as the mask for the view's layer
targetView!.layer.mask = maskLayer
}
func setTopCurve(){
let offset = CGFloat(self.frame.size.height/4)
let bounds = self.bounds
let rectBounds = CGRect(x: bounds.origin.x, y: bounds.origin.y + bounds.size.height/2 , width: bounds.size.width, height: bounds.size.height / 2)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalBounds = CGRect(x: bounds.origin.x , y: bounds.origin.y - offset / 2, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer = CAShapeLayer.init()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
func addBottomRoundedCornerToView(targetView:UIView?, desiredCurve:CGFloat?)
{
let offset:CGFloat = targetView!.frame.width/desiredCurve!
let bounds: CGRect = targetView!.bounds
//Bottom side curve
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width, height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
//Bottom side curve
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
// Create the shape layer and set its path
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
// Set the newly created shape layer as the mask for the view's layer
targetView!.layer.mask = maskLayer
}
func setBottomCurve(){
let offset = CGFloat(self.frame.size.height/4)
let bounds = self.bounds
let rectBounds = CGRect(x: bounds.origin.x, y: bounds.origin.y , width: bounds.size.width, height: bounds.size.height / 2)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalBounds = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer = CAShapeLayer.init()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
}
use it as:
override func viewDidLoad() {
super.viewDidLoad()
self.myTargateView.setTopCurve()
}
and
override func viewWillLayoutSubviews() {
self.myTargateView.addTopRoundedCornerToView(targetView: bottomView, desiredCurve: 10.0)
}
The problem is that you are calling your roundedTop method too soon (probably in viewDidLoad). Wait until after viewDidLayoutSubviews has been called for the first time. Otherwise, your targetView.frame and all the calculations that stem from it are incorrect.
I have a UIBezierPath thats a rounded square. It somewhat looks like this:
Here's my code for the shape:
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x:(0), y: (0), width: (250), height: (400)), cornerRadius: 64).cgPath
shape.position = CGPoint(x: 0, y: 0)
print(shape.position)
shape.fillColor = UIColor.white
shape.strokeColor = UIColor.white
shape.lineWidth = 5
addChild(shape)
I want to center it in the middle of the screen, but using
shape.position = CGPoint(x: self.frame.width, y: self.frame.height)
doesn't work. Thanks!
I suggest that you center the UIBezierPath within the shape node. For example,
let width:CGFloat = 250
let height:CGFloat = 400
shape.path = UIBezierPath(roundedRect: CGRect(x:-width/2, y: -height/2, width: width, height: height), cornerRadius: 64).cgPath
You can center the shape in the scene by setting its position to (0, 0).