Centering label and UITextField inside a TableView - swift

New to xcode/swift, spending a couple of days now trying to fix this one. Creating a universal app and having problems getting the constraint working programmatically. I would like to programmatically add a label and a UITextField inside a TableView. The label should always have a fixed width. The text field should have variable width depending on the device.
Here is what is looks like now:
Here is an idea of how it should look:
The label should be a set width. But the textfield should use the available screen.
Here is the code so far:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Setup Cell
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
// Make cell unselectable
cell.selectionStyle = .none
// Process Each Row
let row = indexPath.row
switch row
{
case 0:
let label = UILabel()
label.text = "First Name:"
label.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(40), height: CGFloat(30))
cell.contentView.addSubview(label)
var textField: UITextField = UITextField()
textField.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(170), height: CGFloat(30))
textField.borderStyle = .roundedRect
textField.backgroundColor = UIColor.magenta
textField.text = "TEST"
textField.textColor = UIColor.black
textField.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(textField)
let leadingConstraint = NSLayoutConstraint(item: cell.contentView, attribute: .leftMargin, relatedBy: .equal, toItem: label, attribute: .leftMargin, multiplier: 1.0, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: cell.contentView, attribute: .rightMargin, relatedBy: .equal, toItem: textField, attribute: .rightMargin, multiplier: 1.0, constant: 0)
cell.contentView.addConstraint(leadingConstraint)
cell.contentView.addConstraint(trailingConstraint)
....
Please let me know if you need additional information before a downvote. Any help would be appreciated. Thanks.
Answer by UpholderOfTruth:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Setup Cell
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
// Make cell unselectable
cell.selectionStyle = .none
// Process Each Row
let row = indexPath.row
switch row
{
case 0:
let label = UILabel()
label.text = "First Name:"
label.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(40), height: CGFloat(30))
label.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(label)
var textField: UITextField = UITextField()
textField.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(170), height: CGFloat(30))
textField.borderStyle = .roundedRect
textField.backgroundColor = UIColor.magenta
textField.text = "TEST"
textField.textColor = UIColor.black
textField.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(textField)
// Horizontal Constraints
cell.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[label(==100)][textField]|", options: .init(rawValue: 0), metrics: nil, views: ["label": label, "textField": textField]))
// Vertical Constraints
cell.contentView.addConstraint(NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: cell.contentView, attribute: .centerY, multiplier: 1, constant: 0))
cell.contentView.addConstraint(NSLayoutConstraint(item: textField, attribute: .centerY, relatedBy: .equal, toItem: cell.contentView, attribute: .centerY, multiplier: 1, constant: 0))
....

I would suggest go full auto layout and don't mix methods. So first set both view to use auto layout via setting the translatesAutoresizingMaskIntoConstraints to false.
Then either set the constraints visually like this:
cell.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[label(==100)][textField]|", options: .init(rawValue: 0), metrics: nil, views: ["label": label, "textField": textField]))
or with individual constraints like this:
cell.contentView.addConstraint(NSLayoutConstraint(item: label, attribute: .left, relatedBy: .equal, toItem: cell, attribute: .left, multiplier: 1, constant: 0))
cell.contentView.addConstraint(NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 100))
cell.contentView.addConstraint(NSLayoutConstraint(item: label, attribute: .right, relatedBy: .equal, toItem: textField, attribute: .left, multiplier: 1, constant: 0))
cell.contentView.addConstraint(NSLayoutConstraint(item: textField, attribute: .right, relatedBy: .equal, toItem: cell, attribute: .right, multiplier: 1, constant: 0))
Of course this just handles horiztonal positioning and sizing you need to do something about vertical positioning and sizing as well but you may be setting that up further down in your code.
Edit:
To centre vertically you can do this:
cell.contentView.addConstraint(NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: cell.contentView, attribute: .centerY, multiplier: 1, constant: 0))
cell.contentView.addConstraint(NSLayoutConstraint(item: textField, attribute: .centerY, relatedBy: .equal, toItem: cell.contentView, attribute: .centerY, multiplier: 1, constant: 0))
Edit2:
Combining into a single line:
cell.contentView.addConstraints([NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: cell.contentView, attribute: .centerY, multiplier: 1, constant: 0), NSLayoutConstraint(item: textField, attribute: .centerY, relatedBy: .equal, toItem: cell.contentView, attribute: .centerY, multiplier: 1, constant: 0)])
Edit3:
cell.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[label(==100)][textField]-8-|", options: .init(rawValue: 0), metrics: nil, views: ["label": label, "textField": textField]))

