How to keep a round imageView round using auto layout? - ios

How do I turn a rectangular image view into a circular image view that can hold shape in auto layout without setting width and height restraints? Thereby allowing the imageView to define it’s size, and size bigger and smaller relative to objects around it with leading, trailing, top, and bottom constraints.
I asked a similar question the other day, but I think this might be posed in a more concise way. Thanks so much!
EDIT
Ok, I started over to make this as simple as possible. I have a view named "Cell" and a UIImageView named "dog" within the cell, and that's it. I don't have "unable to simultaneously satisfy constraints" in the console anymore, just two simple views using auto layout. I'm still trying to use this code to round the UIImageView:
profileImageView.layer.cornerRadius = profileImageView.frame.size.width / 2
profileImageView.clipsToBounds = true
Here is the cell constraint setup:
Here is the profile pic constraint setup:
Here is the result without the code, no rounding, but nice and square:
Here is the result with the code to round:
This makes no sense to me, because without the rounding code the image is square, and with the code it's diamond shaped. If it's square shouldn't it be a circle with no issues?
EDIT 2
Here's what happens when I remove the bottom constraint and add a multiplier of .637 for equal height to superview.

Unfortunately you cannot do this using cornerRadius and autolayout — the CGLayer is not affected by autolayout, so any change in the size of the view will not change the radius which has been set once causing, as you have noticed, the circle to lose its shape.
You can create a custom subclass of UIImageView and override layoutSubviews in order to set the cornerRadius each time the bounds of the imageview change.
EDIT
An example might look something like this:
class Foo: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
let radius: CGFloat = self.bounds.size.width / 2.0
self.layer.cornerRadius = radius
}
}
And obviously you would have to constrain the Foobar instance's width to be the same as the height (to maintain a circle). You would probably also want to set the Foobar instance's contentMode to UIViewContentMode.ScaleAspectFill so that it knows how to draw the image (this means that the image is likely to be cropped).

Setting radius in viewWillLayoutSubviews will solve the problem
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
profileImageView.layer.cornerRadius = profileImageView.frame.height / 2.0
}

create new interface in your .h file like
#interface CornerClip : UIImageView
#end
and implementation in .m file like
#implementation cornerClip
-(void)layoutSubviews
{
[super layoutSubviews];
CGFloat radius = self.bounds.size.width / 2.0;
self.layer.cornerRadius = radius;
}
#end
now just give class as "CornerClip" to your imageview.
100% working... Enjoy

First of all, I should mention that u can get a circle shape for your UIView/UIImageView only if the width and height will be equal. It's important to understand. In all other cases (when width != height), you won't get a circle shape because the initial shape of your UI instance was a rectangle.
OK, with this so UIKit SDK provides for developers a mechanism to manipulate the UIview's layer instance to change somehow any of layer's parameters, including setting up a mask to replace the initial shape of UIView element with the custom one. Those instruments are IBDesignable/IBInspectable. The goal is to preview our custom views directly through Interface Builder.
So using those keywords we can write our custom class, which will deal only with the single condition whether we need to round corners for our UI element or not.
For example, let's create the class extended from the UIImageView.
#IBDesignable
class UIRoundedImageView: UIImageView {
#IBInspectable var isRoundedCorners: Bool = false {
didSet { setNeedsLayout() }
}
override func layoutSubviews() {
super.layoutSubviews()
if isRoundedCorners {
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(ovalIn:
CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.width, height: bounds.height
)).cgPath
layer.mask = shapeLayer
}
else {
layer.mask = nil
}
}
}
After setting the class name for your UIImageView element (where the dog picture is), in your storyboard, you will get a new option, appeared in the Attributes Inspector menu (details at the screenshot).
The final result should be like this one.

It seems when you add one view as a subview of another that netted view will not necessarily have the same height as its superview. That's what the problem seems like. The solution is to not add your imageView as a subview, but have it on top of your backgroundView. In the image below I'm using a UILabel as my backgroundView.
Also in your case, when you're setting the cornerRadius use this: let radius: CGFloat = self.bounds.size.height / 2.0.

