UIPickerView placement on top of view - ios

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

Related

How to add a view as subview for certain controllers

I have multiple storyboards in my app. I want to add a view on always on the top just below the navigation bar for some of the controllers. How Can I achieve this?
I already used navigation delegate and add a view in the window but no luck. Steps to show the gray view in the attached image is.
1. On click of a button on that view controller; a gray view should show and remain on the top of the controllers until all the scanning of the device is not done whether the user should go any of the viewControllers.
You can create a UINavigationController subclass and add the view in it.
class NavigationController: UINavigationController {
let customView = UIView()
let iconImgView = UIImageView()
let msgLbl = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
customView.isHidden = true
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = .gray
view.addSubview(customView)
iconImgView.contentMode = .scaleAspectFit
iconImgView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(iconImgView)
msgLbl.numberOfLines = 0
msgLbl.lineBreakMode = .byWordWrapping
msgLbl.textColor = .white
msgLbl.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(msgLbl)
customView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
iconImgView.widthAnchor.constraint(equalToConstant: 40).isActive = true
iconImgView.heightAnchor.constraint(equalToConstant: 40).isActive = true
iconImgView.centerYAnchor.constraint(equalTo: customView.centerYAnchor).isActive = true
iconImgView.leadingAnchor.constraint(equalTo: customView.leadingAnchor, constant: 15).isActive = true
iconImgView.trailingAnchor.constraint(equalTo: msgLbl.leadingAnchor, constant: 15).isActive = true
msgLbl.topAnchor.constraint(equalTo: customView.topAnchor, constant: 10).isActive = true
msgLbl.bottomAnchor.constraint(equalTo: customView.bottomAnchor, constant: 10).isActive = true
msgLbl.trailingAnchor.constraint(equalTo: customView.trailingAnchor, constant: -15).isActive = true
msgLbl.heightAnchor.constraint(greaterThanOrEqualToConstant: 30).isActive = true
}
func showCustomView(message: String, icon: UIImage) {
msgLbl.text = message
iconImgView.image = icon
customView.isHidden = false
}
func hideCustomView() {
customView.isHidden = true
}
}
Embed all your view controllers in this navigation controller. When you want to show/hide the gray view in a view controller use
Show
(self.navigationController as? NavigationController)?.showCustomView(message: "Any Message", icon: UIImage(named: "anyImage")!)
Hide
(self.navigationController as? NavigationController)?.hideCustomView()
When you push another view controller from the same navigation controller the view won't be hidden until you call the hide method
You can simply create a custom UIView with the relevant frame and call addSubview() on the view you want to add it to.
lazy var customView: UIView = {
let customView = UIView(frame: CGRect.init(x: 0, y: self.view.safeAreaInsets.top, width: UIScreen.main.bounds.width, height: 100))
customView.backgroundColor = .gray
return customView
}()
#IBAction func onTapButton(_ sender: UIButton) {
self.view.addSubview(customView)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.customView.removeFromSuperview()
}
To add it below the navigationBar, use y position of frame as self.view.safeAreaInsets.top. With this your customView will always be aligned below the navigationBar.
You can create the view with the height as per your requirement. I've used height = 100.
Give the correct frame and you can add any view as a subView to another view.

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

UILabel doesn't always show up in its superview

I'm trying to create a stack view with nested stack views of views to create a grid. Every cell in a grid should have a label. The problem I have is that label is showing only in the very first view as in this screenshot:
Code of my main stack view is straightforward:
for _ in 1...5 {
let view = WeekView()
addArrangedSubview(view)
}
And this is the code of a nested stack view.
// Function called in init(frame:)
func configureView() {
spacing = 8
for _ in 1...7 {
let view = UIView()
let label = UILabel()
view.addSubview(label)
addArrangedSubview(view)
label.text = "hi"
label.textAlignment = .center
view.widthAnchor.constraint(equalToConstant: 40).isActive = true
view.translatesAutoresizingMaskIntoConstraints = false
label.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
label.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
}
}
Any suggestions will be much appriciated.
You are not setting the position for the label. You could add the horizontal and vertical constraints, but you can also simply do:
label.translatesAutoresizingMaskIntoConstraints = true
label.frame = view.bounds
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
instead of
label.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
label.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
The problem might be that you haven't set
label.translatesAutoresizingMaskIntoConstraints = false
Please try to set that directly after you create the UI element:
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
...
view.widthAnchor.constraint(equalToConstant: 40).isActive = true
view.heightAnchor.constraint(equalToConstant: 40).isActive = true
Yet, in your case I would highly recommend using a UICollectionView. You get a lot of things for free if you use it (including smooth horizontal scrolling).
The image looks like you are building some kind of calendar app. If you use a UICollectionView you also get a very flexible API for spacing and overall layout.

Trouble with Swift 3 - MSStickerBrowserView and scrollview?

Alright, I'm having some issues in Swift 3 while trying desperately to add an MSStickerBrowserView to a UIScrollview.
I'm working in a message extension app and have added my scrollview via storyboard (I usually make them programmatically but I thought it'd be easier with constraints). I have connected the scrollview as an outlet in my MessagesViewController script.
I need to add an MSStickerBrowser I have created as a subview of this scrollview, meaning the user would be able to scroll TO the browser view and then scroll through the actual browser.
This is how I create my browser view normally and add it to the view - when added to the view it all works as it should:
func createStickerBrowser() {
let controller = MSStickerBrowserViewController(stickerSize: .large)
addChildViewController(controller)
view.addSubview(controller.view)
controller.stickerBrowserView.backgroundColor = UIColor.gray
controller.stickerBrowserView.dataSource = self
//resize this programmatically later
view.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
view.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
}
I don't know a lot about MSStickerBrowsers because the tutorials have been limited, however I know this ties the browser's size to that of the changing Message VC and adds it as a normal subview.
I then tried logically to just add it as a subview of the scrollview instead:
let controller = MSStickerBrowserViewController(stickerSize: .large)
//controller.view.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height * 0.5)
addChildViewController(controller)
scrollView.addSubview(controller.view)
controller.stickerBrowserView.backgroundColor = UIColor.gray
controller.stickerBrowserView.dataSource = self
//resize this programmatically later
scrollView.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
I've tried several variations but no matter what the sticker browser view is nowhere. The scrollview is there, not the browser, and no errors on that are printed to the console.
This seems like it should be simple - what am I doing wrong?

Add constraints to a button programmatically using Swift

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

Resources