Expandable cell, self sizing height - ios

I made expandable view with dynamic amount of UILabels (created progammatically according to the count of results query produces). However I am not able to resize the expanded view height according to how many UILabels I have in the expanded view.
Using for loop I make as many labels as there are results after query.
Now I put them into view like this:
for (index, _) in timesBetween2Stations.enumerated() {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 20))
cell.ExpandableView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.center = CGPoint(x: cell.ExpandableView.frame.width / 2, y: 0)
label.textAlignment = NSTextAlignment.center
var substationTime = timesBetween2Stations[index]?.time
let substation = timesBetween2Stations[index]?.station
substationTime = substationTime!.removeTrainTimeZeros()
label.text = substation! + " " + substationTime!
cell.ExpandableView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-(10)-[label]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["label": label, "expandable": cell.ExpandableView]))
if (previousLabel == nil){
cell.ExpandableView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-(10)-[label]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["label": label, "expandable": cell.ExpandableView]))
}
else {
cell.ExpandableView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[previousLabel]-(10)-[label]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["label": label, "previousLabel": previousLabel as Any]))
}
previousLabel = label
}
Question is - why it's not resizing the view height according to the constraints? :)

Try this:
cell.setNeedsLayout()
cell.layoutIfNeeded()
or
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
after lor.

Related

Swift 3 dynamic button height depends on title length in scrollable stackview

