ios - Animación de UISearchBar rota incrustada en NavigationItem
animation ios11 (5)
Estoy experimentando un problema con la nueva forma de agregar la barra de búsqueda al elemento de navegación.
Como puede ver en la imagen de abajo, hay dos UIViewControllers uno tras otro, y ambos tienen la barra de búsqueda. El problema es la animación, que es fea cuando la barra de búsqueda está visible en el primer controlador de vista pero no en el segundo. El área ocupada por la barra de búsqueda permanece en la pantalla y desaparece repentinamente.
El código es muy básico (no se realizaron otros cambios en el proyecto):
(Escribo principalmente en C #, por lo que podría haber errores en este código).
ViewController.swift:
import UIKit
class ViewController: UITableViewController, UISearchResultsUpdating {
override func loadView() {
super.loadView()
definesPresentationContext = true;
navigationController?.navigationBar.prefersLargeTitles = true;
navigationItem.largeTitleDisplayMode = .automatic;
navigationItem.title = "VC"
tableView.insetsContentViewsToSafeArea = true;
tableView.dataSource = self;
refreshControl = UIRefreshControl();
refreshControl?.addTarget(self, action: #selector(ViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
tableView.refreshControl = refreshControl;
let stvc = UITableViewController();
stvc.tableView.dataSource = self;
let sc = UISearchController(searchResultsController: stvc);
sc.searchResultsUpdater = self;
navigationItem.searchController = sc;
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell1");
if (cell == nil) {
cell = UITableViewCell(style: .default, reuseIdentifier: "cell1");
}
cell?.textLabel?.text = "cell " + String(indexPath.row);
return cell!;
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20;
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = ViewController();
navigationController?.pushViewController(vc, animated: true);
}
@objc func handleRefresh(_ refreshControl: UIRefreshControl) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
refreshControl.endRefreshing();
})
}
func updateSearchResults(for searchController: UISearchController) {
}
}
AppDelegate.swift:
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds);
window?.rootViewController = UINavigationController(rootViewController: ViewController());
window?.makeKeyAndVisible();
UINavigationBar.appearance().barTintColor = UIColor.red;
return true
}
}
Ideas?
En el VC1
:
override func viewDidLoad() {
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = searchController
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = true
}
}
En el VC2
, usa el mismo código.
Esto resolverá su problema más claramente, que establecer serchController
en nil
He agregado este código en viewDidLoad () y está funcionando, cuando me moví en b / w de pestañas
searchController.dimsBackgroundDuringPresentation
La respuesta aceptada resuelve el problema en algunas situaciones, pero lo estaba experimentando, lo que provocó la eliminación completa del elemento de navigationItem
en el controlador de vista insertada si la primera barra de búsqueda estaba activa.
Se me ha ocurrido otra solución alternativa, similar a la respuesta de stu , pero que no requiere entrometerse con restricciones. El enfoque es determinar, en el punto del segmento, si la barra de búsqueda es visible. Si es así, le indicamos al controlador de vista de destino que haga visible su barra de búsqueda desde la carga. Esto significa que la animación del elemento de navegación se comporta correctamente:
Suponiendo que los dos controladores de vista se llaman UIViewController1
y UIViewController2
, donde 1
empuja 2
, el código es el siguiente:
class ViewController1: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController(searchResultsController: nil)
searchController.obscuresBackgroundDuringPresentation = false
navigationItem.searchController = searchController
definesPresentationContext = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let viewController2 = segue.destination as? ViewController2, let searchController = navigationItem.searchController {
// If the search bar is visible (but not active, which would make it visible but at the top of the view)
// in this view controller as we are preparing to segue, instruct the destination view controller that its
// search bar should be visible from load.
viewController2.forceSearchBarVisibleOnLoad = !searchController.isActive && searchController.searchBar.frame.height > 0
}
}
}
class ViewController2: UITableViewController {
var forceSearchBarVisibleOnLoad = false
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController(searchResultsController: nil)
searchController.obscuresBackgroundDuringPresentation = false
navigationItem.searchController = searchController
// If on load we want to force the search bar to be visible, we make it so that it is always visible to start with
if forceSearchBarVisibleOnLoad {
navigationItem.hidesSearchBarWhenScrolling = false
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// When the view has appeared, we switch back the default behaviour of the search bar being hideable.
// The search bar will already be visible at this point, thus achieving what we aimed to do (have it
// visible during the animation).
navigationItem.hidesSearchBarWhenScrolling = true
}
}
Mi solución para este problema es actualizar la restricción que mantiene visible UISearchBar
, cuando se está descartando UIViewController
. No pude usar la solución de silicon_valley, ya que incluso con el envío asíncrono recibí el bloqueo que mencionó. Esta es una solución bastante desordenada, pero Apple no lo ha hecho tan fácil.
El siguiente código asume que tiene una propiedad que contiene una instancia de UISearchController
dentro de su subclase UIViewController
llamada searchController
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if
animated
&& !searchController.isActive
&& !searchController.isEditing
&& navigationController.map({$0.viewControllers.last != self}) ?? false,
let searchBarSuperview = searchController.searchBar.superview,
let searchBarHeightConstraint = searchBarSuperview.constraints.first(where: {
$0.firstAttribute == .height
&& $0.secondItem == nil
&& $0.secondAttribute == .notAnAttribute
&& $0.constant > 0
}) {
UIView.performWithoutAnimation {
searchBarHeightConstraint.constant = 0
searchBarSuperview.superview?.layoutIfNeeded()
}
}
}
Puede eliminar performWithoutAnimation
y layoutIfNeeded
, y aún así se animará; Sin embargo, descubrí que la animación nunca se activó la primera vez y, de todos modos, no se ve tan bien.
Espero que Apple solucione esto en una versión posterior de iOS, la versión actual es 12.1.4 en el momento de la escritura.
Parece que Apple todavía necesita resolver el uso de la barra UISearch en el nuevo estilo de título grande. Si el UIViewController
que presiona no tiene su conjunto de UIViewController
, la animación funciona bien. Cuando navega entre dos instancias de UIViewController
que tienen un conjunto de UIViewController
, se obtiene el problema que describe al saltar la altura de la barra de navegación.
Puede resolver (solucionar) el problema creando el UISearchController
cada vez que se llama a viewDidAppear
(en lugar de crearlo en loadView
) y configurando navigationItem.searchController
en nil en viewDidDisappear
.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
let stvc = UITableViewController()
stvc.tableView.dataSource = self
let sc = UISearchController(searchResultsController: stvc)
sc.searchResultsUpdater = self
self.navigationItem.searchController = sc
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.navigationItem.searchController = nil
}
El motivo del envío asíncrono es que cuando se configura la línea de información de navigationItem.searchController
en el método viewDidAppear
, se viewDidAppear
una excepción:
Terminating app due to uncaught exception ''NSInvalidArgumentException'', reason: ''Only one palette with a top boundary edge can be active outside of a transition. Current active palette is <_UINavigationControllerManagedSearchPalette: 0x7fad67117e80; frame = (0 116; 414 0); layer = <CALayer: 0x60400002c8e0>>''
Sé que esto es solo una solución, pero espero que esto le ayude por ahora, hasta que Apple resuelva el problema con la navegación entre dos controladores de vista que tengan un UISearchController
configurado en su elemento de navigationItem
.