Setting the size of UIImageView proportionally - ios

I have a question about how to set the size of UIImageView based on iPhone's display size dynamically.
If the size of a display of iPhone decreases, I want to make the size of UIImageView decreased proportionally.
I'm setting lowerThumbImageView and upperThumbImageView like below for now.
override init(frame: CGRect) {
super.init(frame: frame)
trackLayer.rangeSlider = self
// contentsScale: CGFloat - The scale factor applied to the layer.
// scale: CGFloat - The natural scale factor associated with the screen.
trackLayer.contentsScale = UIScreen.main.scale
// UIControl -> UIView -> layer
layer.addSublayer(trackLayer)
lowerThumbImageView.image = thumbImageA
addSubview(lowerThumbImageView)
upperThumbImageView.image = thumbImageB
addSubview(upperThumbImageView)
}
// 1
private func updateLayerFrames() {
// insetBy(dx:dy:) - Returns a rectangle that is smaller or larger
// than the source rectangle, with the same center point.
trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
trackLayer.setNeedsDisplay()
lowerThumbImageView.frame = CGRect(origin: thumbOriginForValue(lowerValue),
size: thumbImageA.size)
upperThumbImageView.frame = CGRect(origin: thumbOriginForValue(upperValue),
size: thumbImageB.size)
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.commit()
}
// 2
func positionForValue(_ value: CGFloat) -> CGFloat {
return bounds.width * value
}
// 3
private func thumbOriginForValue(_ value: CGFloat) -> CGPoint {
let x = positionForValue(value) - thumbImageA.size.width / 2.0
return CGPoint(x: x, y: (bounds.height * 0.001 - thumbImageA.size.height) / 2.0)
}
Could you give me your advice?
Thanks for reading.

You can set size of UIImageView proportional to any other View. Here are the steps to do it using Interface Builder Autolayout Constraints.
Go to Screen containing UIImageView on (Interface Builder/StoryBoard/XIB).
Press control button + drag mouse from UIImageView to SuperView or main View, Select "Equal Widths" & "Equal Heights".
Now from the right side in SizeInspector, Edit "Proportional Width" constraint & set its Multiplier value to 0.8 if you want to set width of UIImageView 80% to SuperView.
Edit "Proportional Height" constraint & set its Multiplier value to 0.7 if you want to set width of UIImageView 70% to SuperView.
I've attached images for your ease.

Related

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

iPad: Specific/exact position of items for different resolutions

