UIImageView in UIScrollView does not zoom correctly - ios

I want to allow the user to side pan an image. The image should be scaled to the height of the device and the user is supposed to only be able to scroll left and right. The users is not supposed to be able to zoom.
I have a UIViewController, to which I add a custom subclass ImageScrollView.
This is supposed to display an image in full height, but instead the image is basically displayed un-zoomed. Even though the zoomScale gets calculated correctly, but does not have an effect.
What am I doing wrong?
Thanks
override func viewDidLoad() {
super.viewDidLoad()
let i = UIImage(named: "test.jpg")
let iSV = ImageScrollView(image: i)
self.view.addSubview(iSV)
iSV.fillSuperview()
}
class ImageScrollView: UIScrollView {
let image: UIImage
let imageView = UIImageView()
init(image img: UIImage) {
image = img
imageView.image = image
super.init(frame: .zero)
self.addSubview(imageView)
imageView.fillSuperview()
self.contentSize = image.size
self.showsHorizontalScrollIndicator = false
self.showsVerticalScrollIndicator = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.zoomScale = getZoomScale()
}
func getZoomScale() -> CGFloat{
let boundSize = self.frame.size
let yScale = boundSize.height / image.size.height
return yScale
}
}
And just for the case it has to do with auto-layout I included the fillSuperview extension.
extension UIView {
public func fillSuperview() {
translatesAutoresizingMaskIntoConstraints = false
if let superview = superview {
topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor, constant: 0).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
rightAnchor.constraint(equalTo: superview.rightAnchor, constant: 0).isActive = true
}
}
}

Since you don't want the image to zoom, I recommend you don't even bother with the zoom controls. A UIImageView knows how to scale its content.
I recommend you do it like this:
Add a constraint that sets the imageView height equal to the scrollView height. This will prevent vertical scrolling.
Add a constraint that sets the imageView width equal to the imageView height with multiplier image.size.width / image.size.height.
Set imageView content mode to .scaleToFill.
To allow you to change the image, keep an aspectRatio property that retains the aspect ratio constraint set in step 2. Set aspectRatio.isActive = false, and then create and activate a new constraint for the new image.
Also, if you might ever have images that aren't wide enough to fill the scrollView horizontally when scaled to fit vertically, consider these changes:
Replace the constraint that sets the imageView width with one that sets the width equal to the imageView height with multiplier max(image.size.width / image.size.height, scrollView.bounds.width / scrollView.bounds.height).
Set imageView content mode to .scaleAspectFit.
Then, when you have a narrow image, the imageView will fill the scrollView, but the .scaleAspectFit will show the entire image centered in the scrollView. This will still work correctly for wide images because the multiplier will match the image aspect ratio and .scaleAspectFit will fill the entire imageView.

You forgot to implement scrollview delegate. And set min & max zoom level for scrollview.
var iSV: ImageScrollView?
let i = UIImage(named: "noWiFi")!
iSV = ImageScrollView(image: i)
if let iSV = iSV {
self.view.addSubview(iSV)
iSV.fillSuperview()
iSV.delegate = self
}
override func viewDidLayoutSubviews() {
if let iSV = iSV {
let scale = iSV.getZoomScale()
iSV.minimumZoomScale = scale
iSV.maximumZoomScale = scale
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return iSV?.imageView
}
}
Note: I just did rough. It may not fulfil your complete requirement

Related

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.

How to change WIDTH of UITableView with animation?

