Add "constant" value in xib in auto layout ios - ios

I have successfully set AutoLayout for iPhone8+,iPhone X,iphone 7 but there is problem for iPhone SE i.e Reduced the space between two labels, so I need to change "constant value" in constraints but I don't want to add outlet add to constant value is there any possibility for add constant value for iPhone SE ?

storyboard solution:
screen width (or super view's width)
⬇︎
base view's leading
⬇︎
target view's spacing between base view.
use left button as the base reference, set right or target button's trailing constraint with left button's leading(or something else horizontal), and set target button's
constraint with multiplier.
change multiplier value based on storyboard's virtual view, that's easy.
set left button's leading based on its' super view's trailing, also with multiplier, so left button's leading based on super view's width, and the right
button based on super view's width too, the spacing between them will grow or shrink with the actual screen size.
case all these iPhone devices are (wC, hR), so, so can not set size classes in storyboard, but, you can still check screen size and make your own logic to layout based on screen sizes in code.
hope this post may help you. :)

Create a class and following code in it.
import UIKit
class DynamicVerticalConstraint: NSLayoutConstraint {
override init() {
super.init()
updateConstant()
}
override func awakeFromNib() {
super.awakeFromNib()
updateConstant()
}
//It will send contraint constant according to devices. For this you have to set your contraint according to iPhone6
func updateConstant(){
self.constant = self.constant * (UIScreen.main.bounds.height / 667)
}
}
class DynamicHorizontalConstraint: NSLayoutConstraint {
override init() {
super.init()
updateConstant()
}
override func awakeFromNib() {
super.awakeFromNib()
updateConstant()
}
//It will send contraint constant according to devices. For this you have to set your contraint according to iPhone6
func updateConstant(){
self.constant = self.constant * (UIScreen.main.bounds.height / 375)
}
}
Now set your constraint's constant value according to iphone 6.
After that you have to assign class to specific contrant.

Related

UIButton not render exactly as XIB's contentVerticalAlignment value

I want to button 's top is align to the button's titleLabel's top , so I set the content vertical alignment to top in xib . this is works well in xib , but after build , the titleLabel seems still layout with center vertical alignment . What did I miss?
First, a comment: What you see in Storyboard / Interface Builder:
is not always exactly what UIKit renders in the Simulator
which is not always exactly what UIKit renders on a Device
This is why we test, test, test... on different simulators and devices.
So, what's going on here?
UIKit uses the frame of the button's .titleLabel to determine the button's intrinsic size.
For a default button, with no explicit width or height set, UIKit insets the title label by 6-pts on the Top and Bottom.
Here are 2 buttons - both with no Height constraint. Button 1 is the default Content Alignment of center/center, Button 2 is set to Left/Top. The button title label background is cyan, so we can easily see its frame.
Storyboard / IB:
Runtime:
Debug View Hierarchy (note the label frame vs the button frame):
So, with an 18-pt system font, the title label height is 21.0 ... add 6-pts top and bottom and the button frame height is 33-pts.
It doesn't matter whether you set the Control Alignment to Top / Center / Bottom or Fill ... UIKit still takes the label height and adds 6-pts Top and Bottom "padding."
What to do to get actual Top alignment? Let's look at a couple approaches.
Here are 6 buttons in a stack view:
Button 1 is at the default center/center, with no Height constraint.
Button 2 as we've seen, has Control Alignment Left / Top ... but has no effect on the vertical alignment.
Button 3 is also Left/Top, but let's give it an explicit Height (we'll use 80 to make things obvious). Looks better - but there is still 6-pts of "padding" added to the top of the title label.
Now, you may have seen this at the top of the Size Inspector panel:
This looks promising! Let's set the Title Insets Top to Zero!
Whoops -- it's already Zero?!?!?!?
Same thing with the Content Insets!
Turns out, if the the edge insets are at the default, the padding is added anyway.
We could try setting the Title Inset Top to -6 and, because labels center the text vertically, we'll also have to set the Bottom inset to 6. This works... but we may not want to rely on that value of 6 to be accurate in future iOS versions.
Button 4 - lets try Content Insets -> Bottom: 1 ... and it looks like we're on our way! The label is now top-aligned with the button frame!
So...
Button 5 - remove the Height: 80 constraint so we can use the default button height. D'oh! The button frame height is now title-label-height plus zero-top plus one-bottom...
Button 6 - finally, we'll use Content Insets -> Bottom: 1 with an explicit Height constraint of 33 (the default button height).
So... what if we don't want to set an explicit Height? Perhaps we're going to change the title label's font size later? Or we run into some other issue?
You could use a custom UIButton subclass.
Here's an example, marked as #IBDesignable so we can see its layout in Storyboard / IB:
#IBDesignable
class MyTopLeftAlignedButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() {
self.contentVerticalAlignment = .top
self.contentHorizontalAlignment = .left
// if we want to see the title label frame
//titleLabel?.backgroundColor = .cyan
}
override var bounds: CGRect {
didSet {
if let v = titleLabel {
var t = titleEdgeInsets
let h = (bounds.height - v.frame.height) * 0.5
t.top = -h
t.bottom = h
titleEdgeInsets = t
}
}
}
}
Edit - response to comment...
If your goal is to match the "Button 5" style - where the button height matches the title label height - best bet is probably...
Select the button, then in the Size Inspector panel use these settings:
The 0.1 Top and Bottom values will override the default 6-pt Top/Bottom "padding," reducing it to effectively Zero.

