【问题标题】:Broken UISearchBar animation embedded in NavigationItemNavigationItem 中嵌入的 UISearchBar 动画损坏
【发布时间】:2018-02-28 07:40:54
【问题描述】:

我在向导航项添加搜索栏的新方法时遇到问题。

如下图所示,有两个 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
}
}

想法?

【问题讨论】:

  • 签入设备.. 可能是模拟器问题。尝试使用 viewDidLoad() (而不是 loadView() )
  • 不,在设备上是一样的。
  • 和viewDidLoad的结果是一样的。

标签: ios animation uisearchbar ios11


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    此问题已在 iOS 13 beta 1 中修复。在为新版本更新应用之前检查您的解决方法。

    【讨论】:

      【解决方案3】:

      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】:

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

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

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

        【讨论】:

        【解决方案5】:

        我对这个问题的解决方案是在 UIViewController 被解除时更新保持 UISearchBar 可见的约束。我无法使用 Silicon_valley 的解决方案,因为即使使用异步调度我也遇到了他提到的崩溃。诚然,这是一个相当混乱的解决方案,但 Apple 并没有让这一切变得简单。

        下面的代码假设您的UIViewController 子类中有一个包含UISearchController 实例的属性,称为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。

        【讨论】:

          【解决方案6】:

          我在 viewDidLoad() 中添加了这段代码,当我移入黑白标签时它正在工作

          searchController.dimsBackgroundDuringPresentation
          

          【讨论】:

          • 这如何改变一些事情?您是否忘记在代码行中添加 = false
          【解决方案7】:

          看起来 Apple 仍然需要在新的大标题样式中消除 UISearchBar 的使用。如果您推送到的UIViewController 没有设置navigationItem.searchController,则动画效果很好。在两个都设置了 searchController 的 UIViewController 实例之间导航时,您会遇到您描述的导航栏高度跳跃的问题。

          您可以通过在每次调用viewDidAppear 时创建UISearchController(而不是在loadView 中创建它)并在viewDidDisappear 上将navigationItem.searchController 设置为nil 来解决(解决)问题。

          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
          }
          

          异步调度的原因是在viewDidAppear方法中设置navigationItem.searchController内联时,引发异常:

          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 解决在两个视图控制器之间导航的问题,这两个视图控制器都在其 navigationItem 上设置了 UISearchController

          【讨论】:

          • iOS 11...苹果小姐好..?
          • 在 TabBarController 的选项卡之间切换时仍然会出现同样的崩溃:( 有什么提示吗?
          • @silicon_valley 您知道如何处理后退操作吗?有同样的故障
          • 您是否尝试过将viewWillAppearviewDidDisappear 上的navigationItem.searchController 设置为nil
          • iOS 12 上仍然存在
          猜你喜欢
          • 2023-04-02
          • 1970-01-01
          • 2021-02-10
          • 2013-09-21
          • 2014-09-04
          • 2014-07-18
          • 1970-01-01
          • 1970-01-01
          • 2023-03-11
          相关资源
          最近更新 更多