Как я программно масштабирую UIScrollView?

Исключение нулевого указателя генерируется, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  1. Вызов метода экземпляра объекта null.
  2. Доступ или изменение поля объекта null.
  3. Принимая длину null, как если бы это был массив.
  4. Доступ или изменение слотов null, как если бы это был массив.
  5. Бросок null как будто это было значение Throwable.

Приложения должны бросать экземпляры этого класса, чтобы указать на другие незаконные использования объекта null.

Ссылка: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html

28
задан Cœur 10 April 2017 в 16:38
поделиться

4 ответа

Я отвечаю на свой собственный вопрос после игры с вещами и получения его работа.

Apple имеет очень простой пример этого в их документации относительно того, как обработать двойные выстрелы.

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

  • Корректируют кадр и границы внутреннего представления.
  • Mark внутреннее представление как нуждающийся в дисплее.
  • Говорят UIScrollView о новом размере содержания.
  • Вычисляют часть Вашего внутреннего представления, которое должно быть отображено после масштабирования, и иметь панорамирование UIScrollView к тому местоположению.

Также ключ: как только Вы говорите UIScrollView о Вашем новом размере содержания, что это, кажется, сбрасывает свое понятие текущего уровня масштабирования. Вы теперь в новых 1,0 коэффициентах масштабирования. Таким образом, Вы почти наверняка захотите сбросить минимальные и максимальные коэффициенты масштабирования.

14
ответ дан Darron 28 November 2019 в 03:25
поделиться

Darren, можно ли предоставить ссылку на упомянутый пример Apple? Или заголовок так, чтобы я мог найти его? Я вижу http://developer.apple.com/iphone/library/samplecode/Touches/index.html , но это не покрывает изменение масштаба.

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

Спасибо, РЕДАКТИРОВАНИЕ-andrew

: Я просто понял, Вы работаете вокруг того, что Вы имеете мало контроля над внутренним коэффициентом масштабирования UIScrollView путем изменения размеров и изменения значения коэффициента масштабирования 1.0. Определенный взлом, но кажется, что вся Apple оставила нас с. Возможно, пользовательский класс мог инкапсулировать этот прием...

0
ответ дан 28 November 2019 в 03:25
поделиться

Я думаю, что выяснил, к какой документации Darron обращался. В документе "Руководство по программированию iPhone OS" существует раздел "Handling Multi-Touch Events". Это содержит список 7-1:

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
 UIScrollView  *scrollView = (UIScrollView*)[self superview];
 UITouch       *touch = [touches anyObject];
 CGSize        size;
 CGPoint       point;

 if([touch tapCount] == 2) {
    if(![_viewController _isZoomed]) {
        point = [touch locationInView:self];
        size = [self bounds].size;
        point.x /= size.width;
        point.y /= size.height;

        [_viewController _setZoomed:YES];

        size = [scrollView contentSize];
        point.x *= size.width;
        point.y *= size.height;
        size = [scrollView bounds].size;
        point.x -= size.width / 2;
        point.y -= size.height / 2;
        [scrollView setContentOffset:point animated:NO];
    }
        else
        [_viewController _setZoomed:NO];
    }
 }

}
4
ответ дан 28 November 2019 в 03:25
поделиться

ПРИМЕЧАНИЕ: это ужасно устарело. Это примерно со времен iOS 2.x и фактически было исправлено в iOS 3.x.

Сохраняем его здесь только для исторических целей.


Я думаю, что нашел чистое решение для этого и создал подкласс UIScrollView

Пример кода, который иллюстрирует как программное масштабирование (+ обработка двойным касанием), так и разбиение на страницы в стиле библиотеки фотографий + масштабирование + прокрутка, вместе с классом ZoomScrollView, доступен по адресу github.com/andreyvit/ ScrollingMadness .

Вкратце, мое решение - вернуть новое фиктивное представление из viewForZoomingInScrollView: , временно сделав ваше представление содержимого (UIImageView, что угодно) его дочерним элементом. В scrollViewDidEndZooming: мы изменили это, удалив фиктивное представление и вернув представление содержимого обратно в представление прокрутки.