Change these properties:
switch row
{
case 0:
let leadingConstraint = NSLayoutConstraint(item: cell.contentView, attribute: .leftMargin, relatedBy: .equal, toItem: label, attribute: .leftMargin, multiplier: 1.0, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: cell.contentView, attribute: .rightMargin, relatedBy: .equal, toItem: textField, attribute: .rightMargin, multiplier: 1.0, constant: 0)
cell.contentView.addConstraint(leadingConstraint)
cell.contentView.addConstraint(trailingConstraint)
let label = UILabel()
label.text = "First Name:"
//here is the trick: play with x and width. It might also be cell.contentView.size().width
label.frame = CGRect(x: CGFloat(35), y: CGFloat(0), width: CGFloat(cell.frame.width * 1/3), height: CGFloat(30))
cell.contentView.addSubview(label)
var textField = UITextField()
textField.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(cell.frame.width * 2/3), height: CGFloat(30))
textField.borderStyle = .roundedRect
textField.backgroundColor = UIColor.magenta
textField.text = "TEST TEST TEST"
textField.textColor = UIColor.black
textField.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(textField)
Let me know if this works.

Related

Center button in footerView

I want my button to be in the center of my footerView. I tried several methods but failed every time:
1.
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView(frame: CGRect.zero)
footerView.backgroundColor = UIColor(rgba: "#F9FAFB")
let button = UIButton(frame: CGRect(x:(footerView.frame.size.width - 30) / 2, y:(footerView.frame.size.height - 30) / 2, width: 30, height: 30)) button.setImage(UIImage(named: "gen-cog"), for: .normal)
button.addTarget(self, action: #selector(footerButtonAction), for: .touchUpInside)
footerView.addSubview(button)
}
Used auto layout, this centers my button but breaks width and height of it, which I set to 30 above.
let centerYCon = NSLayoutConstraint(item: button,
attribute: .centerY,
relatedBy: .equal,
toItem: footerView,
attribute: .centerY,
multiplier: 1.0,
constant: 0.0);
footerView.addConstraint(centerYCon)
let centerXCon = NSLayoutConstraint(item: button,
attribute: .centerX,
relatedBy: .equal,
toItem: footerView,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0);
footerView.addConstraint(centerXCon)
footerView.addSubview(button)
3.
button.center = footerView.center
button.translatesAutoresizingMaskIntoConstraints = false
footerView.addSubview(button)
Now Add two more additional Constraints.
let widthConstraint = NSLayoutConstraint(item: button, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30)
let heightConstraint = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30)
button.addConstraints([widthConstraint, heightConstraint])

Setting 2 label in header programatically - not label seen

I'm trying to set 2 labels, one under other with different font size in the header. The function is called like this:
viewController.navigationItem.titleView = self.setHeader()
And code responsible for generating label is :
private func setHeader(agentName: String = "", isTyping: Bool = false) -> UIView {
let headerLabel: UILabel = {
let label = UILabel()
label.text = self.title
label.font = UIFont.systemFont(ofSize: 21)
label.textColor = UIColor.white
return label
}()
let subheaderLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 10)
return label
}()
let headerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(headerLabel)
view.addConstraints([
NSLayoutConstraint(item: headerLabel, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16),
NSLayoutConstraint(item: headerLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16)
])
return view
}()
if (!agentName.isEmpty) {
if (isTyping) {
subheaderLabel.text = agentName + " is typing ..."
} else {
subheaderLabel.text = agentName
}
headerView.addSubview(subheaderLabel)
}
return headerView
}
When I running IOS app there is nothing shown in the header. What is a reason?
I think you need to set the frame for the headerView. So in the initialization code for the headerView, use initializer with frame:
let headerView: UIView = {
// initialize the view with frame
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 35))
// you want to call this on the headerLabel, not on view
headerLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(headerLabel)
view.addConstraints([
NSLayoutConstraint(item: headerLabel, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: headerLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16),
NSLayoutConstraint(item: headerLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 16)
])
return view
}()
Moreover, I believe headerLabel you want to set translatesAutoresizingMaskIntoConstraints on headerLabel rather than on headerView (headerView is positioned through frame and not Autolayout).
Also, notice that you add subheaderLabel to the view, but you never position it, don't forget about it either (although this should cause only subheaderLabel not to be rendered properly).

UICollectionViewFlowLayout issues with collectionview and textfields