I have UITableView on the left size and plain UIView on the right side in my UIViewController
UITableView connects to .top, .leading and .bottom of
superview
UIView connects to .top, .trailing and .bottom of
superview, also it has .width
And UITableView .leading ==
.trailing of UIView
All these constraints you can see on the screenshot:
Here is my animation code in the controller, as you can see I update .width of UIView and also I cycle the animation.
class ViewController: UIViewController {
#IBOutlet var widthConstaint: NSLayoutConstraint!
#IBOutlet var tableView: UITableView!
#IBOutlet var watchContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
if self.widthConstaint.constant == 50 {
self.change(width: 100)
} else {
self.change(width: 50)
}
self.start()
}
}
func change(width: CGFloat) {
widthConstaint.constant = width
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 300
}
}
As a result you can see this on .gif, what I should for smooth animation, without this glitch?
UPDATE
I tried to reproduce the UILabel wrong behavior with my own UIView subclass:
class MyView: UIView {
var currentSize: CGSize = .zero
override func layoutSubviews() {
super.layoutSubviews()
guard currentSize != bounds.size else { return }
currentSize = bounds.size
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
UIColor.purple.setFill()
UIBezierPath(rect: rect).fill()
let square = CGRect(x: rect.width - 50, y: rect.origin.y, width: 50, height: rect.height)
UIColor.blue.setFill()
UIBezierPath(rect: square).fill()
}
}
The view draws a little blue rectangle at its right, as a text. And… well… the animation is also good!
But if I change the contentMode to left (the default value is scaleToFill):
The blue square jumps from one place to the other.
So, I found out this is not only related to the UILabel's textAlignment. It's its combinaison with the default left content mode of the UILabel.
contentMode defines how the content should be adjusted if the content's size and the bounds size are different - this is case during the animation, the view redraws itself with its new size right before being resized.
So
label.contentMode = .left // default
label.textAlignment = .left
will behave as:
label.contentMode = .right
label.textAlignment = .right
PREVIOUS ANSWER
I spent some time on your problem. Guess what?
It's not related to the table view at all. It's because of the .right text alignment of the label.
You can reproduce the glitch with a simple UILabel:
class ViewController: UIViewController {
let label = UILabel()
var rightConstraint: NSLayoutConstraint!
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
start()
}
func start() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
if self.rightConstraint.constant == 50 {
self.change(width: 100)
} else {
self.change(width: 50)
}
self.start()
}
}
func change(width: CGFloat) {
rightConstraint.constant = width
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
}
func setUpView() {
label.text = "Label"
label.textAlignment = .right
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
rightConstraint = view.trailingAnchor.constraint(equalTo: label.trailingAnchor)
rightConstraint.isActive = true
label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
}
Why?
To find out the error, I created a UILabel subclass and overrode action(for: CALayer, forKey: String). It's where each UIView decides which animation to apply in response to a property change (ref). I was looking for some weird animation behaviors when a label is inside a table view.
If you print key, you'd see that actions are requested for the position, the bounds and the contents of the label when UIKit is about to commit the animations related to your UIView.animate(withDuration:) call.
In fact, setNeedsDisplay is called each time the size of an UILabel is changed (it makes sens). So I guess there is a conflict between the content redrawing of the label and its frame change.
I think you should recreate the right text alignment of the label without using textAlignment = .right.
Horizontally, pin the label to the right only. When layoutIfNeeded will be called, only the label's position will change, not its content.
As mentioned in the #Gaétanz answer. The abnormal behaviour is due to the Lable inside the cell.
You can remove the glitches by changing the Labels leading constraint relation to >= and change the priority to 999 from 1000.
Please refer the screenshot attached for your reference.
When you add constraint with a priority less than 1000 it will be an optional constraint. Priority 1000 is the required priority for a constraint. Here if you added a priority of 999, all the other constraints with priority 1000 will layout first and the less priority constraint will layout last.
You are changing the width of your side view outside of the animation block. put you widthConstaint.constant = width inside your animation block.
func change(width: CGFloat) {
UIView.animate(withDuration: 1.0) {
self.widthConstaint.constant = width
self.view.layoutIfNeeded()
}
}
According to your code the View is animated but the constraint is not put in the UIView animation block. So it looks like a glitch cause it changes the frame directly. Please try to change the code as below and try.
func change(width: CGFloat) {
UIView.animate(withDuration: 1.0) {
widthConstaint.constant = width
self.tableView.layoutIfNeeded()
self.view.layoutIfNeeded()
}
}

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

How to create a button with an icon on the right-hand side?

I am trying to put an icon next to a button label.
Problem: Interface Builder puts the button-image not to the right side. So there is still some space next to the (x).
Thanks for your help
You can do this in Interface Builder by setting the title and image insets:
In this case I've set the title right inset to 30 and the image left inset to 80. You can achieve the same in code by setting the UIButton's imageEdgeInset and titleEdgeInset properties. Knowing the size of the UIButton's subviews, you could probably calculate the edge insets using something like this:
CGSize labelWidth = myButton.titleLabel.frame.size.width;
CGSize imageWidth = myButton.imageView.frame.size.width;
myButton.titleEdgeInsets = (UIEdgeInsets){0.0, -imageWidth, 0.0, imageWidth};
myButton.imageEdgeInsets = (UIEdgeInsets){0.0, labelWidth, 0.0, -labelWidth};
In Xcode 8 you will find those attributes in the Size inspector:
select image(if (x) is image) or title (if (x) is simply key) on the window and set left , Right offsets according to your need
i have achieved this:
class CustomButton: UIButton {
open var rightIcon: UIImage? {
didSet {
rightIconImage = UIImageView(image: rightIcon?.withRenderingMode(.alwaysTemplate))
rightIconImage?.tintColor = .white
if let rightIconImage = rightIconImage {
addSubview(rightIconImage)
}
layoutSubviews()
}
}
open override func layoutSubviews() {
super.layoutSubviews()
setupRightIconIfNeeded()
}
func setupRightIconIfNeeded() {
if let rightIconImage = rightIconImage {
rightIconImage.translatesAutoresizingMaskIntoConstraints = false
titleLabel?.translatesAutoresizingMaskIntoConstraints = false
if let titleLabel = titleLabel, titleLabel.frame.width > 0 {
let paddingLeft = (frame.width - titleLabel.frame.width - spacing - iconSize) / 2
NSLayoutConstraint.deactivate(constraints)
NSLayoutConstraint.deactivate(titleLabel.constraints)
NSLayoutConstraint.activate([
//set height constraint for button
heightAnchor.constraint(equalToConstant: apolloSize.height),
//set constraint for title label
titleLabel.widthAnchor.constraint(equalToConstant: titleLabel.frame.width),
titleLabel.heightAnchor.constraint(equalToConstant: titleLabel.frame.height),
titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: paddingLeft),
//set constraint for right icon
rightIconImage.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: spacing),
rightIconImage.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor),
rightIconImage.widthAnchor.constraint(equalToConstant: iconSize),
rightIconImage.heightAnchor.constraint(equalToConstant: iconSize)
])
}
}
}
}

Resources