Add constraints to a button programmatically using Swift - ios

I am struggling to add constraints to a location finder button I want to add to a UIMapView in swift, so far none of my constraints seem to be working and the button is stuck in the top left hand corner, here is my code:
override func loadView()
{
mapView = MKMapView()
view = mapView
let margins = view.layoutMarginsGuide
let locationButton = UIButton()
mapView.addSubview(locationButton)
locationButton.setTitle("find location", for: [])
locationButton.backgroundColor = UIColor.lightGray().withAlphaComponent(0.6)
locationButton.layer.cornerRadius = 3
locationButton.sizeToFit()
locationButton.translatesAutoresizingMaskIntoConstraints = true
locationButton.bottomAnchor.constraint(equalTo: margins.bottomAnchor, constant: 20).isActive = true
locationButton.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
locationButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
}
any help will be sincerely appreciated.

Set
locationButton.translatesAutoresizingMaskIntoConstraints = false

Related

UIPickerView placement on top of view

I am trying to place a UIPickerView on top of my TableView when a button is clicked. I tried using the storyboard, but it moves the rows of the table down. What I want is the picker to appear as a popover when the button is clicked, then disappear when an option is selected.
Here is the code I am using to get the picker to display:
let picker = UIPickerView()
let container = UILayoutGuide()
view.addLayoutGuide(container)
picker.backgroundColor = UIColor.white
picker.dataSource = self
picker.delegate = self
view.addSubview(picker)
picker.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
picker.heightAnchor.constraint(equalToConstant: 100).isActive = true
picker.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
However, the picker shows up like this:
I don't understand why the picker view is not stretched until the end because I am setting the trailingAnchor as the parent view's trailingAnchor.
Have you set translatesAutoresizingMaskIntoConstraints of your picker view to false? It ignores constraints unless you do that
If trailing anchor is not working, then try setting left, top and right to 0 and provide a height as well (if you don't wish to cover the complete tableView) .
Also, add your pickerView on top of your tableView for these
constraints to be applied
If you wish to do it programatically, you can provide the constraints while you're creating the UIPickerView object
let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
Instead of the layout guide, you can directly use your table view as of your relative view.
Coding Example:
func addPickerView() {
let picker = UIPickerView()
// let container = UILayoutGuide()
// view.addLayoutGuide(container)
picker.translatesAutoresizingMaskIntoConstraints = false
picker.backgroundColor = UIColor.red
picker.dataSource = self
picker.delegate = self
view.addSubview(picker)
picker.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
picker.heightAnchor.constraint(equalToConstant: 100).isActive = true
picker.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
}
Above code produces the following output which is stretched to its parent view.
Set constraints equal to parent view then show perfect.
let topBarHeight = UIApplication.shared.statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
let picker = UIPickerView()
picker.translatesAutoresizingMaskIntoConstraints = false
let container = UILayoutGuide()
view.addLayoutGuide(container)
picker.backgroundColor = UIColor.lightGray
picker.dataSource = self
picker.delegate = self
view.addSubview(picker)
picker.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: view.topAnchor, constant: topBarHeight).isActive = true
picker.heightAnchor.constraint(equalToConstant: 200).isActive = true
picker.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
I figured out the best way to do it, in the Interface Builder itself. I had to change the TableViewController to a ViewController and put the TableView inside of it (setting the ViewController as a TableViewDelegate and TableViewDataSource). This allowed me to place the PickerView on the IB, as a layered view on top of my table cells. Then I could hide and show it as I pleased by creating an IBOutlet for it and setting the .isHidden property appropriately.
More info here: Swift: TableView in ViewController

How to center button in horizonatal stackview?

I have a horizontal stackview with two buttons. Now I want, if one button is hidden other should be in the center.
like,
[x x]
[ x ]
Set the distribution attribute of the UIStackView to Fill Equally either from storyboard or programatically:
stackView.distribution = .fillEqually
Once a button is hidden, the other will be centered.
How about using some hidden UIViews?
Declare the stackView in your class:
var myFirstStack: UIStackView!
var bool = true
bool is a boolean that is going to simulate the condition when you'd want to hide or show the button.
Initialize the stackView in viewDidLoad:
myStack = UIStackView(arrangedSubviews: createButtons("1", "2"))
let v1 = UIView()
v1.isHidden = true
let v2 = UIView()
v2.isHidden = true
myStack.insertArrangedSubview(v1, at: 1)
myStack.addArrangedSubview(v2)
myStack.translatesAutoresizingMaskIntoConstraints = false
myStack.axis = .horizontal
myStack.spacing = 20
myStack.distribution = .fillEqually
view.addSubview(myStack)
It uses this function:
func createButtons(_ named: String...) -> [UIButton] {
var i = true
return named.map { name in
let btn = UIButton()
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle(name, for: .normal)
btn.backgroundColor = i ? .yellow : .red
i.toggle()
btn.setTitleColor(.black, for: .normal)
return btn
}
}
Add the autolayout constraints:
myFirstStack.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
myFirstStack.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
myFirstStack.heightAnchor.constraint(equalToConstant: 100).isActive = true
myFirstStack.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
Here is what it looks like so far:
When a third button is hit, the UIViews are hidden or shown:
#IBAction func hide(_ sender: Any) {
myFirstStack.arrangedSubviews[0].isHidden = bool //This is the button to hide
myFirstStack.spacing = bool ? -100 : 20 //Adjust the spacing to your liking
myFirstStack.arrangedSubviews[1].isHidden = !bool //Hide or show the first UIView
myFirstStack.arrangedSubviews[3].isHidden = !bool //Hide or show the second UIView
bool.toggle()
}
And here is the result:

