iOS 7 - 布局建议

18
我在视图控制器中有以下布局。我希望能够垂直滚动,使标题栏滚动到视图之外,而UISegmentedControl保持在视图的顶部,其余的滚动应由CollectionView处理。
但是我对实现这种布局的最佳方法有些困惑。
我尝试了一些实现方式,但结果不尽相同:
1. UIScrollView作为父视图,CollectionView、标题栏和分段控件作为子视图。这种方法的问题是嵌套滚动似乎无法正常工作。要滚动UIScrollView,点击需要在CollectionView区域之外,否则只有CollectionView会滚动,标题栏和分段控件不会移动。
2. 在集合视图中添加标题栏和分段控件作为单个Header单元格的子视图。当分段控件值更改时,我切换CollectionView的数据源属性以获得所需的3个视图。从视觉上看,一切都完美。唯一的问题是在第一个、第二个和第三个标签之间快速切换时出现竞争条件。我从网络服务加载数据,如果网络服务花费时间并且仍在加载数据,而我快速切换选项卡,则会遇到错误,返回的数据与当前选定的集合视图不同,很多问题没有按顺序同步。
3. 更改应用于“Header”视图的自动布局约束的常量值。然后,我添加了一个手势到视图控制器的视图来跟踪滚动,当用户垂直滚动时,我调整自动布局约束的常量,以便“header”单元格弹出视图。再次,这种方法似乎不太流畅,但我想我可以调整它,但它似乎是一种hack。
是否有更好的方法来实现这种布局?

