Stronicowanie UIScrollView w krokach mniejszych niż rozmiar ramki
Mam widok przewijania, który jest szerokością ekranu, ale tylko około 70 pikseli wysokości. Zawiera wiele ikon 50 x 50 (z miejscem wokół nich), które chcę, aby użytkownik mógł wybrać. Ale zawsze chcę, aby widok przewijania zachowywał się w sposób paged, zawsze zatrzymując się z ikoną w ścisłym centrum.
Gdyby ikony były szerokości ekranu, nie byłoby to problemem, ponieważ stronicowanie UIScrollView zajęłoby się tym. Ale ponieważ moje małe ikony są znacznie mniejsze niż Rozmiar zawartości, to nie działa.
Widziałem to zachowanie wcześniej w aplikacji call AllRecipes. Po prostu nie wiem, jak to zrobić.
Jakieś pomysły na to, jak uruchomić stronicowanie na podstawie wielkości ikon?
13 answers
Spróbuj zmienić widok przewijania na mniejszy niż rozmiar ekranu (w zależności od szerokości), ale odznacz pole wyboru "podgląd klipu" w polu IB. Następnie nakładamy na niego przezroczysty, userInteractionEnabled = NO view (na pełnej szerokości), który nadpisuje hitTest:withEvent:, aby zwrócić widok przewijania. To powinno dać ci to, czego szukasz. Zobacz Ta odpowiedź aby uzyskać więcej szczegółów.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:33
Istnieje również inne rozwiązanie, które jest prawdopodobnie nieco lepsze niż nakładanie widoku przewijania innym widokiem i nadpisywanie hitTest.
Możesz podklasować UIScrollView i nadpisać jego punkt. Następnie widok przewijania może reagować na dotknięcia poza ramką. Oczywiście reszta jest taka sama.
@interface PagingScrollView : UIScrollView {
UIEdgeInsets responseInsets;
}
@property (nonatomic, assign) UIEdgeInsets responseInsets;
@end
@implementation PagingScrollView
@synthesize responseInsets;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint parentLocation = [self convertPoint:point toView:[self superview]];
CGRect responseRect = self.frame;
responseRect.origin.x -= responseInsets.left;
responseRect.origin.y -= responseInsets.top;
responseRect.size.width += (responseInsets.left + responseInsets.right);
responseRect.size.height += (responseInsets.top + responseInsets.bottom);
return CGRectContainsPoint(responseRect, parentLocation);
}
@end
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-21 11:19:06
Widzę wiele rozwiązań, ale są one bardzo złożone. o wiele łatwiejszym sposobem , aby mieć małe strony, ale nadal zachować przewijanie całego obszaru, jest zmniejszenie przewijania i przeniesienie scrollView.panGestureRecognizer
do widoku nadrzędnego. Są to kroki:
Upewnij się, że widok przewijania jest podzielony na strony i nie jest przycinany
-
W kodzie przesuń gest panoramowania widoku przewijania do widoku kontenera nadrzędnego to jest pełna szerokość:
override func viewDidLoad() {
super.viewDidLoad()
statsView.addGestureRecognizer(statsScrollView.panGestureRecognizer)
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-15 08:09:13
Przyjęta odpowiedź jest bardzo dobra, ale będzie działać tylko dla klasy UIScrollView
, A żaden z jej potomków. Na przykład, jeśli masz wiele widoków i przekonwertujesz je na UICollectionView
, nie będziesz mógł użyć tej metody, ponieważ widok kolekcji usunie widoki, które jego zdaniem są "niewidoczne" (więc nawet jeśli nie są przycięte, znikną).
Komentarz o tym wspomina scrollViewWillEndDragging:withVelocity:targetContentOffset:
jest, moim zdaniem, poprawną odpowiedzią.
Możesz to zrobić, wewnątrz tego metoda delegate oblicza bieżącą stronę / indeks. Następnie decydujesz, czy prędkość i przesunięcie celu zasługują na ruch "następnej strony". Możesz zbliżyć się do pagingEnabled
zachowania.
Uwaga: w dzisiejszych czasach zazwyczaj jestem programistą RubyMotion, więc proszę, aby ktoś udowodnił poprawność kodu Obj-C. Przepraszam za mieszankę camelCase i snake_case, skopiowałem i wkleiłem większość tego kodu.
- (void) scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetOffset
{
CGFloat x = targetOffset->x;
int index = [self convertXToIndex: x];
CGFloat w = 300f; // this is your custom page width
CGFloat current_x = w * [self convertXToIndex: scrollView.contentOffset.x];
// even if the velocity is low, if the offset is more than 50% past the halfway
// point, proceed to the next item.
if ( velocity.x < -0.5 || (current_x - x) > w / 2 ) {
index -= 1
}
else if ( velocity.x > 0.5 || (x - current_x) > w / 2 ) {
index += 1;
}
if ( index >= 0 || index < self.items.length ) {
CGFloat new_x = [self convertIndexToX: index];
targetOffset->x = new_x;
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-31 22:58:09
Spójrz na metodę -scrollView:didEndDragging:willDecelerate:
na UIScrollViewDelegate
. Coś w stylu:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
int x = scrollView.contentOffset.x;
int xOff = x % 50;
if(xOff < 25)
x -= xOff;
else
x += 50 - xOff;
int halfW = scrollView.contentSize.width / 2; // the width of the whole content view, not just the scroll view
if(x > halfW)
x = halfW;
[scrollView setContentOffset:CGPointMake(x,scrollView.contentOffset.y)];
}
Nie jest idealny-Ostatnio próbowałem tego kodu, miałem jakieś brzydkie zachowanie (skakanie, o ile pamiętam)po powrocie z gumowego zwoju. Możesz tego uniknąć, ustawiając właściwość bounces
widoku przewijania na NO
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-04 23:24:38
Ponieważ wydaje mi się, że nie mogę jeszcze komentować, dodam Moje komentarze do odpowiedzi Noah tutaj.
Udało mi się to osiągnąć metodą, którą opisał Noah Witherspoon. Obejrzałem zachowanie skoków, po prostu nie wywołując metodysetContentOffset:
, gdy widok przewijania jest poza krawędziami.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// Don't snap when at the edges because that will override the bounce mechanic
if (self.contentOffset.x < 0 || self.contentOffset.x + self.bounds.size.width > self.contentSize.width)
return;
...
}
Odkryłem również, że potrzebuję zaimplementować metodę -scrollViewWillBeginDecelerating:
w UIScrollViewDelegate
, aby wyłapać wszystkie przypadki.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-08 01:50:48
Wypróbowałem rozwiązanie powyżej, które nakładało przezroczysty widok z pointInside: withEvent: overridden. To działało całkiem dobrze dla mnie, ale zepsuł się w niektórych przypadkach-zobacz mój komentarz. Skończyło się właśnie implementacji stronicowania siebie z kombinacją scrollViewDidScroll do śledzenia bieżącego indeksu strony i scrollViewWillEndDragging:withVelocity:targetContentOffset i scrollViewDidEndDragging: willDecelerate przyciągnąć do odpowiedniej strony. Uwaga, metoda will-end jest tylko dostępny iOS5+, ale jest całkiem słodki za celowanie określonego przesunięcia, jeśli prędkość != 0. W szczególności możesz powiedzieć rozmówcy, gdzie chcesz, aby widok przewijania wylądował z animacją, jeśli prędkość jest w określonym kierunku.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-25 16:40:22
- (void) scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetOffset
{
static CGFloat previousIndex;
CGFloat x = targetOffset->x + kPageOffset;
int index = (x + kPageWidth/2)/kPageWidth;
if(index<previousIndex - 1){
index = previousIndex - 1;
}else if(index > previousIndex + 1){
index = previousIndex + 1;
}
CGFloat newTarget = index * kPageWidth;
targetOffset->x = newTarget - kPageOffset;
previousIndex = index;
}
KPageWidth to szerokość, jaką chcesz, aby Twoja strona była. kPageOffset jest, jeśli nie chcesz, aby komórki były wyrównane do lewej (tzn. jeśli chcesz, aby były wyrównane do środka, ustaw to na połowę szerokości komórki). W przeciwnym razie powinno być zero.
Pozwoli to również na przewijanie tylko jednej strony na raz.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-17 01:44:29
Podczas tworzenia widoku przewijania upewnij się, że ustawiłeś to:
scrollView.showsHorizontalScrollIndicator = false;
scrollView.showsVerticalScrollIndicator = false;
scrollView.pagingEnabled = true;
Następnie dodaj swoje podglądy do przewijarki z przesunięciem równym ich indeksowi * wysokości przewijarki. Jest to dla przewijarki pionowej:
UIView * sub = [UIView new];
sub.frame = CGRectMake(0, index * h, w, subViewHeight);
[scrollView addSubview:sub];
Jeśli uruchomisz go teraz, widoki są rozmieszczone, a z włączonym stronicowaniem przewijają się pojedynczo.
Więc umieść to w swojej metodzie viewDidScroll:
//set vars
int index = scrollView.contentOffset.y / h; //current index
float y = scrollView.contentOffset.y; //actual offset
float p = (y / h)-index; //percentage of page scroll complete (0.0-1.0)
int subViewHeight = h-240; //height of the view
int spacing = 30; //preferred spacing between views (if any)
NSArray * array = scrollView.subviews;
//cycle through array
for (UIView * sub in array){
//subview index in array
int subIndex = (int)[array indexOfObject:sub];
//moves the subs up to the top (on top of each other)
float transform = (-h * subIndex);
//moves them back down with spacing
transform += (subViewHeight + spacing) * subIndex;
//adjusts the offset during scroll
transform += (h - subViewHeight - spacing) * p;
//adjusts the offset for the index
transform += index * (h - subViewHeight - spacing);
//apply transform
sub.transform = CGAffineTransformMakeTranslation(0, transform);
}
Ramki podwidywań są nadal rozmieszczone, po prostu przesuwamy je razem za pomocą Przekształć w miarę przewijania użytkownika.
Masz również dostęp do powyższej zmiennej p, której możesz użyć do innych rzeczy, takich jak alpha lub transforms w podwidach. Gdy p = = 1, ta strona jest w pełni pokazana, a raczej zmierza w kierunku 1.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-26 20:54:49
Spróbuj użyć właściwości contentInset widoku przewijania:
scrollView.pagingEnabled = YES;
[scrollView setContentSize:CGSizeMake(height, pageWidth * 3)];
double leftContentOffset = pageWidth - kSomeOffset;
scrollView.contentInset = UIEdgeInsetsMake(0, leftContentOffset, 0, 0);
Może potrwać trochę zabawy z wartościami, aby osiągnąć pożądane stronicowanie.
Okazało się, że to działa bardziej czysto w porównaniu do alternatyw zamieszczonych. Problem z użyciem metody scrollViewWillEndDragging:
delegate polega na tym, że przyspieszanie wolnych ruchów nie jest naturalne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-14 07:28:13
To jedyne realne rozwiązanie problemu.
import UIKit
class TestScrollViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var cellSize:CGFloat!
var inset:CGFloat!
var preX:CGFloat=0
let pages = 8
override func viewDidLoad() {
super.viewDidLoad()
cellSize = (self.view.bounds.width-180)
inset=(self.view.bounds.width-cellSize)/2
scrollView=UIScrollView(frame: self.view.bounds)
self.view.addSubview(scrollView)
for i in 0..<pages {
let v = UIView(frame: self.view.bounds)
v.backgroundColor=UIColor(red: CGFloat(CGFloat(i)/CGFloat(pages)), green: CGFloat(1 - CGFloat(i)/CGFloat(pages)), blue: CGFloat(CGFloat(i)/CGFloat(pages)), alpha: 1)
v.frame.origin.x=CGFloat(i)*cellSize
v.frame.size.width=cellSize
scrollView.addSubview(v)
}
scrollView.contentSize.width=cellSize*CGFloat(pages)
scrollView.isPagingEnabled=false
scrollView.delegate=self
scrollView.contentInset.left=inset
scrollView.contentOffset.x = -inset
scrollView.contentInset.right=inset
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
preX = scrollView.contentOffset.x
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let originalIndex = Int((preX+cellSize/2)/cellSize)
let targetX = targetContentOffset.pointee.x
var targetIndex = Int((targetX+cellSize/2)/cellSize)
if targetIndex > originalIndex + 1 {
targetIndex=originalIndex+1
}
if targetIndex < originalIndex - 1 {
targetIndex=originalIndex - 1
}
if velocity.x == 0 {
let currentIndex = Int((scrollView.contentOffset.x+self.view.bounds.width/2)/cellSize)
let tx=CGFloat(currentIndex)*cellSize-(self.view.bounds.width-cellSize)/2
scrollView.setContentOffset(CGPoint(x:tx,y:0), animated: true)
return
}
let tx=CGFloat(targetIndex)*cellSize-(self.view.bounds.width-cellSize)/2
targetContentOffset.pointee.x=scrollView.contentOffset.x
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: velocity.x, options: [UIViewAnimationOptions.curveEaseOut, UIViewAnimationOptions.allowUserInteraction], animations: {
scrollView.contentOffset=CGPoint(x:tx,y:0)
}) { (b:Bool) in
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-06 10:22:12
Oto moja odpowiedź. W moim przykładzie collectionView
, który ma nagłówek sekcji, jest przewijaniem, które chcemy zrobić, ma niestandardowy efekt isPagingEnabled
, a wysokość komórki jest stałą wartością.
var isScrollingDown = false // in my example, scrollDirection is vertical
var lastScrollOffset = CGPoint.zero
func scrollViewDidScroll(_ sv: UIScrollView) {
isScrollingDown = sv.contentOffset.y > lastScrollOffset.y
lastScrollOffset = sv.contentOffset
}
// 实现 isPagingEnabled 效果
func scrollViewWillEndDragging(_ sv: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let realHeaderHeight = headerHeight + collectionViewLayout.sectionInset.top
guard realHeaderHeight < targetContentOffset.pointee.y else {
// make sure that user can scroll to make header visible.
return // 否则无法手动滚到顶部
}
let realFooterHeight: CGFloat = 0
let realCellHeight = cellHeight + collectionViewLayout.minimumLineSpacing
guard targetContentOffset.pointee.y < sv.contentSize.height - realFooterHeight else {
// make sure that user can scroll to make footer visible
return // 若有footer,不在此处 return 会导致无法手动滚动到底部
}
let indexOfCell = (targetContentOffset.pointee.y - realHeaderHeight) / realCellHeight
// velocity.y can be 0 when lifting your hand slowly
let roundedIndex = isScrollingDown ? ceil(indexOfCell) : floor(indexOfCell) // 如果松手时滚动速度为 0,则 velocity.y == 0,且 sv.contentOffset == targetContentOffset.pointee
let y = realHeaderHeight + realCellHeight * roundedIndex - collectionViewLayout.minimumLineSpacing
targetContentOffset.pointee.y = y
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-16 10:59:47
Dopracowana wersja rozwiązaniaUICollectionView
:
- Ograniczenie do jednej strony na przesunięcie
- zapewnia szybkie przyciąganie do strony, nawet jeśli przewijanie było powolne
override func viewDidLoad() {
super.viewDidLoad()
collectionView.decelerationRate = .fast
}
private var dragStartPage: CGPoint = .zero
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
dragStartOffset = scrollView.contentOffset
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Snap target offset to current or adjacent page
let currentIndex = pageIndexForContentOffset(dragStartOffset)
var targetIndex = pageIndexForContentOffset(targetContentOffset.pointee)
if targetIndex != currentIndex {
targetIndex = currentIndex + (targetIndex - currentIndex).signum()
} else if abs(velocity.x) > 0.25 {
targetIndex = currentIndex + (velocity.x > 0 ? 1 : 0)
}
// Constrain to valid indices
if targetIndex < 0 { targetIndex = 0 }
if targetIndex >= items.count { targetIndex = max(items.count-1, 0) }
// Set new target offset
targetContentOffset.pointee.x = contentOffsetForCardIndex(targetIndex)
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-24 06:17:06