I have an app that uses a collectionview that scrolls horizontally, and has collectionviewcells inside of it. Everything was going fine until I tried to implement a login/register cell with 2 textfields in it using textfielddelegate. When I press on one of the textfields, the keyboard shows for a split second and then hides. After it does this, the view is pushed down a little bit and if I press the textfield again, it is pushed up and wont come down. Here are some screenshots of what it looks like:
before touching a textfield vs. after I touch a textfield
I get various UICollectionViewFlowLayout errors that call for me to make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes.
The behavior of the UICollectionViewFlowLayout is not defined because:
2017-04-26 13:55:03.199199-0400 Eyetube[1500:243622] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
2017-04-26 13:55:03.200490-0400 Eyetube[1500:243622] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x10453ee90>, and it is attached to <UICollectionView: 0x104806800; frame = (0 0; 768 960); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x17405a610>; layer = <CALayer: 0x17002f0a0>; contentOffset: {2304, 0}; contentSize: {3072, 910}> collection view layout: <UICollectionViewFlowLayout: 0x10453ee90>.
2017-04-26 13:55:03.200575-0400 Eyetube[1500:243622] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
I tried debugging this multiple times, but it isn't too helpful in finding where exactly the error is coming from. I've been stuck on this issue for hours and can't seem to figure it out.
Where I initialize my layout in my collectionviewcontroller:
func setupCollectionView() {
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
collectionView?.backgroundColor = UIColor.white
collectionView?.register(VideoFeedCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.register(ChannelFeedCell.self, forCellWithReuseIdentifier: channelCellId)
collectionView?.register(ARFeedCell.self, forCellWithReuseIdentifier: augmentedRealityCellId)
collectionView?.register(LoginRegisterCell.self, forCellWithReuseIdentifier: loginRegisterCellId)
collectionView?.contentInset = UIEdgeInsetsMake(0, 0, 50, 0)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 50, 0)
collectionView?.isPagingEnabled = true
}
My sizeForItemAt func, also in my collectionviewcontroller (P.S., I am subtracting 50 from the height because of the bottom menubar I have added as a subview of the view. I also changed the collectionview's contentInset and scrollIndicatorInsets because of this):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellSize = CGSize(width: view.frame.width, height: view.frame.height - 50)
return cellSize
}
Here is the complete code of the collectionviewcell, where I am having the issues:
import UIKit
class LoginRegisterCell: BaseCell, UITextFieldDelegate {
let logoImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "eyetube_logo_font")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
let emailTextField: LeftPaddedTextField = {
let tf = LeftPaddedTextField()
tf.keyboardType = .emailAddress
tf.placeholder = "Enter email"
tf.substituteFontName = "SourceSansPro-Regular"
tf.layer.borderColor = UIColor.rgb(220, green: 220, blue: 220).cgColor
tf.layer.borderWidth = 1
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let passwordTextField: LeftPaddedTextField = {
let tf = LeftPaddedTextField()
tf.placeholder = "Enter password"
tf.substituteFontName = "SourceSansPro-Regular"
tf.layer.borderColor = UIColor.rgb(220, green: 220, blue: 220).cgColor
tf.layer.borderWidth = 1
tf.isSecureTextEntry = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
lazy var loginButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.rgb(225, green: 31, blue: 40)
button.titleLabel?.font = UIFont(name: "SourceSansPro-SemiBold", size: 20)
button.setTitle("Log In", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
return button
}()
lazy var registerLink: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Don't have an account? Register here", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.titleLabel?.font = UIFont(name: "SourceSansPro-Regular", size: 18)
button.setTitleColor(UIColor.darkGray, for: .normal)
let underlineAttribute = [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue]
let underlineAttributedString = NSAttributedString(string: (button.titleLabel?.text)!, attributes: underlineAttribute)
button.titleLabel?.attributedText = underlineAttributedString
button.addTarget(self, action: #selector(handleRegister), for: .touchUpInside)
return button
}()
var containerView: UIView!
override func setupViews() {
super.setupViews()
self.emailTextField.delegate = self
emailTextField.returnKeyType = .next
self.passwordTextField.delegate = self
passwordTextField.returnKeyType = .done
containerView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
containerView.backgroundColor = .green
addSubview(containerView)
containerView.addSubview(logoImageView)
containerView.addSubview(emailTextField)
containerView.addSubview(passwordTextField)
containerView.addSubview(loginButton)
containerView.addSubview(registerLink)
setupLogoImageView()
setupInputs()
setupLoginButton()
setupRegisterLink()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func handleLogin() {
//first check if the email/password textfields are empty or not
guard let email = emailTextField.text , !email.isEmpty else {
let alert = UIAlertView (title: "Invalid Email", message: "Please enter an email to log in", delegate: self, cancelButtonTitle: "OK")
alert.show()
return
}
guard let password = passwordTextField.text , !password.isEmpty else {
let alert = UIAlertView (title: "Invalid Password", message: "Please enter a password to log in", delegate: self, cancelButtonTitle: "OK")
alert.show()
return
}
//create session here
ApiLoginAuthentication.sharedInstance.login_now(username: email, password: password, onCompletion: {(loginSuccessful: Bool) -> Void in
guard (loginSuccessful) else {
DispatchQueue.main.async {
let alert = UIAlertView (title: "Invalid Account info", message: "The account information entered is invalid. Please log in with a valid account.", delegate: self, cancelButtonTitle: "OK")
alert.show()
}
return
}
DispatchQueue.main.async {
self.emailTextField.text = ""
self.passwordTextField.text = ""
}
print("the login was successful")
})
/**
let profileUrl: String = "https://eyetube.net/user/profile.asp"
ApiLoginAuthentication.sharedInstance.getContent(contentUrl: profileUrl, onCompletion: {(responseString: String, isLoggedIn: Bool) -> Void in
print(responseString)
print("user status: \(isLoggedIn)")
let json: Any?
do {
let data: NSData = responseString.data(using: String.Encoding.utf8)! as NSData
json = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)
print(json)
} catch let error {
print("error: \(error)")
}
})**/
}
func handleRegister() {
let eyetubeRegisterLink = "https://eyetube.net/user/register.asp?rUrl="
UIApplication.shared.openURL(URL(string: eyetubeRegisterLink)!)
}
func setupLogoImageView() {
var sizeConstant: CGFloat!
var centerYConstant: CGFloat!
if UI_USER_INTERFACE_IDIOM() == .pad {
sizeConstant = 400
centerYConstant = -260
} else {
sizeConstant = 200
centerYConstant = -160
}
NSLayoutConstraint(item: logoImageView, attribute: .centerY, relatedBy: .equal, toItem: containerView, attribute: .centerY, multiplier: 1, constant: centerYConstant!).isActive = true
NSLayoutConstraint(item: logoImageView, attribute: .centerX, relatedBy: .equal, toItem: containerView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: logoImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: sizeConstant).isActive = true
NSLayoutConstraint(item: logoImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: sizeConstant).isActive = true
}
func setupInputs() {
var widthConstant: CGFloat!
var leftConstant: CGFloat!
if UI_USER_INTERFACE_IDIOM() == .pad {
widthConstant = -64
leftConstant = 32
} else {
widthConstant = -32
leftConstant = 16
}
NSLayoutConstraint(item: emailTextField, attribute: .top, relatedBy: .equal, toItem: logoImageView, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: emailTextField, attribute: .left, relatedBy: .equal, toItem: containerView, attribute: .left, multiplier: 1, constant: leftConstant).isActive = true
NSLayoutConstraint(item: emailTextField, attribute: .width, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: 1, constant: widthConstant).isActive = true
NSLayoutConstraint(item: emailTextField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 50).isActive = true
NSLayoutConstraint(item: passwordTextField, attribute: .top, relatedBy: .equal, toItem: emailTextField, attribute: .bottom, multiplier: 1, constant: 8).isActive = true
NSLayoutConstraint(item: passwordTextField, attribute: .left, relatedBy: .equal, toItem: containerView, attribute: .left, multiplier: 1, constant: leftConstant).isActive = true
NSLayoutConstraint(item: passwordTextField, attribute: .width, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: 1, constant: widthConstant).isActive = true
NSLayoutConstraint(item: passwordTextField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 50).isActive = true
}
func setupLoginButton() {
NSLayoutConstraint(item: loginButton, attribute: .centerX, relatedBy: .equal, toItem: containerView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: loginButton, attribute: .top, relatedBy: .equal, toItem: passwordTextField, attribute: .bottom, multiplier: 1, constant: 16).isActive = true
NSLayoutConstraint(item: loginButton, attribute: .width, relatedBy: .equal, toItem: passwordTextField, attribute: .width, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: loginButton, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 50).isActive = true
}
func setupRegisterLink() {
NSLayoutConstraint(item: registerLink, attribute: .centerX, relatedBy: .equal, toItem: containerView, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: registerLink, attribute: .top, relatedBy: .equal, toItem: loginButton, attribute: .bottom, multiplier: 1, constant: 12).isActive = true
NSLayoutConstraint(item: registerLink, attribute: .width, relatedBy: .equal, toItem: loginButton, attribute: .width, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: registerLink, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 40).isActive = true
}
}

Programmatically adding root view to view controller does not position correctly

I am trying to add the root view and subviews to a viewcontroller programmatically, but the view is not filling to screen as expected:
override func loadView() {
self.view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
self.view.backgroundColor = UIColor.blue
}
override func viewDidLoad() {
let alertView = Bundle.main.loadNibNamed("CHRApptTakenAlertView", owner: self, options: nil)![0] as! CHRApptTakenAlertView
self.view.translatesAutoresizingMaskIntoConstraints = false
alertView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(alertView)
self.view.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0))
self.view.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0))
alertView.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.width, multiplier: 1, constant: 350))
alertView.addConstraint(NSLayoutConstraint(item: alertView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.height, multiplier: 1, constant: 250))
alertView.closeBtn.addTarget(self, action: #selector(self.closeBtnTouch), for: UIControlEvents.touchUpInside)
}
Remove the line self.view.translatesAutoresizingMaskIntoConstraints = false, and call self.view.layoutIfNeeded() after the adding the constraints.

Swift 3 - Create title bar constraints programmatically

I am using Swift 3, iOS 10, XCode 8.2.
In my code, I need to create a UIViewController programmatically and hence, specify its layout and content programmatically as well.
#IBAction func testViewController() {
let detailViewController = UIViewController()
detailViewController.view.backgroundColor = UIColor.white
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.blue
label.text = "Scan Results"
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = UIColor.white
return label
}()
let titleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: titleLabel, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: titleLabel, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: titleLabel, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
]
detailViewController.view.addSubview(titleLabel)
detailViewController.view.addConstraints(titleConstraints)
self.navigationController?.pushViewController(detailViewController, animated: true)
}
In the vertical view (ignore all the other junk; just focus on the blue title bar):
But in the horizontal view:
What is the correct constraint to set so that it takes up the entire width of the bar and there isn't that extra space from the top since the status bar disappears when horizontal?
EDIT
After making #thexande suggestions, I do get an error:
[LayoutConstraints] The view hierarchy is not prepared for the
constraint: <NSLayoutConstraint:0x608000098100
UILabel:0x7fe35b60edc0'Scan Results'.left ==
UIView:0x7fe35b405c20.left (inactive)> When added to a view, the
constraint's items must be descendants of that view (or the view
itself). This will crash if the constraint needs to be resolved before
the view hierarchy is assembled. Break on
-[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug. 2017-02-24 21:01:59.807 EOB-Reader[78109:10751346] * Assertion failure in
-[UIView _layoutEngine_didAddLayoutConstraint:roundingAdjustment:mutuallyExclusiveConstraints:],
/BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.6.21/NSLayoutConstraint_UIKitAdditions.m:649
2017-02-24 21:01:59.951 EOB-Reader[78109:10751346] * Terminating app
due to uncaught exception 'NSInternalInconsistencyException', reason:
'Impossible to set up layout with view hierarchy unprepared for
constraint.'
I've also updated my code in the original post.
The reason this is happening is because you are using frames. You calculated the frame based on the width of the screen. You do not need frames, you can do this all using auto layout. Instead, you should use constraints to pin your label to it's super view bounds, and give it a static height. for example:
lazy var titleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: self.titleLabel, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.titleLabel, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.titleLabel, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.titleLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
]
then, in viewDidLoad()
self.view.addConstraints(titleConstraints)
You could simplify your label declaration like so. dont forget the auto resizing mask flag to get constraints to work correctly:
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.blue
label.text = "Scan Results"
label.textAlignment = .center
label.textColor = UIColor.white
return label
}()
Finally, you are doing strange math to get the top of your view controller to abut the bottom of your nav bar controller. Remove all that garbage and put the following in viewDidLoad() to get the top of your view controller right against the bottom of your UINavigationBar:
self.edgesForExtendedLayout = []
UPDATES:
The problem here is you are appending views and constraints into a View Controller which has not allocated yet.
The reason we append sub views and constraints within viewDidLoad() is because we cannot add subviews and constraints before the view....did....load into memory. Otherwise, it's not there, and you get the error above. Consider breaking out your detailViewController into a class declaration, like so:
class detailViewController: UIViewController {
let eobTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.blue
label.text = "Scan Results"
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textColor = UIColor.white
return label
}()
lazy var eobTitleConstraints: [NSLayoutConstraint] = [
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.eobTitleLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
]
override func viewDidLoad() {
self.view.addSubview(eobTitleLabel)
self.view.addConstraints(self.eobTitleConstraints)
}
}
Also, not to come off as offensive, but your code is kind of a mess. Things you should avoid in the future:
adding constraints to a label which does not exist. ( rename the label of fix the constraints)
you are declaring vars in a outlet method. dont do this, declare methods and properties at the class level.
Read about OOP and how it is implemented in swift. This will help you understand the methods and patterns to complete your task :)