With my hacky solution you'll get smooth corner radius animation alongside frame size change.
Let's say you have ViewSubclass : UIView. It should contain the following code:
class ViewSubclass: UIView {
var animationDuration : TimeInterval?
let imageView = UIImageView()
//some imageView setup code
override func layoutSubviews() {
super.layoutSubviews()
if let duration = animationDuration {
let anim = CABasicAnimation(keyPath: "cornerRadius")
anim.fromValue = self.imageView.cornerRadius
let radius = self.imageView.frame.size.width / 2
anim.toValue = radius
anim.duration = duration
self.imageView.layer.cornerRadius = radius
self.imageView.layer.add(anim, forKey: "cornerRadius")
} else {
imageView.cornerRadius = imageView.frame.size.width / 2
}
animationDuration = nil
}
}
An then you'll have to do this:
let duration = 0.4 //For example
instanceOfViewSubclass.animationDuration = duration
UIView.animate(withDuration: duration, animations: {
//Your animation
instanceOfViewSubclass.layoutIfNeeded()
})
It's not beautiful, and might not work for complex multi-animations, but does answer the question.

Swift 4+ clean solution based on omaralbeik's answer
import UIKit
extension UIImageView {
func setRounded(borderWidth: CGFloat = 0.0, borderColor: UIColor = UIColor.clear) {
layer.cornerRadius = frame.width / 2
layer.masksToBounds = true
layer.borderWidth = borderWidth
layer.borderColor = borderColor.cgColor
}
}
Sample usage in UIViewController
1.Simply rounded UIImageView
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
imageView.setRounded()
}
2.Rounded UIImageView with border width and color
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
imageView.setRounded(borderWidth: 1.0, borderColor: UIColor.red)
}

write this code
override viewDidLayoutSubViews() {
profileImageView.layer.cornerRadius = profileImageView.frame.size.width / 2
profileImageView.clipsToBounds = true
}
in this case it will called after calculating the autolayout calculations in the first code you called the cornerradius code before calculating the actual size of the view cuz it's dynamically calculated using aspect ratio , the actual corner radius is calculated before equaling the width and the height of the view

I have same problem, and Now I get what happened here, hope some ideas can help you:
there are something different between the tows:
your profileImageView in storyboard
your profileImageView in viewDidLoad
the size of bound and frame is different when viewDidLoad and in storyboard,just because view is resize for different device size.
You can try it print(profileImageView.bounds.size) in viewDidLoad and viewDidAppear you will find the size in viewDidLoad you set cornerRadius is not the real "running" size.
a tips for you:
you can use a subClass of ImageView to avoid it, or do not use it in storyboard,

If you have subclassed the UIImageView. Then just add this piece of magical code in it.
Written in : Swift 3
override func layoutSubviews() {
super.layoutSubviews()
if self.isCircular! {
self.layer.cornerRadius = self.bounds.size.width * 0.50
}
}

I am quite new to iOS native development, but I had the same problem and found a solution.
So the green background has this constraints:
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.topAnchor.constraint(equalTo: superview!.safeAreaLayoutGuide.topAnchor).isActive = true
backgroundView.leftAnchor.constraint(equalTo: superview!.leftAnchor).isActive = true
backgroundView.widthAnchor.constraint(equalTo: superview!.widthAnchor).isActive = true
backgroundView.heightAnchor.constraint(equalTo: superview!.heightAnchor, multiplier: 0.2).isActive = true
The image constraints:
avatar.translatesAutoresizingMaskIntoConstraints = false
avatar.heightAnchor.constraint(equalTo: backgroundView.heightAnchor, multiplier: 0.8).isActive = true
avatar.widthAnchor.constraint(equalTo: backgroundView.heightAnchor, multiplier: 0.8).isActive = true
avatar.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor, constant: 0).isActive = true
avatar.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 20).isActive = true
on viewWillLayoutSubviews() method I set the corner radius
to
avatar.layer.cornerRadius = (self.frame.height * 0.2 * 0.8) / 2
Basically, I am simply calculating the height of the image and then divide it by 2. 0.2 is the backgroungView height constraint multiplier and 0.8 the image width/height constraint multiplier.

