Prevent vertical UIStackView from stretching a subview? - ios

I have a UITableViewCell with a vertical UIStackView that currently has .alignment = fill and distribution = fill. Inside the stack view, I have a UILabel and a UIImageView.
The label should be left aligned and stretch across the width of the stackview (and it does). The image view has a content mode of aspect fit and should be centered in the middle of the stackview. I thought this was working as I wanted it to until I set a background color on the UIImageView and then realized that while the image itself appears correct (scaled and centered) the view itself stretches across the full width of the stackview (just like the label does).
Is it possible to have the label stretch the width of the stack view but not the image view? Ideally, I'd like the image view to just be the size of the scaled image and not stretch all the way to the edges of the stack view.

Make these changes:
Set your stack view's alignment to .center instead of .fill.
Constrain the label's width to equal the stack view's width.
In code, when you set the image view's image, also create an aspect-ratio constraint on the image view, like this:
class MyCell: UITableViewCell {
#IBOutlet private var myImageView: UIImageView!
private var imageViewAspectConstraint: NSLayoutConstraint?
func setImage(_ image: UIImage) {
myImageView.image = image
imageViewAspectConstraint?.isActive = false
imageViewAspectConstraint = myImageView.widthAnchor.constraint(
equalTo: myImageView.heightAnchor,
multiplier: image.size.width / image.size.height)
imageViewAspectConstraint!.isActive = true
}
}
Note that, since cells can be reused, you also have to remove a prior aspect-ratio constraint if there is one. The code I posted uses imageViewAspectConstraint to save the constraint and remove it when the image changes.

One approach:
calculate appropriate aspect-fit frame for your image
When the user taps on the image view, evaluate the tap position and only take action if the tap is within the aspect-fit frame.
Another approach:
calculate appropriate aspect-fit frame for your image
embed the UIImageView horizontally centered inside a UILayoutGuide
add the UILayoutGuide as the stack view's arranged subview
This will keep your label stretched across the width of the stack view (the cell) and center your image view, allowing you to detect the taps.

Related

Textview bounds UIImageView out of the View

I'm trying to rebuild the twitter post tweet feature as well as the UI. I'm currently at the point that, when I type something long in the UITextView. My selected image that I want to tweet get bounds out of the container view that is in a scrollview.
See this example:
The blue background is the scrollview background and the grey background is the container views background. I can't scroll too.
My UIImageView has initially 0 height and is directly attached to the top of the textview.
I change the height of the UIImageView with their constraint:
// sets the image
let newHeight = self.postedImageView.getImageHeight(forImage: image)
self.postedImageViewHeightConstraint.constant = newHeight
self.view.layoutIfNeeded()
self.postedImageView.showImage(image)
How can I make this the scrollviews contentView height to grow as I type something in the textview so it doesn't get bounds out the view?
These are my constraints:
Update:
With the postedimageviews bottom constraint added to the content views bottom

UIImageView frame same as scaled UIImage inside it (with Aspect Fit)

I'm using UIImageView with Content Mode set to Aspect Fit. UIImageView resizes correctly my image but it doesn't change own frame after resizing even there is no constraints set.
What I need:
I need UIImageView to resize own frame according image inside. I've configured image view at Storyboard.
I need to layout again other objects at view because UIImageView's size is changed.
What I've tried:
I've created:
UIViewController (white background) → UIImageView (gray background) → sample image inside it.
Constraints: leading & trailing constraints set to zero, Y constraint is set to align UIImageView vertically.
As you see after image scaled correctly UIImageView's frame is still has incorrect size (gray background).
I've tried to set UIImageView size manually:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let newSize = AVMakeRect(aspectRatio: backgroundImageView.image!.size, insideRect: backgroundImageView.frame)
backgroundImageView.frame = newSize
}
In that example code I've calculated image size and set frame size to image's size. I don't know if it is better way so I don't need to use AVFoundation's methods.
Problem: other object is place incorrectly because UIImageView's size changed and constraints are invalid now.
That methods doesn't work for me:
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
Even I will call my UIImageView size calculation at viewDidLayoutSubviews() other constraints is still using old UIImageView's size.
Questions
How to change UIImageView's size correctly? Maybe I can do it without code? Maybe I need to play with Hugging/Compression? I've tried something but got no luck.
How to force view to recalculate other constraints so they will use new UIImageView size? For example I can put red square at the top of UIImageView, but after UIImageView has been resized by my method, red square still will be at the old UIImageView's position.

what is wrong with constraints in my UIImageView and why I cannot stretch the image on my UIViewController?

This is my current situation:
I have a UIViewController with UIImageView on it.
The latter has the following constraints: trailing to superview, leading to superview, top space to top layout guide, width equals 320, height equals 820.
Height and width are connected as outlets to my class and I'm setting them in viewDidLoad:
#IBOutlet weak var imageHeightconstraint: NSLayoutConstraint!
#IBOutlet weak var imageWidthConstraint: NSLayoutConstraint!
#IBOutlet weak var backgroundImage: UIImageView!
override viewDidLoad(){
backgroundImage.af_setImageWithURL(NSURL(string: photoURL)!)
imageHeightconstraint.constant = backgroundImage.image!.size.height
imageWidthConstraint.constant = self.view.frame.size.width
}
Now, my image is this:
it is 480x640px photo. I tried in my storyboard to set the mode to center, scale to fill, aspect fill, aspect fit and top.
For example, this is center:
aspect fit:
aspect fill:
but I don't know how to stretch this image from left to right and keep the aspect ratio, so the whole photo is visible and its left and right edges stick to the left and right edge of the screen.
I thought those two constraints could fix it:
imageHeightconstraint.constant = backgroundImage.image!.size.height
imageWidthConstraint.constant = self.view.frame.size.width
but it does not work. Maybe the problem is that the resolution of the photo is low (480x640), so the height is only that small? Can you give me any hint how could I fix it?
To have the image stick to both sides of screen and maintain the aspect ratio, set these 4 constraints:
Leading Edge of ImageView to Leading Edge of SuperView.
Trailing Edge of ImageView to Trailing Edge of SuperView.
Top Edge of ImageView to Top Layout Guide Bottom.
Set an aspect ratio constraint: ImageView Width equal to ImageView Height with multiplier 480:640. To set this, control-drag diagonally in the ImageView and select Aspect Ratio from the pop-up. Then change the multiplier to 480:640.
Set the content mode for the image to Scale to fill.
You may not be calling setNeedsLayout() and then layoutIfNeeded().
In order for the UI to update, you need to tell the view to update, not just change the constraints.
Either way, if you want the imageView to consistently fill the screen and you shouldn't have a height/width on the ImageView, only hugging the sides. Also make sure those constraints are ignoring margins.
If you want the image to fill the imageView, set the resizing mode to aspectFill, and If you're looking for it to resize until the left/right or top/bottom reaches the edge of the view, aspectFit is what you need.

ScrollView not resizing properly

I have a ScrollView embedded in my ViewController. Inside the ScrollView I have embedded a content view (UIView) which has a UIImage set to match the left, top and right of the ScrollView with a dynamic height that changes depending on the aspect ratio of an image that the user can load after the ViewController loads. There are three buttons all horizontally aligned in the content view and spaced evenly apart from each other.
When the user loads in a photo that is too big for the screen it should just resize the ScrollView and the content view appropriately to allow scrolling to see the buttons but instead it just bunches up the buttons at the bottom of the screen.
Here is how the buttons should look:
Here is what happens when the photo is too big:
Here are the constraints of the ScrollView:
Here is my resizing code:
let img : UIImage = info[UIImagePickerControllerOriginalImage] as! UIImage
let screenSize: CGRect = UIScreen.mainScreen().bounds
let multiplyNum = screenSize.width / img.size.width
imageViewHeightConstraint.constant = (multiplyNum*img.size.height)
imageView.image = img
Even when I try and change the ScrollViews height programatically to a very large number it still won't get any larger than the ViewController (no scrolling).
Constraints of ContentView:
Constraint of ImageView:
Constraints of first 2 buttons:
Constraints of last button:
Make sure to set all the constraints that completely define the vertical layout of all elements (top constraint for image, vertical space between elements and bottom constraint of last element), and try changing the priority of the content hugging or compression resistance of the elements.
Edit:
You can achieve that behaviour with this view hierarchy:
- UIView
- UIScrollView
- UIImage
- UIButton
- UIButton
- UIButton
There is no necessity to add a container view if you set the constraints like this:
scrollView:
Leading, trailing, top and bottom constraints to view (all 0)
inner views (horizontal):
Leading constraint from imageView to scrollView
Trailing constraint from imageView to scrollView
Equal width from imageView to view
inner views (vertical):
Top constraint from imageView to scrollView
Height constraint of imageView (this constraint constant will change depending on the size of the image)
Horizontal space from imageView to button1
Horizontal space from button1 to button2
Horizontal space from button2 to button3
Bottom constraint from button3 to scrollView
There is no necessity to change the priority of the content hugging or compression resistance of the elements.
I already check that it works in a project.

Flexible image views

I'm working on a view that contains three UIImageViews. The view and the child imageviews are created in the storyboard. The images for these imageviews are loaded from a server.
When the server returns images for all three images, it should look like this:
When there is no image available for the third imageview, the layout should dynamically change like this (with image scaling into the imageview etc, I know that part):
I am already working quite a while with the Interface Builder, but have no idea how to achieve this..
Anyone can help me out figuring this, preferred using autosizing in IB?
This kind of possible using autolayout, but you still have to modify one constraint programatically, based on whether you are displaying one or two UIImageViews.
Here's how you do it, with autolayout:
Create The Container View
Create a UIView as a container.
Move the left edge of the container view to the left margin.
Move the right edge of the container view to the right margin.
Pin the container view's height.
Delete the constraint for the bottom space to superview.
Create The UIImageViews
Create the three UIImageViews inside the container view.
Size and align the UIImage views as you want them to display.
Pin the horizontal spacing between the first and second image view.
Pin the horizontal spacing between the second and third image view.
Pin the leading space on the first image view to the superview.
Pin the trailing space on the third image view to the superview.
Select all three image views and pin the widths equally.
Delete the align center x constraint on the second image view.
Select the trailing space to image view constraint on the second image view and set the priority to 900 (important).
Create the Magic Extra Constraint
Pin the trailing space to superview on the second image view.
Select the new constraint and set the priority to 850.
Set the constant value for the constraint to 0.
The container UIView is simply to maintain the margins and height for the containing views.
The second image view should now have two trailing constraints, both appear as dotted lines. Rotating the screen should have the container view stretching to fit, and the three UIImageViews also stretch to fit.
Currently, the constraint between the second and third image views is higher priority than the "magic" constraint between the second image view and the container view, causing the second image view's right edge to be spaced away from the third image view's left edge. To adjust the layout for just two images, the "magic" constraint has to be higher priority than the other one, causing the second image view's right edge to align with the superview's right edge.
Simply create an IBOutlet for the "magic" constraint (trailing space to superview on the second image view) and raise and lower the priority as needed.
if (imageCount == 2) {
// Second image view should favor right edge of superview
self.myConstraint.priority = 950;
} else if (imageCount == 3) {
// Second image view should favor left edge of third image view
self.myConstraint.priority = 850;
}
You may also wish to hide the third image view when necessary.
My option would be that when third image is null set the isHidden property of third imageView to YES. Then specify the bounds of the other two imageviews accordingly(which, you said, you know how to do).
Check if imageView_3 have no image and then adjust size of rest two imageViews such as:
if (imageView_3.image == nil) {
CGRect frame = imageView_1.frame;
frame.size.width += imageView_3.frame.size.width / 2;
imageView_1.frame = frame;
frame = imageView_2.frame;
frame.size.width += imageView_3.frame.size.width / 2;
frame.origin.x += imageView_1.frame.origin.x + imageView_1.frame.size.width + 10/*the gap*/;
imageView_2.frame = frame;
}
In my opinion you can check the image count coming from server that is whether there is three image or two image by counting the array count. Then you can set different frame to UIImageView based on the array count.
if([imageArray count] == 3) {
//set the frame of the images so that it can fit the screen.
}
else if([imageArray count] == 2) {
// set the frame of the UIImageView so that it can fit the screen and hide the third UIImageView
[thirdImageView setHidden:YES];
}
Like this you can set the frame for UIImageView

Resources