iOS 9 center position of path change with size screen - ios

I would like to add a path in an UIView and i am doing like this :
let Circle = UIBezierPath(arcCenter: CGPoint(x: InterfaceView.bounds.size.width/2, y: InterfaceView.bounds.size.height/2), radius: 10, startAngle: 0, endAngle: CGFloat(M_PI*2), clockwise: true)
let Layer = CAShapeLayer()
Layer.path = Circle.CGPath
Layer.strokeColor = UIColor.redColor().CGColor
Layer.lineWidth = 1.0
InterfaceView.layer.addSublayer(Layer)
That's work on iPhone 6 and 6S but when the screen is tiny or bigger, the circle doesn't have a good position :
iPhone 6S :
iPhone 5S :
Maybe an idea why ?
Thanks

You probably draw the path at a moment where the autolayout constraints didn't kick in yet, so the drawing is made relative to the design-time view frame.
Two possible solutions:
1) Draw the circle in a subview that is centered in the full view with autolayout constraints and always has the same size.
2) Redraw your path on any size change in viewDidLayoutSubviews.

Probably you should use center parameter. I test this on different screen sizes and circle is directly on the middle of screen.
let Circle = UIBezierPath(arcCenter: self.view.center, radius: 10, startAngle: 0, endAngle: CGFloat(M_PI*2), clockwise: true)
let Layer = CAShapeLayer()
Layer.path = Circle.CGPath
Layer.strokeColor = UIColor.redColor().CGColor
Layer.lineWidth = 1.0
self.view.layer.addSublayer(Layer)

Related

How to set UIView in semi circle in Swift 5

I want to crop my UIView to the semi-circle and that will support all iPhone devices. Here, we can't set height and width to fix. I am using a multiplier.
Here is some of the SO answer I checked.
How to crop the UIView as semi circle?
Draw a semi-circle button iOS
It's work but for the fix height and width not with multiplier and I am targeting universal devices.
Below is the image for semicircle UIView.
Thanks in advance.
I have just modified the circle code from this link code. The problem is in the original code it used the initial frame when again layoutSubviews method called at that time semiCirleLayer == nil this condition is prevented from the update bezier path.
class SemiCirleView: UIView {
var semiCirleLayer: CAShapeLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let arcCenter = CGPoint(x: bounds.size.width / 2, y: bounds.size.height)
let circleRadius = bounds.size.width / 2
let circlePath = UIBezierPath(arcCenter: arcCenter, radius: circleRadius, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 2, clockwise: true)
semiCirleLayer.path = circlePath.cgPath
semiCirleLayer.fillColor = UIColor.red.cgColor
semiCirleLayer.name = "RedCircleLayer"
if !(layer.sublayers?.contains(where: {$0.name == "RedCircleLayer"}) ?? false) {
layer.addSublayer(semiCirleLayer)
}
// Make the view color transparent
backgroundColor = UIColor.clear
}
}

Fill color not working using CAShapeLayer in swift

