I'm new to iPhone developing. I have a screen with image, title and content:
Image has dynamic size and also all text can have any length. What I want to achievie is to fit the image exactly in screen and make all labels to be only as long as they should be.
Afterk about 5 hours of work I have:
Working labels length, achieved by setting Lines=0 and Preferred Width=content width
Not working image, which lefts blank spaces
I have read a lot, I understand Content Hugging Priority and Content Compression Resistance Priority. I know differences in UIViewContentMode. I know that I can manually set UIImage.frame=CGRectMake(...). Nothing works. My image is more wider than higher (lets assume that width/height = 3). If I have UIViewContentMode=Aspect Fit then I will have blank space above and below the image. If set to Aspect fill then image is too high and also cropped. Changing frame in code doesn't change anything in view. What I want is to have image with size that was made using Aspect fit, but without blank space.
Finally I dit it. First of all, everyone writes that UIImageView.Frame should be modified to shrink the image, but be aware, that if your view is UITableViewCell then you should do that in layoutSubviews method (remember to invoke super.layoutSubviews() at the begging).
Secondly, make in IB a height constraint for your UIImageView for temporary value (for example 100). You will change that constraint in layoutSubviews method.
And finally: fitting image is NOT about its ratio. Set UIViewContentMode=Aspect Fit. Then the case with blank top and bottom areas occurs only when the initial image is wider than screen. Calculating the proper height should also take place in layoutSubviews.
Example implementation:
override func layoutSubviews() {
super.layoutSubviews()
var imageWidth = (imageViewObject.image?.size.width)!
var imageHeight = (imageViewObject.image?.size.height)!
var frameWidth = UIScreen.mainScreen().bounds.width
var properHeight: CGFloat
if imageWidth > frameWidth {
var ratio = frameWidth / imageWidth
properHeight = imageHeight * ratio
}
else {
properHeight = imageHeight
}
imageHeightConstraint.constant = properHeight
imageViewObject.frame = CGRectMake(0, 0, frameWidth, properHeight)
}
Related
I am developing an app where I have this problem, regarding showing some products.
The product cell consists of an image and a label under it.
The image and label is inside a UIView because I need corner radius and shadow around the image and label.
But if the label text increases it will resize my image and make it smaller instead of making the parent UIView height larger.
Does anyone know or have an example for that? I want to tell it that it should make the parent UIView bigger instead of making the image inside the UIView smaller.
Image of my problem:
let ratioConstId = "ratio_const"
if let ratioConst = ivPetIcon.constraints.first(where: { $0.identifier == ratioConstId }) {
ivPetIcon.removeConstraint(ratioConst)
}
let ratio = ivPetIcon.image!.size.height / ivPetIcon.image!.size.width
let const = ivPetIcon.heightAnchor.constraint(equalTo: widthAnchor, multiplier: ratio)
const.isActive = true
const.identifier = ratioConstId
I want my view to have the following properties (the numbers are arbitrarily chosen):
width is equal to height divided by 1.2
stays at the bottom right of the screen
height is 1/7 of the screen's height when in portrait
width and height does not change when the orientation changes
The first three requirements can be easily translated into UILayoutConstraints. I have done them with SnapKit just because it reads more clearly. You should see what I mean even if you have never used SnapKit before.
let myView = UIView(frame: .zero)
myView.backgroundColor = .green
view.addSubview(myView)
myView.snp.makeConstraints { (make) in
make.right.equalToSuperview().offset(-8)
make.bottom.equalToSuperview().offset(-8)
make.width.equalTo(myView.snp.height).dividedBy(1.2)
make.height.equalTo(view.snp.height).dividedBy(7) // *
}
The problem is the last bullet point. When I rotate the device from portrait to landscape, what was originally the width before the rotation, becomes the height after the rotation. This causes my view to become smaller as a result.
Basically, I want to replace the constraint marked with * with something like this:
make.height.equalTo(max(view.snp.height, view.snp.width)).dividedBy(7)
but I don't think max(a, b) is a thing in SnapKit or the UILayoutConstraint API.
Surely there is some other way of expressing "equal to whichever length is longer", right?
P.S. I didn't tag this with snapkit because I would also accept an answer that uses the UILayoutConstraint API.
Looks like you have 2 options:
Hardcode the height value.
Try to use nativeBounds:
This rectangle is based on the device in a portrait-up orientation. This value does not change as the device rotates.
In this case the height is always be for portrait mode.
myView.snp.makeConstraints { make in
make.right.bottom.equalToSuperview().offset(-8)
let screenHeight = UIScreen.main.nativeBounds.height / UIScreen.main.nativeScale
let height = screenHeight / 7
make.width.equalTo(height).dividedBy(1.2)
make.height.equalTo(height)
}
I am working through the tutorial of JTCalendar (version 6.1.5). When I run on smaller phones such as the iPhone SE, one side of the circle in the Selection View gets clipped. This because the cells are about 45x45 points, but the Selection View's size is 50x50 points and is therefore too large to fully fit into the cell.
How can I make my selection view fit properly into date cells of varying sizes?
How can I get the proper value of cornerRadius for the selection view circle?
I was able to resolve this issue. The problem is that the tutorial set the size of the Selected View and left it at that. What I did was
Made Outlets for both the width and height constraints in CellView.swift
In ViewContoller.swift, I modified cell selection as follows:
if cellState.isSelected {
var parentMinDimension = min(view.frame.width, view.frame.height)
parentMinDimension = round(parentMinDimension - 0.5)
myCustomCell.widthConstraint.constant = parentMinDimension
myCustomCell.heightConstraint.constant = parentMinDimension
myCustomCell.selectedView.layer.cornerRadius = parentMinDimension / 2
myCustomCell.selectedView.isHidden = false
} else {
myCustomCell.selectedView.isHidden = true
}
This gets the parent view and determines the smaller dimension. This needs to be rounded down. I then uses this parent view dimension to set the width and height of the CellView as well as for determining the corner radius.
How can I get the height of my custom control?
The idea is I will use it to dynamically set the height of some buttons inside the custom control. I've set the Placeholder height to 44 in the Xcode size inspector.
Working off Apple's Start Developing iOS Apps (Swift) tutorial, I am attempting to access frame.size.height and it gives a value of 1000 while the tutorial seems to suggest it should be 44.
class RatingControl: UIView {
...
override public var intrinsicContentSize: CGSize {
let buttonSize = Int(frame.size.height)
print(buttonSize) // prints 1000
let width = (buttonSize * starCount) + (spacing * (starCount - 1))
return CGSize(width: width, height: buttonSize)
}
...
You should never access frame inside intrinsicContentSize. intrinsicContentSize should return the size that perfectly fits the contents of the view, regardless of its current frame.
In your case, I think you can just use 44 for your buttonSize.
The placeholder intrinsic size is just that, placeholder, so that IB interpreter is has some value to work with and can layout the rest of the scene. But in your intrinsicContentSize getter, you implement the real size, which will be used in runtime by the AutoLayout engine. Since you return 1000 as the intrinsic content height, that's what you will see in runtime.
I am trying to make my multiline UILabel as small as possible with given max-width, but the sizeToFit() and sizeThatFits(_,_) methods aren't giving the results I want.
Take a look at this image:
The red rectangle represents the width that I pass in sizeThatFits (while height being Max). Obviously, as you can see, this method does in fact return a size that fits, but it does not give me the smallest size possible, which I want.
Let's say I specified max-width: 300. This actual result is giving me a size of ca. 280*50.
As you can see in the image, the text is now written like this:
Here is some text that is supposed to
align nicely
What I want to achieve is this:
Here is some text that is
supposed to align nicely
This result would've had the same height, but a much smaller width, e.g 200*50
I realize that it's difficult to define "smallest size possible", as it could return this:
Here
is
some
text
that
...
Or even just a single letter per line. But given that sizeThatFits returns this, with a given width and height, why doesn't it return my wanted result, which is the same height, but with smaller width. Why isn't the smallest fit returned? Does a function like this exist?
Sti,
You can try this buddy :) I have been using it and seems to do a pretty good job :)
CGRect rect = [yourText boundingRectWithSize:CGSizeMake(maxWidth_you_Can_afford, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:16]}
context:nil];
The rect after executing will have the minimum width and height that will be required to render the text :)
Once you have the frame now you can set the same for your label and set the text as well :)
I have used it in chat bubble :) and it works well :)
Happy coding :)
I made my own solution, which works pretty well:
private func calculateWantedSize(label:UILabel)->CGSize{
var lastAcceptableWidth = label.bounds.width
let currentHeight = label.sizeThatFits(CGSize(width: label.bounds.width, height: CGFloat.max)).height
var tempHeight = currentHeight
while(tempHeight == currentHeight){
let newWidth = lastAcceptableWidth - 1
tempHeight = label.sizeThatFits(CGSize(width: newWidth, height: CGFloat.max)).height
if tempHeight == currentHeight{
lastAcceptableWidth = newWidth
}
}
return CGSize(width: lastAcceptableWidth, height: label.bounds.height)
}
Here I'm sending my label to a function which first calculates the current width and height of the label, then loops through width-1 and checks the resulting height until the height is changed. The height will change when the width is so low that it needs a new line. When this happens, I return the last valid width which still needed the same height as original.
This is perfect for my problem, using attributed text etc.
Of course, when using this, be sure to do some checking before calling. I don't know what will happen if you use this function when the label has a single word, or a single letter, or no text at all. I have only tested this for the cases I know will happen with this app, and it works well.