Rounded UIImageView

I'm trying to get a rounded UIImageView but it seems to render differently on different devices;
Looks like this on an iPhone Xr:
Looks like this on an iPhone 7:
I have a height constraint of 60 and the following code:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.userAvatar.layer.cornerRadius = self.userAvatar.frame.height / 2
self.userAvatar.layer.masksToBounds = false
self.userAvatar.clipsToBounds = true
self.userAvatar.layer.borderWidth = 0
}
Any ideas?
It seems to me that you have given the image leading and trailing constraints instead of a fixed width.
To achieve a circle give image view width equal to height.
This happens due to different widths of devices.
If you're managing this view using Interface Builder (i.e. Storyboard or XIB), you can enforce a square shape (which becomes a circle when combined with the rounded corners you already have) for the view directly from there by defining a constraint for its Aspect Ratio. No need to code anything.
Control-drag (like you do to create Outlets, Actions, etc.) from the image view to itself, and the following popup will appear.
Select Aspect Ratio, which will create a constraint matching whatever the view's current ratio is (in this example, it's 15:8). If the view was already square, the constraint created should already be correct.
If not, you can find that constraint by clicking the following icon (for the Size inspector):
From there, you can double-click on that constraint to edit it, and change the Multiplier to 1:1:
In fact, an even easier option is, once you've Control-dragged from the view to itself, hold down Alt/Opt and the option displayed in the popup will change to Aspect Ratio (1:1), meaning you can set it directly from there without even having to edit the constraint.
Constrain the height equal to the width.
And, create a simple UIImageView subclass:
class RoundedImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height / 2
}
}
The frame can (and will) change based on view lifecycle. By updating the cornerRadius in layoutSubviews() it will keep it "round".

Change View Position based on ImageView size

I have Imageview and UIView on View Controller. If Imageview is nil or image is not available then UIView replace its postion.Do any know how is it possible using auto layout?
For trying purpose, I have fixed height and width of both(Imageview and UIView). Imageview have "top 8 pixel" and "Horizontally in container" margin. UIView have "top 0 from Imageview" and "Horizontally in container" margin. Set Imageview to nil but it doesn't work.
A good suggestion would be to add both of the image view and the view in a stackView and follow the steps mentioned in: UIStackView Distribution Fill Equally.
However, you can achieve what are you asking for by adding additional constraint between the bottom view and the top layout guide:
and then, set its priority value to be less than the default (1000) -in my example I set it to 500- and the its constant value to 0:
Its appearance should be displayed as dotted line, meaning that there is another constraint -with a higher priority value- deciding the x axis of the view, if this constraint has been removed/deactivated the dotted one should be activated.
Finally, make sure that if there is no available image you have to remove image view from its super view (call imageView.removeFromSuperview()), setting it as hidden or setting its alpha to 0.0 doesn't activate the dotted constraint:
class ViewController: UIViewController {
//...
#IBOutlet weak var imageView: UIImageView!
// I'm checking in 'viewDidLoad' method just for describing purposes,
// of course, you can do the check when needed...
override func viewDidLoad() {
// if there is something wrong, you should call:
imageView.removeFromSuperview()
}
//...
}
The output would be:
Take the outlet of height constraint of UIImageView and do below code
if (imageview == nil){
imageViewHeightConstraint.constant = 0
}
else{
imageViewHeightConstraint.constant = 60
}
And other query you can ask.
You can manage this by giving priority to the constraints using auto layout. Give your UIView top space from ImageView and the TopLayout. Assign a lower priority to the constraint (any value less than 1000) where you have given top space from top layout guide. If your image is nil , remove the image view from the view using imageview.RemoveFromSuperView(), the UIView will automatically take the next constraint i.e. from TopLayout guide. I am attaching a screenshot where to find the priority of the constraint on storyBoard.

