(Swift) programmatically constrain UIButton to corner - ios

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)

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

Programmatic UIButton as a subview of UIView is not responding

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

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

How to make auto resizing buttons via swift 3 that work between different Iphones

I'll post down below a part of my code on an app I'm working on for fun, the concept it's different, but the design of the camera session is very similar with the instagram app. I'm also using the PageMenu cocoa pod, that is really good and easy to implement via code.
override func viewDidLoad() {
super.viewDidLoad()
pageMenu?.delegate = self
photoControllerBG.frame = CGRect(x: 0, y : self.view.frame.minY, width: self.view.frame.width, height: self.view.frame.height )
let cameraButton = UIButton(frame: CGRect(x: self.view.frame.midX - , y: photoControllerBG.frame.minY + (self.view.frame.maxY/20*1), width: 130, height: 130))
cameraButton.addTarget(self, action: #selector(cameraButtonPressed(button:)), for: .touchUpInside)
cameraButton.setImage(#imageLiteral(resourceName: "Camera Button.png"), for: UIControlState.normal)
self.view.addSubview(cameraButton)
}
At the moment, the button on an iPhone 7 Plus will display like I want it to, centered in the ViewController and with the Image of the camera button with the same width and height so it stay a perfect circle. The problem presents itself when i switch the simulator with a iPhone SE or iPhone 6, where the button will become too big and not centered. Can you guys help me to find a math formula that it can be used to solve this big but small problem?
Your question implies doing the following in code (swift). Unless you are creating the button in swift I wouldn't do that at all. So, If you have a button created in a storyboard you can scale it appropriately as follows:-
I would use a proportional width constraint combined with an aspect ratio constraint in the story board to achieve this.
It may not be apparent straight away how to do this so I attach a picture journey.
Add your button to the view controller. Image shows final constraints.
Add an Equal Width constraint between the button and it's superview
The button doesn't have equal width to it's superview! My button is 100 px wide and the superview is 375 px wide. So let's calculate that ratio to help us calculate the proportional width.
Edit the Equal width constraint by using the proportional width above as the multiplier.
Add an aspect ratio to the button
Add any remaining constraints (In my case I centred the button horizontally and vertically)
Your constraints for the button should look like this:
let label = UILabel()
label.text = "title"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(#imageLiteral(resourceName: "image"), for: .normal)
view.addSubview(button)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 16)
])
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.topAnchor.constraint(equalTo: label.bottomAnchor),
button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3), //change your multiplier
button.heightAnchor.constraint(equalTo: button.widthAnchor)
])

Resources