Solution: Crop the image
[imageView setImage:[[imageView image] imageWithRoundedCorners:imageView.image.size.width/2]];
imageView.contentMode = UIViewContentModeScaleAspectFill;
I was looking for the same solution for profile pictures. After some hit and try and going through available functions, I came across something which works and is a nice way to ensure its safe. You can use the following function to crop out a round image from the original image and then you need not worry about the corner radius.
Post this even if your view size changes the image remains round and looks good.

Add a 1:1 aspect ratio constraint to the imageView for it to remain circular, despite any height or width changes.

I added custom IBInspectable cornerRadiusPercent, so you can do it without any code.
class RoundButton: UIButton {
override var bounds: CGRect {
didSet {
updateCornerRadius()
}
}
//private var cornerRadiusWatcher : CornerRadiusPercent?
#IBInspectable var cornerRadiusPercent: CGFloat = 0 {
didSet {
updateCornerRadius()
}
}
func updateCornerRadius()
{
layer.cornerRadius = bounds.height * cornerRadiusPercent
}
}

Can be easily done by creating an IBOutlet for the constraint which needs to be changed at runtime. Below is the code:
Create a IBOutlet for the constraint that needs to be changed at run time.
#IBOutlet var widthConstraint: NSLayoutConstraint!
Add below code in viewDidLoad():
self.widthConstraint.constant = imageWidthConstraintConstant()
Below function determines for device types change the width constraint accordingly.
func imageWidthConstraintConstant() -> CGFloat {
switch(self.screenWidth()) {
case 375:
return 100
case 414:
return 120
case 320:
return 77
default:
return 77
}
}

Related

When does a subview of a custom UIView know its own bounds?

I've created a custom UIView with a subview containing several square subviews which are sized using layout constraints. I'd like for each subview to be circular so I'm holding a reference to the view in an array, then iterating through the array of views in the layoutSubviews method and applying a corner radius that is half the view.bounds.height.
The problem is, this only works 70% of the time. Sometimes the view doesn't seem to know its bounds and the views are rendered as square.
I'm probably approaching this all wrong... does anyone have any advice for me? Where is it safe to apply the corner radius so that it always renders as a perfect circle?
Thanks
The most reliable way to manage this is to subclass UIView and let it handle its own rounding.
For example:
class RoundView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
// this assumes constraints keep
// self at a 1:1 ratio (square)
layer.cornerRadius = bounds.height * 0.5
}
}
This simple controller example:
class RoundDemoVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue
]
let widths: [CGFloat] = [
200, 140, 140
]
let xPos: [CGFloat] = [
20, 120, 180
]
let g = view.safeAreaLayoutGuide
for i in 0..<colors.count {
let v = RoundView()
v.backgroundColor = colors[i]
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
v.widthAnchor.constraint(equalToConstant: widths[i]).isActive = true
// make it square
v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: xPos[i]).isActive = true
v.centerYAnchor.constraint(equalTo: g.centerYAnchor).isActive = true
}
}
}
Produces this output:
No need to iterate through subviews and setting values based on frame sizes... all you need to do is set the size/position constraints.
Donmag's example is cool.Also, you can do it this way without writing code to layoutsubview. You must add this settings after adding the constraints of the subviews in for iteration.
.......
....... // constraint settings
subView.layoutIfNeeded()
subView.layer.cornerRadius = subView.frame.size.height/2
You can apply the corner radius inside the layoutSubviews() method
override func layoutSubviews() {
super.layoutSubviews()
// This is the place to apply corner radius
}

SWIFT - Setting layer.cornerRadius greater than the view height causes issues