I am trying below code to draw shape and that's okay but UIView background color not showing since I Have given orange color to View
See my code:
let myView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
myView.backgroundColor = .orange
let circlePath = UIBezierPath(arcCenter: CGPoint(x: myView.bounds.size.width / 2, y: 0), radius: myView.bounds.size.height, startAngle: 0.0, endAngle: .pi, clockwise: false)
let circleShape = CAShapeLayer()
circleShape.masksToBounds = false
circleShape.path = circlePath.cgPath
circleShape.fillColor = UIColor.orange.cgColor
circleShape.strokeColor = UIColor.orange.cgColor
myView.layer.mask = circleShape
print(myView)
Output At Playground:
Try this code:
let myView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
myView.backgroundColor = .orange
let circlePath = UIBezierPath(arcCenter: myView.center,
radius: myView.bounds.size.width / 2,
startAngle: 0.0,
endAngle: .pi,
clockwise: false)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
myView.layer.mask = circleShape
I think you don't see expecting result because of wrong arcCenter or start and end angels
result of the code:
The issue seems to be with your circlePath, that has:
1) Wrong arcCenter (CGPoint's y is not centered)
2) Radius (It should be half the view's height/width, considering height = width and you need a circle)
3) Angle (It should be 360 degrees)
Replace its definition with:
let circlePath = UIBezierPath(arcCenter: myView.center, radius: myView.bounds.height/2, startAngle: 0.0, endAngle: .pi * 2, clockwise: false)
and it should work as intended...
that's okay but UIView background color not showing since I Have given
orange color to View
The path you've used doesn't even display a layer since you've specified its corner radius to be the same as its height, and you've applied the generated supposedly invisible layer to be your view's mask.
A few observations:
You are just looking at a visual representation of the path, not of the view. To see the view, you should set the liveView of the PlaygroundPage:
import PlaygroundSupport
And
PlaygroundPage.current.liveView = myView
PlaygroundPage.current.needsIndefiniteExecution = true
An an aside, your circle view is going from 0 to π counterclockwise. I.e. that is the top half of a circle. So if the circle’s center has a y of 0, that means your path is above the view and doesn’t overlap with it. Either set it to go clockwise (to get the bottom half of a circle) or move the y down (so the top half half of the circle overlaps the view).
I find that playgrounds resize their liveView (even if I set wantsFullScreenLiveView to false), so if I want a view of a particular size, I use a “container view”. I.e., I will add myView as a subview of containerView, add constraints to put it in the center of that container, and then set containerView as the liveView.
Only tangentially related, but you’re setting the color of the shape layer of the mask to be orange. Masks only use their alpha channel, so using orange is meaningless. We’d generally set masks to be white, but I just wanted to make sure we didn’t conflate the color of the mask with the color of the masked view.
So, pulling that all together:
import UIKit
import PlaygroundSupport
// create container
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
// create view that we'll mask with shape layer
let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
myView.backgroundColor = .orange
myView.clipsToBounds = true
containerView.addSubview(myView)
// create mask
let circlePath = UIBezierPath(arcCenter: CGPoint(x: rect.midX, y: rect.minY), radius: rect.height, startAngle: 0, endAngle: .pi, clockwise: true)
let circleShape = CAShapeLayer()
circleShape.masksToBounds = false
circleShape.path = circlePath.cgPath
circleShape.fillColor = UIColor.white.cgColor
circleShape.strokeColor = UIColor.white.cgColor
myView.layer.mask = circleShape
// center masked view in container
NSLayoutConstraint.activate([
myView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
myView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
myView.widthAnchor.constraint(equalToConstant: 200),
myView.heightAnchor.constraint(equalToConstant: 200)
])
// show it
PlaygroundPage.current.liveView = containerView
PlaygroundPage.current.needsIndefiniteExecution = true
That yields:
So, by making the arc go clockwise and by using the liveView, we can now see it. Obviously an arc whose center is at midX, minY and with a radius of height, cannot show the full half circle. You’d have to set the radius to be width / 2 (or increase the width of the rect or whatever) if you wanted to see the full half circle.
Just remove path.move(to:) calls everywhere after drawing. That's helped me with the same issue

Center of UIView not centered

I have a CAShapeLayer that I am adding to the center of a UIView, but instead of appearing in the center, it appears in the top left corner of the UIView.
Here's the view setup:
let trayProgress: UIView = {
let view = UIView()
view.backgroundColor = .yellow
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: view.center, radius: 20, startAngle: -CGFloat.pi / 2, endAngle: CGFloat.pi * 2, clockwise: false)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 6
trackLayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(trackLayer)
return view
}()
Here's what is showing in the view (I expect the gray circle to be in the center of the yellow view):
I have also tried to replace view.center with CGPoint(x: view.center.x, y: view.center.y), but I get the same result.
Can anyone tell me why this is happening and how I can fix it? Thanks!
The problem is in part this line:
arcCenter: view.center
That makes no sense, because the view’s center is where it is in its superview. You want to make the shape layer’s frame the same as the view’s bounds! The arc center should then be the shape layer’s own center (which, because it is a layer, is its position).
But another part of the problem is that in the code you’ve shown neither the view nor the shape layer has any size. You cannot create the shape layer until the view has size.
Example (I have starred the key changes in the code):
let trayProgress: UIView = {
let view = UIView(frame:CGRect(x: 30, y: 60, width: 50, height: 50)) // *
view.backgroundColor = .yellow
let trackLayer = CAShapeLayer()
trackLayer.frame = view.bounds // *
let circularPath = UIBezierPath(arcCenter: trackLayer.position, // *
radius: 20, startAngle: -CGFloat.pi / 2, endAngle: CGFloat.pi * 2, clockwise: false)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 6
trackLayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(trackLayer)
return view
}()
Result:
You can tweak that, of course. For example you can move the center of the view to wherever you like.

How to make a gradient circular path in Swift

I want to create a gradient circular path like the following image:
and I have the following code:
circularPath = UIBezierPath(arcCenter: .zero, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
outerTrackShapeLayer = CAShapeLayer()
outerTrackShapeLayer.path = circularPath.cgPath
outerTrackShapeLayer.position = position
outerTrackShapeLayer.strokeColor = outerTrackColor.cgColor
outerTrackShapeLayer.fillColor = UIColor.clear.cgColor
outerTrackShapeLayer.lineWidth = lineWidth
outerTrackShapeLayer.strokeEnd = 1
outerTrackShapeLayer.lineCap = CAShapeLayerLineCap.round
outerTrackShapeLayer.transform = rotateTransformation
addSublayer(outerTrackShapeLayer)
innerTrackShapeLayer = CAShapeLayer()
innerTrackShapeLayer.strokeColor = innerTrackColor.cgColor
innerTrackShapeLayer.position = position
innerTrackShapeLayer.strokeEnd = progress
innerTrackShapeLayer.lineWidth = lineWidth
innerTrackShapeLayer.lineCap = CAShapeLayerLineCap.round
innerTrackShapeLayer.fillColor = UIColor.clear.cgColor
innerTrackShapeLayer.path = circularPath.cgPath
innerTrackShapeLayer.transform = rotateTransformation
let gradient = CAGradientLayer()
gradient.frame = circularPath.bounds
gradient.colors = [UIColor.magenta.cgColor, UIColor.cyan.cgColor]
gradient.position = innerTrackShapeLayer.position
gradient.mask = innerTrackShapeLayer
addSublayer(gradient)
but it doesn't work correctly, you can see the result in the following image:
I would appreciate if someone help me, thanks.
It looks like the gradient layer frame is set equal to the path frame which doesn't include the thickness of the stroke of the CAShapeLayer which is why it is cut off in a square. I can't see from the code whether you have the circular path on a subview but if you set the gradient frame to the same as the subview frame that should sort the cut off out as well as the misalignment of the progress view on the track.
Hope that helps.
I also face this issue and fixed it by changing the radius value as my radius value was larger than the expected
In circularPath, you need to make sure that your radius value should not be larger than the following
let radius = (min(width, height) - lineWidth) / 2
circularPath = UIBezierPath(arcCenter: .zero, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)

UIImageView Pie Mask in Swift 2

After searching numerous sources on how to mask an image in Swift, I've found many that utilize UIImage rather than UIImageView. It's getting quite confusing because they are almost similar yet they don't share some of the same subclasses & properties.
I have an image that will be added into a stacks view although I'd like this image to be masked with a circular shape with a fixed start and end angle.
Example:
Original Picture and Desired Mask Result
I'd like to emphasize that I am simulating a pie being sliced out so essentially I am cropping an image with the Pie's size.
Here's what I've attempted so far to get started (and I keep failing)...
let testPicture:UIImageView = UIImageView(image: UIImage(named: "myPicture"))
testPicture.contentMode = .ScaleAspectFit
testPicture.layer.borderWidth = 1
testPicture.clipsToBounds = true
testPicture.layer.masksToBounds = true
view.layer.addSublayer(shapeLayer)
// ???
// UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: clockwise).CGPath
self.myStackView.addArrangedSubview(testPicture)
self.myStackView.layoutIfNeeded()
I just can't figure out how to apply the same implementation of masking images with geometric functions when other sources out there solely use UIImage.
New Code (Attempt) to Generate Pie Mask
let center = CGPointMake(testPicture.frame.size.width / 2, testPicture.frame.size.height / 2)
let radius = CGFloat((CGFloat(testPicture.frame.size.width) - CGFloat(1.0)) / 2)
let midX = (testPicture.layer.frame.width-testPicture.layer.frame.width)/2
let midY = (testPicture.layer.frame.height-testPicture.layer.frame.height)/2
let circlePath = UIBezierPath(arcCenter: CGPoint(x: midX , y: midY), radius: radius, startAngle: CGFloat(10), endAngle:CGFloat(M_PI * 2), clockwise: true)
let maskLayer = CAShapeLayer()
maskLayer.path = circlePath.CGPath
testPicture.layer.mask = maskLayer
testPicture.clipsToBounds = true
testPicture.layer.masksToBounds = true
To mask a layer you assign a separate layer to the mask property. In this case, I think what you're looking for is:
testPicture.layer.mask = shapeLayer

Resources