web-dev-qa-db-fra.com

Verrouillage d'un UISearchBar au sommet d'un UITableView comme Game Center

Il y a cette fonctionnalité intéressante dans UITableViews dans Game Center et les barres de recherche qu'ils ont au sommet. Contrairement aux applications où la barre de recherche est placée dans la vue d’en-tête du tableau (elle compte donc comme une cellule de tableau standard), elle semble être boulonnée à la barre de navigation par-dessus. Ainsi, lors du défilement du tableau, la barre de recherche bouge, mais si vous faites défiler au-dessus des limites du tableau, la barre de recherche ne cesse de toucher la barre de navigation.

Est-ce que quelqu'un sait comment cela aurait pu être fait? Je me demandais si Apple avait peut-être placé la barre de recherche et le tableau dans une vue de défilement parent, mais je me demandais si cela pouvait être plus simple.

27
TiM

La réponse de Bob est inversée: il doit s'agir de MIN (0, scrollView.contentOffset.y).

De plus, afin de prendre en charge correctement le redimensionnement (ce qui se produirait lors d'une rotation), les autres valeurs de trame devraient être réutilisées.

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

Vous pouvez placer la barre de recherche dans l'en-tête de la table et implémenter la méthode de délégation - (void) scrollViewDidScroll: (UIScrollView *) scrollView pour la table. Faire quelque chose comme ça devrait marcher:

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

Si vous avez utilisé searchDisplayController, vous accéderiez à la barre de recherche à l'aide de self.searchDisplayController.searchbar.

7
Bob Vork

Sous Swift 2.1 et 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
       }
5
Muhammad Qasim

Si vous souhaitez émuler complètement les barres de recherche de Game Center, il vous reste encore une étape.

Si vous commencez par friedenberg excellente réponse, ainsi que followben modification pour iOS 6+ mentionnée dans les commentaires, vous devez toujours ajuster les fonctionnalités lorsque la barre de recherche est active.

Dans Game Center, les barres de recherche défilent avec le tableau au fur et à mesure que vous défilez, mais restent fixes en dessous de la barre de navigation lorsque vous essayez de faire défiler les limites du tableau. Cependant, lorsque la barre de recherche est active et que les résultats de la recherche sont affichés, la barre de recherche ne défile plus avec le tableau; il reste en place sous la barre de navigation.

Voici le code complet (pour iOS 6+) pour l'implémenter. (Si vous ciblez iOS 5 ou une version antérieure, vous n'avez pas besoin d'encapsuler UISearchBar dans un 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à! Désormais, lorsque la barre de recherche est inactive, elle se déplace avec la table et reste fixe lorsque vous essayez de vous déplacer au-delà des limites de la table. Lorsqu'il est actif, il restera en place, comme dans Game Center.

3
Nick Schneble

Tandis que d'autres réponses semblent utiles et remplissent partiellement le travail, cela ne résout pas le problème de la non-réception des touches de l'utilisateur par la barre de recherche, car celle-ci sort des limites de sa vue parente lorsque vous modifiez son cadre.

Le pire est que, lorsque vous cliquez sur la barre de recherche pour en faire le premier intervenant, il est très probable que le délégué de tableView appellera tableView:didSelectRowAtIndexPath: dans une cellule placée sous la barre de recherche.

Afin de résoudre les problèmes décrits ci-dessus, vous devez insérer la barre de recherche dans une vue UIView simple, une vue capable de traiter les touches effleurées survenues en dehors de ses limites. De cette manière, vous pouvez relayer ces contacts vers la barre de recherche. 

Alors faisons cela en premier:

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

À l'heure actuelle, nous avons une sous-classe UIView nommée SearchBarView qui est capable de recevoir des touches qui se sont produites en dehors de ses limites. 

Deuxièmement, nous devrions placer la barre de recherche dans cette nouvelle vue pendant que le contrôleur de vue charge sa vue:

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

Enfin, nous devrions mettre à jour le cadre de la barre de recherche au fur et à mesure que l'utilisateur fait défiler la vue du tableau afin qu'il reste en haut de l'écran:

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

Voici le résultat:

 enter image description here

-

Remarque importante: Si votre vue Table comporte des sections, elles masqueront probablement la barre de recherche. Vous devez donc la placer au-dessus de celle-ci chaque fois que la variable bounds de la vue Table est mise à jour. 

 enter image description here

viewDidLayoutSubviews est un bon endroit pour le faire:

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

-

J'espère que cela t'aides. Vous pouvez télécharger l'exemple de projet depuis ici .

1
ozgur

Si votre cible de déploiement est iOS 9 et supérieur, vous pouvez utiliser des ancres et définir par programme UISearchBar et 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)
            ])
    }

Je suppose que vous créez UISearchBar et UITableView à partir de code, pas dans un storyboard.

0
Filipp Ignatov

Toutes les autres réponses ici m'ont fourni des informations utiles, mais aucune d'entre elles n'a fonctionné sous iOS 7.1. Voici une version simplifiée de ce qui a fonctionné pour moi:

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

Remarque: Si vous utilisez l'option "dérouler pour actualiser" de votre liste, vous devrez remplacer scrollView.contentInset.top dans scrollViewDidScroll: par une constante pour permettre à la barre de recherche de faire défiler l'animation d'actualisation.

0
Brian