当隐藏 navigationItem.titleView 时,Tableview y 原点无法正确设置动画 (Swift 5)

Tableview y origin not animating properly when navigationItem.titleView is hidden (Swift 5)

我正在尝试让 tableView 在搜索栏向上移动时向上移动。看问题:

我想我明白问题出在哪里了,但我想不出解决办法。在 SearchResultsUpdating 中,我有一个动画块:

func updateSearchResults(for searchController: UISearchController) {

    UIView.animateKeyframes(withDuration: 1, delay: 0, options: UIView.KeyframeAnimationOptions(rawValue: 7)) {

    self.tableView.frame = CGRect(x: 20, y: self.view.safeAreaInsets.top, width: 
    self.view.frame.size.width-40, height: self.view.frame.size.height - 
    self.view.safeAreaInsets.top)

    }
}

在我看来,动画块仅接收 y 原点的 previous 坐标,因此动画不同步。我尝试将目标添加到 tableView、navigationBar 或 searchBarTextField,但没有任何效果。

感谢任何帮助,谢谢!

编辑:实施 Shawn 的第二个建议后,结果如下:

我无法想象为什么现在动画不流畅...非常令人沮丧!

编辑 2 - 请求代码:

class ViewController: UIViewController{

//City TableView
let cityTableView = UITableView()

let searchVC: UISearchController = {
    let searchController = UISearchController(searchResultsController: nil)
    searchController.obscuresBackgroundDuringPresentation = true
    searchController.searchBar.placeholder = "Search"
        return searchController
    }()

//viewDidLoad
override func viewDidLoad() {
    super.viewDidLoad()

    //Do any setup for the view controller here
    setupViews()
    
    //CityViewController
    setupCityViewTableView()
    
}

//setupViews
func setupViews(){
    //NAVIGATIONBAR:
    //title
    title = "Weather"
    //set to hidden because on initial load there is a scroll view layered over top of the CityViewTableView (code not shown here). This gets set to false when the scrollView alpha is set to 0 and the CityViewTableView is revealed
    navigationController?.navigationBar.isHidden = true
    navigationController?.navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
    
    //NAVIGATION ITEM:
    navigationItem.searchController = searchVC
    
    //UISEARCHBARCONTROLLER:
    searchVC.searchResultsUpdater = self
    }
}

//MARK:  -CityViewController Functions
extension ViewController{

//setUp TableView
func setupCityViewTableView(){
    
    cityTableView.translatesAutoresizingMaskIntoConstraints = false
    //set tableView delegate and dataSource
    cityTableView.delegate = self
    cityTableView.dataSource = self
    //background color
    cityTableView.backgroundColor = .black
    //separator color
    cityTableView.separatorColor = .clear
    //is transparent on initial load
    cityTableView.alpha = 0
    //set tag
    cityTableView.tag = 1000
    //hide scroll indicator
    cityTableView.showsVerticalScrollIndicator = false
    //register generic cell
    cityTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cityCell")
    //add subview
    view.addSubview(cityTableView)
    //Auto Layout
    cityTableView.leadingAnchor
        .constraint(equalTo: view.leadingAnchor,
                    constant: 20).isActive = true

    cityTableView.topAnchor
        .constraint(equalTo: view.topAnchor,
                    constant: 0).isActive = true

    cityTableView.trailingAnchor
        .constraint(equalTo: view.trailingAnchor,
                    constant: -20).isActive = true

    cityTableView.bottomAnchor
        .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
                    constant: 0).isActive = true
    
    }
}

//MARK: -TableView Controller
extension ViewController: UITableViewDelegate, 
UITableViewDataSource{

//number of rows
func tableView(_ tableView: UITableView, numberOfRowsInSection 
section: Int) -> Int {
    
    if tableView.tag == 1000{
        return 5
    }
    return self.models[tableView.tag].count
    
}

//cell for row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: 
IndexPath) -> UITableViewCell {
    
    //CityViewController
    if tableView.tag == 1000{
        
        let cell = tableView.dequeueReusableCell(withIdentifier: 
"cityCell", for: indexPath)
        cell.textLabel?.text = "Test"
        cell.textLabel?.textAlignment = .center
        cell.backgroundColor = .systemGray
        cell.selectionStyle = .none
        cell.layer.cornerRadius = 30
        cell.layer.borderColor = UIColor.black.cgColor
        cell.layer.borderWidth = 5
        cell.layer.cornerCurve = .continuous

        return cell
    }
    
    //WeatherViewController
    //code here for scrollView tableViews
}