Why does sizeThatFits() return a size that is too small?

I'm learning swift with cs193p and I have a problem with UITextView.sizeThatFits(...). It should return a recommended size for popover view to display an [int] array as a text. As you can see in Paul Hegarty's example (https://youtu.be/gjl2gc70YHM?t=1h43m17s), he gets perfectly-fit popover window without scrollbar. I'm using almost the same code that was in this lecture, but instead i've got this:
the text string equals [100], but the sizeThatFits() method is returning a size that is too small to display it nicely, even though there is plenty of free space.
It is getting a bit better after I've added some text, but still not precise and with the scrollbar:
Here is the part of the code where the size is being set:
override var preferredContentSize: CGSize {
get {
if textView != nil && presentingViewController != nil {
// I've added these outputs so I can see the exact numbers to try to understand how this works
print("presentingViewController!.view.bounds.size = \(presentingViewController!.view.bounds.size)")
print("sizeThatFits = \(textView.sizeThatFits(presentingViewController!.view.bounds.size))")
return textView.sizeThatFits(presentingViewController!.view.bounds.size)
} else { return super.preferredContentSize }
}
set { super.preferredContentSize = newValue }
}
What should I do so this will work in the same way as in the lecture?
It looks like there are 16 pt margins between the label and its parent view. You need to take that into account when returning the preferred size of the popover.
You should try both of the following:
Add 32 to the width that's returned from preferredContentSize
In Interface Builder, clear the layout constraints on your UILabel, then re-add top, bottom, leading, and trailing constraints and make sure that "Constrain to Margins" option is not enabled.
Finally, instead of overriding preferredContentSize, you can simply set the preferredContentSize when your view is ready to display, and you can ask Auto Layout to choose the best size:
override func viewDidLayoutSubviews() {
self.preferredContentSize = self.view.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
}
If your layout is configured correctly, systemLayoutSizeFitting(UILayoutFittingCompressedSize) will return the smallest possible size for your view, taking into account all of the margins and sub-views.

Where to update Autolayout constraints when size changes?

I have several UIViews laid out along the bottom of a containing UIView. I want these views to always be equal width, and always stretch to collectively fill the width of the containing view (like the emoji keyboard buttons at the bottom). The way I'm approaching this is to set equal widths to one of the views, then just update the width constraint of that view to be superviewWidth / numberOfViews which will cause all of the other views to update to that same value.
I am wondering where the code to change the constraint constant needs to go. It needs to be set before the keyboard appears on screen for the first time and update when rotating the device.
My first attempt at a solution was to place it in updateViewConstraints and calculate the width via containerView.frame.size.width. But this method is called twice upon load, the first time it calculates the values correctly, but the second time for some reason the containerView's width is 0.0. Another issue is that when rotating, the containerView's width is not the value that it will be after rotation, it's the current value before rotation. But I don't want to wait until after the rotation completes to update the constraint, because the buttons will be the original size then change which will be jarring to the user.
My question is: where is the most appropriate place to put this code? Is there a better way to calculate what the width will be? I can guarantee it will always be the exact same width as the screen width. And I am using Size Classes in Xcode 6, so willRotateToInterfaceOrientation and similar methods are deprecated.
On all classes that implement the UITraitEnvironment protocol the method traitCollectionDidChange will be called when the trait collection changes, like on rotation. This is the appropiate place to manually update the constraints when using the new Size Classes. You can also animate the transition with the method willTransitionToTraitCollection
Basic example:
class ViewController: UIViewController {
var constraints = [NSLayoutConstraint]()
func updateConstraintsWithTraitCollection(traitCollection: UITraitCollection) {
// Remove old constraints
view.removeConstraints(constraints)
// Create new constraints
}
override func willTransitionToTraitCollection(newCollection: UITraitCollection!,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) {
super.willTransitionToTraitCollection(newCollection, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext!) in
self.updateConstraintsWithTraitCollection(newCollection)
self.view.setNeedsLayout()
}, completion: nil)
}
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection!) {
updateConstraintsWithTraitCollection(traitCollection)
}
}
Besides that I want to recommend Cartography, which is a nice library that helps to make auto layout more readable and enjoyable. https://github.com/robb/Cartography
There is no reason to update the width manually:
Place all the views with equal width in your view with no spacing in between each other
Add an equal width constraint to all of them
Add constraints with 0 width for spacing between sides and each other
Lower the priority of one or more of the equal width constraints just in case the width cannot be divided equally.
Then auto layout will handle everything for you.

Resources