Creating view with overlapping circle

I'm trying to create a simple UIView line and then a circle on top if it. i'm however not sure how i'm suppose to draw the circle without making hierarchy problems? so far i have below, which jut creates the line
line
self.stepLine = UIView()
self.stepLine.translatesAutoresizingMaskIntoConstraints = false
stepView.addSubview(self.stepLine)
self.stepLine.backgroundColor = Color.theme.value
self.stepLine.bottomAnchor.constraint(equalTo: stepView.bottomAnchor, constant: -4).isActive = true
self.stepLine.heightAnchor.constraint(equalToConstant: 6).isActive = true
self.stepLine.leftAnchor.constraint(equalTo: stepView.leftAnchor).isActive = true
override func layoutSubviews() {
self.stepLine.widthAnchor.constraint(equalToConstant: self.frame.width/2).isActive = true
}
Illustration
let circleView = UIView()
circleView.layer.cornerRadius = 10 // half of the width/height dimension
circleView.translatesAutoresizingMaskIntoConstraints = false
stepView.addSubview(circleView)
circleView.centerYAnchor.constraint(equalTo: stepLine.centerYAnchor).isActive = true
circleView.trailingAnchor.constraint(equalTo: stepLine.trailingAnchor).isActive = true
circleView.widthAnchor.constraint(equalToConstant: 20).isActive = true
circleView.heightAnchor.constraint(equalToConstant: 20).isActive = true
Or some variation thereof. But this is the general concept I would suggest given the nature of the code you presented.

Swift UITapGesture on view in a titleView not working

I have a UINavigationItem and I set it's titleView to a UIView which has a UILabel and UIImageView embedded. I'm attempting to add a UITapGestureRecognizer to the view but it doesn't seem to work. Any solutions? Also, adding a gestureRecognizer to the whole navigationBar isn't an option as I have a rightBarButtonItem and want to make use of the back button.
Here is my code:
func configureTitleView() {
guard let profile = profile else {
// Pop navController
return
}
let titleView = UIView()
titleView.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
titleView.addSubview(containerView)
let profileImageView = UIImageView()
profileImageView.translatesAutoresizingMaskIntoConstraints = false
profileImageView.contentMode = .scaleAspectFill
profileImageView.clipsToBounds = true
let imageURL = URL(string: profile!.firstProfilePicture!)
profileImageView.sd_setImage(with: imageURL)
containerView.addSubview(profileImageView)
profileImageView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
profileImageView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 36).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 36).isActive = true
profileImageView.layer.cornerRadius = 36 / 2
let nameLabel = UILabel()
containerView.addSubview(nameLabel)
nameLabel.text = profile!.displayName!
nameLabel.textColor = .white
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.leftAnchor.constraint(equalTo: profileImageView.rightAnchor, constant: 8).isActive = true
nameLabel.centerYAnchor.constraint(equalTo: profileImageView.centerYAnchor).isActive = true
nameLabel.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
nameLabel.heightAnchor.constraint(equalTo: profileImageView.heightAnchor).isActive = true
containerView.centerXAnchor.constraint(equalTo: titleView.centerXAnchor).isActive = true
containerView.centerYAnchor.constraint(equalTo: titleView.centerYAnchor).isActive = true
self.navigationItem.titleView = titleView
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.openProfile))
tapGesture.numberOfTapsRequired = 1
titleView.addGestureRecognizer(tapGesture)
titleView.isUserInteractionEnabled = true
}
Beginning with iOS 11, views added to toolbars as UIBarButtonItem using UIBarButtonItem(customView:) are now laid out using auto layout. This includes title views added to a UINavigationBar through the navigationItem.titleView property of a UIViewController. You should add sizing constraints on your titleView. For example:
titleView.widthAnchor.constraint(equalToConstant: 100).isActive = true
titleView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
Otherwise, auto layout will use the intrinsic content size of your title view which is CGSize.zero. Gestures are masked to the bounds of the view they are attached to even if the sub views of that view are not. Because the bounds of titleView without constraints is CGRect.zero it will never fire. Add constraints and it works as expected.
For more information see the WWDC 2017 session Updating your app for iOS 11.
You do not need to add explicit height and width constant constraint to custom view.
Just add subviews to custom view, add width and height anchor.
let customView = UIView()
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(button)
[
button.widthAnchor.constraint(equalTo: customView.widthAnchor),
button.heightAnchor.constraint(equalTo: customView.heightAnchor),
].forEach({$0.isActive = true})
navigationItem.titleView = customView

