Обработка сенсорных событий в дочернем UIScrollView - PullRequest
6 голосов
/ 27 октября 2008

Я отображаю серию изображений в UIScrollView. Я очень хочу повторить приложение "Фотографии".

Моя текущая архитектура:

Родительский UIScrollView с достаточно широким размером контента для x количества страниц + некоторое дополнительное пространство для полей между изображениями. Каждое изображение содержится в UIImageView. Каждый UIImageView содержится в своем собственном UIScrollview, которые затем являются подпредставлениями родительского UIScrollView.

Итак, у меня есть строка UIScrollViews в родительском UIScrollView.

В родительском UIScrollView включена подкачка страниц, поэтому я могу без проблем прокручивать страницу на страницу.

Проблема в том, как плавно перемещаться по увеличенному изображению. Я переопределил viewForZoomingInScrollView метод для возврата соответствующего UIImageView, когда пользователь вводит / выводит. Я переопределил метод scrollViewDidEndZooming, чтобы установить для свойства canCancelContentTouches родительского представления значение NO, если масштаб увеличения больше 1.

Таким образом, пользователи могут перемещаться по изображению. Тем не менее, они должны удерживать палец на мгновение, чтобы преодолеть небольшую задержку, которую имеет родительский вид прокрутки, прежде чем отправлять события касания в подпредставления. Кроме того, когда пользователь выполняет панорамирование на одном изображении, следующие / предыдущие изображения не входят в видимую область, когда пользователь достиг границы текущего изображения.

Есть идеи?

Спасибо.

Ответы [ 4 ]

5 голосов
/ 06 мая 2009

Ура! Я попытался подойти к проблеме только с одним UIScrollView, и я думаю, что нашел решение.

Прежде чем пользователь начнет масштабирование (в viewForZoomingInScrollView:), я переключаю вид прокрутки в режим масштабирования (удаляет все дополнительные страницы, сбрасывает размер содержимого и смещение). Когда пользователь уменьшает масштаб 1,00 (в scrollViewDidEndZooming:withView:atScale:), я снова переключаюсь в режим просмотра страниц (добавление всех страниц назад, настройка размера и смещения содержимого).

Вот код простого контроллера представления, который делает именно это. Этот образец переключает, увеличивает и перемещает три больших UIImageViews.

Обратите внимание, что все, что требуется, - это один контроллер представления с несколькими функциями, нет необходимости создавать подкласс UIScrollView или что-то в этом роде.

typedef enum {
    ScrollViewModeNotInitialized,           // view has just been loaded
    ScrollViewModePaging,                   // fully zoomed out, swiping enabled
    ScrollViewModeZooming,                  // zoomed in, panning enabled
    ScrollViewModeAnimatingFullZoomOut,     // fully zoomed out, animations not yet finished
    ScrollViewModeInTransition,             // during the call to setPagingMode to ignore scrollViewDidScroll events
} ScrollViewMode;

@interface ScrollingMadnessViewController : UIViewController <UIScrollViewDelegate> {
    UIScrollView *scrollView;
    NSArray *pageViews;
    NSUInteger currentPage;
    ScrollViewMode scrollViewMode;
}

@end

@implementation ScrollingMadnessViewController

- (void)setPagingMode {
    NSLog(@"setPagingMode");
    if (scrollViewMode != ScrollViewModeAnimatingFullZoomOut && scrollViewMode != ScrollViewModeNotInitialized)
        return; // setPagingMode is called after a delay, so something might have changed since it was scheduled
    scrollViewMode = ScrollViewModeInTransition; // to ignore scrollViewDidScroll when setting contentOffset

    // reposition pages side by side, add them back to the view
    CGSize pageSize = scrollView.frame.size;

    NSUInteger page = 0;
    for (UIView *view in pageViews) {
        if (!view.superview)
            [scrollView addSubview:view];
        view.frame = CGRectMake(pageSize.width * page++, 0, pageSize.width, pageSize.height);
    }

    scrollView.pagingEnabled = YES;
    scrollView.showsVerticalScrollIndicator = scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.contentSize = CGSizeMake(pageSize.width * [pageViews count], pageSize.height);
    scrollView.contentOffset = CGPointMake(pageSize.width * currentPage, 0);

    scrollViewMode = ScrollViewModePaging;
}

- (void)setZoomingMode {
    NSLog(@"setZoomingMode");
    scrollViewMode = ScrollViewModeInTransition; // to ignore scrollViewDidScroll when setting contentOffset

    CGSize pageSize = scrollView.frame.size;

    // hide all pages besides the current one
    NSUInteger page = 0;
    for (UIView *view in pageViews)
        if (currentPage != page++)
            [view removeFromSuperview];

    // move the current page to (0, 0), as if no other pages ever existed
    [[pageViews objectAtIndex:currentPage] setFrame:CGRectMake(0, 0, pageSize.width, pageSize.height)];

    scrollView.pagingEnabled = NO;
    scrollView.showsVerticalScrollIndicator = scrollView.showsHorizontalScrollIndicator = YES;
    scrollView.contentSize = pageSize;
    scrollView.contentOffset = CGPointZero;

    scrollViewMode = ScrollViewModeZooming;
}

- (void)loadView {
    CGRect frame = [UIScreen mainScreen].applicationFrame;
    scrollView = [[UIScrollView alloc] initWithFrame:frame];
    scrollView.delegate = self;
    scrollView.maximumZoomScale = 2.0f;
    scrollView.minimumZoomScale = 1.0f;

    UIImageView *imageView1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"red.png"]];
    UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"green.png"]];
    UIImageView *imageView3 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"yellow-blue.png"]];

    // in a real app, you most likely want to have an array of view controllers, not views;
    // also should be instantiating those views and view controllers lazily
    pageViews = [[NSArray alloc] initWithObjects:imageView1, imageView2, imageView3, nil];

    self.view = scrollView;
}

- (void)setCurrentPage:(NSUInteger)page {
    if (page == currentPage)
        return;
    currentPage = page;
    // in a real app, this would be a good place to instantiate more view controllers -- see SDK examples
}

- (void)viewDidLoad {
    scrollViewMode = ScrollViewModeNotInitialized;
    [self setPagingMode];
}

- (void)viewDidUnload {
    [pageViews release]; // need to release all page views here; our array is created in loadView, so just releasing it
    pageViews = nil;
}

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(setPagingMode) object:nil];
    CGPoint offset = scrollView.contentOffset;
    NSLog(@"scrollViewDidScroll: (%f, %f)", offset.x, offset.y);
    if (scrollViewMode == ScrollViewModeAnimatingFullZoomOut && ABS(offset.x) < 1e-5 && ABS(offset.y) < 1e-5)
        // bouncing is still possible (and actually happened for me), so wait a bit more to be sure
        [self performSelector:@selector(setPagingMode) withObject:nil afterDelay:0.1];
    else if (scrollViewMode == ScrollViewModePaging)
        [self setCurrentPage:roundf(scrollView.contentOffset.x / scrollView.frame.size.width)];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)aScrollView {
    if (scrollViewMode != ScrollViewModeZooming)
        [self setZoomingMode];
    return [pageViews objectAtIndex:currentPage];
}

- (void)scrollViewDidEndZooming:(UIScrollView *)aScrollView withView:(UIView *)view atScale:(float)scale {
    NSLog(@"scrollViewDidEndZooming: scale = %f", scale);
    if (fabsf(scale - 1.0) < 1e-5) {
        if (scrollView.zoomBouncing)
            NSLog(@"scrollViewDidEndZooming, but zoomBouncing is still true!");

        // cannot call setPagingMode now because scrollView will bounce after a call to this method, resetting contentOffset to (0, 0)
        scrollViewMode = ScrollViewModeAnimatingFullZoomOut;
        // however sometimes bouncing will not take place
        [self performSelector:@selector(setPagingMode) withObject:nil afterDelay:0.2];
    }
}

@end

Пример запускаемого проекта доступен по адресу http://github.com/andreyvit/ScrollingMadness/ (если вы не используете Git, просто нажмите здесь кнопку Download). README доступен там, объясняя, почему код был написан таким, какой он есть.

(Пример проекта также иллюстрирует, как программно масштабировать представление прокрутки, и имеет класс ZoomScrollView, который инкапсулирует решение для этого. Это классный класс, но для этого не требуется. используйте ZoomScrollView, вернитесь на несколько коммитов в истории коммитов.)

P.S. Для полноты картины есть TTScrollView - UIScrollView, переопределенный с нуля. Это часть великой и знаменитой библиотеки Three20 . Мне не нравится, что он чувствует к пользователю, но он делает реализацию пейджинга / прокрутки / масштабирования очень простой.

P.P.S. Настоящее приложение Photo от Apple имеет код pre-SDK и использует классы pre-SDK. Можно обнаружить два класса, производных от варианта UIScrollView до SDK, внутри фреймворка PhotoLibrary, однако не сразу понятно, что они делают (и они делают довольно много). Я легко могу поверить, что раньше этот эффект было труднее достичь до SDK.

2 голосов
/ 27 октября 2008

Мне нужно было сделать аналогичную настройку, но я в основном все написал на заказ. Я не уверен, как вы собираетесь обойти проблему «передачи» сенсорных событий от дочернего UIScrollView к родительскому UISscrollView, когда вы достигнете края. Вы можете попробовать переопределить UITouchesBegan: withEvent: в родительском UIScrollView и создать дамп непосредственно для дочернего элемента. Удачи!

1 голос
/ 26 ноября 2009

Это код Андрея с переводом c # для разработчиков Monotouch ... Сначала вам нужно отредактировать файл xib. Вставьте 3 контроллера вида и создайте розетки, такие как _page1, _page2, _mainpage .. и свяжите эти розетки с представлениями. обратите внимание, что вы должны ссылаться на просмотр представления контроллера на _mainpage view. (извините за мой английский)

    public partial class Test_Details_Controller : UIViewController
{
    private UIPageControl _pageCont;

    private UIScrollView _scView;
    private Object[] _pageViews;
    private int _currentPageIndex;        
    private bool _rotationInProgress;


    void InitializeAfterLoad ()
    { 

        this.Title = "Test";

        this._pageCont = CreatePageControll();

    }

    private UIPageControl CreatePageControll()
    {
        UIPageControl pageControll = new UIPageControl( new RectangleF( 146,348, 38, 20 ) );
        pageControll.BackgroundColor = UIColor.Red;
        pageControll.Alpha = 0.7f;

        return pageControll;
    }

    private void UpdatePageControll(UIPageControl cont, int current, int pages, UIView showed)
    {
        cont.CurrentPage = current;
        cont.Pages = pages;
        cont.UpdateCurrentPageDisplay();

        UIPageControl.AnimationsEnabled = true;
        UIPageControl.BeginAnimations(string.Empty, this.Handle);
        cont.Frame = new RectangleF(showed.Frame.Location.X 
                                        , cont.Frame.Location.Y , pageSize().Width, cont.Frame.Height);         
        UIPageControl.CommitAnimations();

    }

    private UIView loadViewForPage(int pageIndex){
        UIView _view = null;
        switch ( pageIndex ) {
        case 1:
            _view = this._page1;
        break;
        case 2:
            _view = this._page2;
        break;
        default:
            _view = this._page1;
        break;
        }
        return _view;
    }

    private int numberOfPages(){
        return (int)this._pageViews.Count();
    }

    private UIView viewForPage( int pageIndex ){
        UIView pageView;
        if(this._pageViews.ElementAt( pageIndex ) == null)
        {
            pageView = loadViewForPage( pageIndex );
            _pageViews[ pageIndex ] = pageView;
        }
        else{
            pageView = (UIView)_pageViews[ pageIndex ];
        }

        _scView.AddSubview( pageView );

        return pageView;                
    }

    private SizeF pageSize(){
        return this._scView.Frame.Size;
    }

    private bool isPageLoaded( int pageIndex ){
        return this._pageViews.ElementAt( pageIndex ) != null;
    }

    private void layoutPage( int pageIndex ){

        SizeF pageSize = this.pageSize();
        ((UIView)this._pageViews[pageIndex]).Frame = new RectangleF( pageIndex * pageSize.Width,0, pageSize.Width, pageSize.Height );
        this.viewForPage( pageIndex );  

    }

    private void loadView(){
        this._scView = new UIScrollView();
        this._scView.Delegate = new ScViewDelegate( this );
        this._scView.PagingEnabled = true;
        this._scView.ShowsHorizontalScrollIndicator = false;
        this._scView.ShowsVerticalScrollIndicator = false;
        this._scView.Layer.BorderWidth = 2;
        this._scView.AddSubview( _pageCont );
        this.View = this._scView;

    }

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        InitializeAfterLoad ();
        this._pageViews = new Object[]{ _page1, _page2 };
        this.loadView();    
    }           

    private void currentPageIndexDidChange(){
        this.layoutPage( _currentPageIndex );

        if(_currentPageIndex+1 < this.numberOfPages()){
            this.layoutPage( _currentPageIndex + 1 );
        }
        if(_currentPageIndex >0){
            this.layoutPage( _currentPageIndex - 1 );
        }

        this.UpdatePageControll( _pageCont, _currentPageIndex, this.numberOfPages(), ((UIView)this._pageViews[_currentPageIndex]) );
        this._scView.BringSubviewToFront( _pageCont );


        this.NavigationController.Title = string.Format( "{0} of {1}", _currentPageIndex + 1, this.numberOfPages() );
    }

    private void layoutPages(){
        SizeF pageSize = this.pageSize();
        this._scView.ContentSize = new SizeF( this.numberOfPages() * pageSize.Width, pageSize.Height );
        // move all visible pages to their places, because otherwise they may overlap
        for (int pageIndex = 0; pageIndex < this.numberOfPages(); pageIndex++) {
            if(this.isPageLoaded( pageIndex ))
                this.layoutPage( pageIndex );
        }
    }

    public override void ViewWillAppear (bool animated)
    {
        this.layoutPages();
        this.currentPageIndexDidChange();
        this._scView.ContentOffset = new PointF( _currentPageIndex * this.pageSize().Width, 0 );
    }

    class ScViewDelegate : UIScrollViewDelegate
    {
        Test_Details_Controller id;
        public ScViewDelegate ( Test_Details_Controller id )
        {
            this.id = id;
        }
        public override void Scrolled (UIScrollView scrollView)
        {

            if(id._rotationInProgress)
                return;// UIScrollView layoutSubviews code adjusts contentOffset, breaking our logic

            SizeF pageSize = id.pageSize();
            int newPageIndex = ((int)id._scView.ContentOffset.X + (int)pageSize.Width / 2) / (int)pageSize.Width;
            if( newPageIndex == id._currentPageIndex )
                return;

            id._currentPageIndex = newPageIndex;
            id.currentPageIndexDidChange();
        }
    }

    public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
    {
        return toInterfaceOrientation != UIInterfaceOrientation.PortraitUpsideDown;
    }

    public override void WillRotate (UIInterfaceOrientation toInterfaceOrientation, double duration)
    {           
        _rotationInProgress = true;
        // hide other page views because they may overlap the current page during animation
        for (int pageIndex = 0; pageIndex < this.numberOfPages(); pageIndex++) {
            if(this.isPageLoaded( pageIndex ))
                this.viewForPage( pageIndex ).Hidden = ( pageIndex != _currentPageIndex );
        }
    }
    public override void WillAnimateRotation (UIInterfaceOrientation toInterfaceOrientation, double duration)
    {
    // resize and reposition the page view, but use the current contentOffset as page origin
    // (note that the scrollview has already been resized by the time this method is called)
        SizeF pageSize = this.pageSize();
        UIView pageView = this.viewForPage( _currentPageIndex );
        this.viewForPage( _currentPageIndex ).Frame = new RectangleF( this._scView.ContentOffset.X, 0, pageSize.Width, pageSize.Height );           
    }
    public override void DidRotate (UIInterfaceOrientation fromInterfaceOrientation)
    {
        base.DidRotate (fromInterfaceOrientation);

        // adjust frames according to the new page size - this does not cause any visible changes
        this.layoutPages();
        this._scView.ContentOffset = new PointF( _currentPageIndex * this.pageSize().Width, 0 );

        //unhide
        for (int pageIndex = 0; pageIndex < this.numberOfPages(); pageIndex++) {
            if( this.isPageLoaded( pageIndex ) )
                this.viewForPage( pageIndex ).Hidden = false;
        }

        _rotationInProgress = false;
    }               

    public override void DidReceiveMemoryWarning ()
    {
        //SuperHandle = DidReceiveMemoryWarning();
        if(this._pageViews != null)
        {
            // unload non-visible pages in case the memory is scarse
            for (int pageIndex = 0; pageIndex < this.numberOfPages(); pageIndex++) {
                if( pageIndex < _currentPageIndex - 1 || pageIndex > _currentPageIndex + 1 )
                    if( this.isPageLoaded(pageIndex) ){
                        UIView pageview = (UIView)this._pageViews[ pageIndex ];
                        this._pageViews[ pageIndex ] = null;
                        pageview.RemoveFromSuperview();
                    }
            }
        }
    }
    public override void ViewDidUnload ()
    {
        this._pageViews = null;
        this._scView = null;
    }       
}
0 голосов
/ 21 мая 2011

У меня есть пример проекта, и он отлично работает.

https://code.google.com/p/uiscrollview-touch-events/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...