在 tableview 滚动和再次 tableview 拖动上动画 UIView 位置

Animate UIView position on tableview scroll and again tableview drag

我有一个项目列表,顶部有一个 header 菜单。

当用户向上滚动菜单时,菜单也应该滚出屏幕,当他们return到列表顶部时,菜单应该可见。

tableViewHeader 的行为方式大致相同。

但是,如果 header 不在屏幕上并且用户结束了在列表上的向下拖动,则此 header 视图应该从顶部向下移动。如果用户随后结束了在列表中的向上拖动,header 应该动画后退屏幕。

我已经实现了下面的第一部分,但是我很难实现动画效果。

我考虑过为菜单使用 2 个视图,一个可以滚动,一个可以动画显示它的位置,但是感觉不对,我相信一定有更好的方法来避免重复视图。

class TableViewScene: UIViewController {

  let data = Array(0...99)

  var headerViewOneTopConstraint: NSLayoutConstraint!
  var tableViewTopConstraint: NSLayoutConstraint!

  lazy var headerViewOne: UIView = {
    let view = UIView(frame: .zero)
    view.translatesAutoresizingMaskIntoConstraints = false
    view.backgroundColor = .purple
    return view
  }()

  lazy var tableView: UITableView = {
    let view = UITableView(frame: .zero)
    view.translatesAutoresizingMaskIntoConstraints = false
    view.delegate = self
    view.dataSource = self
    view.tableFooterView = .init()
    view.refreshControl = .init()
    return view
  }()

  override func viewDidLoad() {
    super.viewDidLoad()

    navigationController?.navigationBar.isTranslucent = false

    headerViewOneTopConstraint = headerViewOne.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
    tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 184)

    [headerViewOne, tableView].forEach(view.addSubview)

    NSLayoutConstraint.activate([
      headerViewOneTopConstraint,
      headerViewOne.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      headerViewOne.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      headerViewOne.heightAnchor.constraint(equalToConstant: 184),

      tableViewTopConstraint,
      tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
      tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
    ])
  }
}

extension TableViewScene: UITableViewDelegate {
  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offsetY = scrollView.contentOffset.y

    headerViewOneTopConstraint.constant = max(-184, min(0, -offsetY))
    tableViewTopConstraint.constant = 184 - max(0, offsetY)
  }
}

我也曾尝试将其添加到视图中

  func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)

    if translation.y > 0 {
      headerViewOne.transform = .init(translationX: 0, y: 184)
    } else if translation.y < 0 {
      headerViewOne.transform = .identity
    }
  }

这不起作用,只是呈现黑色 space 用于填充滚动到顶部的视图。

很难准确地说出您在寻找什么,但这可能对您有用。在此实现中,当用户向上拖动时 header 将隐藏,当用户向下拖动时 header 将显示。无论滚动位置如何,都是如此。

我对您的约束之一进行了更改,以便将 tableView 的顶部固定到 header:

的底部
    tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: headerViewOne.bottomAnchor)

我也在跟踪 header 的状态,如下所示:

enum HeaderState {
    case hidden
    case revealed
    case hiding
    case revealing
}

var headerState: HeaderState = .revealed

现在是滚动逻辑。如果滚动位置靠近顶部那么我们要手动 show/hide header。但是,如果滚动位置靠近底部或中心,那么我们要为 show/hide 功能设置动画。

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)

    if scrollView.contentOffset.y < 184 {
        if translation.y > 0 && headerState != .revealed && headerState != .revealing {
            headerViewOneTopConstraint.constant = max(-184, min(0, -scrollView.contentOffset.y))
        } else if translation.y < 0 {
            headerViewOneTopConstraint.constant = max(-184, min(0, -scrollView.contentOffset.y))
        }
        return
    }

    if translation.y > 0 && headerState == .hidden {

        self.headerState = .revealing
        UIView.animate(withDuration: 0.4, animations: {
            self.headerViewOneTopConstraint.constant = 0
            self.view.layoutIfNeeded()
        }, completion: { _ in
            self.headerState = .revealed
        })

    } else if translation.y < 0 && headerState == .revealed {

        self.headerState = .hiding
        UIView.animate(withDuration: 0.4, animations: {
            self.headerViewOneTopConstraint.constant = -184
            self.view.layoutIfNeeded()
        }, completion: { _ in
            self.headerState = .hidden
        })
    }
}

试一试,看看它是否满足您的需求。