I actually want to add a corner radius to a view on only one side. The radius is supposed to be of full height. This is my implementation.
someView.layer.cornerRadius = someView.frame.size.height
someView.layer.maskedCorners = [.layerMaxXMaxYCorner]
This does the job as required, but it adds extra shapes on other sides as shown below.
What might be the problem?
This looks like an iOS bug, I was able to reproduce it
But usually you don't wanna set cornerRadius greater than half view minimum side, in your case:
someView.layer.cornerRadius = someView.frame.size.height / 2
I assume it'll produce result you're expecting:
Corner radius is radius of a circle inscribed in the corner of a rectangle, I think that's why there may be problems with radius bigger than side/2: circle doesn't fit a rectangle anymore
i had the same problem. I needed to make the height of the view 16 and the bottom corners radius also 16.
My solution:
add the view you want to round to the bottom view
clip the bottomView to specific size (in my case width = window width and height=16)
make height of the rounded view = cornerRadius * 2 (in my case 32)
pin rounded view to superView, excluding the opposite edge which you want to round off (in my case i wanted to round bottom edge roundedView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top))
code
private let bottomView: UIView = {
let view = UIView(forAutoLayout: ())
view.clipsToBounds = true
return view
}()
private let roundedView: UIView = {
let view = UIView(forAutoLayout: ())
view.clipsToBounds = true
view.contentMode = .scaleAspectFill
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
return view
}()
...
contentView.addSubview(bottomView)
bottomView.addSubview(roundedView)
...
func setupConstraints() {
if roundedView.layer.cornerRadius >= roundedView.frame.height / 2 {
roundedView.autoSetDimension(.height, toSize: roundedView.layer.cornerRadius * 2)
} else {
roundedView.autoPinEdge(toSuperviewEdge: .top)
}
roundedView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
bottomView.autoPinEdgesToSuperviewEdges()
}
result:
enter image description here

How to make UIView's width increase when a upload percentage increases? SWIFT 4

I have a project that uploads multiple images to Firebase, and have made a constant that calculates the percentage of the current upload progress:
uploadTask.observe(.progress, handler: { (snapshot) in
guard let progress = snapshot.progress else {
return
}
let percentage = (Float(progress.completedUnitCount) / Float(progress.totalUnitCount))
progressBlock(Double(percentage))
})
I have made a UIView element but have not connected it. I am trying to make the UIView acts as a progress bar for the user to visibly see how the upload is progressing. I have been trying to do this but have been unsuccessful at it. How can I do this? By the way: The UIView should be increasing as the percentage increases, and at 100% the UIView will reset and hide.
Thank you so much!
Create a custom subclass of UIView. Give the custom subclass a property percentComplete.
Have the view use a CAShapeLayer to draw a filled area that expands to fill the view as the percentage value increases from 0.0 to 1.0. (You could also override the drawRect() method and use Quartz drawing, but shape layers are easier and more performant.
Do some searching on CAShapeLayer for ideas on how to do that.
You can either have your view add a shape layer as an additional layer on the view, or you can have your view provide a layerClass property that causes the view's content layer to be a single CAShapeLayer
Here is the code to change width of your view dynamically.
import UIKit
class ViewController: UIViewController {
var widthAnchor:NSLayoutConstraint!
let perecentageView:UIView={
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false//Its needed becasue we are using anchors
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(perecentageView)
perecentageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
perecentageView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10).isActive = true
perecentageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
widthAnchor = perecentageView.widthAnchor.constraint(equalToConstant: 0) // By default its 0
widthAnchor.isActive = true
self.uploadProgess()
}
func uploadProgess(){
uploadTask.observe(.progress, handler: { (snapshot) in
guard let progress = snapshot.progress else {
return
}
let percentage = (Float(progress.completedUnitCount) / Float(progress.totalUnitCount))
let newWidth = self.view.frame.width * percentage / 100
DispatchQueue.main.async(execute: {
self.widthAnchor.constant = newWidth
// self.view.frame.width is the parent view (And max width will be = self.view.frame.width)
//if you are using view from storybard with aoutolayout then just take IBOutlet of view's with and here widthAnchor = your IBOutlet
/*
if you are using view from storybard and without autolayout then use below code
self.perecentageView.frame = CGRect(x: self.perecentageView.frame.origin.x, y: self.perecentageView.frame.origin.y, width: newWidth, height: self.perecentageView.frame.height)
*/
})
})
}
}

UIView not shows round

