2013-04-12 27 views
5

Những gì tôi muốn nhận được là hành vi tương tự mà xem di chuyển này có:Làm thế nào để thực hiện một UIScrollView snap vào biểu tượng (như App Store: Feature)

App Store feature scroll view (left)

tôi biết rằng đây là cách sử dụng HTML và không phải là API gốc, nhưng tôi đang cố gắng triển khai nó như một thành phần UIKit.

Bây giờ, với hành vi Tôi đang tìm:

  • Chú ý rằng đó là một cái nhìn cuộn paged, nhưng "kích thước trang" nhỏ hơn chiều rộng của view.
  • Khi bạn cuộn từ trái sang phải mỗi trang "chụp" sang mục ngoài cùng bên trái.
  • Khi bạn cuộn từ đầu phải sang bên trái, hãy "chụp nhanh" sang mục ngoài cùng bên phải.

Trang cùng nhưng bây giờ phải sang trái:

App Store feature scroll view (right)

Những gì tôi đã cố gắng:

  • Tôi đã thử làm xem di chuyển nhỏ hơn đó là siêu xem và ghi đè hitTest, và điều đó đã cho tôi rằng hành vi từ trái sang phải.
  • Tôi đã thử triển khai scrollViewWillEndDragging: withVelocity: targetContentOffset: và đặt targetContentOffset tôi muốn nhưng vì tôi không thể thay đổi vận tốc, nó chỉ cuộn quá chậm hoặc quá nhanh.
  • Tôi đã thử triển khai scrollViewDidEndDecelerating: và sau đó tạo hoạt ảnh cho độ lệch chính xác nhưng chế độ xem cuộn đầu tiên dừng lại sau đó di chuyển, nó trông không tự nhiên.
  • Tôi đã thử triển khai scrollViewDidEndDragging: willDecelerate: và sau đó tạo hoạt ảnh cho độ lệch chính xác nhưng chế độ xem cuộn "nhảy" và không hoạt ảnh chính xác.

Tôi hết ý tưởng.

Cảm ơn!

Cập nhật:

tôi đã kết thúc sử dụng phương pháp Rob Mayoff, nó trông sạch sẽ. Tôi đã thay đổi nó để nó hoạt động khi vận tốc là 0, ví dụ khi người dùng kéo, dừng và nhả ngón tay.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView 
        withVelocity:(CGPoint)velocity 
       targetContentOffset:(CGPoint *)targetContentOffset { 
    CGFloat maxOffset = scrollView.contentSize.width - scrollView.bounds.size.width; 
    CGFloat minOffset = 0; 

    if (velocity.x == 0) { 
     CGFloat targetX = MAX(minOffset,MIN(maxOffset, targetContentOffset->x)); 

     CGFloat diff = targetX - baseOffset; 

     if (ABS(diff) > offsetStep/2) { 
      if (diff > 0) { 
       //going left 
       baseOffset = MIN(maxOffset, baseOffset + offsetStep); 
      } else { 
       //going right 
       baseOffset = MAX(minOffset, baseOffset - offsetStep); 
      } 
     } 
    } else { 
     if (velocity.x > 0) { 
      baseOffset = MIN(maxOffset, baseOffset + offsetStep); 
     } else { 
      baseOffset = MAX(minOffset, baseOffset - offsetStep); 
     } 
    } 

    targetContentOffset->x = baseOffset; 
} 

Vấn đề duy nhất với giải pháp này là trượt chế độ xem cuộn không tạo ra hiệu ứng "thoát". Nó cảm thấy "bị mắc kẹt".

+0

Bất kỳ cơ hội nào có thể đạt được bằng cách sử dụng UITableView/UICollectionView?Tôi muốn cùng một cuộn và hiệu ứng snap nhưng với bảng/bộ sưu tập để xem trước các mục tiếp theo/trước như một trong iTunes. Làm một cái gì đó giống như 4 hàng trong bảng 1 cột/bộ sưu tập. –

+0

Đối với tất cả những người muốn sửa chữa cảm giác "mắc kẹt": Nếu abs (velocity.x) lớn hơn 0.8, bạn có thể cộng/trừ hai offsetStep thay vì một. – lassej

+0

Đừng quên cách cực kỳ đơn giản để làm điều này. JUST MAKE THE SCROLL VIEW, ** SIMPLY SIZE của "UNIT" ** Và làm cho nó trang. Nó chỉ đơn giản như vậy. Sử dụng siêu khéo léo [kỹ thuật một cái gì đó như thế này] (http: // stackoverflow.com/a/37505837/294884) nếu cần thiết để điều chỉnh "khu vực có thể chạm" – Fattie

Trả lời

14

Thiết scrollView.decelerationRate = UIScrollViewDecelerationRateFast, kết hợp với việc thực hiện scrollViewWillEndDragging:withVelocity:targetContentOffset:, dường như làm việc cho tôi sử dụng một cái nhìn bộ sưu tập.

Trước tiên, tôi cung cấp cho bản thân mình một số biến Ví dụ:

@implementation ViewController { 
    NSString *cellClassName; 
    CGFloat baseOffset; 
    CGFloat offsetStep; 
} 

Trong viewDidLoad, tôi đặt của xem decelerationRate:

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    cellClassName = NSStringFromClass([MyCell class]); 
    [self.collectionView registerNib:[UINib nibWithNibName:cellClassName bundle:nil] forCellWithReuseIdentifier:cellClassName]; 
    self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast; 
} 

Tôi cần offsetStep là kích thước của một số không thể thiếu của vật phẩm phù hợp với giới hạn trên màn hình của chế độ xem. Tôi tính toán nó trong viewDidLayoutSubviews:

