This question already has answers here:
How to make a square view resize with its superview using auto layout
(4 answers)
Closed 7 years ago.
This seems like it should be easier that it currently is being.
I am trying to center a UIView with an aspect ratio of 1:1 (A square) in any iOS device it is drawn in, regardless of orientation.
For detail:
My view has a draw that updates on a timeInterval. I was using a full screen view and computing my square on each draw. On orientation change the whole view went to hell. I assumed that if the view was square, I could trust the orientation change animation.
My constraints have been failing repeatedly, which is why this seems like it should be easier:
View (Square)
Constraints
aspect 1:1
Constraints
View.centerX = centerX
View.centerY = centerY
View.leading ≥ leadingMargin + 5 # 800
View.top ≥ Top Layout Guide.bottom + 5 # 800
trailingMargin ≥ View.trailing + 5 # 800
Bottom Layout Guide.top ≥ View.bottom + 5 # 800
I have the Content Hugging Prioity at 250
I have the content Compression Resistance at 750
This leaves the constraint errors:
- Missing Constraint: Need constrains for: X position or width
- Missing Constraint: Need constrains for: Y position or height
My confusion is that I can't lock into one dimension because on rotation I need to lock into the other.
As mentioned... this seems like it should be easier.
Center a Square UIView with a border of 5 at the thinner dimension.
(5 to the sides in portrait, 5 to the top and bottom in landscape)
Suggestions warmly appreciated, Explanations would be beyond helpful.
Here is an approach that I just put together. I couldn't do it entirely in IB, but at least the run-time code is limited to activating/deactivating constraints rather than having to add/remove or compute/change any sizes.
In my storyboard I have an inner UIView with the following constraints
Center X
Center Y
Leading space to superview = 5, priority = 999
Trailing space to superview = 5, priority = 999
Top space to superview=5, priority=1000
Bottom space to superview=5, priority=1000
I created IBOutlets for the last four constraints and this is my view controller -
class ViewController: UIViewController {
#IBOutlet var leadingConstraint: NSLayoutConstraint!
#IBOutlet var trailingConstraint: NSLayoutConstraint!
#IBOutlet var topConstraint: NSLayoutConstraint!
#IBOutlet var bottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.setConstraintsForSize(self.view.frame.size)
}
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
self.setConstraintsForSize(size)
}
func setConstraintsForSize(size:CGSize) {
if (size.width > size.height) {
self.leadingConstraint.active=false;
self.trailingConstraint.active=false;
self.topConstraint.active=true;
self.bottomConstraint.active=true;
} else {
self.leadingConstraint.active=true;
self.trailingConstraint.active=true;
self.topConstraint.active=false;
self.bottomConstraint.active=false;
}
}
}
This works on iPhone and iPad, including iPad in windowed/splitscreen mode
The square needs to specify its size.
Square centerX = Parent centerX
Square centerY = Parent centerY
At this point, the view is centered, but there's nothing to let autolayout determine the size of the view.
Square width = 200
Square aspect = 1
If you want the square to be a portion of the parent's width, that's straightforward as well.
Square width = Parent width * 0.5 //or any other multiplier
You'll need to adjust the width constraint depending on how you want the square to look in landscape.
To have "the largest square possible", you can programmatically set the width constraint to min(parentWidth, parentHeight) and adjust on orientation.
Alternatively, leading, trailing, centerX, centerY, and aspect should get you similar results. Any set of constraints needs to be able to logically determine position and size without ambiguity.
Update:
In IB:
Square centerX = Parent centerX
Square centerY = Parent centerY
Square aspect = 1
In code:
if parent width > parent height
Square width = parent height - 2 * margin
else
Square width = parent width - 2 * margin
Adjust this on rotation
Related
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.
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.
I try to figure out autoshrink option in UILabel with regarding top constraint.
I have UIView and three labels. One of them has autoshrink option on. It has constraint to be centered, and has Trailing and Top constraint which should shrink label when changing size of UIView. If I make UIVIew thinner, font size is decreased, but if I change height of UIView font is not changed.
Constraints on UILabels :
Align Center X to Superview
Align Center Y to Superview
Trailing Space to Superview >= 50
Top Space to Superview >= 40
Align Center X to label2
Top Space to label1 equals :15
Bottom space to label2 equals :3
Label 1 constraints :
Align Center x to superview
Trailing Space to superview >=10
Leading Space to superview >=10
Bottom Space to Shrink Label equal 15
Label 2 constraint :
Align Center X to Shrink label
Top Space to Shrink label equals 3
How to change this?
What I want is, on last image that label will be nice autoshrink. So if I change width or height of the UIView label should shrink.
plz select your Shrink label set
Number of lines is 0
Line Breaks: Clip
Autoshrink: Minimum Font Scale 0.25
I hope below code will help you in some way,
extension Double {
/// Returns propotional width according to device width
var propotional: CGFloat {
if UIDevice.current.userInterfaceIdiom == .pad {
return CGFloat(414.0) / CGFloat(375.0) * CGFloat(self)
}
return CGFloat(Screen.width) / CGFloat(375.0) * CGFloat(self)
}
}
extension UILabel {
/// This property is change font-size of Label's text propotionaly, if assigned `On` from Interface builder
#IBInspectable
var isPropotional: Bool{
get {
return true
}
set {
if newValue == true {
let fontSize = Double((self.font!.pointSize))
self.font = UIFont(name: self.font!.fontName, size: fontSize.propotional)
}
}
}
}
In extension of Double, propotional value is calculated by considering current device set in storyboard as iPhone 6 size.
After adding this code set on from interface builder for Label where you can see isPropotional attribute in attribute inspector tab. Please refer image.
I'm having an issue where I have a UIImageView that spans the entire width of the screen using the following constraints.
Align Leading to : Superview (Equals: -20)
Align Trailing to : Superview (Equals: -20)
Top Space to : Top Layout Guide (Equals: 0)
Aspect Ratio : 1:1
The image assumes the width of the screen while gaining aspect ratio (in this case a square). The issue comes with when I started attempting to make a circle out of the image, that I noticed that my UIIMageView's frames do not have proper values.
You'll have to excuse to code below for not being in Objective-C as I'm using Java through RoboVM but with very little effort you can mentally convert the code to Objective-C as it's pretty straightforward.
public class MyViewController extends UIViewController {
#IBOutlet private UIImageView myImageView;
#Override public void viewDidLoad() {
System.out.println("Image view width: " + myImageView.getFrame().getWidth());
System.out.println("Superview width: " + this.getView().getFrame().getWidth());
}
}
The results of running the application with this Controller shows the following:
Image view width: 240.0
Superview width: 320.0
When cutting the frame width of the image in half and applying that to the CALayer#cornerRadius property, the image does not make a complete circle, however when using half of the superviews width (Which is actually the size of the image) the result is a perfect circle.
In viewDidLoad, it has just loaded your view from the XIB or storyboard, not sized it yet. The size should be correct when viewWillAppear is called. So try putting your code in viewWillAppear and see if that helps.
The reason you need the -20 margin is because your superview has a margin of 20, and the constraints are created by default to 'Relative to margin' - if you uncheck that, you can set the 'real' constraint - in this case zero rather than -20
If you select the constraint in the assistant editor, you will see the check box to toggle this value
I have a circle in the centre of a screen with a margin constraint of 50 on either end. Hence, the width of the circle is dependent on the screen size.
So, what I tried was this:
Approach 1
I set up the margins in the storyboard to define the circle width (50 on left and right)
Then I used the following code:
#IBOutlet weak var helpButHeight: NSLayoutConstraint!
#IBOutlet weak var helpBut: UIButton!
ViewDidLoad:
helpButHeight.constant = helpBut.frame.size.width
This didn't work.
Since the screen width is 400, and the margin is 50 on either end, then helpBut.frame.size.width should have given me 300.
Instead it gave me 46.
Approach 2
This was my work-around:
ViewDidLoad:
let screenSize: CGRect = UIScreen.mainScreen().bounds
helpButHeight.constant = screenSize.width - 100
because 100 = 50 + 50, the two margins.
Works fine !
Question
Why did I have to do this? Why did the first approach not work? Why 46 and not 300?
The reason is that constraints haven't kicked in, in the viewDidLoad function. The lifecycle looks something like
viewDidLoad -- Constraints haven't set
viewWillAppear -- Constraints haven't set
viewWillLayoutSubviews -- Constraints are setting
viewDidLayoutSubviews -- Constraints are set
viewDidAppear -- Constraints are set
If you want any view to be in center just put the horizontal/vertical center constraint...No code required.. If you want to have padding just put the left and right constraints...Just to remind you don't use both of them together...It'll break...