Сломанная анимация UISearchBar, встроенная в NavigationItem

У меня возникла проблема с новым способом добавления панели поиска к элементу навигации.

Как вы можете видеть на рисунке ниже, есть два UIViewController один за другим, и оба имеют панель поиска. Проблема в анимации, которая выглядит ужасно, когда строка поиска видна на первом контроллере представления, но не на втором. Область, занимаемая строкой поиска, остается на экране и внезапно исчезает.

демонстрация

Код очень простой (никаких других изменений в проекте не было):

(Я пишу в основном на C#, поэтому в этом коде могут быть ошибки.)

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

Идеи?

2 ответа

Решение

Похоже, Apple по-прежнему необходимо сгладить использование UISearchBar в новом стиле большого заголовка. Если UIViewController вы нажимаете, чтобы не иметь его navigationItem.searchController установить, анимация работает отлично. При навигации между двумя экземплярами UIViewController что оба имеют установленный searchController, вы получаете проблему, которую вы описываете, где выскакивает панель навигации.

Вы можете решить (обойти) проблему, создав UISearchController каждый раз viewDidAppear вызывается (вместо создания в loadView) и настройка navigationItem.searchController ноль на 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
}

Причиной асинхронной отправки является то, что при настройке navigationItem.searchController встроенный в viewDidAppear метод, исключение:

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

Я знаю, что это только обходной путь, но, надеюсь, это поможет вам, пока Apple не решит проблему с перемещением между двумя контроллерами представления, которые оба имеют UISearchController установить на их navigationItem,

Принятый ответ действительно решает проблему для некоторых ситуаций, но я испытывал его, приводя к полному удалению navigationItem в контроллере push-сообщений, если активна первая строка поиска.

Я придумал другой обходной путь, похожий на ответ от stu, но не требующий вмешательства с ограничениями. Подход заключается в том, чтобы определить в момент перехода, видна ли строка поиска. Если это так, мы указываем целевому контроллеру представления сделать его строку поиска видимой из загрузки. Это означает, что анимация элемента навигации ведет себя правильно:

Предполагая, что два контроллера представления называются UIViewController1 а также UIViewController2, где 1 выталкивает 2 код выглядит следующим образом:

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

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

в VC2, используйте тот же код.

Это решит вашу проблему более четко, чем установлено serchController в nil

У меня такая же проблема, после того как я потратил несколько дней на обходное решение и расследование. Наконец, я создаю настраиваемое представление и заменяю titleView этим представлением. Это означает, что я не использую контроллер поиска. iOS 11 - это плохо, Apple.

Эта проблема была исправлена ​​в бета-версии 1 iOS 13. Перед обновлением приложения проверьте свои обходные пути.

Мое решение для этой проблемы состоит в том, чтобы обновить ограничение, которое сохраняет UISearchBar видно, когда UIViewController увольняется. Я не смог использовать решение silic_valley, так как даже с асинхронной отправкой я получал описанную им аварию. По общему признанию это довольно грязное решение, но Apple не сделала это легким.

Код ниже предполагает, что у вас есть свойство, содержащее UISearchController экземпляр в вашем UIViewController подкласс называется 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()
        }
    }
}

Вы можете удалить performWithoutAnimation а также layoutIfNeededи это все равно будет оживлять; однако я обнаружил, что анимация никогда не запускалась с первого раза, и в любом случае она выглядит не очень хорошо.

Я надеюсь, что Apple исправит это в более позднем выпуске iOS, текущий выпуск - 12.1.4 на момент написания.

Я добавил этот код в viewDidLoad(), и он работает, когда я перешел в ч / б вкладок

searchController.dimsBackgroundDuringPresentation
Другие вопросы по тегам