Programmatic UIButton as a subview of UIView is not responding - ios

I'm making a class that extends UITableViewCell--it's going to display a social media newsfeed, with each cell being someone's post.
I've added a UIButton as a subview of a UIView, So the UIView will contain both the button and a label as subviews, like this. (Sorry the image is kind of small but I think you'll get the idea of it).
However, the UIButton isn't responding to taps, not even highlighted. Its superview itself has a number of superviews, all stacks, but I checked and every superview's isUserInteractionEnabled is true.
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button(UIImage(named: "vertical_dots")?.withRenderingMode(.alwaysOriginal), for: .normal)
button.backgroundColor = .white
button.imageView?.contentMode = .scaleAspectFit
button.addTarget(self, action: #selector(myButtonAction), for: .touchUpInside)
label.text = "\(userPost?.minutesAgo)m"
label.font = label.font.withSize(11.0)
label.textColor = .lightGray
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 1
let superview = UIView()
superview.translatesAutoresizingMaskIntoConstraints = false
superview.addSubview(button)
superview.addSubview(label)
label.leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true
label.heightAnchor.constraint(equalToConstant: 40).isActive = true
label.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true
button.leftAnchor.constraint(equalTo: label.rightAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true
button.heightAnchor.constraint(equalToConstant: 20).isActive = true
button.widthAnchor.constraint(equalToConstant: 15).isActive = true
I'm getting a lot of constraint warning but I don't think that would affect things?
Note that this is only a chunk of my code, and I am adding the superview to other views, but I'm not showing all that here. None of my views have frames, only constraints, and they're all appearing correctly.
I don't think this question has been asked before because others are working with Interface Builder, while I'm doing it all programmatically.

Based on the constraints you've shown, you cannot tap your button because it is outside the bounds of your superview.
To confirm this, add this line:
superview.clipsToBounds = true
and you almost certainly won't even see your button or label.
You need to either give superview width and height constraints, or you need to add constraints so the subview(s) determine the superview's bounds.
For example, add these lines:
label.topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
button.rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true

Could be that the table view cell thinks it is being selected and is cancelling the touch of the button.
Try this:
tableView.allowsSelection = false
If that's not the case, try checking the frame of your button to see if you're actually tapping on it. You can use the Debug View Hierarchy button to check this.
Furthermore, you can try:
button.clipSubviews = true
If you can't see your button, then the frame is wrong, and you can't tap outside of the frame of a button. (Even if you can see it.)

Related

How to mask a UIButton in Swift?

I have a complicated Stack view that has many arranged labels and buttons. When I have attempted to mask the whole stack view in order to enforce a corner radius, the buttons within the stack view no longer worked (action selectors not triggered upon touch inside). I have decided to simplify the problem first to one button and one mask like so:
let mask = UIView()
mask.translatesAutoresizingMaskIntoConstraints = false
mask.backgroundColor = .green
mask.layer.cornerRadius = 50
view.addSubview(mask)
mask.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
mask.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
mask.widthAnchor.constraint(equalToConstant: 200).isActive = true
mask.heightAnchor.constraint(equalToConstant: 100).isActive = true
btn = UIButton()
btn.backgroundColor = .red
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("More info", for: .normal)
btn.tintColor = .black
btn.addTarget(self, action: #selector(moreInfoTapped), for: .touchUpInside)
view.addSubview(btn)
btn.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
btn.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
btn.widthAnchor.constraint(equalToConstant: 200).isActive = true
btn.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.layoutSubviews()
view.setNeedsLayout()
btn.mask = mask
This masks the button correctly but unfortunately the mask is placed in front of the button and thus taps on the button are never registered.
This is the view hierarchy:
Am I missing something?
I dont know why you are masking for achieving round corners if you guide me i can suggest you better solution .. but for your current case if you turn off user interaction of mask view .. your button will start responding
mask.isUserInteractionEnabled = false
I'll use the button example you specified above, as I don't know what you want to do with the stackview.
You'll style the mask as you want with the corner radius. And then you'll add the button to the mask but with a transparent background and make it fill the parent i.e. mask
let maskView = UIView()
view.addSubview(maskView)
maskView.setCornerRadius() // implement this
maskView.setupConstraints() // implement this
let button = UIButton()
button.backgroundColor = .clear
maskView.addSubview(button)
button.fillParent() // implement this
The same logic will apply for the stackview. You just need to style the container view or mask view.
Alternatively, you can include both the button and mask view in a container view:
the mask beneath the button
the button to be clear
the container view to be clear
style the mask view
have both mask and button fill the container view (or size them as would work best for you

Adding buttons programmatically to stackView in Swift

I've tried to add buttons dynamically/programmatically in UIStackView that I've built with interface builder but they failed to show up when I run the application. The number of buttons that's supposed to be added ranging normally from 4-6. Can you guys tell me what's wrong with the code
#Nowonder I just recreate what you are trying to achieve. The following are the steps.
Add a UIStackView in viewController from Interface Builder and add required constraints.
Add the following code.
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("btn 1", for: .normal)
button.backgroundColor = UIColor.red
button.translatesAutoresizingMaskIntoConstraints = false
let button2 = UIButton()
button2.setTitle("btn 2", for: .normal)
button2.backgroundColor = UIColor.gray
button2.translatesAutoresizingMaskIntoConstraints = false
let button3 = UIButton()
button3.setTitle("btn 3", for: .normal)
button3.backgroundColor = UIColor.brown
button3.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.alignment = .fill
buttonStackView.distribution = .fillEqually
buttonStackView.spacing = 8.0
buttonStackView.addArrangedSubview(button)
buttonStackView.addArrangedSubview(button2)
buttonStackView.addArrangedSubview(button3)
}
Following is the outcome.
Hope it helps.
I think you need to do few more steps before the button will start showing up.
Add the stack view to current view's subviews
view.addSubview(buttonStackView)
Now each of these views buttons, as well as the stackView, needs to set the translatesAutoresizingMaskIntoConstraints to false.
button1.translatesAutoresizingMaskIntoConstraints = false
button2.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
Now set the stackView contraints
NSLayoutConstraint.activate([
buttonStackView.topAnchor.constraint(equalTo: view.topAnchor),
buttonStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
buttonStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
buttonStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor)])
You will need to provide the constraints, or can override the intrinsic size of stackView.
override func intrinsicContentSize() -> CGSize
{
return CGSizeMake(200, 40)
}
As UILabel have the intrinsic size and so, the button will show, if not you will have to set the constraints for them also to be safe.
SOLVED!
The button showed up when I set the type of the button with the instance method init(type:)

Programmatically changing size of 1 subview in UIStackView

I am currently making a calculator and want to change the size of my 0 button to the size of 2 subviews - which is half the size of the entire view. I want it to look exactly like apples calculator app, where the 0 is bigger than all the other buttons.
The way i layout my view is by having a vertical UIStackView and adding horizontal UIStackView's to it, just like the picture below.
Therefore, i want the last horizontal stack to have 3 arranged subviews but make the 0 button fill the exceeding space, so the , and = buttons are the same size as all other buttons.
Thank you.
Programmatically, you could set multiplier with constraint(equalTo:multiplier:), official docs: https://developer.apple.com/documentation/uikit/nslayoutdimension/1500951-constraint
So we could constraint the last two button with same width and make the first one two times longer than one of the other two.
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let btn1 = UIButton()
let btn2 = UIButton()
let btn3 = UIButton()
btn1.backgroundColor = .red
btn2.backgroundColor = .yellow
btn3.backgroundColor = .blue
let hStack = UIStackView(arrangedSubviews: [btn1, btn2, btn3])
hStack.axis = .horizontal
hStack.spacing = 1
view.addSubview(hStack)
hStack.translatesAutoresizingMaskIntoConstraints = false
hStack.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
hStack.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
hStack.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Here would be what you need:
btn2.widthAnchor.constraint(equalTo: btn3.widthAnchor).isActive = true
btn1.widthAnchor.constraint(equalTo: btn2.widthAnchor, multiplier: 2).isActive = true
}
You can use stackview.distribution = .fillEquallly for first 4 horizontal stackviews and use stackview.distribution = .fillPropotionally for the last horizontal stackview.
Then set with constraint for the 0 button to 50% of last horizontal stackview's width.

define UIButton programmatically swift

I'm trying to add a button programmatically as follows.
lazy var myButton: UIButton = {
let button = UIButton(type: .System)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blueColor()
button.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height / 2)
button.addTarget(self, action: "buttonAction", forControlEvents: .TouchUpInside)
return button
}()
My intention was to create a button with view's width and height, half of view's height. And I also have LeftAnchor, RightAnchor, WidthAnchor, TopAnchor constraints to this button.
I have the following piece of code in my viewDidLoad()
view.addSubview(myButton)
when I try to run this code, I'm not able to see exactly what i want to have on my simulator.
I would like to see button width = view' width and height of button = view height /2
instead I see small button on the left top of the simulator. How do i resolve this issue?
Thanks !
A better bet would be to use AutoLayoutConstraints.
var myButtonHeight: NSLayoutConstraint
view.addSubview(myButton)
myButton.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
myButtonHeight = myButton.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5)
myButtonHeight.isActive = true
It's not clear from your comment if changing of the button height is onTouchDown, of if its a toggle as a result of onTouchUpInside, but either way
myButtonHeight.constant = (buttonShouldBeTall) ? 20 : 0
view.setNeedsLayout()
Please keep in mind you'll need to position the leading/trailing/centerX anchor and leading/trailing/centerY anchor as well, depending on where you need it to be

(Swift) programmatically constrain UIButton to corner

I have the following code for an image that is a round button:
let settingsButton = UIButton(type: .Custom)
settingsButton.frame = CGRectMake(160, 100, 50, 50)
settingsButton.layer.cornerRadius = 0.5 * settingsButton.bounds.size.width
settingsButton.setImage(UIImage(named:"settingsButton.png"), forState: .Normal)
settingsButton.clipsToBounds = true
view.addSubview(settingsButton)
I want to constrain it to the top left corner of my view controller, but since I made this button programmatically, I can't see it in my storyboard and therefore cannot move and constrain it manually. Is there a way to be able to see this programmatically created button in my storyboard's view controller? If not, how can I programmatically constrain this button I created to the top left corner of my view controller?
Any help is greatly appreciated!
The easiest way to do this, since you're creating the button in code, is to use the button's autoresizing mask. First, set the button's frame so it's in the top right corner of the superview. Then set the button's autoresizingMask to allow only the distances to the left and bottom edges of the superview to vary:
settingsButton.frame = CGRect(x: view.bounds.maxX - 50, y: 0, width: 50, height: 50)
settingsButton.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin]
view.addSubview(settingsButton)
My code is in Swift 3 syntax but it should be trivial to convert it so Swift 2.
Keep in mind that autoresizing masks work fine under auto layout. Many of Apple's standard classes still use autoresizing masks internally. Xcode 8 added the ability to mix constraints and autoresizing masks in a storyboard or xib, so clearly Apple thinks you should use autoresizing when it's a good fit.
If I interpret constrain as-in using constraints, you can use layout anchors. The gotchas here are:
Always set translatesAutoresizingMaskIntoConstraints = false on your view.
Set .active = true on your layout anchors.
Only available in iOS >= 9.0 (use normal NSlayoutConstraints if you need to support earlier versions of iOS).
Example code:
let settingsButton = UIButton(type: .Custom)
view.addSubview(settingsButton)
settingsButton.backgroundColor = UIColor.redColor()
settingsButton.translatesAutoresizingMaskIntoConstraints = false
settingsButton.widthAnchor.constraintEqualToConstant(50).active = true
settingsButton.heightAnchor.constraintEqualToConstant(50).active = true
settingsButton.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 10).active = true
settingsButton.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor, constant: -10).active = true
settingsButton.setNeedsLayout()
settingsButton.layoutIfNeeded()
settingsButton.layer.cornerRadius = 0.5 * settingsButton.bounds.size.width
settingsButton.setImage(UIImage(named:"settingsButton.png"), forState: .Normal)
settingsButton.clipsToBounds = true
Note that using constraints is superior to setting frames because constraints adapt to changes in the parent view sizeĀ (e.g device rotation to landscape mode).
You can use this :
let settingsButton = UIButton()
settingsButton.translatesAutoresizingMaskIntoConstraints = false
settingsButton.trailingAnchor.constraint(equalTo: view.leadingAnchor,constant: 160).isActive = true
settingsButton.bottomAnchor.constraint(equalTo: view.topAnchor,constant: 100).isActive = true
settingsButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
settingsButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
settingsButton.widthAnchor.constraint(equalToConstant: 50).isActive = true
settingsButton.layer.cornerRadius = 0.5 * settingsButton.bounds.size.width
settingsButton.clipsToBounds = true
view.addSubview(settingsButton)

Resources