Почему это помогает? Это способ победить постоянный масштаб представления, который мы не можем изменить программно. UIScrollView не сохраняет текущий масштаб вида. Вместо этого каждый UIView может сохранять свой текущий масштаб представления (внутри объекта UIGestureInfo, на который указывает поле _gestureInfo). Предоставляя новый UIView для каждой операции масштабирования, мы всегда начинаем со шкалы масштабирования 1,00.

И как , что помогает? Мы сохраняем текущий масштаб масштабирования сами и применяем его вручную к нашему представлению содержимого, например contentView.transform = CGAffineTransformMakeScale (zoomScale, zoomScale) . Однако это противоречит тому, что UIScrollView хочет сбросить преобразование, когда пользователь зажимает представление. Предоставляя UIScrollView другое представление с преобразованием идентичности для увеличения, мы больше не боремся за преобразование того же представления. UIScrollView может с радостью поверить, что он начинается с масштабирования 1,00 каждый раз и масштабирует представление, начиная с преобразования идентичности, а его внутреннее представление имеет преобразование, соответствующее нашему фактическому текущему масштабу масштабирования.

Теперь ZoomScrollView инкапсулирует все это. Вот его код для полноты картины, однако я действительно рекомендую загрузить образец проекта с GitHub (вам не нужно использовать Git, там есть кнопка «Загрузить»). Если вы хотите получать уведомления об обновлениях образцов кода (а вы должны - я планирую поддерживать и обновлять этот класс!), Следите за проектом на GitHub или отправьте мне электронное письмо по адресу ZoomScrollView инкапсулирует все это. Вот его код для полноты картины, однако я действительно рекомендую загрузить образец проекта с GitHub (вам не нужно использовать Git, там есть кнопка «Загрузить»). Если вы хотите получать уведомления об обновлениях образцов кода (а вы должны - я планирую поддерживать и обновлять этот класс!), Следите за проектом на GitHub или отправьте мне электронное письмо по адресу ZoomScrollView инкапсулирует все это. Вот его код для полноты картины, однако я действительно рекомендую загрузить образец проекта с GitHub (вам не нужно использовать Git, там есть кнопка «Загрузить»). Если вы хотите получать уведомления об обновлениях образцов кода (а вы должны - я планирую поддерживать и обновлять этот класс!), Следите за проектом на GitHub или отправьте мне электронное письмо по адресуandreyvit@gmail.com.

Интерфейс:

/*
 ZoomScrollView makes UIScrollView easier to use:

 - ZoomScrollView is a drop-in replacement subclass of UIScrollView

 - ZoomScrollView adds programmatic zooming
   (see `setZoomScale:centeredAt:animated:`)

 - ZoomScrollView allows you to get the current zoom scale
   (see `zoomScale` property)

 - ZoomScrollView handles double-tap zooming for you
   (see `zoomInOnDoubleTap`, `zoomOutOnDoubleTap`)

 - ZoomScrollView forwards touch events to its delegate, allowing to handle
   custom gestures easily (triple-tap? two-finger scrolling?)

 Drop-in replacement:

 You can replace `[UIScrollView alloc]` with `[ZoomScrollView alloc]` or change
 class in Interface Builder, and everything should continue to work. The only
 catch is that you should not *read* the 'delegate' property; to get your delegate,
 please use zoomScrollViewDelegate property instead. (You can set the delegate
 via either of these properties, but reading 'delegate' does not work.)

 Zoom scale:

 Reading zoomScale property returns the scale of the last scaling operation.
 If your viewForZoomingInScrollView can return different views over time,
 please keep in mind that any view you return is instantly scaled to zoomScale.

 Delegate:

 The delegate accepted by ZoomScrollView is a regular UIScrollViewDelegate,
 however additional methods from `NSObject(ZoomScrollViewDelegateMethods)` category
 will be called on your delegate if defined.

 Method `scrollViewDidEndZooming:withView:atScale:` is called after any 'bounce'
 animations really finish. UIScrollView often calls it earlier, violating
 the documented contract of UIScrollViewDelegate.

 Instead of reading 'delegate' property (which currently returns the scroll
 view itself), you should read 'zoomScrollViewDelegate' property which
 correctly returns your delegate. Setting works with either of them (so you
 can still set your delegate in the Interface Builder).

 */

@interface ZoomScrollView : UIScrollView {
@private
    BOOL _zoomInOnDoubleTap;
    BOOL _zoomOutOnDoubleTap;
    BOOL _zoomingDidEnd;
    BOOL _ignoreSubsequentTouches;                                // after one of delegate touch methods returns YES, subsequent touch events are not forwarded to UIScrollView
    float _zoomScale;
    float _realMinimumZoomScale, _realMaximumZoomScale;           // as set by the user (UIScrollView's min/maxZoomScale == our min/maxZoomScale divided by _zoomScale)
    id _realDelegate;                       // as set by the user (UIScrollView's delegate is set to self)
    UIView *_realZoomView;                      // the view for zooming returned by the delegate
    UIView *_zoomWrapperView;               // the disposable wrapper view actually used for zooming
}

// if both are enabled, zoom-in takes precedence unless the view is at maximum zoom scale
@property(nonatomic, assign) BOOL zoomInOnDoubleTap;
@property(nonatomic, assign) BOOL zoomOutOnDoubleTap;

@property(nonatomic, assign) id<UIScrollViewDelegate> zoomScrollViewDelegate;

@end

@interface ZoomScrollView (Zooming)

@property(nonatomic, assign) float zoomScale;                     // from minimumZoomScale to maximumZoomScale

- (void)setZoomScale:(float)zoomScale animated:(BOOL)animated;    // centerPoint == center of the scroll view
- (void)setZoomScale:(float)zoomScale centeredAt:(CGPoint)centerPoint animated:(BOOL)animated;

@end

@interface NSObject (ZoomScrollViewDelegateMethods)

// return YES to stop processing, NO to pass the event to UIScrollView (mnemonic: default is to pass, and default return value in Obj-C is NO)
- (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

@end

Реализация:

@interface ZoomScrollView (DelegateMethods) <UIScrollViewDelegate>
@end

@interface ZoomScrollView (ZoomingPrivate)
- (void)_setZoomScaleAndUpdateVirtualScales:(float)zoomScale;           // set UIScrollView's minimumZoomScale/maximumZoomScale
- (BOOL)_handleDoubleTapWith:(UITouch *)touch;
- (UIView *)_createWrapperViewForZoomingInsteadOfView:(UIView *)view;   // create a disposable wrapper view for zooming
- (void)_zoomDidEndBouncing;
- (void)_programmaticZoomAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIView *)context;
- (void)_setTransformOn:(UIView *)view;
@end


@implementation ZoomScrollView

@synthesize zoomInOnDoubleTap=_zoomInOnDoubleTap, zoomOutOnDoubleTap=_zoomOutOnDoubleTap;
@synthesize zoomScrollViewDelegate=_realDelegate;

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        _zoomScale = 1.0f;
        _realMinimumZoomScale = super.minimumZoomScale;
        _realMaximumZoomScale = super.maximumZoomScale;
        super.delegate = self;
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        _zoomScale = 1.0f;
        _realMinimumZoomScale = super.minimumZoomScale;
        _realMaximumZoomScale = super.maximumZoomScale;
        super.delegate = self;
    }
    return self;
}

- (id<UIScrollViewDelegate>)realDelegate {
    return _realDelegate;
}
- (void)setDelegate:(id<UIScrollViewDelegate>)delegate {
    _realDelegate = delegate;
}

- (float)minimumZoomScale {
    return _realMinimumZoomScale;
}
- (void)setMinimumZoomScale:(float)value {
    _realMinimumZoomScale = value;
    [self _setZoomScaleAndUpdateVirtualScales:_zoomScale];
}

- (float)maximumZoomScale {
    return _realMaximumZoomScale;
}
- (void)setMaximumZoomScale:(float)value {
    _realMaximumZoomScale = value;
    [self _setZoomScaleAndUpdateVirtualScales:_zoomScale];
}

@end


@implementation ZoomScrollView (Zooming)

- (void)_setZoomScaleAndUpdateVirtualScales:(float)zoomScale {
    _zoomScale = zoomScale;
    // prevent accumulation of error, and prevent a common bug in the user's code (comparing floats with '==')
    if (ABS(_zoomScale - _realMinimumZoomScale) < 1e-5)
        _zoomScale = _realMinimumZoomScale;
    else if (ABS(_zoomScale - _realMaximumZoomScale) < 1e-5)
        _zoomScale = _realMaximumZoomScale;
    super.minimumZoomScale = _realMinimumZoomScale / _zoomScale;
    super.maximumZoomScale = _realMaximumZoomScale / _zoomScale;
}

- (void)_setTransformOn:(UIView *)view {
    if (ABS(_zoomScale - 1.0f) < 1e-5)
        view.transform = CGAffineTransformIdentity;
    else
        view.transform = CGAffineTransformMakeScale(_zoomScale, _zoomScale);
}

- (float)zoomScale {
    return _zoomScale;
}

- (void)setZoomScale:(float)zoomScale {
    [self setZoomScale:zoomScale animated:NO];
}

- (void)setZoomScale:(float)zoomScale animated:(BOOL)animated {
    [self setZoomScale:zoomScale centeredAt:CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2) animated:animated];
}

- (void)setZoomScale:(float)zoomScale centeredAt:(CGPoint)centerPoint animated:(BOOL)animated {
    if (![_realDelegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
        NSLog(@"setZoomScale called on ZoomScrollView, however delegate does not implement viewForZoomingInScrollView");
        return;
    }

    // viewForZoomingInScrollView may change contentOffset, and centerPoint is relative to the current one
    CGPoint origin = self.contentOffset;
    centerPoint = CGPointMake(centerPoint.x - origin.x, centerPoint.y - origin.y);

    UIView *viewForZooming = [_realDelegate viewForZoomingInScrollView:self];
    if (viewForZooming == nil)
        return;

    if (animated) {
        [UIView beginAnimations:nil context:viewForZooming];
        [UIView setAnimationDuration: 0.2];
        [UIView setAnimationDelegate: self];
        [UIView setAnimationDidStopSelector: @selector(_programmaticZoomAnimationDidStop:finished:context:)];
    }

    [self _setZoomScaleAndUpdateVirtualScales:zoomScale];
    [self _setTransformOn:viewForZooming];

    CGSize zoomViewSize   = viewForZooming.frame.size;
    CGSize scrollViewSize = self.frame.size;
    viewForZooming.frame = CGRectMake(0, 0, zoomViewSize.width, zoomViewSize.height);
    self.contentSize = zoomViewSize;
    self.contentOffset = CGPointMake(MAX(MIN(zoomViewSize.width*centerPoint.x/scrollViewSize.width - scrollViewSize.width/2, zoomViewSize.width - scrollViewSize.width), 0),
                                     MAX(MIN(zoomViewSize.height*centerPoint.y/scrollViewSize.height - scrollViewSize.height/2, zoomViewSize.height - scrollViewSize.height), 0));

    if (animated) {
        [UIView commitAnimations];
    } else {
        [self _programmaticZoomAnimationDidStop:nil finished:nil context:viewForZooming];
    }
}

- (void)_programmaticZoomAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIView *)context {
    if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)])
        [_realDelegate scrollViewDidEndZooming:self withView:context atScale:_zoomScale];
}

- (BOOL)_handleDoubleTapWith:(UITouch *)touch {
    if (!_zoomInOnDoubleTap && !_zoomOutOnDoubleTap)
        return NO;
    if (_zoomInOnDoubleTap && ABS(_zoomScale - _realMaximumZoomScale) > 1e-5)
        [self setZoomScale:_realMaximumZoomScale centeredAt:[touch locationInView:self] animated:YES];
    else if (_zoomOutOnDoubleTap && ABS(_zoomScale - _realMinimumZoomScale) > 1e-5)
        [self setZoomScale:_realMinimumZoomScale animated:YES];
    return YES;
}

// the heart of the zooming technique: zooming starts here
- (UIView *)_createWrapperViewForZoomingInsteadOfView:(UIView *)view {
    if (_zoomWrapperView != nil) // not sure this is really possible
        [self _zoomDidEndBouncing]; // ...but just in case cleanup the previous zoom op

    _realZoomView = [view retain];
    [view removeFromSuperview];
    [self _setTransformOn:_realZoomView]; // should be already set, except if this is a different view
    _realZoomView.frame = CGRectMake(0, 0, _realZoomView.frame.size.width, _realZoomView.frame.size.height);
    _zoomWrapperView = [[UIView alloc] initWithFrame:view.frame];
    [_zoomWrapperView addSubview:view];
    [self addSubview:_zoomWrapperView];

    return _zoomWrapperView;
}

// the heart of the zooming technique: zooming ends here
- (void)_zoomDidEndBouncing {
    _zoomingDidEnd = NO;
    [_realZoomView removeFromSuperview];
    [self _setTransformOn:_realZoomView];
    _realZoomView.frame = _zoomWrapperView.frame;
    [self addSubview:_realZoomView];

    [_zoomWrapperView release];
    _zoomWrapperView = nil;

    if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)])
        [_realDelegate scrollViewDidEndZooming:self withView:_realZoomView atScale:_zoomScale];
    [_realZoomView release];
    _realZoomView = nil;
}

@end


@implementation ZoomScrollView (DelegateMethods)

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    if ([_realDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)])
        [_realDelegate scrollViewWillBeginDragging:self];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)])
        [_realDelegate scrollViewDidEndDragging:self willDecelerate:decelerate];
}

- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
    if ([_realDelegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)])
        [_realDelegate scrollViewWillBeginDecelerating:self];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)])
        [_realDelegate scrollViewDidEndDecelerating:self];
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
    if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)])
        [_realDelegate scrollViewDidEndScrollingAnimation:self];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    UIView *viewForZooming = nil;
    if ([_realDelegate respondsToSelector:@selector(viewForZoomingInScrollView:)])
        viewForZooming = [_realDelegate viewForZoomingInScrollView:self];
    if (viewForZooming != nil)
        viewForZooming = [self _createWrapperViewForZoomingInsteadOfView:viewForZooming];
    return viewForZooming;
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    [self _setZoomScaleAndUpdateVirtualScales:_zoomScale * scale];

    // often UIScrollView continues bouncing even after the call to this method, so we have to use delays
    _zoomingDidEnd = YES; // signal scrollViewDidScroll to schedule _zoomDidEndBouncing call
    [self performSelector:@selector(_zoomDidEndBouncing) withObject:nil afterDelay:0.1];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (_zoomWrapperView != nil && _zoomingDidEnd) {
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_zoomDidEndBouncing) object:nil];
        [self performSelector:@selector(_zoomDidEndBouncing) withObject:nil afterDelay:0.1];
    }

    if ([_realDelegate respondsToSelector:@selector(scrollViewDidScroll:)])
        [_realDelegate scrollViewDidScroll:self];
}

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
    if ([_realDelegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)])
        return [_realDelegate scrollViewShouldScrollToTop:self];
    else
        return YES;
}

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
    if ([_realDelegate respondsToSelector:@selector(scrollViewDidScrollToTop:)])
        [_realDelegate scrollViewDidScrollToTop:self];  
}

@end


@implementation ZoomScrollView (EventForwarding)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    id delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(zoomScrollView:touchesBegan:withEvent:)])
        _ignoreSubsequentTouches = [delegate zoomScrollView:self touchesBegan:touches withEvent:event];
    if (_ignoreSubsequentTouches)
        return;
    if ([touches count] == 1 && [[touches anyObject] tapCount] == 2)
        if ([self _handleDoubleTapWith:[touches anyObject]])
            return;
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    id delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(zoomScrollView:touchesMoved:withEvent:)])
        if ([delegate zoomScrollView:self touchesMoved:touches withEvent:event]) {
            _ignoreSubsequentTouches = YES;
            [super touchesCancelled:touches withEvent:event];
        }
    if (_ignoreSubsequentTouches)
        return;
    [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    id delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(zoomScrollView:touchesEnded:withEvent:)])
        if ([delegate zoomScrollView:self touchesEnded:touches withEvent:event]) {
            _ignoreSubsequentTouches = YES;
            [super touchesCancelled:touches withEvent:event];
        }
    if (_ignoreSubsequentTouches)
        return;
    [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    id delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(zoomScrollView:touchesCancelled:withEvent:)])
        if ([delegate zoomScrollView:self touchesCancelled:touches withEvent:event])
            _ignoreSubsequentTouches = YES;
    [super touchesCancelled:touches withEvent:event];
}

@end
13
ответ дан 28 November 2019 в 03:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: