iOS表格/列表中的粘滞菜单

时间:2018-11-21 19:54:52

标签: ios swift uitableview uicollectionview

我正在尝试创建一个与metup应用程序中的Home场景类似的界面。您可以在下面看到它的运行情况。我想重新创建[All, Going, ...]菜单行为。我希望它从列表的中间开始并向上滚动直到它到达列表的顶部并坚持下去。与节头在UITableView中的工作方式非常相似。

enter image description here

创建菜单不是问题。我的问题是创建粘性行为,并使其与列表的其余部分一起正常工作。

我尝试使用UITableView,但菜单菜单无法粘贴。我无法将菜单放在节标题中,因为我想将节标题用于菜单下方的数据,并且UITableView的行为是在下一个节到达列表顶部时将节标题向上推。我无法将菜单放在UITableView.tableHeader中,因为菜单从列表中其他数据的下面开始。

UITableView
  - UITableViewCell -> Label
  - UITableViewCell -> UICollectionView of UIImageViews
  - UITableViewCell -> Label
  - UITableViewCell -> MyMenu (Sticky)
  - UITableViewHeaderFooterView - Section 1
  - UITableViewCell -> Data
  - UITableViewCell -> Data
  - UITableViewHeaderFooterView - Section 1
  - UITableViewCell -> Data
  - UITableViewCell -> Data

我尝试使用包含菜单的UIScrollView和菜单下面的UITableView,但在{{1}内使用UITableView(即UIScrollView) }很痛苦。我无法使滚动行为感觉自然。

UIScrollView

我将尝试编写UIScrollView - UIView -> (Container) - Label - UICollectionView of UIImageViews - Label - MyMenu (Sticky) - UITableView - Data 来完成我想要的事情,但是我觉得我将不得不重新创建使用UICollectionViewLayout免费获得的功能。

有什么想法要解决这个问题吗?也许有一种可靠的方法可以制作UITableView棍子,并在其下放置后续节标题?

3 个答案:

答案 0 :(得分:1)

一种实现类似方法的方法是使用这样的视图层次结构:

UIView
  - UITableView
  - UIView -> (Container)
    - Label
    - UICollectionView of UIImageViews
    - Label
    - MyMenu (Sticky)

带有菜单的容器是表视图的同级,但是它与表视图重叠。

在滚动视图委托方法scrollViewDidScroll(_:)中,您可以重新放置菜单容器视图的位置,使菜单位于表内容的上方。然后,您需要告诉表格视图在顶部和第一个表格单元之间保留一些空间。为此,您可以配置表格视图的contentInset

答案 1 :(得分:1)

我将使用表格视图。

添加一个空单元格,该控件将在可见时将其放置在其中,并避免控件覆盖任何内容。

将控件添加为表格视图的子视图。

然后覆盖scrollViewDidScrollUITableViewUIScrollView的子类,因此它们共享委托方法)。

scrollViewDidScroll中,滚动视图滚动时,至少在每一帧中都会调用它,更新内容的位置,如下所示:

let controlFrame = tableView.rectForRow(at: indexPathOfYourBlankCell)
controlFrame.origin.y = max(0, tableView.contentOffset.y - controlFrame.y)
control.frame = controlFrame
tableView.bringSubviewToFront(control)

请记住,如果表格视图具有顶部插图,则必须调整第二行,例如,如果它在透明导航栏下,或者您使用带缺口的iPhone。

我建议首先在没有导航栏的无缺口iPhone模拟器上实现它,一旦它起作用,您可以通过添加插图来调整y属性的计算方式。

认为类似的方法可以工作,但我不确定。

controlFrame.origin.y = max(0, tableView.contentOffset.y + tableView.contentInsets.top - controlFrame.y)

答案 2 :(得分:0)

我实现了@EmilioPelaez的建议,即使用单独的菜单视图,将其放在一个空单元格上,并在表滚动时移动它。为了使其正常工作,我必须执行以下操作:

  1. 找到空单元格的框架,以便将菜单放在其上方
  2. 当空视图移到表格视图的可见区域之外时,菜单也会移动,使其停留在表格视图的可见区域之内。看起来应该停靠在表格视图的顶部。
  3. 当空白单元格到达顶部时,请调整tableView.contentInsets.top,以使下面的节标题看起来像粘贴在菜单底部一样
  4. 当表格向另一个方向滚动时,请重置tableView.contentInsets.top
  5. 支持动态类型和旋转更改

我最终完成了viewDidLayoutSubviews中的所有操作,因为我需要处理旋转和动态文本更改,而并非每次旋转和动态文本更改都会调用scrollViewDidScroll。几乎每个viewDidLayoutSubviews之后都会调用scrollViewDidScroll

let menuCellPath = IndexPath(row: 1, section: 1)
var tableViewInsetCached = false
var cachedTableViewInsetTop: CGFloat = 0.0

override func viewDidLayoutSubviews() {
    // Cache the starting tableView.contentInset.top because I need to change it later
    // when the menu is docked to the top of the table
    if !tableViewInsetCached {
        cachedTableViewInsetTop = tableView.contentInset.top
        tableViewInsetCached = true
    }

    // Get the frame of the empty cell. Use rectForRow instead of cellForIndexPath so it 
    // works even if the cell has been reused.
    let menuCellFrame = tableView.rectForRow(at: menuCellPath)

    // Calculate how far the menu must move to continue to be within the
    // visible area of the scroll view. If the delta is a negative number
    // the cell is within the visible area so clamp it at 0, i.e., don't move it.
    // Use `tableView.safeAreaInsets.top` to take into account the notch, translucent 
    // UINavigationBar, and status bar.
    let menuFrameDeltaY = max(0, tableView.safeAreaInsets.top + tableView.contentOffset.y - menuCellFrame.origin.y)

    // Add the delta to the menu's frame
    var newMenuFrame = menuCellFrame
    newMenuFrame.origin.y = menuCellFrame.origin.y + menuFrameDeltaY
    menuView.frame = newMenuFrame

    if menuFrameDeltaY > 0 {
        print("cell outside visible area -> change contentInset")
        // Change the contentInset so subsequent section headers
        // stick to the bottom of the menu
        tableView.contentInset.top = menuCellFrame.size.height
    } else {
        print("cell inside visible area -> reset contentInset")
        // The empty cell is inside the visible area so we should
        // reset the contentInset
        tableView.contentInset.top = cachedTableViewInsetTop
    }
}

重要的是要记住,我们正在处理一个UIScrollView。滚动表时,其子视图的框架不会改变。仅contentOffset发生变化,这意味着max(0, tableView.safeAreaInsets.top + tableView.contentOffset.y - menuCellFrame.origin.y)计算菜单必须移动的数量,才能继续位于表格视图的可见区域内。如果增量小于零,则空单元格位于表格视图的可见区域内,而我不必移动菜单,只需为其提供与空单元格相同的框架,这就是为什么我使用max(0,x )以将其钳位为零。如果增量大于零,则空单元格将不再位于表格视图的可见区域内,并且必须移动菜单以继续位于可见区域内。