当 SuperViews 约束改变时更新 SubView 约束
Posted
技术标签:
【中文标题】当 SuperViews 约束改变时更新 SubView 约束【英文标题】:Update SubView Constraints when SuperViews Constraints Change 【发布时间】:2018-07-05 11:53:05 【问题描述】:我有一个UIView
,它位于UITableView
之上(如屏幕截图所示)。当用户滚动UITableView
时,我将UIView
移出屏幕(从顶部)。 UIView
可以正确移动,但 UIView 内的项目保持在其位置。我怎样才能移动它们?
P.S:我从下面的帖子中得到了帮助,感谢它的发布者:),也很抱歉有很多代码,但我无法决定如何缩小它。
https://github.com/MichiganLabs/AnimatingTableViewHeader
编辑:我通过将所有约束添加到同一个视图而犯了错误,我修复了这个问题,但问题仍然存在。
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.white
setupUI()
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
self.headerHeightConstraint.constant = self.maxHeaderHeight
updateHeader()
private func setupUI()
self.view.backgroundColor = UIColor.white
self.tableView.delegate = self
self.tableView.dataSource = self
self.headerView.backgroundColor = Color.Common.welcomeScreenBackgroundColor.withAlphaComponent(0.5)
self.view.addSubview(tableView)
self.view.addSubview(headerView)
headerView.translatesAutoresizingMaskIntoConstraints = false
tableView.translatesAutoresizingMaskIntoConstraints = false
//cityBtn.translatesAutoresizingMaskIntoConstraints = false
cityLbl.text = "İL"
headerView.addSubview(cityLbl)
headerView.addSubview(cityBtn)
//Header = 20 from left edge of screen
let cn1 = NSLayoutConstraint(item: headerView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 20)
//Header view trailing end is 20 px from right edge of the screen
let cn2 = NSLayoutConstraint(item: headerView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: -20)
let cn3 = NSLayoutConstraint(item: headerView, attribute: .bottom, relatedBy: .equal, toItem: self.tableView, attribute: .top, multiplier: 1.0, constant: -20)
//Header view height = constant 240
headerHeightConstraint = NSLayoutConstraint(item: headerView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant:230)
//Header view vertical padding from the top edge of the screen = 20
let topConstraint = NSLayoutConstraint(item: headerView, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 20)
//Header = 20 from left edge of screen
let tb1 = NSLayoutConstraint(item: tableView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 0)
//Header view trailing end is 20 px from right edge of the screen
let tb2 = NSLayoutConstraint(item: tableView, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1.0, constant: 0)
let tb3 = NSLayoutConstraint(item: tableView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0)
let tb4 = NSLayoutConstraint(item: tableView, attribute: .top, relatedBy: .equal, toItem: self.headerView, attribute: .bottom, multiplier: 1.0, constant: 0)
//Header view trailing end is 20 px from right edge of the screen
let cb1 = NSLayoutConstraint(item: cityBtn, attribute: .width, relatedBy: .equal, toItem: self.headerView, attribute: .width, multiplier: 0.6, constant: 0)
let cb2 = NSLayoutConstraint(item: cityBtn, attribute: .trailing, relatedBy: .equal, toItem: self.headerView, attribute: .trailing, multiplier: 1.0, constant: -20)
cityBtnTopConstraint = NSLayoutConstraint(item: cityBtn, attribute: .top, relatedBy: .equal, toItem: self.headerView, attribute: .top, multiplier: 1.0, constant: 20)
let cb4 = NSLayoutConstraint(item: cityBtn, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant:50)
self.view.addConstraints([cn1,cn2,cn3,headerHeightConstraint,topConstraint,tb1,tb2,tb3,tb4])
self.headerView.addConstraints([cb1,cb2,cityBtnTopConstraint,cb4])
override func viewDidLayoutSubviews()
cityLbl.leftAnchor.constraint(equalTo: headerView.leftAnchor, constant: 20).isActive = true
cityLbl.centerYAnchor.constraint(equalTo: cityBtn.centerYAnchor).isActive = true
func scrollViewDidScroll(_ scrollView: UIScrollView)
let scrollDiff = scrollView.contentOffset.y - self.previousScrollOffset
let absoluteTop: CGFloat = 0;
let absoluteBottom: CGFloat = scrollView.contentSize.height - scrollView.frame.size.height;
let isScrollingDown = scrollDiff > 0 && scrollView.contentOffset.y > absoluteTop
let isScrollingUp = scrollDiff < 0 && scrollView.contentOffset.y < absoluteBottom
if canAnimateHeader(scrollView)
// Calculate new header height
var newHeight = self.headerHeightConstraint.constant
if isScrollingDown
newHeight = max(self.minHeaderHeight, self.headerHeightConstraint.constant - abs(scrollDiff))
else if isScrollingUp
newHeight = min(self.maxHeaderHeight, self.headerHeightConstraint.constant + abs(scrollDiff))
// Header needs to animate
if newHeight != self.headerHeightConstraint.constant
self.headerHeightConstraint.constant = newHeight
self.updateHeader()
self.setScrollPosition(self.previousScrollOffset)
self.previousScrollOffset = scrollView.contentOffset.y
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
self.scrollViewDidStopScrolling()
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
if !decelerate
self.scrollViewDidStopScrolling()
func scrollViewDidStopScrolling()
let range = self.maxHeaderHeight - self.minHeaderHeight
let midPoint = self.minHeaderHeight + (range / 2)
if self.headerHeightConstraint.constant > midPoint
self.expandHeader()
else
self.collapseHeader()
func canAnimateHeader(_ scrollView: UIScrollView) -> Bool
// Calculate the size of the scrollView when header is collapsed
let scrollViewMaxHeight = scrollView.frame.height + self.headerHeightConstraint.constant - minHeaderHeight
// Make sure that when header is collapsed, there is still room to scroll
return scrollView.contentSize.height > scrollViewMaxHeight
func collapseHeader()
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.2, animations:
self.headerHeightConstraint.constant = self.minHeaderHeight
self.updateHeader()
self.view.layoutIfNeeded()
)
func expandHeader()
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.2, animations:
self.headerHeightConstraint.constant = self.maxHeaderHeight
self.updateHeader()
self.view.layoutIfNeeded()
)
func setScrollPosition(_ position: CGFloat)
self.tableView.contentOffset = CGPoint(x: self.tableView.contentOffset.x, y: position)
func updateHeader()
let range = self.maxHeaderHeight - self.minHeaderHeight
let openAmount = self.headerHeightConstraint.constant - self.minHeaderHeight
let percentage = openAmount / range
self.cityBtn.alpha = percentage
//self.titleTopConstraint.constant = -openAmount + 10
//self.logoImageView.alpha = percentage
【问题讨论】:
【参考方案1】:问题是你将所有约束添加到 self.view 你需要在 headerView 和它的子视图之间添加约束到 headerView ,所以拆分这个
self.view.addConstraints([cn1,cn2,cn3,headerHeightConstraint,topConstraint,tb1,tb2, tb3,tb4,cb1,cb2,cityBtnTopConstraint,cb4])
到
self.view.addConstraints([cn1,cn2,cn3,headerHeightConstraint,topConstraint,tb1,tb2, tb3,tb4])
&&
headerView.addConstraints([cb1,cb2,cityBtnTopConstraint,cb4])
【讨论】:
是的,我也注意到了。我解决了这个问题,但问题仍然存在。 在 viewDidLayoutSubviews 中设置了多次调用的约束,同时将 clipsToBounds = true 设置为 header 也取消注释这个 cityBtn.translatesAutoresizingMaskIntoConstraints = false 并添加这个 cityLbl.translatesAutoresizingMaskIntoConstraints = false 这也不能解决我的问题,但是当我从教程中获得大部分代码时,我发现我正在更新视图的高度约束而不是顶部约束。我需要做到——(减号) 根据你对 button 和 label 设置的约束,改变 headerView 的高度约束不会影响它们的位置以上是关于当 SuperViews 约束改变时更新 SubView 约束的主要内容,如果未能解决你的问题,请参考以下文章
当设备方向发生变化时,UIViewController 不会更新视图的约束