1
你是否需要使用AutoLayout? - Christian Schnorr
1
是的,需要自动布局。 - newbie
2
你在第二种方法中遇到的问题可能的解决方案(实际上,我认为这个问题不是你选择的布局的问题)可以在“在iOS上构建并发用户界面”[WWDC 2012](https://developer.apple.com/videos/wwdc/2012/)会议的第一部分中找到。 - Anastasia
6个回答

9

#2看起来是一个好的解决方案 —— 滚动手势将与用户期望的一致,因为它是一个单独的滚动视图。(我同意#3听起来像是一个hack。) 你可以使用一些自定义布局属性使标题"固定"。

唯一的问题在于快速在第一个、第二个和第三个选项卡之间切换时出现了竞争条件。

这是异步加载时常见的问题,当视图被切换时(特别是当您将数据加载到单个单元格中并随着滚动而重用它们时),很重要的是在接收到数据后始终检查接收器是否仍然需要它;即,在更改后备数据源之前应检查分段控件的值。您还可以:

  • 为不同的段使用单独的数据源对象,让每个对象管理自己的数据获取,以避免混淆。
  • 在快速切换选项卡时取消未完成的请求(如果可以),以避免不必要的网络请求。
  • 缓存数据以避免每次切换选项卡时重新获取。

2

我认为您想要与Pinterest个人资料页面相同的功能。为了以简单的方式实现这样的功能,您需要执行以下操作。

步骤1:将UIView添加为tableHeaderView,这些内容在向上滚动时显示。

self.tableHeaderView = yourView

步骤二:在章节头视图中添加UISegmentControl

 - (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section{
  return your_segmentcontrolView;

}

第三步:将UICollectionView添加到第一节的第一行中。

通过实现以下方式,您可以获得所需的功能。

希望这能帮助您。


听起来可能,我不确定是否会遇到相同的奇怪滚动中的滚动混乱问题,基本上与我的解决方案#1相同,只是你建议我使用TableView而不是ScrollView,而TableView本身使用ScrollView。你过去尝试过这种方法吗? - newbie
@newbie,是的,我已经在我的一个应用程序中实现了。建议使用UITableView,因为部分视图的标题本身会紧贴屏幕顶部,直到新的部分到达顶部。所以你不需要调整偏移量。如果你想使用UIScrollview,那么即使你也无法像UItableView提供的那样获得平滑的行为。 - Jatin Patel - JP
1
@新手,如果我的回答对你有帮助,你可以授予奖励吗? - Jatin Patel - JP
2
我不喜欢用户要求OP将赏金授予他们,仅仅因为他们的回答可能有所帮助,但问题可能存在更好的答案。从我的个人观点来看,我认为这并没有完全回答问题,如果是我,我会说它需要更多的描述才能获得200声望值。像1jbandes提供的描述就是我期望在授予赏金之前看到的类型描述。 - Popeye
@Popeye,有时候解决问题并不需要过多的解释。在这种情况下,提问者知道自己想要什么,我的回答只是满足了他的需求。 - Jatin Patel - JP
1
@没有答案。这里有一些回答比你的好得多,因为它们附带了解释。但无论如何,你都不应该要求他们奖励你赏金,你需要让他们自己做决定。如果意味着他们会奖励你,那就太棒了,如果没有,那也没关系,只需转到下一个问题并帮助下一个人。 - Popeye

2

你可以考虑另一种方法:

使用UITableView来包含你的UI

  • 创建一个UITableView,并将你的标题设置为UITableView的headerView。
  • 使用sectionHeader来包含segmentedControl。
  • 将你的collectionView放在一个单独的UITableViewCell中。或者,你也可以尝试使用UITableView的footerView来包含gridView。

通过使用sectionHeader,这将允许标题滚动消失,但是sectionHeader将保持在navigationBar或contentView的顶部,直到另一个section出现(在你的情况下,你只有一个section)。


1
  • 将页眉视图、正文视图(包含段落视图和集合视图)添加到滚动视图中。
  • 最初将集合视图的userInteractionEnabled属性设置为“NO”。
  • 始终跟踪滚动视图的滚动位置。
  • 如果滚动位置的y坐标大于页眉视图的高度,则将集合视图的userInteractionEnabled属性设置为“YES”,以便以后可以滚动集合视图。
  • 如果用户从滚动视图外部向下拖动页眉视图,即滚动视图的y坐标小于页眉视图的高度,则立即更改集合视图的用户交互模式,并允许用户滚动滚动视图至顶部。

1

虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。 - Mayank Jain
感谢@MayankJain的输入。该链接指向一个库,可以完成问题所要求的功能(而不是教程之类的东西),因此我将更新我的答案以明确这一点。 - BevTheDev

-5
我只能说的是,您需要对UIView进行子类化,并使其成为UIGestureRecognizerDelegate和UICollectionViewDelegate的代理。然后,在您的UIView子类中,执行以下操作。我不能再提供更多有关此代码的信息,因为虽然是我拥有的,但它的保密性可能会激怒很多我用过此代码的组织,所以这就是秘密调味料:
CGPoint contentOffset = [scrollView contentOffset];
CGFloat newHeight = [_headerView maxHeight] - contentOffset.y;
CGRect frame = [_headerView frame];

if (newHeight > [_headerView maxHeight]) {
    frame.origin.y = contentOffset.y;
    frame.size.height = [_headerView maxHeight];
    [_headerView setFrame:frame];
} else if (newHeight < [_headerView minHeight]) {

    frame.origin.y = contentOffset.y;
    frame.size.height = [_headerView minHeight];
    [_headerView setFrame:frame];
} else {
    frame.origin.y = contentOffset.y;
    frame.size.height = newHeight;
    [_headerView setFrame:frame];
}
if ([_delegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
    return [_delegate scrollViewDidScroll:scrollView];
}

您必须子类化另一个UIView,该UIView被定义为自定义UiCollectionView的标题。然后,您必须在UIView / UICollectionView委托的自定义子视图中声明UIView自定义标题视图,然后将该自定义子视图的标题设置为UICollctionViewdelegate。然后,您应该将UIView / UIcollectionView的这个复合子类引入到您的UIViewController中。哦,是的,在您的layoutSubViews中,请确保进行通过双层子类传递的高度计算。因此,您将拥有以下文件:

  1. UIVew 这是UICollectionView的委托和我之前提到的内容

  2. UIView 这是UISCrollViewDelegate,也是标题视图

  3. UIViewController 将第1步子类化的UIView引入其中

  4. UIView 第1步的子类,引入第2步并将其设置为其标题

在第4部分中,请确保执行以下操作:

- (CGFloat)maxHeight
{
    if (SCREEN_WIDTH == 414)
    {
        return 260;

    }else if (SCREEN_WIDTH == 375)
    {
        return 325;

    }else
    {
        return 290;
    }
}

- (CGFloat)minHeight
{
    if (SCREEN_WIDTH == 414)
    {

        return 90;
    }else if (SCREEN_WIDTH == 375)
    {
        return 325;
    }else
    {
        return 290;
    }
}

这将会传递给UIView子类,该子类是一个复合子类,正如我先前解释的那样。这个想法是在这个header UIView的子类(上面的数字2)中捕获您的header的最大高度,然后将其传递到截取scrollViewDidScroll中这些值的主UIView子类中。
最后一点信息,请确保在所有方法中设置好您的layoutSubviews以拦截滚动事件。例如,在上面的数字1中,layoutsubviews方法如下:
- (void)layoutSubviews
{
    CGRect frame = [_headerView frame];
    frame.size.width = [self frame].size.width;
    [_headerView setFrame:frame];
    [super layoutSubviews];
}

这就是我能给你的全部了,希望我能提供更多,但这应该可以让你了解在生产环境中为大型应用程序完成的方式。

还有一件事需要注意。当你开始进行像这样的强烈实现时,不要感到惊讶,例如,在使用我所解释的方法工作的应用程序中,单个视图控制器将具有30-40个自定义子类,这些子类要么是自己的子类,要么是复合子类或我的自己的子类的子类。我告诉你这个,让你了解到需要多少代码才能做到这一点,而不是吓唬你,但也让你知道可能需要一段时间才能做好,并且如果需要一段时间才能使其正常工作,请不要责备自己。祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接