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

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
}

Related

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

Dynamically add buttons in swift

I am writing an iOS app. Server gives me buttons array to draw on view. So, the number of buttons can vary according to server response,
example:
let buttonArray=["Button 1","Button 2","Button 3"]
or
let buttonArray=["Button 1","Button 2","Button 3"," Button 4", "Button 5"]
I have to stack these buttons vertically.
I created a stackview, add constraints to it, and then add buttons to this stackview as arrangedsubviews.
Buttons should have gap of 5 points between them:
Using stackview:
func addButtonsUsingStackView()
{
//create stackview:
let stackView=UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .fill
stackView.spacing = 5
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
//stackview constraints:
let viewsDictionary = ["stackView":stackView]
let stackView_H = NSLayoutConstraint.constraints(
withVisualFormat: "H:|-20-[stackView]-20-|",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: viewsDictionary)
let stackView_V = NSLayoutConstraint.constraints(
withVisualFormat: "V:|-30-[stackView]-30-|",
options: NSLayoutFormatOptions(rawValue:0),
metrics: nil,
views: viewsDictionary)
view.addConstraints(stackView_H)
view.addConstraints(stackView_V)
//adding buttons to stackview:
//let buttonArray=["Button 1","Button 2","Button 3"]
let buttonArray=["Button 1","Button 2","Button 3"," Button 4"]
for buttonName in buttonArray{
let button=UIButton()
button.setTitle(buttonName, for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.backgroundColor=UIColor.blue
button.translatesAutoresizingMaskIntoConstraints=false
stackView.addArrangedSubview(button)
}
}
Without stackview:
var buttonArray=["Button 1","Button 2","Button 3"," Button 4"," Button 5"," Button 6"," Button 7"]
func addButtonsLoop()
{
for _view in view.subviews{
_view.removeFromSuperview()
}
var i=0
var buttonY = 20
let buttonGap=5
for btn in buttonArray {
let buttonHeight=Int(Int(view.frame.height) - 40 - (buttonArray.count * buttonGap))/buttonArray.count
print(buttonHeight)
let buttonWidth=Int(view.frame.width - 40)
let button = UIButton()
button.backgroundColor = UIColor.orange
button.setTitle(btn, for: .normal)
button.titleLabel?.textColor = UIColor.white
button.frame = CGRect(x: 20, y: buttonY, width:buttonWidth , height:buttonHeight)
button.contentMode = UIViewContentMode.scaleToFill
buttonY += buttonHeight + buttonGap
button.tag = i
button.addTarget(self, action: #selector(self.buttonTapped(_:)), for: UIControlEvents.touchUpInside)
view.addSubview(button)
i+=1
}
}
func buttonTapped( _ button : UIButton)
{
buttonArray.remove(at: button.tag)
addButtonsLoop()
}
My question is that instead of above code, how to apply NSLayoutConstraints or LayoutAnchors to solve this?
Instead of stack view you can use a tableview with the tableview cell containing a button and the number of rows can be the buttonarray count. In cellForRowAtIndexPath delegate method set the button title from button array.
you can use scroll view to add buttons.
var btnY = 5
let btnHeight = 40
func addButtonsUsingStackView()
{
for view in self.view.subviews{
view.removeFromSuperview()
}
for i in 0..< buttonArray.count {
let btnFloor = UIButton()
btnFloor.backgroundColor = Orange
btnFloor.titleLabel?.textColor = UIColor.white
btnFloor.frame = CGRect(x: 10, y: btnY, width: Int(scrView.frame.width - 20), height: btnHeight)
btnFloor.contentMode = UIViewContentMode.scaleToFill
btnY += btnHeight + 5
btnFloor.tag = buttonArray.index(of: i)
btnFloor.addTarget(self, action: #selector(self.btnTappedFloor(_:)), for: UIControlEvents.touchUpInside)
self.view.addSubview(btnFloor)
}
return cell
}
func btnTappedFloor( _ button : UIButton)
{
buttonArray.remove(at: button.tag)
addButtonsUsingStackView()
}

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 ..

Expandable cell, self sizing height

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.

addTarget and addGestureRecognizer not working, no crash/error

I a have an overlay with a table in and I'd like to add a Tap gesture recogniser to the background to dismiss the view and also addTarget to a button within the overlay which does the same thing.
The overlay displays fine as expected, however whenever I tap the black background or the cancel button, nothing happens. I've searched for an answer here but nothing found has worked. My code is as follows, followed by a screenshot of the overlay:
class importedFileView: NSObject {
let blackView = UIView()
let importedFileContainerView: UIView = {
let importedFileContainerView = UIView(frame: .zero)
importedFileContainerView.backgroundColor = .white
importedFileContainerView.layer.cornerRadius = 10
importedFileContainerView.layer.masksToBounds = true
return importedFileContainerView
}()
let headerLabel: UILabel = {
let headerLabel = UILabel()
headerLabel.translatesAutoresizingMaskIntoConstraints = false
headerLabel.font = UIFont(name: "HelveticaNeue-Thin" , size: 24)
headerLabel.text = "Attach file"
headerLabel.textColor = .darkGray
headerLabel.adjustsFontSizeToFitWidth = true
return headerLabel
}()
let fileTableView: UITableView = {
let fileTableView = UITableView()
return fileTableView
}()
let updateDetailsButton: UIButton = {
let updateDetailsButton = UIButton()
updateDetailsButton.translatesAutoresizingMaskIntoConstraints = false
updateDetailsButton.backgroundColor = UIColor(r:40, g:86, b:131)
updateDetailsButton.setTitleColor(UIColor.white, for: .normal)
updateDetailsButton.setTitle("Attach selected files", for: .normal)
updateDetailsButton.titleLabel!.font = UIFont(name: "HelveticaNeue-Light" , size: 18)
updateDetailsButton.layer.cornerRadius = 2
return updateDetailsButton
}()
let cancelButton: UIButton = {
let cancelButton = UIButton()
cancelButton.translatesAutoresizingMaskIntoConstraints = false
cancelButton.backgroundColor = UIColor.white
cancelButton.setTitleColor(UIColor(r:40, g:86, b:131), for: .normal)
cancelButton.setTitle("Cancel", for: .normal)
cancelButton.titleLabel!.font = UIFont(name: "HelveticaNeue-Light" , size: 18)
cancelButton.layer.cornerRadius = 2
return cancelButton
}()
let frameHeight: CGFloat = 450
func showFormView(){
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
window.addSubview(blackView)
window.addSubview(importedFileContainerView)
importedFileContainerView.addSubview(headerLabel)
importedFileContainerView.addSubview(fileTableView)
importedFileContainerView.addSubview(updateDetailsButton)
importedFileContainerView.addSubview(cancelButton)
cancelButton.addTarget(self, action: #selector(handleDismiss), for: .touchUpInside)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
layoutViews()
fileTableView.frame = CGRect(x: 30, y: window.frame.height, width: window.frame.width - 60, height: 230)
let frameY = (window.frame.height - frameHeight) / 2
importedFileContainerView.frame = CGRect(x: 20, y: window.frame.height, width: window.frame.width - 40, height: self.frameHeight)
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.importedFileContainerView.frame = CGRect(x: 20, y: frameY, width: window.frame.width - 40, height: self.frameHeight)
}, completion: nil
)
}
}
func layoutViews(){
let views = ["v0" : headerLabel, "v1": fileTableView, "v2": updateDetailsButton, "v3": cancelButton]
let leftSpace = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v0]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let leftSpace1 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v1]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let leftSpace2 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v2]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let leftSpace3 = NSLayoutConstraint.constraints(withVisualFormat: "H:|-20.0-[v3]-20.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
let topSpacing = NSLayoutConstraint.constraints(withVisualFormat: "V:|-20.0-[v0(40)]-20.0-[v1(230)]-20.0-[v2(50)]-10.0-[v3(50)]-10.0-|", options: NSLayoutFormatOptions(), metrics: nil, views: views)
importedFileContainerView.addConstraints(topSpacing)
importedFileContainerView.addConstraints(leftSpace)
importedFileContainerView.addConstraints(leftSpace1)
importedFileContainerView.addConstraints(leftSpace2)
importedFileContainerView.addConstraints(leftSpace3)
}
func handleDismiss() {
UIView.animate(withDuration: 0.5,
delay: 0.0,
options: .curveEaseInOut,
animations: {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.importedFileContainerView.frame = CGRect(x: 20, y: window.frame.height, width: window.frame.width - 40, height: self.frameHeight)
}
},
completion: { [weak self] finished in
self?.blackView.removeFromSuperview()
self?.importedFileContainerView.removeFromSuperview()
})
}
override init() {
super.init()
}
}
self.blackView.isUserInteractionEnabled = true;
is all you need to add to the blackView (UIView).
Without that, the view doesn't have any interactions enabled and so the gesture recognizer's target/action isn't triggered.
Events are ignored.
https://developer.apple.com/reference/uikit/uiview/1622577-isuserinteractionenabled
You might also want to disable it during animations.
Are you keeping a strong reference to the instance of your importedFileView while your overlays are visible? As far as I tested, all actions are silently ignored when the target is lost.
For example, this does not work:
#IBAction func someAction(_ sender: Any) {
let ifv = importedFileView()
ifv.showFormView()
//`ifv` is released at the end of this method, then your overlays are shown...
}
This works:
let ifv = importedFileView() //keep the instance as a property of your ViewController.
#IBAction func someAction(_ sender: Any) {
ifv.showFormView()
}
Programmatically generated UIViews isUserInteractionEnabled is default to true. You have no need to explicitly set it to true.
By the way, you'd better not name your non-UIView class as ...View, and better make your type names start with capital letter, which makes your code more readable to experienced Swift programmers.

Resources