Hide view when user scrolls down in tableview - ios

I have a filterbar, which is a UIView as well as a tableView in my controller like so:
override func viewWillLayoutSubviews() {
sortSalonsByDistanceAndReload()
}
func setupViews() {
view.addSubview(filterBar)
view.addSubview(tableView)
filterBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
filterBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
filterBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
filterBarHeightLC = filterBar.heightAnchor.constraint(equalToConstant: 44)
filterBarHeightLC?.isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: filterBar.bottomAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
I want to make the filterBar dissapear when the user scrolls down on the tableView, and then reappear when the user scrolls up

You can use Search Controller. Take a look at How to implement UISearchController in UITableView - SWIFT
if it doesn't work out for you , you could add as a section or part of header view

The delegate of your tableView should implement the UIScrollViewDelegate method scrollViewDidScroll: and query the contentOffset.y property on the scrollView (your tableView). Then it depends on when you want the bar to appear: only on the top, or also whenever the user scroll up inside the tableView. You could sync the contentOffset.y difference with the filterBars top constraint's constant (ranging from -44 to 0). It might be better to inset your tableView and update that inset as well (from 0 to 44).
If your filterBar could be implemented with a navigation bar from a UINavigationController you might just do navigationController?.hidesBarsOnSwipe = true, but I don't know your view controller setup.

Related

Setting a bottom scroll indicator inset on a UIScrollView view has unexpected effect on horizontal scrollbar position

UIScrollView lets us set a scrollIndicatorInset. However, when setting a bottom inset for this value on a landscape iPhone with a safeAreaInset (i.e. a 'notch'), the horizontal position of the scrollbar is unexpectedly updated.
Here is a scroll view on an iPhone X with no changes to scrollIndicatorInset - note that the scrollbar on the right edge is horizontally flush with the edge of the screen.
Now I add one line of code:
scrollView.scrollIndicatorInsets.bottom = 1
The bottom edge of the scroll indicator is inset as expected. But the scrollbar is now also relocated horizontally to align with the safe area, rather than the screen edge.
What is the cause of this horizontal inset and how can I prevent it?
Setting the bottom scroll inset to anything other than 0 adds the additional margin, and setting it back to 0 removes it. The same thing applies when setting any of the scroll insets edges.
Some logging before and after setting the scrollIndicatorInset shows no change to the safeAreaInset nor the layoutMargins on the view, the scroll view, or the content view inside the scroll view.
One place this is a problem is when placing a text field inside a scroll view and adjusting the bottom inset to accommodate the keyboard. The scrollbar jumps around as the keyboard is presented and dismissed.
I am providing a small view controller below in case you want to try it out for yourself.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scrollView = UIScrollView()
scrollView.backgroundColor = .white
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
contentView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
contentView.widthAnchor.constraint(equalToConstant: 50).isActive = true
contentView.heightAnchor.constraint(equalToConstant: 500).isActive = true
// this causes the issue
scrollView.scrollIndicatorInsets.bottom = 1
}
}
This is an old question, but as a workaround you can also set, in your case, the scroll view's right indicator inset to an appropriate negative distance, such as the parent view's right safe area inset.
override func viewDidAppear(_ animated: Bool) {
scrollView.verticalScrollIndicatorInsets.right = -self.view.safeAreaInsets.right
}
In iOS 13 and above, we should be using horizontalScrollIndicatorInsets and verticalScrollIndicatorInsets as scrollIndicatorInsets has been deprecated.
This isn't a perfect workaround, as you'll find the top and bottom insets also need adjusting. For instance, maybe you're trying to set the bottom inset to end halfway up the screen, and you're happy with that. But after these adjustments the top inset will now be zero by default, so you'll probably want to adjust that, too, or the scroll indicator will go all the way up into the rounded corner of the device.

Typing on search bar breaks layout

I have a tableView:
tableView = UITableView()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
let constraints = [tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.heightAnchor.constraint(equalToConstant: view.frame.height * 0.6)]
NSLayoutConstraint.activate(constraints)
And my searchController looks like:
func setupSearchController() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search for a candy"
navigationItem.searchController = searchController
definesPresentationContext = true
}
When it loads for the first time everything is fine. But when I tap on the search bar to type there, my search bar moves to the top, navigation bar becomes smaller and it breaks my layout and white space appears between tableView and searchBar:
How can I improve my constraints to move up and down my tableView when the navigation bar sizes are changed?
You have to change your top constraint of the tableview.
Try replacing this 2 constraint
tableView.topAnchor.constraint(equalTo: searchController.searchBar.bottomAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor)
With this
tableView.topAnchor.constraint(equalTo: view.topAnchor)
The automatic content inset adjustment will do the rest.
Note also that in your table view constraints list you doesn't have a leading/horizontal position constraint. I suggest to add also this constraint
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor)

MSMessagesAppViewController messes up UITableViewController scrolling

When my MSMessagesAppViewController changes the presentationStyle from compact to expanded and back to compact, my UITableView is messed up regarding its scrolling.
I am using AutoLayout to setup a UITableView inside of a View called contentView.
// Inside MSMessagesAppViewController
func createTableView() {
let tableViewController = MyTableViewController()
self.addChildViewController(tableViewController)
tableViewController.tableView.backgroundColor = UIColor.clear
tableViewController.tableView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(tableViewController.tableView)
NSLayoutConstraint.activate([
tableViewController.tableView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
tableViewController.tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0),
tableViewController.tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0),
tableViewController.tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0)
])
}
After expanding and collapsing the MSMessagesAppViewController, I can suddenly scroll past my last cell in the tableView...
Everything is working perfectly fine in the initial state. I noticed, that the scrollbar is visible on startup, but is not present after the size change...
This is how I setup my tableView in the controller:
// Inside MyTableViewController
func setupTableView() {
tableView.dataSource = self
tableView.register(QuickStandardTableViewCell.self, forCellReuseIdentifier: "standardCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 64
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorColor = .clear
tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
tableView.estimatedSectionHeaderHeight = 8.0
self.extendedLayoutIncludesOpaqueBars = false
}
What I checked:
The contentView frame changes correctly
The tableView frame changes correctly
The contentInset does not change
The contentSize does not change
Does anybody know what I am missing?
Thanks in advance!
Try using a UIViewController with a UITableView inside instead of using UITableViewController.
I had the same problem with an UICollectionViewController and switching to UIViewController solved this problem.

UIScrollView not scrolling at all when added programmatically in Swift?

So, I have looked through almost all of Stackoverflow's answers to this particular question and even looked through tutorials that supposedly teach you how to use a scroll view but It doesn't seem to apply for my project..
Here is what I know so far, in order for a Scroll View to properly work you first need to give it a content size. This determines the scrollable height etc.
I have provided some code to give you all a better idea of how I am adding said items into my scrollview. If there is something that I am doing wrong or if there is a better way to go about doing this please let me know, I am still fairly new to Swift and iOS development and in my mind it feels like I am doing it correctly.
The steps I am taking
Create items that I want to display (Input fields, Imageviews etc..)
Add said items to the view of the viewcontroller. (view.addsubview(etc..))
Create a scrollView and set its constraints to be same as the screen / view
Add our view with all the items in it into said scroll view
Relax and everything should work out perfect?????
Here is my code, I know it might be lengthy but I think it might be needed so that the scope of my question is understood
class JobRegistrationController: UIViewController {
// ... Omitted for clarity
lazy var scrollView: UIScrollView = {
let view = UIScrollView(frame: UIScreen.main.bounds)
view.backgroundColor = .red
view.contentSize = CGSize(width: self.view.bounds.width, height: self.view.bounds.height * 2)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//... Omitted for clarity
let scrollContentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Need so that view controller is not behind nav controller
self.edgesForExtendedLayout = []
view.addSubview(scrollView)
scrollView.addSubview(scrollContentView)
scrollContentView.addSubview(jobTypeField)
scrollContentView.addSubview(jobTypeDividerLine)
// x, y, width and height constraints
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
// x, y, width and height constraints
scrollContentView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollContentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollContentView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
// x, y, width and height constraints
jobTypeField.leftAnchor.constraint(equalTo: scrollContentView.leftAnchor, constant: 20).isActive = true
jobTypeField.topAnchor.constraint(equalTo: scrollContentView.topAnchor).isActive = true
jobTypeField.rightAnchor.constraint(equalTo: scrollContentView.rightAnchor, constant: -8).isActive = true
jobTypeField.heightAnchor.constraint(equalToConstant: 30).isActive = true
// x, y, width and height constraints
jobTypeDividerLine.leftAnchor.constraint(equalTo: scrollContentView.leftAnchor, constant: 20).isActive = true
jobTypeDividerLine.topAnchor.constraint(equalTo: jobTypeField.bottomAnchor).isActive = true
jobTypeDividerLine.rightAnchor.constraint(equalTo: scrollContentView.rightAnchor).isActive = true
jobTypeDividerLine.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
Use this method in your class
override func viewDidLayoutSubviews()
{
scrollView.delegate = self
scrollView.contentSize = CGSize(width:self.view.frame.size.width, height: 1000) // set height according you
}
view.contentSize = CGSize(width: self.view.bounds.width, height: self.view.bounds.height * 2)
You should try to log the contentSize in your console after trying to access it. I am not sure if you are setting the correct contentSize here if the self.view.bounds has been calculated correctly when this gets called at that moment. Since it takes time for self.view frame and bounds to be calculated.
Try setting your contentSize after you have added the actual content to it based on the actual total content size.
EDIT:
Add a single UIView inside the scrollView, with the constraints set to top-bottom-leading-trailing, and add your subviews to it. Also, set the same constraints on the scrollView to the superView top-bottom-leading-trailing.
I believe the line of code below is the problem
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
You are setting your content view to the top of the view, when you should be setting it to the top of the scrollview.
I've just overcome a similar issue were I was setting the topAnchor of my first view to the safeAreaLayoutGuide.topAnchorof the scrollView. Everything laid out correctly but the constraint wouldn't show and therefore the entire content of the scrollView didn't move.
The problem is that you don't tell where the bottom of your content is. In other words you need some bottom constraints.
If you use...
scrollContentView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
...you need also to add a constraint to bind at least one view to the bottom of your UIScrollView like:
scrollContentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
... and also bind the last view in the scrollContentView to its bottomAnchor.
jobTypeDividerLine.bottomAnchor.constraint(equalTo: scrollContentView.bottomAnchor).isActive = true
This will sure fix your issue. Because this way the whole constraint sequence is linked from top to bottom.
Bottom line, the UIScrollView is not that smart that it determines its own bottom in every possible way. It is a kind of lazy. If you don't tell him enough it wouldn't simply scroll, while it is clear that your content disappears behind the bottom of your UIScrollView container.

dynamic UIStackView inside of UIScrollView

Due to the extensive updates since iOS7, I wanted to ask this question because of my limited experience with autolayout and the new stackview, and I am wondering what is the best design practice to implement the following in Objective-C (not swift yet):
In my view, there is a container scroll view, with a child container UIView. Within this UIView, there are a number of elements. One of the elements is a stack of UIViews which differ in number once in a while.
This element is followed by a map and other views.
This is how I plan on organizing it:
Questions
Is this the correct thing to do? How would I modify the height constraint for the stackview when I remove and add elements programmatically?
How do you add a subview to the UIStackView through interface builder? When I do, the subview takes the size of the containing stackview.
Swift 4.2
If you want use code instead of story board, i create an example using auto layout that don't need to estimate size of content.
you just need to add to stack view or remove from it and scroll height modify automatically.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
scrollViewContainer.addArrangedSubview(redView)
scrollViewContainer.addArrangedSubview(blueView)
scrollViewContainer.addArrangedSubview(greenView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// this is important for scrolling
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let redView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 500).isActive = true
view.backgroundColor = .red
return view
}()
let blueView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 200).isActive = true
view.backgroundColor = .blue
return view
}()
let greenView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 1200).isActive = true
view.backgroundColor = .green
return view
}()
}
So you might want to make the whole layout contained within the stackview. Just something to consider.
There isn't really any right way to do things. I would not set a height constraint on your UIStackView (do add a width constraint that's equal to the view's width). Only set it on the elements you add to the stack view. You will get an error, but it's just IB complaining until you add an element to your UIStackView.
To size the elements in your stackview, you have to set a horizontal constraint on them. You can then modify that single horizontal constraint in code to change the height of the object.
To add a subview you simply do:
stackView.addArrangedSubview(childVC.view)
Or in interface builder, you just drag the element into the stack view. Make sure it has that horizontal constraint or it will resize on you.

Resources