破碎的UIearchBar动画嵌入在NavigationItem中

时间:2017-09-19 13:30:17

标签: ios animation uisearchbar ios11

我遇到了将搜索栏添加到导航项的新方法的问题。

如下图所示,一个接一个地有两个UIViewControllers,两个都有搜索栏。问题是动画,当搜索栏在第一个视图控制器上可见但在第二个视图控制器上不可见时,这很难看。搜索栏占用的区域停留在屏幕上并突然消失。

Demo

代码非常基本(项目中没有其他更改):

(我主要用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
}
}

想法?

7 个答案:

答案 0 :(得分:17)

看起来苹果仍然需要在新的大型标题风格中使用UISearchBar。如果您推送的UIViewController没有设置navigationItem.searchController,则动画可以正常工作。在两个都设有searchController的UIViewController实例之间导航时,您会遇到导航栏高度跳跃的问题。

您可以通过每次调用UISearchController来创建viewDidAppear来解决(解决)问题(而不是在loadView中创建)并将navigationItem.searchController设置为nil在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

答案 1 :(得分:1)

在某些情况下,accepted answer确实解决了该问题,但我遇到的问题是,如果第一个搜索栏处于活动状态,则会完全删除推入视图控制器中的navigationItem

我想出了另一种解决方法,类似于answer by stu,但不需要干预约束。该方法是在搜索点确定搜索栏是否可见。如果是这样,我们将指示目标视图控制器使其搜索栏在加载时可见。这意味着导航项动画的行为正确:

Search bar correctly animating

假设两个视图控制器分别称为UIViewController1UIViewController2,其中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
    }
}

答案 2 :(得分:0)

对于这个问题,我的解决方案是在解除UISearchBar时更新保持UIViewController可见的约束。我无法使用Silicon_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()
        }
    }
}

您可以删除performWithoutAnimationlayoutIfNeeded,它仍然会动画;但是我发现动画从来没有第一次触发过,而且看起来还是不太好。

我希望Apple在以后的iOS版本中解决此问题,在撰写本文时,当前版本为12.1.4。

答案 3 :(得分:0)

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

相比,这将更清楚地解决您的问题。

答案 4 :(得分:0)

此问题已在iOS 13 beta 1中修复。在更新应用程序以获取新版本之前,请先检查您的变通方法。

答案 5 :(得分:0)

花了几天的时间来解决并调查后,我遇到了同样的问题。最后,我创建一个自定义视图,并用该视图替换titleView。 这意味着,我不使用搜索控制器。 iOS 11不好,Apple。

答案 6 :(得分:-3)

我已经在viewDidLoad()中添加了这段代码,当我移入选项卡的黑白时,它可以正常工作

searchController.dimsBackgroundDuringPresentation