【问题标题】:Locking a UISearchBar to the top of a UITableView like Game Center将 UISearchBar 锁定到 UITableView 的顶部,例如 Game Center
【发布时间】:2011-06-19 16:44:01
【问题描述】:

Game Center 的 UITableViews 和顶部的搜索栏有这个很酷的功能。与将搜索栏放置在表格标题视图中的应用程序不同(因此它被视为标准表格单元格),相反,它似乎被固定在其上方的父导航栏上。 因此,当滚动表格时,搜索栏确实会移动,但如果您在表格边界上方滚动,搜索栏将永远不会停止接触导航栏。

有人知道这是怎么做到的吗?我想知道 Apple 是否可能将搜索栏和表格都放在父滚动视图中,但我想知道它是否比这更简单。

【问题讨论】:

    标签: ios uitableview uiscrollview uisearchbar game-center


    【解决方案1】:

    Bob 的回答正好相反:应该是 MIN(0, scrollView.contentOffset.y)。

    此外,为了正确支持调整大小(旋转时会发生),其他帧值应该被重用。

    -(void)scrollViewDidScroll:(UIScrollView *)scrollView 
    {
        UISearchBar *searchBar = searchDisplayController.searchBar;
        CGRect rect = searchBar.frame;
        rect.origin.y = MIN(0, scrollView.contentOffset.y);
        searchBar.frame = rect;
    }
    

    【讨论】:

    • 哦哈哈是的,我注意到了。非常感谢!这就是我最终使用的代码。 ^_^
    • 我猜应该是 MAX 而不是 MIN。
    • MIN 应该是正确的,因为我们正在寻求的行为是防止搜索栏滚动到视图顶部。当用户向上滚动(视图向下移动)并且 contentOffset.y 变为负数时。我们想通过将搜索栏框架的 y 原点设置为相同(负)值来抵消这种情况,这会导致搜索栏在负滚动期间保持固定。这仅适用于 contentOffset.y 为负数,因此为 MIN。
    • UISearchBartableHeaderView 时,在-scrollViewDidScroll: 中直接操作它的框架适用于iOS 5.x 但不适用于iOS 6。为了让它工作,我必须包装在普通的UIView 中搜索栏并将其添加为表头。
    • followben 的回答对我有帮助! :) 谢谢!
    【解决方案2】:

    您可以将 searchBar 放在表头中并为 tableView 实现 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 委托方法。做这样的事情应该可行:

    -(void) scrollViewDidScroll:(UIScrollView *)scrollView {
        searchBar.frame = CGRectMake(0,MAX(0,scrollView.contentOffset.y),320,44);
    } 
    

    如果您使用了 searchDisplayController,您将使用 self.searchDisplayController.searchbar 访问搜索栏。

    【讨论】:

    • 感谢鲍勃!是的,我昨天在发布问题后与一些朋友交谈,我们提出了基本相同的问题。虽然,我正在查看 UITableView 的子类化并在其中进行操作,而无需接触委托。如果我完成代码,我会在这里发布。 :)
    • 嘿伙计。上面接受的答案中的代码是我最终使用的。这只是用 UITableView 的委托拦截滚动事件的问题。
    • +1 这应该被接受,因为其他不起作用。 Thax 它有帮助。
    • 当我滚动表格视图然后点击搜索栏时,他会触发“didSelectAtTableRow”事件并打开详细视图而不是打开键盘开始搜索。他触发了搜索栏后面的单元格点击。有人有想法吗?
    【解决方案3】:

    在 Swift 2.1 和 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
           }
    

    【讨论】:

    • 您确定在表格视图滚动时,搜索栏可以使用吗?因为当我尝试搜索时,只有当 tableview 位于顶部时我才能这样做......如果我向下滚动一点,我就无法搜索。
    【解决方案4】:

    虽然其他答案似乎有帮助并且部分完成了工作,但它并不能解决搜索栏无法接收用户触摸的问题,因为它会在您更改其框架时移动到其父视图的范围之外。

    更糟糕的是,当您单击搜索栏使其成为第一响应者时,tableView 委托很可能会在搜索栏下方的单元格上调用tableView:didSelectRowAtIndexPath:

    为了解决上述问题,您需要将搜索栏包装在一个普通的 UIView 中,该视图能够处理发生在其边界之外的触摸。通过这种方式,您可以将这些触摸传递到搜索栏。

    所以让我们先这样做:

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

    现在,我们有一个名为SearchBarView 的 UIView 子类,它能够接收发生在其边界之外的触摸。

    其次,我们应该在视图控制器加载其视图时将搜索栏放入新视图中:

    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
      }
    }
    

    最后,我们应该在用户向下滚动表格视图时更新搜索栏的框架,使其保持固定在顶部:

    override func scrollViewDidScroll(scrollView: UIScrollView) {
      searchBar.frame.origin.y = max(0, scrollView.contentOffset.y)
    }
    

    结果如下:

    --

    重要提示:如果您的表格视图有部分,它们可能会遮住您的搜索栏,因此每次更新表格视图的bounds 时,您需要将搜索栏置于其顶部。

    viewDidLayoutSubviews 是这样做的好地方:

    override func viewDidLayoutSubviews() {
      super.viewDidLayoutSubviews()
      ...
      if let tableHeaderView = tableView.tableHeaderView {
        tableView.bringSubviewToFront(tableHeaderView)
      }
    }
    

    --

    希望这会有所帮助。您可以从here下载示例项目。

    【讨论】:

      【解决方案5】:

      如果您想完全模拟 Game Center 中的搜索栏,还有一步。

      如果你从friedenberg的出色的答案开始,以及followben的在cmets中提到的对iOS 6+的修改,你仍然需要调整搜索栏时的功能本身是活跃的。

      在 Game Center 中,当您向下滚动时,搜索栏会随着表格一起滚动,但当您尝试向上滚动超出表格边界时,搜索栏将保持固定在导航栏下方。但是,当搜索栏处于活动状态并且正在显示搜索结果时,搜索栏不再随表格滚动; 它仍然固定在导航栏下方

      这是实现此功能的完整代码(适用于 iOS 6+)。 (如果您的目标是 iOS 5 或更低版本,则无需将 UISearchBar 包装在 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;
      }
      

      瞧!现在,当搜索栏处于非活动状态时,它将与表格一起移动,并在尝试超出表格边界时保持固定。激活时,它将保持固定不变,就像在 Game Center 中一样。

      【讨论】:

      • 嗨@Nick 此代码不适用于iOS 7。拖动表格时我看不到UISearchBar。有什么建议吗?你能告诉我应该为 UISearchbar 设置什么框架吗?
      • 嗨@Rushi-我还没有在 iOS 7 中使用过这段代码。当我这样做的时候,我会发布一个包含任何更改的更新。
      • @Rushi:我也有同样的问题,你有 iOS 7 的解决方案吗?
      • @Rushi 您是否在UINavigationController 中使用它?在这种情况下,新的 iOS7 '导航栏下的内容'开始发挥作用。 scrollView.contentOffset.y 也包含此偏移量,因此在 tableview 顶部时实际上将返回 -64 而不是 0。因此 MIN 行应为 MIN(0, scrollView.contentOffset.y + scrollView.contentInset.top)
      • 我可以让它按照您在 iOS 8.3 下描述的方式工作,但滚动后,触摸搜索栏不会使其成为第一响应者,但是,如果我一直向上滚动,(到原始,搜索栏的预期偏移量)它再次开始工作!你是如何解决这个问题的?
      【解决方案6】:

      这里的所有其他答案都为我提供了有用的信息,但它们都没有使用 iOS 7.1。这是对我有用的简化版本:

      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
      

      注意:如果您在列表中使用“下拉刷新”,则需要将 scrollViewDidScroll: 中的 scrollView.contentInset.top 替换为常量,以允许搜索栏滚动刷新动画。

      【讨论】:

      • 这似乎在 iOS 8 中不起作用。确实显示了搜索栏,并且它的框架按预期移动,但是当搜索栏“粘住”并且 uitableview 单元格在它下面滑动时,它是在接收到用于搜索栏的触摸时,搜索栏下方的任何一个单元格。我错了吗?
      • @SAHM,您找到解决方案了吗?这里有同样的问题。谢谢。
      【解决方案7】:

      如果您的部署目标是 iOS 9 或更高版本,那么您可以使用锚点并以编程方式设置 UISearchBar 和 UITableView:

          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)
                  ])
          }
      

      我假设您是从代码中创建 UISearchBar 和 UITableView,而不是在情节提要中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-13
        • 1970-01-01
        相关资源
        最近更新 更多