I am creating a UIButton programmatically in the code and am constraining it to the top and the right of the ViewController. When the ViewController loads, the UIButton is in the correct position on the screen. The problem comes when I rotate the device, the constraints don't seem to have taken. Here is the code I use to create the UIButton:
let xValue = UIScreen.mainScreen().bounds.width - 44
let closeButton = UIButton(frame: CGRect(origin: CGPoint(x: xValue, y: 0), size: CGSize(width: 44, height: 44)))
closeButton.setImage(UIImage(named: "close"), forState: .Normal)
closeButton.addTarget(self, action: "closeClicked", forControlEvents:.TouchUpInside)
self.view.addSubview(closeButton)
self.view.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Right, relatedBy: .Equal, toItem: self.view, attribute: .Right, multiplier: 1, constant: 0))
Is there something else I need to do to ensure the constraints are applied after an orientation change?
You shouldn't combine frames with constraints. By default, iOS will convert your frame to constraints, and that is causing a conflict with the constraints you are setting. After creating your button, you need to call:
For Swift 1.2:
closeButton.setTranslatesAutoresizingMaskIntoConstraints(false)
For Swift 2.0:
closeButton.translatesAutoresizingMaskIntoConstraints = false
so that iOS doesn't try to convert your frame into constraints. Then you need to add two additional constraints for the width and height of your button:
closeButton.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Width,
relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 44))
closeButton.addConstraint(NSLayoutConstraint(item: closeButton, attribute: .Height,
relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 44))
Since your button is no longer using the frame, you can create it with:
let closeButton = UIButton()
Now your button will stick to the upper right of your screen in all orientations for all devices.
All You need is to disable animation (uncheck "Animation" checkbox) on segue and then will be no need to update constraints.
Can be found in Segue options in right panel while editing storyboard.
Related
I am trying to add a button to a view in my iOS app. In the Main.storyboard file the button appears as this:
However, whenever I start the simulator the button appears as:
If it helps, I have the button paired to a custom class that consists of:
import UIKit
class RoundedButton: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
layer.borderWidth = 1
layer.borderColor = UIColor.blue.cgColor
contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
}
}
But I do not believe this is the cause of the issue. I think the problem stems from my lack of understanding of how scaling works in xCode but I am unable to find any resources on how to fix this issue.
Thanks!
Add constraints to your button and check. It will be perfect if you add the necessary constraints.
Constraints are used to tell devices where objects should be and their sizes, even with different devices, you can create a pattern. Remember you always have to configure X, Y, width and height so your elements are placed perfectly. Except for labels, which sometimes doesn't require width and height constraints.
For your button, adding constraints to align horizontally, vertically, width and height should work.
Add necessary constraints for the button. you can add in storyboard or programmatically.
In storyboard, you can add constraints like this:
For programmatically:
let horConstraint = NSLayoutConstraint(item: yourButton!, attribute: .centerX, relatedBy: .equal,
toItem: view, attribute: .centerX,
multiplier: 1.0, constant: 0.0)
let verConstraint = NSLayoutConstraint(item: yourButton!, attribute: .centerY, relatedBy: .equal,
toItem: view, attribute: .centerY,
multiplier: 1.0, constant: 0.0)
let widConstraint = NSLayoutConstraint(item: yourButton!, attribute: .width, relatedBy: .equal,
toItem: view, attribute: .width,
multiplier: 0.95, constant: 0.0)
let heiConstraint = NSLayoutConstraint(item: yourButton!, attribute: .height, relatedBy: .equal,
toItem: view, attribute: .height,
multiplier: 0.95, constant: 0.0)
view.addConstraints([horConstraint, verConstraint, widConstraint, heiConstraint])
I created a .xib with freeform and implement it like this
if let alertView = Bundle.main.loadNibNamed(Constants.XIB.titleImageLabelThreeButtonsAlertView, owner: self, options: nil)?.first as? TitleImageLabelThreeButtonsAlertView {
view.addSubview(alertView)
alertView.center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
view.addConstraint(NSLayoutConstraint(item: alertView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 20))
view.addConstraint(NSLayoutConstraint(item: alertView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 20))
alertView.autoresizingMask = [UIViewAutoresizing.flexibleLeftMargin, UIViewAutoresizing.flexibleRightMargin, UIViewAutoresizing.flexibleTopMargin, UIViewAutoresizing.flexibleBottomMargin]
self.view.layoutIfNeeded()
}
I call this code in viewDidAppear. The center thing seems to work, but it seems that the trailing and leading don't have any effect. I want them with a distance of 20, my alertView should have a fixed height and appear in center.
The xib has always the same size (see screenshots)
My originally targeted was to get a xib that I can implement in every view for every device. So what is the best way to get this?
my xib file
simulator iphone 7
simulator iphone 4
You are mixing up auto layout and fixed placement (with autoresizing mask). What you want to do is completely use auto layout so that the view will adjust its layout automatically. You say you want a horizontal distance of 20, a fixed height and to be centred so I would do this:
if let alertView = Bundle.main.loadNibNamed(Constants.XIB.titleImageLabelThreeButtonsAlertView, owner: self, options: nil)?.first as? TitleImageLabelThreeButtonsAlertView {
view.addSubview(alertView)
// Start using auto layout
alertView.translatesAutoresizingMaskIntoConstraints = false
// Set the leading and trailing constraints for horizontal placement
view.addConstraint(NSLayoutConstraint(item: alertView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -20))
view.addConstraint(NSLayoutConstraint(item: alertView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 20))
// Centre it vertically
view.addConstraint(NSLayoutConstraint(item: alertView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0))
// Set the fixed height constraint
let fixedHeight: CGFloat = 100
view.addConstraint(NSLayoutConstraint(item: alertView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 0, constant: fixedHeight))
}
That will get you what you want no matter how the device, superview, orientation, etc changes.
Currently I am using this line of code to set the origin on a UILabel at the top left corner...
addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
However I want the origin to be 0 pixels from left edge H and half of height if that makes sense.
Any suggestions? I've tried changing the multiplier to no avail.
What I am trying to do is move the origin so that (0,0) instead of being the top left corner, is the furthermost left of the label and half of the label height so that I can center the label properly.
Just to clarify, you need the label left aligned and centered top to bottom?
Have you looked at NSLayoutAnchor?
messageLabel = UILabel(/* Initialized somehow */)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(messageLabel)
view.centerYAnchor.constraint(equalTo: messageLabel.centerYAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor).isActive = true
try to set the origin of your label with this:
messageLabel.horizontalAlignmentMode = .Left
messageLabel.position = CGPoint(x:0.0, y:self.size.height)
no need for constraint!!
Hope that help you out :)
First you have to add your view as a subview of its container. Once you have done this you will want to set the translatesAutoResizingMaskToConstraints to false. Then it is time to add your NSLayoutConstraints:
let labelWidth:CGFloat = 320
let labelHeight:CGFloat = 30
// Container is what you are adding label too
let container = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
let label = UILabel(frame: CGRect(x: 0, y: (container.frame.size.height / 2) - (labelHeight / 2), width: labelWidth, height: labelHeight))
container.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
// Add Width and Height (IF is required,
// if not required, anchor your label to appropriately)
label.addConstraints([
NSLayoutConstraint.init(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: labelWidth),
NSLayoutConstraint.init(item: label, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: labelHeight),
])
label.layoutIfNeeded()
// Add left constraint and center in container constraint
container.addConstraints([
NSLayoutConstraint.init(item: label, attribute: .left, relatedBy: .equal, toItem: .container, attribute: .left, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint.init(item: label, attribute: .centerX, relatedBy: .equal, toItem: .container, attribute: .left, multiplier: 1.0, constant: 0.0)
])
container.layoutIfNeeded()
EDIT
Yes there is documentation given to you automatically by Xcode when you use dot syntax. If you go to the parameter of the attribute and just enter a period, you will see a drop down of all the possible options.
I have the following code:
override func viewDidLoad() {
super.viewDidLoad()
let fbLoginButton = FBSDKLoginButton()
fbLoginView.contentMode = UIViewContentMode.scaleAspectFit
fbLoginView.addSubview(fbLoginButton)
fbLoginButton.frame.origin = fbLoginView.frame.origin
fbLoginButton.frame = CGRect(x: fbLoginView.frame.origin.x, y: fbLoginView.frame.origin.y, width: fbLoginView.frame.width, height: fbLoginView.frame.height)
print("INFO width: \(fbLoginView.frame.width) height: \(fbLoginView.frame.height)")
print("INFO width: \(fbLoginButton.frame.width) height: \(fbLoginButton.frame.height)")
// fbLoginButton.delegate = self
}
I am trying to add the facebook login button within a small subview that I have in my main view. For some reason when I print out the width/height of the UIView, and facebook login button I see this:
INFO width: 1000.0 height: 1000.0
INFO width: 1000.0 height: 1000.0
BUT when I run the app on the simulator I see that the view isnt 1000.0 x 1000.0 (it would be overflowing off of the screen but it isn't. It looks like this:
You can se the blue of the button, and that its correctly WITHIN the smaller subview but, the button actually seems to be overflowing larger than the view. What I don't get why the view is the correct size in the simulator, but its saying that its size is so large. I've read posts here which tell me to resize the button to aspect fit, and I've played around with all of the other options but no luck.
I have the smaller uiview set up like this:
In viewDidLoad, the app hasn't yet completed its auto-layout pass so many views are 1000x1000pts.
So in your code when you set the loginButton's size to match the loginView's size, it copies the value of 1000 across.
Later on when your app applies its auto layout constraints, the containing loginView is resized to the correct size, but within it the loginButton has its old, larger size.
The correct solution would be to correctly constrain the loginButton using autolayout - the simplest change would be to add constraints to it within loginView so that it's resized at the same time:
NSLayoutConstraint(item: fbLoginButton, attribute: .Trailing, relatedBy: .Equal, toItem: fbLoginView, attribute: .Trailing, multiplier: 1.0, constant: 0.0).active = true
NSLayoutConstraint(item: fbLoginButton, attribute: .Leading, relatedBy: .Equal, toItem: fbLoginView, attribute: .Leading, multiplier: 1.0, constant: 0.0).active = true
NSLayoutConstraint(item: fbLoginButton, attribute: .Top, relatedBy: .Equal, toItem: fbLoginView, attribute: .Top, multiplier: 1.0, constant: 0.0).active = true
NSLayoutConstraint(item: fbLoginButton, attribute: .Bottom, relatedBy: .Equal, toItem: fbLoginView, attribute: .Bottom, multiplier: 1.0, constant: 0.0).active = true
Or it would probably be cleaner to add the Facebook Button within your storyboard directly (add an empty view and set its class to FBSDKLoginButton).
The problem with frames in viewDidLoad() is that they are not layouted. They do not have their final size yet. You could use viewDidLayoutSubviews() for that.
I would suggest to use NSLayoutConstraints, though. Looking at what you were trying to do with your frame, you can simply create the constraints and add them to your view.
let topConstraint = NSLayoutConstraint(item: fbLoginButton, attribute: .top, relatedBy: .equal, toItem: fbLoginView, attribute: .top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: fbLoginButton, attribute: .bottom, relatedBy: .equal, toItem: fbLoginView, attribute: .bottom, multiplier: 1, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: fbLoginButton, attribute: .leading, relatedBy: .equal, toItem: fbLoginView, attribute: .leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: fbLoginButton, attribute: .trailing, relatedBy: .equal, toItem: fbLoginView, attribute: .trailing, multiplier: 1, constant: 0)
self.view.addConstraints([topConstraint, bottomConstraint, leadingConstraint, trailingConstraint])
I have just started exploring the Uber iOS Sdk. I was checking for RequestButton and have noticed that its not taking any frame which we are passing. Is it possible to put button on desired location of a view?
let button = RequestButton(colorStyle:.White)
view.addSubview(button)
button.frame = CGRectMake(100,300, button.frame.size.width, button.frame.size.height)
It is possible to add the RequestButton in a desired location. The RequestButton is positioned with Auto Layout (or Constraint Based Layout) rather than Frame-Based Layout. See Apple's notes on Auto Layout Versus Frame-Based Layout.
Auto Layout guarantees resizing when the frame changes orientation and for different device sizes. In Auto Layout, you need to place UIViews based on their relative position to other views.
Here is an example of how you would center a RequestButton (button) inside a parent UIView (inView)
func centerButton(forButton button: RequestButton, inView: UIView) {
button.translatesAutoresizingMaskIntoConstraints = false
// position constraints
let horizontalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: inView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: inView, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
// add constraints to view
inView.addConstraints([horizontalConstraint, verticalConstraint])
}
There might be an easier way but I found this solution for positioning the Uber RequestButton using frame based layouts.
Place the request button inside a wrapper UIView. Set the constraints on the wrapper view via Christine Kim's answer. Then you can position the wrapper's frame and the RequestButton will move with it
// MyView.swift
let uber = RequestButton()
let uberWrapper = UIView()
uberWrapper.addSubview(uber)
self.addSubview(uberWrapper)
//Set constraints
let horizontalConstraint = NSLayoutConstraint(item: uber, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: uberWrapper, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: uber, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: uberWrapper, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
// add constraints to view
uberWrapper.addConstraints([horizontalConstraint, verticalConstraint])
//layoutSubviews()
//set your origin
uberWrapper.frame = CGRect(origin: CGPointMake(0, 0), size: uber.intrinsicContentSize())