Adding Tap Gesture to any view triggers nothing

I am dealing with the single most frustrating issue so far in my journey to iOS development. I originally was trying to add a gesture recognizer to my UIImage view but was not having any luck. I did some searching on stack and found that I hadn't set imageView.isUserInteractionEnabled = true which I figured would solve my problem but it didn't. So then I started adding gesture recognizers to everything including the parent view of imageView but still got nothing. I am sure that something I am doing/haven't done is so simple but I have just totally missed it. Please help.
ProfileViewController: UIViewController {
// User's info container (Parent iof imageView)
let userInfoContainer: UIView = {
let head = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
head.backgroundColor = UIColor.white
head.translatesAutoresizingMaskIntoConstraints = false
head.layer.borderWidth = 1
print(head.isUserInteractionEnabled) // Returns true
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
head.addGestureRecognizer(tap)
return head
}()
// Users name
let userName: UITextField = {
let name = UITextField()
name.text = "John Doe"
name.translatesAutoresizingMaskIntoConstraints = false
name.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
name.addGestureRecognizer(tap)
return name
}()
// User's profile image
let profileImageView: UIImageView = {
let imageView = UIImageView(image: #imageLiteral(resourceName: "avatar"))
imageView.layer.cornerRadius = 50
imageView.layer.masksToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapped)))
return imageView
}()
//
//
// skipping other UI elements
//
//
override func viewDidLoad() {
super.viewDidLoad()
// Authentication bits
self.title = "Profile View"
self.view.backgroundColor = UIColor.white
self.view.addSubview(userInfoContainer)
self.view.addSubview(profileImageView)
self.view.addSubview(userName)
} // end viewDidLoad
func tapped() {
print(123)
}
// setup constraints
func setupUserInfoContainer() {
userInfoContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
userInfoContainer.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
userInfoContainer.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
// profile image arrangement
func setupProfileImage() {
profileImageView.leftAnchor.constraint(equalTo: userInfoContainer.leftAnchor, constant: 20).isActive = true
profileImageView.centerYAnchor.constraint(equalTo: userInfoContainer.centerYAnchor).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
func setupLabels() {
userName.leftAnchor.constraint(equalTo: profileImageView.rightAnchor, constant: 20).isActive = true
userName.centerYAnchor.constraint(equalTo: profileImageView.centerYAnchor).isActive = true
userName.heightAnchor.constraint(equalToConstant: 20).isActive = true
userName.isEnabled = false
}
} // end view controller
Rendered View:
View Hierarchy
Extra information that is likely not necessary: ProfileViewController is being handled by a UITabBarController but from what I have seen, that shouldn't make a difference.
Update
From looking around it looks like the appropriate swift 3 syntax is
let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapped(_:))) but I think having that line in the closure is what's causing this error to be thrown ../ProfileViewController.swift:37:74: Value of type '(NSObject) -> () -> ProfileViewController' has no member 'tapped'
If someone could explain how I can fix this that would be stellar.
I don't believe you can assign a gesture recognizer in this manner...
1 let profileImageView: UIImageView = {
2 let imageView = UIImageView(image: #imageLiteral(resourceName: "avatar"))
3 imageView.layer.cornerRadius = 50
4 imageView.layer.masksToBounds = true
5 imageView.translatesAutoresizingMaskIntoConstraints = false
6 imageView.isUserInteractionEnabled = true
7 imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapped)))
8 return imageView
9 }()
On line 7, what is self? It is the function that is returning a UIImageView.
Your func tapped() is not part of that function... it belongs to ProfileViewController.
You might be able to find a way to change the target, but a couple minutes of trying different approaches has not yielded any luck for me.
I think you need to create and add the GestureRecognizer(s) inside ProfileViewController / viewDidLoad() (or elsewhere within that class).
The problem is you didn't set delegate to the tap gesture. Check this
Hope this will work
Why don't you just initiliaise your image view at the top of the class like
let profileImageView = UIImageView()
and create a function to configure the imageview and add it as a subview such in view did load. That should work
func configureImageView() {
#add methods and customize image view
#add image view to sub view
}

Resources