当隐藏 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()
}
按照你现在的方式去做是一种方法,但我认为这是最具挑战性的方法,原因如下:
您对导航栏中搜索控制器动画的实现没有太多的控制权和访问权,因此获得正确的坐标可能是一项任务
即使您确实设法获得了正确的坐标,尝试同步您的动画帧和时间以与导航栏上的搜索动画同步和无缝显示也很棘手
我建议您使用以下 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 实现:
我正在尝试让 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()
}
按照你现在的方式去做是一种方法,但我认为这是最具挑战性的方法,原因如下:
您对导航栏中搜索控制器动画的实现没有太多的控制权和访问权,因此获得正确的坐标可能是一项任务
即使您确实设法获得了正确的坐标,尝试同步您的动画帧和时间以与导航栏上的搜索动画同步和无缝显示也很棘手
我建议您使用以下 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 实现: