如何使用 Scrollview(或最佳实践)进行 Card Deck 样式页面转换,垂直和水平

Posted

技术标签:

【中文标题】如何使用 Scrollview(或最佳实践)进行 Card Deck 样式页面转换,垂直和水平【英文标题】:How to do Card Deck style page transitioning, Vertically & Horizontally, using UIScrollview (or best practice) 【发布时间】:2012-12-23 10:47:46 【问题描述】:

我正在创建一个可以在书的页面之间左右滑动的应用程序(像卡片组一样覆盖或显示),并且上下滑动将向下滑动(并覆盖)设置页面,或者滑动向上(并揭示)这本书的详细信息。

本质上,它是一个“卡片组”滑动功能,类似于我正在考虑使用的这个项目https://github.com/sweetmandm/CardSliderView-for-ios,但是我需要垂直和水平的“卡片组”滑动功能。

为您提供另一个我正在查看的幻灯片/左封面/显示效果的示例,请查看新的 CNN 应用程序,您可以在其中在文章之间滑动。

我也考虑过使用 UIPageViewController,但是这不支持我正在寻找的“滑动和显示/滑动和覆盖”过渡,而只支持“向左或向右滑动”过渡,所以我必须以某种方式破解它并使用多个 UIPageViewControllers,一个在上面,以允许“显示和覆盖”效果工作,仅使用 UIPageViewController 中的手势来允许用户滑动。

我熟悉 UIScrollview 上的 directionalLockEnabled 属性,但我仍然想知道获得我正在寻找的效果的整体最佳方法是什么,一种同时支持垂直和水平的 UIScrollView?页面视图控制器?嵌套的 UIScrollviews?而不是玩弄 directionalLockEnabled 属性?还有什么?

实现我希望提供的确切用户体验的最佳方式是什么?

【问题讨论】:

【参考方案1】:

好的,我找到了解决我自己问题的方法,使用嵌套的 UIScrollViews。我还添加了一个非常粗略的解决方案作为项目到 github:https://github.com/cohen72/InfiniteDeckScroller。

对于水平滚动,我有三个水平滚动视图,一个嵌套在另一个中。 UIScrollview 会自动处理每个的正确滚动。根据内容偏移和正在滚动的滚动视图,我知道如何“重新排列/重新排序”嵌套的滚动视图。

这是我提出的解决方案的 sn-p。

这个特殊的解决方案允许向上滑动显示,但是我还没有实现向下滑动覆盖,但是这样做会使用与水平解决方案相同的方法。

#define BOTTOM 1
#define MIDDLE 2
#define TOP 3
#define VERTICAL_SUB 4

- (void)viewDidLoad

    [super viewDidLoad];

    UIScrollView *scroll1 = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    UIScrollView *scroll2 = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    UIScrollView *scroll3 = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    UIScrollView *scroll4 = [[UIScrollView alloc] initWithFrame:self.view.bounds];

    scroll1.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height * 1);
    scroll1.tag = BOTTOM;
    scroll1.pagingEnabled = YES;
    scroll1.bounces = NO;
    scroll1.delegate = self;
    [scroll1 addSubview:[self labelForScrollView:scroll1 withBgColor:[UIColor redColor]]];
    [scroll1 setContentOffset:CGPointMake(0, 0)];

    scroll2.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height * 1);
    scroll2.tag = MIDDLE;
    scroll2.pagingEnabled = YES;
    scroll2.bounces = NO;
    scroll2.delegate = self;
    [scroll2 addSubview:[self labelForScrollView:scroll2 withBgColor:[UIColor orangeColor]]];
    [scroll2 setContentOffset:CGPointMake(0, 0)];

    scroll3.contentSize = CGSizeMake(self.view.frame.size.width * 2, self.view.frame.size.height * 1);
    scroll3.tag = TOP;
    scroll3.pagingEnabled = YES;
    scroll3.bounces = NO;
    scroll3.delegate = self;
    [scroll3 addSubview:[self labelForScrollView:scroll3 withBgColor:[UIColor yellowColor]]];
    [scroll3 setContentOffset:CGPointMake(320, 0)];

    scroll4.contentSize = CGSizeMake(self.view.frame.size.width * 1, self.view.frame.size.height * 2);
    scroll4.delegate = self;
    scroll4.bounces = NO;
    scroll4.pagingEnabled = YES;
    scroll4.alwaysBounceVertical = NO;
    scroll4.tag = VERTICAL_SUB;

    [scroll4 addSubview:scroll1];
    [scroll1 addSubview:scroll2];
    [scroll2 addSubview:scroll3];
    [self.view addSubview:scroll4];



- (UILabel*)labelForScrollView:(UIScrollView*)scrollView withBgColor:(UIColor*)color
    UILabel *lbl = [[UILabel alloc] initWithFrame:scrollView.bounds];
    lbl.textAlignment = NSTextAlignmentCenter;
    lbl.backgroundColor = color;
    lbl.text = [NSString stringWithFormat:@"ScrollView: %d", scrollView.tag];
    return lbl;


-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

    NSLog(@"content offset: %f, tag: %d ", scrollView.contentOffset.x, scrollView.tag);

    UIScrollView *newMiddleScrollView, *newBottomScrollView, *newTopScrollView;

    // swipe left

    if (scrollView.contentOffset.x == 0 && scrollView.tag == TOP) 
        newMiddleScrollView = (UIScrollView*)[self.view viewWithTag:TOP];
        newTopScrollView = (UIScrollView*)[self.view viewWithTag:BOTTOM];
        newBottomScrollView = (UIScrollView*)[self.view viewWithTag:MIDDLE];
    

    // swipe right

    else if (scrollView.contentOffset.x == 320 && scrollView.tag == MIDDLE) 
        newMiddleScrollView = (UIScrollView*)[self.view viewWithTag:BOTTOM];
        newTopScrollView = (UIScrollView*)[self.view viewWithTag:MIDDLE];
        newBottomScrollView = (UIScrollView*)[self.view viewWithTag:TOP];
    
    else 
        return;
    

    newMiddleScrollView.tag = MIDDLE;
    newBottomScrollView.tag = BOTTOM;
    newTopScrollView.tag = TOP;

    newBottomScrollView.contentOffset = CGPointMake(0, 0);
    newMiddleScrollView.contentOffset = CGPointMake(0, 0);
    newTopScrollView.contentOffset = CGPointMake(320, 0);

    UIScrollView *verticalScrollView_sub = (UIScrollView*)[self.view viewWithTag:VERTICAL_SUB];

    [verticalScrollView_sub addSubview:newBottomScrollView];
    [newBottomScrollView addSubview:newMiddleScrollView];
    [newMiddleScrollView addSubview:newTopScrollView];


【讨论】:

【参考方案2】:

这是一个很好的问题 - 如果我在这里错了,请纠正我,但听起来从左到右有一个 任意 数量的页面/卡片,但只有几张标准卡片进来从顶部或底部(您的设置和详细信息面板)。

如果是这种情况,那么您可能希望坚持使用类似于UIPageController 的东西以及一些手势识别器。你设置你的页面控制器(或者你最终使用的任何控制器来实现你想要的卡片动画),然后添加两个手势识别器来向上滑动和向下滑动。

然后,您可以在收到这些手势时为您的详细信息/设置视图设置动画,从而为您提供类似卡片的界面,而无需打扰多个 UIPageViewControllers 或滚动视图。

如果您想要水平和垂直的任意数量的卡片,这种方法不是很好,但听起来好像不是这样。

【讨论】:

没错,我正在水平滚动任意数量的卡片,并带有静态数量的向上/向下卡片。我在这里发布了一个使用嵌套 UIScrollViews 的解决方案,效果很好。我现在正在努力将它创建为一个具有数据源的类,该数据源将存储“卡片”并允许正确排序。【参考方案3】:

有很多不同的方法可以实现这一点,具体取决于您希望不同的控制器如何相互关联。幻灯片转换本身非常简单。我已经在一个控制器中以这种方式实现了它,该控制器是调用该方法的控制器的超类:

-(void)SlideInController:(RDSlideController *) next 
    next.presentingVC = self;
    next.view.frame = CGRectMake(self.view.frame.origin.x + 320, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
    [self.view.window addSubview:next.view];
    [UIView animateWithDuration:1 animations:^
        next.view.frame = self.view.frame;
     completion:^(BOOL finished) 
        self.view.window.rootViewController = next;
    ];

然后从新出现的控制器中,你可以调用这个方法返回:

-(void)SlideOut 
    UIViewController *prev = self.presentingVC;
    prev.view.frame = self.view.frame;
    [self.view.window insertSubview:prev.view belowSubview:self.view];
    [UIView animateWithDuration:1 animations:^
        self.view.frame = CGRectMake(self.view.frame.origin.x + 320, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
     completion:^(BOOL finished) 
        self.view.window.rootViewController = prev;
    ];

【讨论】:

以上是关于如何使用 Scrollview(或最佳实践)进行 Card Deck 样式页面转换,垂直和水平的主要内容,如果未能解决你的问题,请参考以下文章

最佳实践:软件版本控制 [关闭]

最佳实践|从Producer 到 Consumer,如何有效监控 Kafka

使用 Spring JMS 进行错误处理的最佳实践

如何使用 try catch 进行异常处理是最佳实践

最佳实践|从Producer 到 Consumer,如何有效监控 Kafka

使用 @AppStorage 进行设置的 SwiftUI 最佳实践:如何在加载 @AppStorage 之前读取 UserDefaults?