How to resize a view across all screen sizes? - ios

I am creating a simple timer app and want to resize the timer circle to the size of the view with the rounded corners. The timer path is added within the background views class.
On my iPhone XS Max, it looks like this:
But, on the iPhone SE simulator, it looks like this:
How can I ensure the timer circles resize correctly?
This is my code for the sizing of the circle paths:
private func addTimerCircle() {
let translateAmount = -(CGFloat.pi/2)
let circlePath = UIBezierPath(arcCenter: CGPoint.zero, radius: (self.frame.width - 64)/2, startAngle: translateAmount, endAngle: (2*CGFloat.pi)+translateAmount, clockwise: true)
addTrackLayer(withPath: circlePath)
}
private func addTrackLayer(withPath circlePath: UIBezierPath) {
let trackLayer = CAShapeLayer()
trackLayer.path = circlePath.cgPath
trackLayer.strokeColor = UIColor.red.withAlphaComponent(0.5).cgColor
trackLayer.lineWidth = 10
trackLayer.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
trackLayer.fillColor = UIColor.clear.cgColor
layer.addSublayer(trackLayer)
}
Why does the circlePath not resize based on frame.width on the different screen sizes?
Thanks for any help!

View controller views are loaded with the size they have in Interface Builder, and once they are added to the hierarchy the view is resized to the actual size.
I'm guessing you're calling addTimerCircle() on viewDidLoad(), which happens before the final sizing is applied.
You could override viewDidLayoutSubviews(), which will be called when the view size changes, and add/update the shape there, but my suggestion would be to subclass UIView, add the shape layer as a property, and update it's frame on layoutSubviews.

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
}
}

How to draw a border around two overlapping UIViews

I have two UIViews that create a unique shape when overlapping and I want to draw a border around the combined views.
What is the proper method for doing this?
The UIViews are:
Circle image view to display a user profile image
A rectangle view that will container user profile data (i.e. name, dob, etc)
Here is an image of what the two view together will look like:
Ok figured it out by doing the following:
Setting a border on the rectangle UIView
Creating a CAShapeLayer with UIBezierPath to draw a semi-circle and add it to the circle UIView's layer in the drawRect method:
override func draw(_ rect: CGRect) {
super.draw(rect)
let shapeLayer = CAShapeLayer()
let topSemiCirclePath = UIBezierPath(arcCenter: userImageView.center, radius: userImageView.bounds.size.width / 2.0, startAngle: CGFloat(Double.pi), endAngle: CGFloat(Double.pi / 180), clockwise: true)
topSemiCirclePath.lineWidth = 2.0
UIColor.lightGray.setStroke()
topSemiCirclePath.stroke()
shapeLayer.path = topSemiCirclePath.cgPath
userImageView.layer.addSublayer(shapeLayer)
}

Adding circular corner only on the top of the view

I'm trying to achieve a view like this:
I've been trying using this piece of code
let maskPath = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: [.topRight, .topLeft], cornerRadii: CGSize(width: 140, height: 140))
But all I got is something like this:
I've already tried to change width and height values, but the view seems to not change at all, so how do I draw a view which contains that rounded top as shown in the first image?
Thanks for suggesting a solution , but as I said above, I can set the corners rounded, I can't make only the top of my view circular, so this is a different question.
You could try using a circular UIBezierPath and adjusting its radius. This way, as long as your view doesn't extend indefinitely (like a UIScrollView could), you'll get the effect you want.
let radiusRatio = CGFloat(2 / 3)
let screenRect = UIScreen.main.bounds
let screenHeight = screenRect.height
let screenWidth = screenRect.width
let circleCenter = CGPoint(x: screenWidth / 2, y: screenHeight / ((1/radiusRatio) * 2))
let maskPath = UIBezierPath(arcCenter: circleCenter,
radius: screenHeight * radiusRatio,
startAngle: 0,
endAngle: CGFloat.pi,
clockwise: true)
The radiusRatio variable controls how much of the screen's height the circle's radius takes, or in other terms, how down low your circle's center is. Adjusting the radiusRatio should eventually give you what you want.

iOS 9 center position of path change with size screen

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)

How to add a border to a circular image with mask

This is my attempt:
func round() {
let width = bounds.width < bounds.height ? bounds.width : bounds.height
let mask = CAShapeLayer()
mask.path = UIBezierPath(ovalInRect: CGRectMake(bounds.midX - width / 2, bounds.midY - width / 2, width, width)).CGPath
self.layer.mask = mask
// add border
let frameLayer = CAShapeLayer()
frameLayer.path = mask.path
frameLayer.lineWidth = 4.0
frameLayer.strokeColor = UIColor.whiteColor().CGColor
frameLayer.fillColor = nil
self.layer.addSublayer(frameLayer)
}
It works on the iphone 6 simulator (storyboard has size of 4.7), but on the 5s and 6+ it looks weird:
Is this an auto layout issue? Without the border, auto layout works correct. This is my first time working with masks and so I am not sure if what I have done is correct.
round function is called in viewDidLayoutSubviews.
Any thoughts?
If you have subclassed UIImageView, for example, you can override layoutSubviews such that it (a) updates the mask; (b) removes any old border; and (c) adds a new border. In Swift 3:
import UIKit
#IBDesignable
class RoundedImageView: UIImageView {
/// saved rendition of border layer
private weak var borderLayer: CAShapeLayer?
override func layoutSubviews() {
super.layoutSubviews()
// create path
let width = min(bounds.width, bounds.height)
let path = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: width / 2, startAngle: 0, endAngle: .pi * 2, clockwise: true)
// update mask and save for future reference
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
// create border layer
let frameLayer = CAShapeLayer()
frameLayer.path = path.cgPath
frameLayer.lineWidth = 32.0
frameLayer.strokeColor = UIColor.white.cgColor
frameLayer.fillColor = nil
// if we had previous border remove it, add new one, and save reference to new one
borderLayer?.removeFromSuperlayer()
layer.addSublayer(frameLayer)
borderLayer = frameLayer
}
}
That way, it responds to changing of the layout, but it makes sure to clean up any old borders.
By the way, if you are not subclassing UIImageView, but rather are putting this logic inside the view controller, you would override viewWillLayoutSubviews instead of layoutSubviews of UIView. But the basic idea would be the same.
--
By the way, I use a mask in conjunction with this shape layer because if you merely apply rounded corners of a UIView, it can result in weird artifacts (look at very thin gray line at lower part of the circular border):
If you use the bezier path approach, no such artifacts result:
For Swift 2.3 example, see earlier revision of this answer.
The easiest way is to manipulate the layer of the image view itself.
imageView.layer.cornerRadius = imageView.bounds.size.width / 2.0
imageView.layer.borderWidth = 2.0
imageView.layer.borderColor = UIColor.whiteColor.CGColor
imageView.layer.masksToBounds = true
You can include this in viewDidLayoutSubviews or layoutSubviews to make sure the size is always correct.
NB: Maybe this technique makes your circle mask obsolete ;-). As a rule of thumb, always choose the simplest solution.

Resources