For making a circular UIView I am using the cornerRadius property.
I have a UIView with dimension 79*158.
redView.layer.cornerRadius = redView.frame.size.height/2
redView.layer.masksToBounds = true
It shows elipse instead of circle:
Any workaround or does it only work with square type (eg. UIView(100*100))?
I am ok if it resizes dynamically.
use this...
func makeCircle (view: UIView) {
view.clipsToBounds = true
let height = view.frame.size.height
let width = view.frame.size.width
let newHeight = min(height, width) // use "max" if you want big circle
var rectFrame = view.frame
rectFrame.size.height = newHeight
rectFrame.size.width = newHeight
view.frame = rectFrame
view.layer.cornerRadius = newHeight/2
}
use like this:
#IBOutlet var rectView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
makeCircle(view: rectView)
}
You have a UIView with dimension 79*158.So that is wrong. You should have exactly same height and width for rounding exact a view to circle shape.
E.g.
redView.frame.size.height = 79.0
redView.frame.size.width = 79.0
or
redView.frame.size.height = 158.0
redView.frame.size.width = 158.0
And apply corner radius like:
redView.clipsToBounds = true
redView.layer.cornerRadius = redView.frame.size.height / 2.0
Result:
Note: Check your constrains also If you are using Auto Layout. Be sure view frame doesn't change.
If you are using constraints then changing the frame/bounds of the view is not a good idea. Instead you should do the following.
If the view is contained in a UIViewController then set the cornerRadius in viewDidLayoutSubviews method
And if the view is itself a subclass of UIView the set the cornerRadius in layoutSubviews method
Only Squire view make a perfect circle. For example, if your view size is (10*10),(50*50),(100*100), etc. then your view becomes perfect squire else not.
Using IBDesignable, you can display without project run in storyboard ox .XIB #simple way
Step 1. Subclass UIView:
#IBDesignable class RoundedCornerView: UIView {
#IBInspectable var borderWidth:CGFloat = 2 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor:UIColor = UIColor.orangeGradientLight {
didSet {
layer.borderColor = borderColor.cgColor
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height/2
layer.masksToBounds = true
layer.borderColor = borderColor.cgColor
layer.borderWidth = borderWidth
}
}
Step 2. Set custom class in identity inspector:
can't.
Try resize UIView to square: 79*79 OR 158*158
And set:
redView.layer.cornerRadius = redView.frame.size.height/2

How to tile an image from center using UIColor patternImage in Swift

I have a method for repeating or tiling an image across a view using UIColor patternImage:
view.backgroundColor = UIColor(patternImage: UIImage(named:imageName))
The default behaviour of UIColor patternImage is to start the pattern from the top left, the result shown in Image 1.
Question:
How can I get UIColor patternImage to start always from the very center of a view, patterning outwards to give the result shown in Image 2?
This works. It is a generalised method that applies more broadly, adapting dynamically to both pattern images and views of different heights and widths. It gives the desired result in the question and is tested on different iOS simulators.
view.backgroundColor = UIColor(patternImage: UIImage(named: "imageName")!)
view.bounds.origin.x = (UIImage(named: "imageName")!.size.width/2) - (view.bounds.size.width/2)
view.bounds.origin.y = (UIImage(named: "imageName")!.size.height/2) - (view.bounds.size.height/2)
If your image size is 50*50 then you can do something like this,
myView = UIVIew(frame: CGRect(x: (self.view.frame.size.width/2)-25, y: (self.view.frame.size.height/2)-25, width: 50, height: 50))
So this view(imageview in yourcase may be) will be place at exact middle of view. Like wise you can arrange other view by adding or substracting view's width and height to center view's x and y origin. Hope this will help you. :)
I created reusable class PatternView, reusing idea of #user4806509. It's really drop-in component for showing patterns. Also, patternImage can be set via Interface Builder
class PatternView: UIView {
#IBInspectable var patternImage: UIImage?
override func awakeFromNib() {
super.awakeFromNib()
self.setupBackgroundColor()
}
private func setupBackgroundColor() {
guard let patternImage = self.patternImage else {
return
}
self.backgroundColor = UIColor(patternImage: patternImage)
}
override func layoutSubviews() {
super.layoutSubviews()
self.centerPattern()
}
private func centerPattern() {
guard let patternSize = self.patternImage?.size,
patternSize.width > 0, patternSize.height > 0 else {
return
}
let x = -self.bounds.width.remainder(dividingBy: patternSize.width)/2
let y = -self.bounds.height.remainder(dividingBy: patternSize.height)/2
self.bounds.origin = CGPoint(x: x, y: y)
}}

Resources