I want to create scrollview with radiobuttons/checkbox buttons. I tried different ways working with scrollview and stackview, but here are my issues:
Problem: multiline works fine, but button frame doesn`t work
Problem: multiline title and buttons dynamic height doesn't work; dynamic stackview height in scroll view works fine
Question: how can I make button depends on titleHeight works with scrollable stackview
private func testDynamicBtnHeight() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
mVScrollContainer.addSubview(scrollView)
mVScrollContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: .alignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
mVScrollContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView]|", options: .alignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
scrollView.addSubview(stackView)
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[stackView]", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["stackView": stackView]))
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[stackView]|", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["stackView": stackView]))
for i in 0 ... 30 {
var btn1 = RadioButtonMock.mockRadioButton(title: "A B C D", backgroundColor: UIColor.green)
var btn2 = RadioButtonMock.mockRadioButton(title: " 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ", backgroundColor: UIColor.red)
var btn3 = RadioButtonMock.mockRadioButton(title: "B B B B B B B", backgroundColor: UIColor.blue)
stackView.addArrangedSubview(btn1)
stackView.addArrangedSubview(btn2)
stackView.addArrangedSubview(btn3)
if i == 9 {
let url = URL(string: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQacMIFnZ2xZ_ZH7CjdUzzqpsMHwLSiF98lGWVWVHy6Am5qm2mq")
let data = try? Data(contentsOf: url!)
if let imageData = data {
let ivImage = UIImageView()
ivImage.image = UIImage(data: imageData)
ivImage.frame = CGRect(x: 0, y: 0, width: 100, height: 200)
stackView.addArrangedSubview(ivImage)
}
}
}
//mSvContainer.heightAnchor.constraint(equalToConstant: 400)
}
Radio button code:
static func mockRadioButton(title: String, backgroundColor: UIColor) -> RadioButton {
NSLog(TAG + " mockRadioButton(title: String, backgroundColor: UIColor)")
// min requirements to display simple checkBox
let rb = RadioButton()
rb.setTitle(title, for: .normal)
rb.setTitleColor(.black, for: .normal)
//cb.addTarget(self, action: #selector(checkBoxParamTap(sender:)), for: .touchUpInside)
rb.isChecked = false
rb.registerClickListener()
// title advanced
rb.titleLabel?.numberOfLines = 0 //multiline
//debug
rb.backgroundColor = backgroundColor
NSLog(TAG + "ccccc \(rb.bounds.size.height)")
//size
//rb.heightAnchor.constraint(equalToConstant: 30).isActive = true
//let contentSize = self.txtVDetails.sizeThatFits(self.txtVDetails.bounds.size)
//rb.frame = CGRect(x: 10, y: 10, width: 200, height: 50) //doesnt work
rb.heightAnchor.constraint(equalToConstant: 40).isActive = true //works fine
//rb.sizeToFit()
//rb.layoutIfNeeded()
rb.contentEdgeInsets.top = 8
//cb.contentEdgeInsets.right = 16
rb.contentEdgeInsets.left = 8
rb.contentEdgeInsets.bottom = 8
return rb
}

IOS 11 only: Navigation bar label off on top

IOS 11 is causing the main label to move a little bit from the top rather than keeping to the top. The problem only occurs on IOS 11. With different IOS everything looks ok.
Code sample with a setting header:
private func setHeader(agentName: String = "", isTyping: Bool = false) -> Void {
if (agentName.isEmpty) {
self.containerViewController?.navigationItem.titleView = nil
} else {
let headerView: UIView = {
let rect = CGRect(x: 0, y: 0, width: 320, height: 44)
let uiview = UIView(frame: rect)
return uiview
}()
let headerLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: headerView.frame.width, height: 20))
label.font = UIFont.systemFont(ofSize: 18)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let subheaderLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: headerView.frame.width, height: 12))
label.font = UIFont.systemFont(ofSize: 12)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = self.title
return label
}()
headerView.addSubview(headerLabel)
headerView.addSubview(subheaderLabel)
let viewsDictionary = ["header": headerLabel, "subheader": subheaderLabel]
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[header]|", options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[subheader]|", options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
headerView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[header(20)]-[subheader]", options: [], metrics: nil, views: viewsDictionary))
self.containerViewController?.navigationItem.titleView = headerView
}
}
Beginning with iOS 11, views add to toolbars are now laid out using auto layout. You should add sizing constraints on your headerView. For example:
headerView.widthAnchor.constraintEqualToConstant(320.0).isActive = true
headerView.heightAnchor.constraintEqualToConstant(44.0).isActive = true
Otherwise, auto layout will use the intrinsic content size of your header view which is likely not what you expect.
For more information see the WWDC 2017 session Updating your app for iOS 11.
#beyowulf said that you have to add sizing constraints. I think in your case adding height constraint is okay :
headerView.heightAnchor.constraint(equalToConstant: 22.0).isActive = true

UITableview Header Horizontal and Vertical constraints using VSL

I have a header view with a label and a button. I'm adding the constraints like this:
//create a view, button and label
view.addSubview(label)
view.addSubview(button)
label.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
let views = ["label": label, "button": button, "view": view]
let horizontallayoutContraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-19-[label]-60-[button]-22-|", options: .alignAllCenterY, metrics: nil, views: views)
view.addConstraints(horizontallayoutContraints)
let verticalLayoutContraint = NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
view.addConstraint(verticalLayoutContraint)
return view
This works really well but now I'd like to add a divider view that spans the width above the label and button. Something like this:
let frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 10)
let divView = UIView(frame: frame)
divView.backgroundColor = UIColor.lightGray
I can't seem to figure out the combination of constraints to make this happen. Basically I want the divView to span the width of the tableview and the existing views to sit below it. Ideally I could nest it like this:
V:|[divView]-20-[H:|-19-[label]-60-[button]-22-|]-20-|
Any experts out there that can help me figure this out? I could just make a NIB but I'd prefer to do it programmatically.
Granted, I have done very, very little with Visual Format Language, but as far as I know you cannot nest in that manner.
Depending on exactly what you are trying to get as an end result, and what else might get added to this view (labels? images? etc), you might find it easier to use a UIStackView or two.
However, here is an example of using VFL... this will run as-is in a Playground, so it will be easy to make adjustments to see the effect. Also note that I did it two ways - align the label and button to the divider, or align the divider to the label and button. The comments and the if block should be pretty self-explanatory. The colors are just to make it easy to see where the elements' frames end up.
import UIKit
import PlaygroundSupport
let container = UIView(frame: CGRect(x: 0, y: 0, width: 600, height: 600))
container.backgroundColor = UIColor.green
PlaygroundPage.current.liveView = container
// at this point, we have a 600 x 600 green square to use as a playground "canvas"
// create a 400x100 view at x: 40 , y: 40 as a "header view"
let headerView = UIView(frame: CGRect(x: 40, y: 40, width: 400, height: 100))
headerView.backgroundColor = UIColor.blue
// add the Header View to our "main container view"
container.addSubview(headerView)
var label: UILabel = {
let l = UILabel()
l.backgroundColor = UIColor.yellow
l.text = "The Label"
return l
}()
var button: UIButton = {
let l = UIButton()
l.backgroundColor = UIColor.red
l.setTitle("The Button", for: .normal)
return l
}()
var divView: UIView = {
let v = UIView()
v.backgroundColor = UIColor.lightGray
return v
}()
headerView.addSubview(divView)
headerView.addSubview(label)
headerView.addSubview(button)
divView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
var vcs: [NSLayoutConstraint]
var views = ["divView": divView, "label": label, "button": button, "headerView": headerView]
let bAlignToDivider = true
if bAlignToDivider {
// use the width of the divView to control the left/right edges of the label and button
// V: pin divView to the top, with a height of 10
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:|[divView(10)]", options: [], metrics: nil, views: views)
headerView.addConstraints(vcs)
// H: pin divView 20 from the left, and 20 from the right
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-20-[divView]-20-|", options: [], metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin label to bottom of divView (plus spacing of 8)
// using .alignAllLeft will pin the label's left to the divView's left
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:[divView]-8-[label]", options: .alignAllLeft, metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin button to bottom of divView (plus spacing of 8)
// using .alignAllRight will pin the button's right to the divView's right
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:[divView]-8-[button]", options: .alignAllRight, metrics: nil, views: views)
headerView.addConstraints(vcs)
// H: add ">=0" spacing between label and button, so they use intrinsic widths
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"H:[label]-(>=0)-[button]", options: .alignAllCenterY, metrics: nil, views: views)
headerView.addConstraints(vcs)
}
else
{
// use left/right edges of the label and button to control the width of the divView
// H: pin label 20 from left
// pin button 20 from right
// use ">=0" spacing between label and button, so they use intrinsic widths
// also use .alignAllCenterY to vertically align them
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-20-[label]-(>=0)-[button]-20-|", options: .alignAllCenterY, metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin divView to the top, with a height of 10
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:|[divView(10)]", options: [], metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin label to bottom of divView (plus spacing of 8)
// using .alignAllLeft will pin the divView's left to the label's left
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:[divView]-8-[label]", options: .alignAllLeft, metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin button to bottom of divView (plus spacing of 8)
// using .alignAllRight will pin the divView's right to the button's right
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:[divView]-8-[button]", options: .alignAllRight, metrics: nil, views: views)
headerView.addConstraints(vcs)
}
Edit: Here is another variation.
This time, the "header view" will have only the x,y position set... Its width and height will be auto-determined by its content.
The gray "div" view's position and width will be controlled by constraining it to the label and button, which will use your specified values:
"H:|-19-[label]-60-[button]-22-|"
Again, you can just copy/paste this into a playground page...
import UIKit
import PlaygroundSupport
let container = UIView(frame: CGRect(x: 0, y: 0, width: 600, height: 600))
container.backgroundColor = UIColor.green
PlaygroundPage.current.liveView = container
// at this point, we have a 600 x 600 green square to use as a playground "canvas"
var label: UILabel = {
let l = UILabel()
l.backgroundColor = UIColor.yellow
l.text = "This is a longer Label"
return l
}()
var button: UIButton = {
let l = UIButton()
l.backgroundColor = UIColor.red
l.setTitle("The Button", for: .normal)
return l
}()
var divView: UIView = {
let v = UIView()
v.backgroundColor = UIColor.lightGray
return v
}()
var headerView: UIView = {
let v = UIView()
v.backgroundColor = UIColor.blue
return v
}()
// add our header view
container.addSubview(headerView)
// add div, label and button as subviews in headerView
headerView.addSubview(divView)
headerView.addSubview(label)
headerView.addSubview(button)
// disable Autoresizing Masks
headerView.translatesAutoresizingMaskIntoConstraints = false
divView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
var vcs: [NSLayoutConstraint]
var views = ["divView": divView, "label": label, "button": button, "headerView": headerView]
// init "header view" - we'll let its contents determine its width and height
// these two formats will simply put the header view at 20,20
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-20-[headerView]", options: [], metrics: nil, views: views)
container.addConstraints(vcs)
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:|-20-[headerView]", options: [], metrics: nil, views: views)
container.addConstraints(vcs)
// H: pin label 19 from left
// pin button 22 from right
// use 60 spacing between label and button
// width of label and button auto-determined by text
// also use .alignAllCenterY to vertically align them
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"H:|-19-[label]-60-[button]-22-|", options: .alignAllCenterY, metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin divView to the top, with a height of 10
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:|[divView(10)]", options: [], metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin label to bottom of divView (plus spacing of 20)
// using .alignAllLeft will pin the divView's left to the label's left
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:[divView]-20-[label]", options: .alignAllLeft, metrics: nil, views: views)
headerView.addConstraints(vcs)
// V: pin button to bottom of divView (plus spacing of 20)
// using .alignAllRight will pin the divView's right to the button's right
vcs = NSLayoutConstraint.constraints(withVisualFormat:
"V:[divView]-20-[button]|", options: .alignAllRight, metrics: nil, views: views)
headerView.addConstraints(vcs)

CGRect X no effect in the TextField

If you see my code I am trying to add 13 px padding from left of the city label in the textfield but it is not working. I am not sure what is going on there.
Here Is my textField
var cityTextField : RightPaddingTextField = {
var textField = RightPaddingTextField()
textField.textAlignment = .right
textField.translatesAutoresizingMaskIntoConstraints = false
var label = UILabel(frame: CGRect(x: 13, y: 0, width: 44, height: 44))
label.text = "City"
textField.leftViewMode = .always
textField.leftView = label
return textField
}()
Here setup constraints:
func cityLabelWithTextFieldSetup(){
addSubview(cityTextField)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0" : cityTextField]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0" : cityTextField]))
}
I am trying to achieve it like in this image but it's not working.
UPDATE
here is my code output please check only city row ..

UILabel vertical constraints with NSLayoutConstraints (swift 3 xcode)

Just started using NSLayoutConstraints and for some reason I can't get the vertical constraints to work. Whenever I run my application the label sits centered vertically in the view, regardless of what value I give it.
Here is the current code:
override func viewDidLoad() {
super.viewDidLoad()
print(self.view.bounds)
edgesForExtendedLayout = []
setupViews()
}
let headerTitleLabel: UILabel = {
let label = UILabel()
label.text = "Projects"
label.font = UIFont.boldSystemFont(ofSize: 14)
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
return label
}()
func setupViews() {
let v1 = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 200))
v1.backgroundColor = UIColor.red
self.view.addSubview(v1)
let v2 = UITableView(frame: CGRect(x: 0, y: v1.frame.height, width: view.bounds.width, height: (view.bounds.height-v1.frame.height)))
v2.backgroundColor = UIColor.blue
self.view.addSubview(v2)
v1.addSubview(headerTitleLabel)
v1.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": headerTitleLabel]))
v1.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": headerTitleLabel]))
}

Resources