I would like to position UI elements at specific positions on an image, for all iPad resolutions.
Imagine a ground plan with different icons/buttons on top. Each icon should be at a very specific position (e.g. exactly in the kitchen, floor, ...). When changing the device/resolution (iPad only) the icons should stay at the correct position, according to the background/ground plan imageView.
See the images (only quick examples): The smallest iPad (9.7") would be the correct position. The other image (12.9") shows the wrong position. (For all iPad sizes, I only chose two examples)
9.7":
12.9":
I can't get a way or idea to achieve this positioning problem.
You can do this by making your "bulb" positions relative to the size of the "floor plan" image view.
For example:
suppose your floor plan image is 1800 x 1500 and your bulb images are 96 x 96 (I'm estimating, based on the images you posted)...
and let's say the center of the top-left bulb is at 276, 486
and the imageView holding your floor plan is 900 x 750 (half the original size)
You would set:
xScale = 900 / 1800 (0.5)
yScale = 750 / 1500 (0.5)
bulb width = 96 * xScale
bulb height = 96 * yScale
bulb center = 276 * xScale, 486 * yScale
Here is some sample code that may help you get started:
class FloorPlanViewController: UIViewController {
#IBOutlet var floorPlanView: UIImageView!
var bulbs: [UIImageView] = [UIImageView]()
var centers: [CGPoint] = [
CGPoint(x: 276, y: 486),
CGPoint(x: 276, y: 648),
CGPoint(x: 640, y: 486),
CGPoint(x: 640, y: 648),
CGPoint(x: 877, y: 486),
CGPoint(x: 877, y: 648),
]
override func viewDidLoad() {
super.viewDidLoad()
for _ in centers {
let v = bulbImageView()
floorPlanView.addSubview(v)
bulbs.append(v)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let fpImage = floorPlanView.image {
let xScale = floorPlanView.frame.width / fpImage.size.width
let yScale = floorPlanView.frame.height / fpImage.size.height
for i in 0..<centers.count {
let thisCenter = centers[i]
let thisBulb = bulbs[i]
thisBulb.frame.size.width = 96.0 * xScale
thisBulb.frame.size.height = 96.0 * yScale
thisBulb.center = CGPoint(x: thisCenter.x * xScale, y: thisCenter.y * yScale)
}
}
}
func bulbImageView() -> UIImageView {
let v = UIImageView()
if let img = UIImage(named: "bulb96x96") {
v.image = img
} else {
v.backgroundColor = .red
}
return v
}
}
Yes, this will be difficult because in the screenshots you added, the images look to be fitted to the screen size, meaning their content is transformed to fit within the frame. In that case, you will have have to calculate a transform ratio between the actual image size and the frame size and update your child view's positions to the new ratio.
One alternative is to embed the image view inside of a scrollview and have it match the image's full-size height and width. In this case, you would always have a 1:1 aspect ratio, and not have to reposition the child views. However, you would have to scroll within the scrollview to view the image.

Swift 3 - Scale round ImageView

I have a round Imageview in my ReusableCollectionView.
When I scroll down my collectionView I scale my Imageview and as soon as it scrolls back to place I scale it to its original size.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Exit early if swiping up (scrolling down)
if scrollView.contentOffset.y > 0 { return }
// this is just a demo method on how to compute the scale factor based on the current contentOffset
var scale = 1.0 + fabs(scrollView.contentOffset.y) / scrollView.frame.size.height
//Cap the scaling between zero and 1
scale = max(0.0, scale)
// Set the scale to the imageView
headerView.imageview.transform = CGAffineTransform(scaleX: scale, y: scale)
headerView.categoryButton.transform = CGAffineTransform(scaleX: scale, y: scale)
}
The imageview is not round anymore while doing so.
Here is an image visualising the problem:
This is how I solved the problem:
imageView.autoresizesSubviews = false

Limit image size to ScrollView, Swift

I have an image which is set inside a scroll view, though I have set the frame of the scrollView to fixed height and width as shown below, the image goes beyond the bounds (see below picture).
How can I limit the picture to fit inside the scrollView.
imageScrollView.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight-50)
imageScrollView.clipsToBounds = true // Has no affect on the image
Do you have a reference to the UIImageView? If so, then set its content mode to aspect fit. Like this:
theImageView.contentMode = .scaleAspectFit
The clipsToBounds you set only covers up any parts of child views that are sticking out of the bounds of the parent view, so that's why it doesn't do anything for you.
OR if you're using Interface Builder, set this option:
So, what if you don't have the reference to the UIImageView?...
You could iterate through the subviews of your scroll view, and whenever it finds a UIImageView, you can set the content mode like that. Something like:
//This is off the top of my head, so my filtering may not be right...
//This is also a one and done solution if you've got a lot of images in your scroll view
for anImgVw in imageScrollView.subviews.filter({$0.isKind(of: UIImageView.self)})
{
anImgVw.contentMode = .scaleAspectFit
}
Otherwise, I'm not sure if it's possible without a reference to the UIImageView.
The library you are using is coded to match the scaling to the device orientation. So, if the image orientation doesn't match the view orientation, you end up with the image not quite fitting in your scroll view.
You'll need to edit the ImageScrollView.swift source file. Assuming you're using the same version that is currently at the link you provided ( https://github.com/huynguyencong/ImageScrollView ), change the setMaxMinZoomScalesForCurrentBounds() function as follows:
fileprivate func setMaxMinZoomScalesForCurrentBounds() {
// calculate min/max zoomscale
let xScale = bounds.width / imageSize.width // the scale needed to perfectly fit the image width-wise
let yScale = bounds.height / imageSize.height // the scale needed to perfectly fit the image height-wise
// fill width if the image and phone are both portrait or both landscape; otherwise take smaller scale
//let imagePortrait = imageSize.height > imageSize.width
//let phonePortrait = bounds.height >= bounds.width
//var minScale = (imagePortrait == phonePortrait) ? xScale : min(xScale, yScale)
//
// just take the min scale, so the image will completely fit regardless of orientation
var minScale = min(xScale, yScale)
let maxScale = maxScaleFromMinScale*minScale
// don't let minScale exceed maxScale. (If the image is smaller than the screen, we don't want to force it to be zoomed.)
if minScale > maxScale {
minScale = maxScale
}
maximumZoomScale = maxScale
minimumZoomScale = minScale * 0.999 // the multiply factor to prevent user cannot scroll page while they use this control in UIPageViewController
}
you can use the screenHeight rather than the viewHeight
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
imageScrollView.frame = CGRect(x: 0, y: 0, width: viewWidth, height: screenHeight-50)

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