当 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 约束的主要内容,如果未能解决你的问题,请参考以下文章

小议Oracle外键约束修改行为(一)

视图的框架不会改变

当设备方向发生变化时,UIViewController 不会更新视图的约束

我如何告诉我所有的自动布局约束来更新自己? (例如,当用户更新他们的系统大小/动态类型时)?

如何在图像下载和高度约束迅速改变后更新表格视图单元格高度?

在 LongPressEnded 之后迅速从 SuperViews 中删除视图