i'm trying to keep the UISearchBarDisplayController on top of UItableview in swift
i tried this code (which i convert from objectC to swift) but no luck:
override func scrollViewDidScroll(scrollView: UIScrollView) {
//
var searchBar: UISearchBar = (self.searchDisplayController?.searchBar)!
//
var rect = searchBar.frame
self.searchDisplayController?.searchBar.frame = CGRectMake(0, max(0, scrollView.contentOffset.y), rect.width, rect.height);
}
the search bar still scroll with tableview when i scroll the table. Does anyone can help me to fix it? thanks
i found that i have to make a new view controller , add a tableview and add a searchBar outside of the tableview. and the search bar won't scroll with the tableview
In Swift 2.1, iOS 9.2.1 and Xcode 7.2
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
/* Search controller parameters */
searchController.searchResultsUpdater = self // This protocol allows your class to be informed as text changes within the UISearchBar.
searchController.dimsBackgroundDuringPresentation = false // In this instance,using current view to show the results, so do not want to dim current view.
definesPresentationContext = true // ensure that the search bar does not remain on the screen if the user navigates to another view controller while the UISearchController is active.
let tableHeaderView: UIView = UIView.init(frame: searchController.searchBar.frame)
tableHeaderView.addSubview(searchController.searchBar)
self.tableView.tableHeaderView = tableHeaderView
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let searchBar:UISearchBar = searchController.searchBar
var searchBarFrame:CGRect = searchBar.frame
if searchController.active {
searchBarFrame.origin.y = 10
}
else {
searchBarFrame.origin.y = max(0, scrollView.contentOffset.y + scrollView.contentInset.top)
}
searchController.searchBar.frame = searchBarFrame
}
Related
Ran through a quick tutorial with search bars and figured that I could use searchBar.sizeToFit() to autosize the search bar, however the right end of the search bar still extends off of the screen.
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate {
var searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchBar.delegate = self
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
definesPresentationContext = true
}}
I've tried to manually set the frame with something like searchController.searchBar.frame = CGRectMake(0, 0, 200, 200) but the width remained off of the screen. The search bar does not exist in the storyboard, however the tableView does.
Needed to set autolayout constraints for the tableView that the search bar was a part of. Setting these fixed the sizing issues.
If you're using Auto layout, then use leading and trailing edges instead of width constraints.
Just click on Pin option given below and select top,leading(left) and trailing(right) constraints, then click on Add 3 constraints. Make sure "Constraint to margin" checkbox is unchecked. If the constraints satisfy, There will be no warning. Try changing the background color of your search bar to see its position on the screen.
If setting up auto layout constraints doesn't fix the problem, try calling searchController.searchBar.sizeToFit() in viewWillLayoutSubviews() rather than viewDidLoad(). This may help if the search bar is contained in a view other than the table header view.
Swift 4, works for all types of ViewControllers
After countless hours of trial and error here's what I came up for those who want to use it in code:
Set delegate
class SearchDatasourceController: UIViewController , UISearchBarDelegate { }
Set a customTitleView and the searchBar
IMPORTANT: set their frame
lazy var customTitleView: UIView = {
var view = UIView()
let frame = CGRect(x: 0, y: 0, width: 300, height: 44)
view.frame = frame
return view
}()
lazy var searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.placeholder = "Search"
searchBar.autocapitalizationType = .none
searchBar.backgroundImage = UIImage()
(searchBar.value(forKey: "searchField") as? UITextField)?.backgroundColor = UIColor(r: 230, g: 230, b: 230)
searchBar.delegate = self
searchBar.becomeFirstResponder()
let frame = CGRect(x: 0, y: 0, width: 300, height: 44)
searchBar.frame = frame
return searchBar
}()
in viewDidLoad add the customTitleView that holds the searchBar
override func viewDidLoad() {
super.viewDidLoad()
customTitleView.addSubview(searchBar)
navigationItem.titleView = customTitleView
searchBar.becomeFirstResponder()
}
add delegate method to listen for searches
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print(searchText)
}
make sure you present your view controller on the navigation stack
func handleSearchBarButtonItemTapped() {
let controller = SearchDatasourceController()
navigationController?.pushViewController(controller, animated: true)
}
I am trying to add to my code a loop page . When the scroll view get to the last page the next page should be the first and vice versa.
In my project I have a main storyboard with a view controller where a Scroll View is placed and two xib file containing the two pages.
Everything works as should be however I cannot figure out a way how to have an infinite scrolling in both ways (back and forward)
If I add the function scrollViewDidScroll it seems that its not called.
Any Idea?
This is my code:
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Set Scroll Bar Hidden
scrollView.showsHorizontalScrollIndicator = false;
scrollView.showsVerticalScrollIndicator = false
// Create the views used in the swipe container view
let NewsPage :NewsViewController = NewsViewController(nibName: "NewsViewController", bundle: nil);
let MusicPage :MusicViewController = MusicViewController(nibName: "MusicViewController", bundle: nil);
// Add in each view to the container view hierarchy
self.addChildViewController(MusicPage);
self.scrollView!.addSubview(MusicPage.view);
MusicPage.didMoveToParentViewController(self);
self.addChildViewController(NewsPage);
self.scrollView!.addSubview(NewsPage.view);
NewsPage.didMoveToParentViewController(self);
// Set up the frames of the view controllers to align with eachother inside the container view
var adminFrame :CGRect = NewsPage.view.frame;
adminFrame.origin.x = adminFrame.width;
MusicPage.view.frame = adminFrame;
// Set the size of the scroll view that contains the frames
let scrollWidth: CGFloat = 2 * self.view.frame.width
let scrollHeight: CGFloat = self.view.frame.size.height
self.scrollView!.contentSize = CGSizeMake(scrollWidth, scrollHeight);
NSLog("%f",scrollHeight);
}
func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.x > scrollView.frame.size.width){
scrollView.setContentOffset(CGPointMake(0.0,0.0), animated: false)
}
}
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
NSLog("dragg");
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Did you set the scrollView's delegate? Try adding to viewDidLaod
scrollView.delegate = self
I am trying to create a growing UITableViewHeader on UITableView. I have a UITableView and a mapView set in the tableHeaderView of UITableView.
tblView.bounces = true
tblView.bouncesZoom = true
tblView.alwaysBounceVertical = true
mapView.frame = CGRectMake(0, 0, self.view.frame.size.width, CGFloat(kMapHeaderHeight))
mapView.mapType = MKMapType.Standard
mapView.zoomEnabled=true
mapView.scrollEnabled = true
mapView.delegate = mapHelper
tblView.tableHeaderView = mapView
And also implemented scrollViewDidScroll delegate and whenever it scrolls down, I have changed the frame of headerview as
func scrollViewDidScroll(scrollView: UIScrollView) {
var scrollOffset = scrollView.contentOffset.y
println("\(scrollOffset)")
var headerFrame : CGRect = self.mapView.frame
if (scrollOffset < 0){
headerFrame.size.height -= scrollView.contentOffset.y/3
}
self.mapView.frame = headerFrame
}
However, it does not grow as expected without bouncing.Seems very unclear. Any help?
I am following these tutorials to create a Growing UITableViewheader when pulling down as
UITableVIew header without bouncing when pull down ,
Expand UITableView Header View to Bounce Area When Pulling Down
Here is the link of the project :
https://drive.google.com/file/d/0B6dTvD1JbkgBVENUS1ROMzI0Wnc/vie
EDITED: i somehow managed to have the effect but the animation seems very slow
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
var mapViewRect: CGRect = self.mapView.frame
mapViewRect.origin.y = scrollView.contentOffset.y
mapViewRect.size.height = kHeaderHeight+yPos
self.mapView.frame = mapViewRect
}
}
let kHeaderHeight:CGFloat = 200
I suggest you to use this tutorial.
The most important parts of it:
Create a scrollView (or whatever that is a subclass of a UIScrollView) and a separate view (which will be functioning as a headerView, so let's call if headerView)
add the headerView and the scrollView as a subView of your view
implement the scrollViewDidScroll method, and put the framing logic there (of course, if you're using autolayout, you have to manage constraints there)
Actually the animation was not working well in simulator of xcode6.3. I tried 2 days for this and posted a bounty here but when i finally I tested it on real device and found the MapView was properly bouncing.If anyone needs it..here is the piece of logic.
let kHeaderHeight:CGFloat = 380
class NewBookingVC: UIViewController {
#IBOutlet weak var tblView: UITableView!
let mapView : MKMapView = MKMapView()
var customTableHeaderView:UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
tblView.delegate = self
tblView.dataSource = self
mapView.frame = CGRectMake(0, 0, self.view.frame.size.width, 380)
mapView.mapType = MKMapType.Standard
mapView.zoomEnabled=true
mapView.scrollEnabled = true
customTableHeaderView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 380))
customTableHeaderView.addSubview(mapView)
tblView.tableHeaderView = customTableHeaderView
}
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
var mapViewRect: CGRect = self.mapView.frame
mapViewRect.origin.y = scrollView.contentOffset.y
mapViewRect.size.height = kHeaderHeight+yPos
self.mapView.frame = mapViewRect
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Actually what you have to do is implement the scrollview delegate for table view because it inherits from scrollview and you can implement scrollViewDidScroll delegate and whenever it scrolls down, change the frame of headerview.
Idea behind this while scrolling you always have to keep the y-position of the MKMapView at the zero position....and height should incerase accordingly...
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
//adjust your mapview y-pos and height
//adjusting new position is all about real math
}
}
Performance in the iOS Simulator is not expected to match performance on device. The iOS Simulator is meant as a tool for rapid prototyping and fast iteration.In your case too, redrawing of MapView performance is quite slow in the simulator because at each Scroll you are calculating its new frame and height. So it takes some time adjusting new frame and seems very slow.
However it works well while tuning on real devices.
I am updating my app to use iOS 7 and I'm having a problem with a table view. My tab bar is translucent. The problem is when I scroll to the bottom of my table view, part of the last cell is still behind the tab bar. I'd like to have a bit of space between the last cell and the tab bar. I could fix this by using an opaque tab bar instead, but I want to keep it translucent.
Try setting
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
Inside the tableview controller
Swift 4.x
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(0, 0, self.tabBarController!.tabBar.frame.height, 0)
self.yourTableView.contentInset = adjustForTabbarInsets
self.yourTableView.scrollIndicatorInsets = adjustForTabbarInsets
Check the screen shot
Check the under top Bar and Un-checke under Bottom Bar
SWIFT 3
put this inside viewDidLoad of your tableViewController:
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
self.automaticallyAdjustsScrollViewInsets = false
Swift 3.0
This is what worked for me. In your Custom ViewController:
override func viewDidLoad() {
super.viewDidLoad()
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(self.tabBarController!.tabBar.frame.height, 0, 0, 0);
//Where tableview is the IBOutlet for your storyboard tableview.
self.tableView.contentInset = adjustForTabbarInsets;
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets;
}
Not to sure I like the solution but it works for me.
With iOS 11 I have no issue, I simply use the following in viewDidLoad():
self.collectionView.bottomAnchor.constraint(self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
However on iOS 10 I need to hack my way like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let tabBarHeight: CGFloat = (self.parent?.tabBarController?.tabBar.frame.size.height)!
if #available(iOS 11.0, *) {
} else {
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -tabBarHeight).isActive = true
}
}
This is working for me
override func viewDidLoad() {
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
}
If any view shows behind a UITabBar you can grab the bottomLayoutGuide and make adjustments at runtime. What I do is have a BaseViewController that all my view controllers inherit from. Then if the tab bar is visible we adjust the view like so:
import UIKit
class BaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
//Ensures that views are not underneath the tab bar
if tabBarController?.tabBar.hidden == false {
var viewBounds = self.view.bounds;
var bottomBarOffset = self.bottomLayoutGuide.length;
self.view.frame = CGRectMake(0, 0, viewBounds.width, viewBounds.height - bottomBarOffset)
}
}
}
Since I don't use storyboards (where you can click a checkbox in IB to fix this problem), this has been the best solution I have found.
It is really hard to resolve the issue without detail information or actual codes. I have similar issue of tabview behind UItabBar in my project. The solutions offered here do not work in my case. After exploring my codes, I found a solution for my case.
Here is brief explanation of my case. I have a UItabBar in main view with two tab buttons. In one tab view, there is table view. If user taps on a row, a detail view is presented by using navigation controller. In the detail view, the tab bar is hidden, and a toolbar is showing at the bottom.
In order to bring tab bar back and hide the toolbar when the main view is brought back, I have to explicitly show tab bar and hide toolbar in the event of viewWillAppear:
class myMainViewController: UITableViewController {
private var tabBarHidden: Bool? = {
didSet {
self.tabBarController?.tabBar.isHidden = tabBarIsHidden ?? true
}
}
private var toolBarIsHidden: Bool? {
didSet {
let hidden = toolBarIsHidden ?? true
self.navigationController?.toolbar.isHidden = hidden
self.navigationController?.setToolbarHidden(hidden, animated: true)
}
}
...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarIsHidden = false
self.toolBarIsHidden = true
}
...
}
I finally realize that the visibility of bar at the bottom is set in the event of viewWillAppear. At that time, the tableView or scroll view's content insets are set already based on no bar at the bottom. That's why my tableView is behind the bottom bar.
The solution I found is to reset content insets in the event of viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
// In the event of viewWillAppear, visibilities of tool bar and tab bar are set or changed,
// The following codes resets scroll view's content insets for tableview
let topInset = self.navigationController!.navigationBar.frame.origin.y +
self.navigationController!.navigationBar.frame.height
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(
topInset, 0,
self.tabBarController!.tabBar.frame.height, 0)
self.tableView.contentInset = adjustForTabbarInsets
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets
}
The best approch would be to Embed TabBarController to your ViewController (Editor -> Embed In -> TabBar Controller)and set the bottom of the tableview to be bottom of safe area of viewcontroller. The other ways wont be as perfect as this one.
You need to adjust the height of the table view. Just leave 49px at the bottom, as the tabbar height is 49 px. Adjust the height of table view so that it leaves 49px space below it.
There's this cool feature in the UITableViews in Game Center and the search bars they have at their tops. Unlike apps where the search bar is placed in the table header view (so it counts as a standard table cell), instead, it seems to be bolted to the parent navigation bar above it.
So when scrolling the table, the search bar does indeed move, but if you scroll above the boundaries of the table, the search bar never stops touching the navigation bar.
Does anyone know how this might have been done? I was wondering if Apple maybe placed both the search bar and the table in a parent scroll view, but I'm wondering if it may be simpler than that.
Bob's answer is reversed: it ought to be MIN(0, scrollView.contentOffset.y).
Also, in order to properly support resizing (which would occur when rotated), the other frame values should be reused.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UISearchBar *searchBar = searchDisplayController.searchBar;
CGRect rect = searchBar.frame;
rect.origin.y = MIN(0, scrollView.contentOffset.y);
searchBar.frame = rect;
}
You could put the searchBar in the table header and implement the - (void)scrollViewDidScroll:(UIScrollView *)scrollView delegate method for the tableView. Doing something like this should work:
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
searchBar.frame = CGRectMake(0,MAX(0,scrollView.contentOffset.y),320,44);
}
If you used the searchDisplayController, you would access the searchbar using self.searchDisplayController.searchbar.
In Swift 2.1 and iOS 9.2.1
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
/* Search controller parameters */
searchController.searchResultsUpdater = self // This protocol allows your class to be informed as text changes within the UISearchBar.
searchController.dimsBackgroundDuringPresentation = false // In this instance,using current view to show the results, so do not want to dim current view.
definesPresentationContext = true // ensure that the search bar does not remain on the screen if the user navigates to another view controller while the UISearchController is active.
let tableHeaderView: UIView = UIView.init(frame: searchController.searchBar.frame)
tableHeaderView.addSubview(searchController.searchBar)
self.tableView.tableHeaderView = tableHeaderView
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let searchBar:UISearchBar = searchController.searchBar
var searchBarFrame:CGRect = searchBar.frame
if searchController.active {
searchBarFrame.origin.y = 10
}
else {
searchBarFrame.origin.y = max(0, scrollView.contentOffset.y + scrollView.contentInset.top)
}
searchController.searchBar.frame = searchBarFrame
}
While other answers seem helpful and partially do the job, it doesn't solve the issue of search bar not receiving the user's touches because it moves outside the bounds of its parent view as you change its frame.
What's worse is that, when you click on the search bar to make it the first responder, it is very likely that the tableView delegate will call tableView:didSelectRowAtIndexPath: on cell that is laid out under the search bar.
In order to address those issues described above, you need to wrap the search bar in a plain UIView, a view which is capable of processing touches occurred outside of its boundaries. By this way, you can relay those touches to the search bar.
So let's do that first:
class SearchBarView: UIView {
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
for subview in subviews {
if !subview.userInteractionEnabled { continue }
let newPoint = subview.convertPoint(point, fromView: self)
if CGRectContainsPoint(subview.bounds, newPoint) {
return subview.hitTest(newPoint, withEvent: event)
}
}
return super.hitTest(point, withEvent: event)
}
}
Right now, we have a UIView subclass named SearchBarView which is capable of receiving touches occurred outside of its boundaries.
Secondly, we should put the search bar into that new view while the view controller is loading its view:
class TableViewController: UITableViewController {
private let searchBar = UISearchBar(frame: CGRectZero)
...
override func viewDidLoad() {
super.viewDidLoad()
...
searchBar.sizeToFit()
let searchBarView = SearchBarView(frame: searchBar.bounds)
searchBarView.addSubview(searchBar)
tableView.tableHeaderView = searchBarView
}
}
At last, we should update the frame of the search bar as user scrolls down the table view so that it will stay fixed at the top:
override func scrollViewDidScroll(scrollView: UIScrollView) {
searchBar.frame.origin.y = max(0, scrollView.contentOffset.y)
}
Here is the result:
--
Important note: If your table view has sections, they will probably shadow your search bar so you need to bring the search bar on top of them every time the table view's bounds gets updated.
viewDidLayoutSubviews is a good place to do that:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
...
if let tableHeaderView = tableView.tableHeaderView {
tableView.bringSubviewToFront(tableHeaderView)
}
}
--
Hope this helps. You can download the example project from here.
There's one more step if you want to fully emulate the search bars in Game Center.
If you start with friedenberg's excellent answer, as well as followben's modification for iOS 6+ mentioned in the comments, you still need to adjust the functionality when the search bar itself is active.
In Game Center, the search bars will scroll with the table as you scroll down, but will remain fixed below the navigation bar when you attempt to scroll up past the boundaries of the table. However, when the search bar is active and search results are being displayed, the search bar no longer scrolls with the table; it remains fixed in place below the navigation bar.
Here's the complete code (for iOS 6+) for implementing this. (If you're targeting iOS 5 or below, you don't need to wrap the UISearchBar in a UIView)
CustomTableViewController.m
- (void)viewDidLoad
{
UISearchBar *searchBar = [[UISearchBar alloc] init];
...
UIView *tableHeaderView = [[UIView alloc] initWithFrame:searchBar.frame];
[tableHeaderView addSubview:searchBar];
[self.tableView setTableHeaderView:tableHeaderView];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UISearchBar *searchBar = self.tableView.tableHeaderView.subviews.lastObject;
CGRect searchBarFrame = searchBar.frame;
/*
* In your UISearchBarDelegate implementation, set a boolean flag when
* searchBarTextDidBeginEditing (true) and searchBarTextDidEndEditing (false)
* are called.
*/
if (self.inSearchMode)
{
searchBarFrame.origin.y = scrollView.contentOffset.y;
}
else
{
searchBarFrame.origin.y = MIN(0, scrollView.contentOffset.y);
}
searchBar.frame = searchBarFrame;
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
self.inSearchMode = YES;
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
self.inSearchMode = NO;
}
VoilĂ ! Now, when the search bar is inactive it will move with the table, and remain fixed when attempting to move beyond the table boundaries. When active, it will remain fixed in place, just like in Game Center.
All of the other answers here provided me with helpful information, but none of them worked using iOS 7.1. Here's a simplified version of what worked for me:
MyViewController.h:
#interface MyViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate> {
}
#end
MyViewController.m:
#implementation MyViewController {
UITableView *tableView;
UISearchDisplayController *searchDisplayController;
BOOL isSearching;
}
-(void)viewDidLoad {
[super viewDidLoad];
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
searchBar.delegate = self;
searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
searchDisplayController.delegate = self;
searchDisplayController.searchResultsDataSource = self;
searchDisplayController.searchResultsDelegate = self;
UIView *tableHeaderView = [[UIView alloc] initWithFrame:searchDisplayController.searchBar.frame];
[tableHeaderView addSubview:searchDisplayController.searchBar];
[tableView setTableHeaderView:tableHeaderView];
isSearching = NO;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
UISearchBar *searchBar = searchDisplayController.searchBar;
CGRect searchBarFrame = searchBar.frame;
if (isSearching) {
searchBarFrame.origin.y = 0;
} else {
searchBarFrame.origin.y = MAX(0, scrollView.contentOffset.y + scrollView.contentInset.top);
}
searchDisplayController.searchBar.frame = searchBarFrame;
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
isSearching = YES;
}
-(void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
isSearching = NO;
}
#end
Note: If you're using "pull down to refresh" on your list, you'll need to replace scrollView.contentInset.top in scrollViewDidScroll: with a constant to allow the search bar to scroll over the refresh animation.
If your deployment target is iOS 9 and higher then you can use anchors and set UISearchBar and UITableView programmatically:
private let tableView = UITableView(frame: .zero, style: .plain)
private let searchBar = UISearchBar(frame: CGRect .zero)
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.contentInset = UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0)
searchBar.delegate = self
view.addSubview(tableView)
view.addSubview(searchBar)
NSLayoutConstraint.activate([
searchBar.heightAnchor.constraint(equalToConstant: 44.0),
searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
searchBar.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor)
])
}
I assume that you create UISearchBar and UITableView from code, not in storyboard.