- (void)viewDidLayoutSubviews { 
    [super viewDidLayoutSubviews]; 
    UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; 
    CGFloat stepUnit = layout.itemSize.width + layout.minimumLineSpacing; 
    offsetStep = stepUnit * floorf(self.collectionView.bounds.size.width/stepUnit); 
} 

Tôi cần baseOffset đến được X offset của quan điểm trước khi cuộn khởi động. Tôi khởi tạo nó trong viewDidAppear::

- (void)viewDidAppear:(BOOL)animated { 
    [super viewDidAppear:animated]; 
    baseOffset = self.collectionView.contentOffset.x; 
} 

Sau đó, tôi cần phải buộc các quan điểm để di chuyển trong các bước của offsetStep. Tôi làm điều đó trong scrollViewWillEndDragging:withVelocity:targetContentOffset:. Tùy thuộc vào số velocity, tôi tăng hoặc giảm baseOffset bởi offsetStep. Nhưng tôi kẹp baseOffset đến mức tối thiểu là 0 và tối đa là contentSize.width - bounds.size.width.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { 
    if (velocity.x < 0) { 
     baseOffset = MAX(0, baseOffset - offsetStep); 
    } else if (velocity.x > 0) { 
     baseOffset = MIN(scrollView.contentSize.width - scrollView.bounds.size.width, baseOffset + offsetStep); 
    } 
    targetContentOffset->x = baseOffset; 
} 

Lưu ý rằng tôi không quan tâm những gì targetContentOffset->x có trong.

Điều này có tác dụng căn chỉnh với cạnh trái của mục hiển thị ngoài cùng bên trái, cho đến khi người dùng cuộn tất cả các mục đến mục cuối cùng. Tại thời điểm đó, nó sẽ căn chỉnh với cạnh phải của mục nhìn thấy ngoài cùng bên phải, cho đến khi người dùng cuộn hết sang bên trái. Điều này có vẻ phù hợp với hành vi của ứng dụng App Store.

Nếu điều đó không làm việc cho bạn, bạn có thể thử thay thế các dòng cuối cùng (targetContentOffset->x = baseOffset) với điều này:

dispatch_async(dispatch_get_main_queue(), ^{ 
     [scrollView setContentOffset:CGPointMake(baseOffset, 0) animated:YES]; 
    }); 

Đó cũng làm việc cho tôi.

Bạn có thể tìm ứng dụng thử nghiệm của mình in this git repository.

12

Bạn có thể bật pagingEnabled, hoặc sử dụng kết hợp với decelerationRate = UIScrollViewDecelerationRateFastscrollViewDidEndDraggingscrollViewDidEndDecelerating (mà sẽ hạn chế tối đa ảnh hưởng của làm chậm di chuyển đến một địa điểm và sau đó tạo hiệu ứng động một lần nữa để một vị trí khác). Nó có thể không chính xác những gì bạn muốn, nhưng nó khá gần. Và bằng cách sử dụng animateWithDuration nó tránh nhảy tức thời của ảnh chụp cuối cùng đó.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 
{ 
    if (!decelerate) 
     [self snapScrollView:scrollView]; 
} 

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    [self snapScrollView:scrollView]; 
} 

Sau đó bạn có thể viết một snapScrollView, chẳng hạn như:

- (void)snapScrollView:(UIScrollView *)scrollView 
{ 
    CGPoint offset = scrollView.contentOffset; 

    if ((offset.x + scrollView.frame.size.width) >= scrollView.contentSize.width) 
    { 
     // no snap needed ... we're at the end of the scrollview 
     return; 
    } 

    // calculate where you want it to snap to 

    offset.x = floorf(offset.x/kIconOffset + 0.5) * kIconOffset; 

    // now snap it to there 

    [UIView animateWithDuration:0.1 
        animations:^{ 
         scrollView.contentOffset = offset; 
        }]; 
} 
5

Giải pháp đơn giản, hoạt động như App Store, có vận tốc hoặc không có nó. kCellBaseWidth - chiều rộng ô.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView 
        withVelocity:(CGPoint)velocity 
       targetContentOffset:(inout CGPoint *)targetContentOffset 
{ 
    NSInteger index = lrint(targetContentOffset->x/kCellBaseWidth); 
    targetContentOffset->x = index * kCellBaseWidth; 
} 
+0

Đẹp và đơn giản và thực sự hoạt động ok! –

2

Lồng tiếng cho Swift. Câu trả lời của @avdyushin là đơn giản nhất và, như Ben đã đề cập, hoạt động rất tốt. Mặc dù tôi đã thêm một phần từ câu trả lời của @Rob về cuối chế độ xem cuộn. Cùng với nhau, giải pháp này dường như hoạt động hoàn hảo.

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { 

    if ((scrollView.contentOffset.x + scrollView.frame.size.width) >= scrollView.contentSize.width) { 
     // no snap needed ... we're at the end of the scrollview 
     return 
    } 

    let index: CGFloat = CGFloat(lrintf(Float(targetContentOffset.memory.x)/kCellBaseWidth)) 
    targetContentOffset.memory.x = index * kCellBaseWidth 

} 

Chỉ cần thêm bạn minimumLineSpacing-kCellBaseWidth, và thì đấy.

+0

Tôi yêu bạn rất nhiều –

+0

Nhờ tất cả những người đóng góp cho điều này. JUST A REMINDER: nếu bạn đang sử dụng 'minimumLineSpacing' trong FlowLayout thì bạn phải tính toán giá trị đó. Thêm nó vào kCellBaseWidth. – Womble

Các vấn đề liên quan