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

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
}

Related

How to animate movement of UIView between different superViews

Hello I am new at swift and IOS apps , I am trying to animate movement of cardBackImage (UIImage) from deckPileImage to the card view, but everything got different superViews and I have no idea how to do it properly , all the location have different frames ( superviews as described in the Image) , Should I use CGAffineTransform ?
viewHierarchyDescription
try to imagine my abstraction as a "face down card fly from deck into its possition on boardView"
Don't animate the view at all. Instead, animate a snapshot view as a proxy. You can see me doing it here, in this scene from one of my apps.
That red rectangle looks like it's magically flying out of one view hierarchy into another. But it isn't. In reality there are two red rectangles. I hide the first rectangle and show the snapshot view in its place, animate the snapshot view to where the other rectangle is lurking hidden, then hide the snapshot and show the other rectangle.
To help get you going...
First, no idea why you have your "deckPileImage" in a stack view, but assuming you have a reason for doing so...
a simple "card" view - bordered with rounded corners
class CardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
layer.cornerRadius = 16
layer.masksToBounds = true
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
a basic view controller - adds a "deck pile view" to a stack view, and a "card position view" as the destination for the new, animated cards.
class AnimCardVC: UIViewController {
let deckStackView: UIStackView = UIStackView()
let cardPositionView: UIView = UIView()
let deckPileView: CardView = CardView()
let cardSize: CGSize = CGSize(width: 80, height: 120)
// card colors to cycle through
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemCyan, .systemOrange,
]
var colorIDX: Int = 0
// card position constraints to animate
var animXAnchor: NSLayoutConstraint!
var animYAnchor: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
deckStackView.translatesAutoresizingMaskIntoConstraints = false
deckPileView.translatesAutoresizingMaskIntoConstraints = false
cardPositionView.translatesAutoresizingMaskIntoConstraints = false
deckStackView.addArrangedSubview(deckPileView)
view.addSubview(deckStackView)
view.addSubview(cardPositionView)
// always respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
deckStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
deckStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
// we'll let the stack view subviews determine its size
deckPileView.widthAnchor.constraint(equalToConstant: cardSize.width),
deckPileView.heightAnchor.constraint(equalToConstant: cardSize.height),
cardPositionView.topAnchor.constraint(equalTo: deckStackView.bottomAnchor, constant: 100.0),
cardPositionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
cardPositionView.widthAnchor.constraint(equalToConstant: cardSize.width + 2.0),
cardPositionView.heightAnchor.constraint(equalToConstant: cardSize.height + 2.0),
])
// outline the card holder view
cardPositionView.backgroundColor = .systemYellow
cardPositionView.layer.borderColor = UIColor.blue.cgColor
cardPositionView.layer.borderWidth = 2
// make the "deck card" gray to represent the deck
deckPileView.backgroundColor = .lightGray
}
func animCard() {
let card = CardView()
card.backgroundColor = colors[colorIDX % colors.count]
colorIDX += 1
card.translatesAutoresizingMaskIntoConstraints = false
card.widthAnchor.constraint(equalToConstant: cardSize.width).isActive = true
card.heightAnchor.constraint(equalToConstant: cardSize.height).isActive = true
view.addSubview(card)
// center the new card on the deckCard
animXAnchor = card.centerXAnchor.constraint(equalTo: deckPileView.centerXAnchor)
animYAnchor = card.centerYAnchor.constraint(equalTo: deckPileView.centerYAnchor)
// activate those constraints
animXAnchor.isActive = true
animYAnchor.isActive = true
// run the animation *after* the card has been placed at its starting position
DispatchQueue.main.async {
// de-activate the current constraints
self.animXAnchor.isActive = false
self.animYAnchor.isActive = false
// center the new card on the cardPositionView
self.animXAnchor = card.centerXAnchor.constraint(equalTo: self.cardPositionView.centerXAnchor)
self.animYAnchor = card.centerYAnchor.constraint(equalTo: self.cardPositionView.centerYAnchor)
// re-activate those constraints
self.animXAnchor.isActive = true
self.animYAnchor.isActive = true
// 1/2 second animation
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
animCard()
}
}
It looks like this:
Each time you tap anywhere the code will add a new "card" and animate it from the "deck" view to the "card position" view.

How can I adjust the the size of the circle to dynamically adjust to the width and height of any iPhone?

I am using snap kit to set out constraints. The first image is what I'm trying to achieve with the code below. How can I set the constraints off the circle's width and height to be dynamic on any iPhone screen ?
profileImage = UIImageView()
profileImage.layer.borderWidth = 2
profileImage.layer.borderColor = UIColor.lightBlue.cgColor
profileImage.layer.cornerRadius = 130
profileImage.clipsToBounds = true
profileImage.layer.masksToBounds = true
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
profileImage.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
}
enter image description here
enter image description here
Couple points...
First, 71% of the view width will probably be too big. Start around 50% and adjust to your liking.
You are using .cornerRadius = 130 but your imageView may not be that size (certainly not on different devices), so you want to set the corner radius to one-half the width of the image view (or height, doesn't matter since it will be a square 1:1 ratio).
You could wait until viewDidLayoutSubviews() to find out the run-time size of the image view, but if your image view ends up as a subview of another view, it won't be set at that point either.
Much easier to create a very simple UIImageView subclass:
class ProfileImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.borderWidth = 2
layer.borderColor = UIColor.systemBlue.cgColor
layer.cornerRadius = bounds.width * 0.5
clipsToBounds = true
layer.masksToBounds = true
}
}
Then your view controller looks like this:
class ViewController: UIViewController {
var profileImage: ProfileImageView!
// your top margin value
let topMargin: CGFloat = 20
override func viewDidLoad() {
super.viewDidLoad()
profileImage = ProfileImageView()
if let img = UIImage(named: "sampleProfilePic") {
profileImage.image = img
}
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
// respect safe-area
let g = view.safeAreaLayoutGuide
// this should give the same layout as your "snap" constraints
NSLayoutConstraint.activate([
// center horizontally
profileImage.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// your topMargin value * 2 from safe-area top
profileImage.topAnchor.constraint(equalTo: g.topAnchor, constant: topMargin * 2.0),
// width
// 71% of width of safe-area is probably too wide
// try starting at 50%
profileImage.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.50),
// 1:1 ratio (square)
profileImage.heightAnchor.constraint(equalTo: profileImage.widthAnchor),
])
// profileImage.snp.makeConstraints { (make) in
// make.centerX.equalToSuperview()
// make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
// make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
// }
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
}
}
Whenever the size of your profileImage view changes, the ProfileImageView class' layoutSubviews() will automatically set the corner radius appropriately.

adding multiple image views by code

am trying to add image views while looping in an array.. like this:
let v = [1,2,3]
w = self.playgroundimg.frame.width/3 - 48.89/2
h = self.playgroundimg.frame.height/4 - 17
for x in v {
let imageName3 = "lineupcardbg"
let image3 = UIImage(named: imageName3)
let imageView3 = UIImageView(image: image3!)
imageView3.frame = CGRect(x: w, y: h, width: 48.89, height: 70.15)
self.playgroundimg.addSubview(imageView3)
w = w + self.playgroundimg.frame.width/3 - 48.89 - 15
}
this is working fine, but the dimension of the imageview will be different from device to device .. like this:
iphonex:
iphone 7 plus:
as you can see the one in the middle is not centered to the one below it .. how to achieve this so the middle is in the center and the other two are beside it with some space? and be displayed the same in all sizes?
playgroundimg constraints:
Here is a simple example - just to show how to use constraints from code.
You can paste this into a new playground page and run it to see the results.
Change the line:
get { return CGSize(width: 400, height: 400) }
to different sizes to see that the views remain centered.
import PlaygroundSupport
import UIKit
class RoundedImageView: UIImageView {
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 8
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class AlignViewController: UIViewController {
override public var preferredContentSize: CGSize {
get { return CGSize(width: 400, height: 400) }
set { super.preferredContentSize = newValue }
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
var theImageViews = [UIImageView]()
for _ in 0..<4 {
let imgView = RoundedImageView(frame: CGRect.zero)
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.backgroundColor = .white
view.addSubview(imgView)
// make all views the same size and width
NSLayoutConstraint.activate([
imgView.widthAnchor.constraint(equalToConstant: 48.89),
imgView.heightAnchor.constraint(equalToConstant: 70.15),
])
theImageViews.append(imgView)
}
// for easy identification of which views we're referencing
let leftView = theImageViews[0]
let centerView = theImageViews[1]
let rightView = theImageViews[2]
let bottomView = theImageViews[3]
NSLayoutConstraint.activate([
// "center view" will be centered horizontally, and a little above centered vertically
centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25),
// left view is aligned to top of center view, and 20-pts to the left
leftView.topAnchor.constraint(equalTo: centerView.topAnchor),
leftView.trailingAnchor.constraint(equalTo: centerView.leadingAnchor, constant: -20),
// right view is aligned to top of center view, and 20-pts to the right
rightView.topAnchor.constraint(equalTo: centerView.topAnchor),
rightView.leadingAnchor.constraint(equalTo: centerView.trailingAnchor, constant: 20),
// bottom view is centerX aligned to center view, and 20-pts below
bottomView.topAnchor.constraint(equalTo: centerView.bottomAnchor, constant: 20),
bottomView.centerXAnchor.constraint(equalTo: centerView.centerXAnchor),
])
}
}
let viewController = AlignViewController()
PlaygroundPage.current.liveView = viewController
There are many ways to do things in autolayout. One way to solve this is to set the Leading and Trailing autolayout constraints for the playgroundimg view. That would make playgroundimg.frame.width adapt to the device screen width. And make sure that the constraints of the playgroundimg's superview are set.
In Xcode it would look like this for example:
You are using playgroundimg.frame.width in calculating the h and w variables, which I would suggest changing their names to x and y respectively since they denote coordinates and not a width or height:
x = self.playgroundimg.frame.width/3 - 48.89/2
y = self.playgroundimg.frame.height/4 - 17
And update this line too :
x = x + self.playgroundimg.frame.width/3 - 48.89 - 15

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)
*/
})
})
}
}

How to keep a round imageView round using auto layout?

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

Resources