//Height for row
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if tableView.tag == 1000{
        return view.frame.size.height/7
    }
    return view.frame.size.height/10
}

//Should Highlight Row
func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
    if tableView.tag == 1000{
        return true
    }
    return false
}

//Did select row
func tableView(_ tableView: UITableView, didSelectRowAt 
indexPath: IndexPath) {
    
    //calls function for segue to Weather Scroll View (not shown)
    if tableView.tag == 1000{
        segueToWeatherView(indexPath: indexPath)
        
        }
    
    }

}

编辑 3:当我注释掉另一个函数时,它终于起作用了,但我不确定确切原因或如何修复它。这是有问题的功能,addSubViews()

//setup viewController
func addSubViews(){
    //add weatherView as subView of ViewController
    view.addSubview(weatherView)
    //add subviews to weatherView
    weatherView.addSubview(scrollView)
    weatherView.addSubview(pageControl)
    weatherView.addSubview(segueToCityViewButton)
    weatherView.addSubview(segueToMapViewButton)
    
}

具体来说,当我注释掉这一行时它起作用了:

view.addSubview(weatherView)

以下是有关设置 weatherView 及其所有子视图的所有代码:

//Any additional setup goes here
private func setupViews(){
    
    //VIEWCONTROLLER:
    //title
    title = "Weather"
    //Background color of view Controller
    view.backgroundColor = .darkGray
    
    //WEATHERVIEW:
    //Background color of weather view Controller
    weatherView.backgroundColor = .clear
    //weatherView frame
    weatherView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
    
    //SCROLLVIEW:
    //background color of scroll view
    scrollView.backgroundColor = .clear
    //scrollView frame
    scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
    //changed
    
    //PAGECONTROL:
    //page control frame
    pageControl.frame = CGRect(x: 0, y: view.frame.height-view.frame.size.height/14, width: view.frame.width, height: view.frame.size.height/14)
    
    //TRANSITIONVIEW:
    //TransitionView frame
    transitionView.frame = CGRect(x: 20, y: 0, width: view.frame.size.width-40, height: view.frame.size.height)
    
    //BUTTONS:
    //segue to CityView
    segueToCityViewButton.frame = CGRect(x: (weatherView.frame.width/5*4)-20, y: weatherView.frame.height-weatherView.frame.size.height/14, width: weatherView.frame.width/5, height: pageControl.frame.height)
    //segue to MapView:
    segueToMapViewButton.frame = CGRect(x: 20, y: weatherView.frame.height-weatherView.frame.size.height/14, width: weatherView.frame.width/5, height: pageControl.frame.height)
    
    //LABELS:
    transitionViewLabel.frame = transitionView.bounds
    
    //NAVIGATIONBAR:
    //set to hidden on initial load
    navigationController?.navigationBar.isHidden = true
    navigationController?.navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
    
    //NAVIGATION ITEM:
    navigationItem.searchController = searchVC
    
    //UISEARCHBARCONTROLLER:
    searchVC.searchResultsUpdater = self
    
    
}

为了完整起见,这里是完整的 viewDidLoad() 函数:

override func viewDidLoad() {
    super.viewDidLoad()

    //MARK: View Controller
    
    //These two will eventually be moved to the DispatchQueue in APICalls.swift
    configureScrollView()
    pageControl.numberOfPages = models.count
    
    
    //Do any setup for the view controller here
    setupViews()
    
    //setup ViewController
    addSubViews()

    //Add Target for the pageControl
    addTargetForPageControl()
    
    //MARK: CityViewController
    setupCityViewTableViews()
    
}

编辑 4:通过对 viewDidLoad() 进行以下更改,我终于让它工作了!

override func viewDidLoad() {
super.viewDidLoad()

//MARK: CityViewController
//Moved to a position before setting up the other views
setupCityViewTableViews()

//MARK: View Controller

//These two will eventually be moved to the DispatchQueue in APICalls.swift
configureScrollView()
pageControl.numberOfPages = models.count

//Do any setup for the view controller here
setupViews()

//setup ViewController
addSubViews()

//Add Target for the pageControl
addTargetForPageControl()

}

按照你现在的方式去做是一种方法,但我认为这是最具挑战性的方法,原因如下:

  1. 您对导航栏中搜索控制器动画的实现没有太多的控制权和访问权,因此获得正确的坐标可能是一项任务

  2. 即使您确实设法获得了正确的坐标,尝试同步您的动画帧和时间以与导航栏上的搜索动画同步和无缝显示也很棘手

我建议您使用以下 2 种替代方法来替代您目前正在做的事情,这样您就可以立即获得几乎 免费 的新闻体验。

选项 1:使用 UITableViewController 而不是 UIViewController

这是使用 UITableViewController 并向导航栏添加 UISearchController 的所有代码。

class NewsTableViewVC: UITableViewController
{
    private let searchController: UISearchController = {
        let sc = UISearchController(searchResultsController: nil)
        sc.obscuresBackgroundDuringPresentation = false
        sc.searchBar.placeholder = "Search"
        sc.searchBar.autocapitalizationType = .allCharacters
        return sc
    }()
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        view.backgroundColor = .black
        
        title = "Weather"
        
        // Ignore this as you have you own custom cell class
        tableView.register(CustomCell.self,
                           forCellReuseIdentifier: CustomCell.identifier)
        
        setUpNavigationBar()
    }
    
    private func setUpNavigationBar()
    {
        navigationItem.searchController = searchController
    }
}

这是您可以期待的体验

选项 2:使用自动布局而不是框架来配置您的 UITableView

如果您不想使用 UITableViewController,请使用 auto layout 而不是 frames 配置您的 UITableView,后者需要更多工作但不会太多:

class NewsTableViewVC: UIViewController, UITableViewDataSource, UITableViewDelegate
{
    private let searchController: UISearchController = {
        let sc = UISearchController(searchResultsController: nil)
        sc.obscuresBackgroundDuringPresentation = false
        sc.searchBar.placeholder = "Search"
        sc.searchBar.autocapitalizationType = .allCharacters
        return sc
    }()
    
    private let tableView = UITableView()
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        // Just to show it's different from the first
        view.backgroundColor = .purple
        
        title = "Weather"
        
        setUpNavigationBar()
        setUpTableView()
    }
    
    private func setUpNavigationBar()
    {
        navigationItem.searchController = searchController
    }
    
    private func setUpTableView()
    {
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(CustomCell.self,
                           forCellReuseIdentifier: CustomCell.identifier)
        tableView.dataSource = self
        tableView.delegate = self
        tableView.backgroundColor = .clear
        view.addSubview(tableView)
        
        // Auto Layout
        tableView.leadingAnchor
            .constraint(equalTo: view.leadingAnchor,
                        constant: 0).isActive = true
        
        // This important, configure it to the top of the view
        // NOT the safe area margins to get the desired result
        tableView.topAnchor
            .constraint(equalTo: view.topAnchor,
                        constant: 0).isActive = true
        
        tableView.trailingAnchor
            .constraint(equalTo: view.trailingAnchor,
                        constant: 0).isActive = true
        
        tableView.bottomAnchor
            .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
                        constant: 0).isActive = true
    }
}

您可以期待以下体验:

更新

这是基于您更新的代码,您遗漏了一个可能会影响您看到的结果的小细节,这是 UITableView 的最高约束。

您将约束添加到 safeAreaLayoutGuide 顶部锚点:

cityTableView.topAnchor
        .constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,
                    constant: 0).isActive = true

如果您注意到上面的代码,我的建议是将其添加到 view 顶部约束

// This important, configure it to the top of the view
// NOT the safe area margins to get the desired result
cityTableView.topAnchor
    .constraint(equalTo: view.topAnchor,
                constant: 0).isActive = true

试一试,看看您是否接近获得您期望的结果?

如果有帮助,这是我的 link to the complete code 实现: