더 작은 경우 UIScrollView의 컨텐츠 중심


140

확대 / 축소 및 스크롤에 사용 하는 UIImageView내부가 UIScrollView있습니다. 스크롤보기의 이미지 / 내용이 스크롤보기보다 크면 모든 것이 잘 작동합니다. 그러나 이미지가 스크롤보기보다 작아지면 스크롤보기의 왼쪽 상단에 고정됩니다. 사진 앱과 같이 중앙에 유지하고 싶습니다.

UIScrollView더 작을 때 중심 의 내용을 유지하는 것에 대한 아이디어 나 예가 있습니까?

iPhone 3.0으로 작업하고 있습니다.

다음 코드는 거의 작동합니다. 최소 확대 / 축소 수준에 도달 한 후 꼬 으면 이미지가 왼쪽 상단으로 돌아갑니다.

- (void)loadView {
    [super loadView];

    // set up main scroll view
    imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
    [imageScrollView setBackgroundColor:[UIColor blackColor]];
    [imageScrollView setDelegate:self];
    [imageScrollView setBouncesZoom:YES];
    [[self view] addSubview:imageScrollView];

    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"WeCanDoIt.png"]];
    [imageView setTag:ZOOM_VIEW_TAG];
    [imageScrollView setContentSize:[imageView frame].size];
    [imageScrollView addSubview:imageView];

    CGSize imageSize = imageView.image.size;
    [imageView release];

    CGSize maxSize = imageScrollView.frame.size;
    CGFloat widthRatio = maxSize.width / imageSize.width;
    CGFloat heightRatio = maxSize.height / imageSize.height;
    CGFloat initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;

    [imageScrollView setMinimumZoomScale:initialZoom];
    [imageScrollView setZoomScale:1];

    float topInset = (maxSize.height - imageSize.height) / 2.0;
    float sideInset = (maxSize.width - imageSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}

/************************************** NOTE **************************************/
/* The following delegate method works around a known bug in zoomToRect:animated: */
/* In the next release after 3.0 this workaround will no longer be necessary      */
/**********************************************************************************/
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    [scrollView setZoomScale:scale+0.01 animated:NO];
    [scrollView setZoomScale:scale animated:NO];
    // END Bug workaround

    CGSize maxSize = imageScrollView.frame.size;
    CGSize viewSize = view.frame.size;
    float topInset = (maxSize.height - viewSize.height) / 2.0;
    float sideInset = (maxSize.width - viewSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

이 문제를 완전히 해결 한 적이 있습니까? 나는 같은 문제로 고심하고 있습니다.
요나

1
주의 : initialZoom 값을 사용하여 값이 아닌 경우 (확대 없음) 삽입 값을 계산하십시오. 예를 들어 다음 행을 사용하십시오. float topInset = (maxSize.height-imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width-imageSize.width * initialZoom) / 2.0; 마지막으로 초기 확대 / 축소 값을 설정합니다. [imageScrollView setZoomScale : initialZoom];
Felix

답변:


228

매우 간단한 해결책이 있습니다! ScrollViewDelegate를 확대하면서 하위보기의 중앙 (이미지보기)을 업데이트하기 만하면됩니다. 확대 / 축소 된 이미지가 스크롤보기보다 작 으면 subview.center를 조정하고 그렇지 않으면 가운데는 (0,0)입니다.

- (void)scrollViewDidZoom:(UIScrollView *)scrollView 
{
    UIView *subView = [scrollView.subviews objectAtIndex:0];

    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, 
                                 scrollView.contentSize.height * 0.5 + offsetY);
}

5
나 에게이 솔루션은 Liam의 NYOBetterZoom을 사용하는 것보다 더 어색합니다. 어쩌면 그것은 이미지 크기 등에 달려 있습니다. 귀하의 요구에 가장 적합한 솔루션을 사용하십시오
wuf810

2
이것을위한 Stackoverflow GOLD STAR. 나는 이것으로 고투하고 있었지만 해결책은 너무 간단합니다.
n13

2
비트를 단순화하는 방법 :CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R

6
스크롤 조정이 시작될 때이 조정이 도움이되었습니다. CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentInset.left - scrollView.contentInset.right - scrollView.contentSize.width) * 0.5, 0.0); CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom - scrollView.contentSize.height) * 0.5, 0.0);
Kieran Harper

3
다른 많은 센터링 기술과 마찬가지로 이것을 사용할 때 문제가 발생하는 것으로 나타났습니다 zoomToRect:. contentInset해당 기능이 필요한 경우 접근 방식을 사용하는 것이 좋습니다. 자세한 내용은 petersteinberger.com/blog/2013/how-to-center-uiscrollview 를 참조하십시오.
Matej Bukovinski

52

@EvelynCordner의 답변 은 내 앱에서 가장 잘 작동했습니다. 다른 옵션보다 코드가 훨씬 적습니다.

필요한 경우 Swift 버전이 있습니다.

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0, 0)
}

4
이것은 훌륭하게 작동합니다! 축소 후 뷰가 제대로 애니메이션되었습니다.
카터 메 드린

4
이 코드를 func에 넣고 viewDidLayoutSubviews 에서 호출 하여 처음에 올바르게 설정되었는지 확인했습니다.
카터 메 드린

좋은 전화 @CarterMedlin은 Swift 3 초기로드에 많은 도움이되었습니다.
AJ Hernandez

이것은 작동하는 것처럼 보이지만 왜 항상 동일한 삽입물을 계산하는 것처럼 보이기 때문에 이유를 이해할 수 없습니다. 스크롤 뷰의 경계는 내용 크기와 마찬가지로 고정되어 있습니다.
Vaddadi Kartick

있는 ScrollView의 크기는 고정되어 확대 할 때 contentSize 변경
마이클 Ziobro

25

좋아, 나는 지난 이틀 동안이 싸움을하고 마침내 마침내 믿을만한 (지금까지 ...) 해결책을 찾았으며 그것을 공유하고 다른 사람들에게 약간의 고통을 덜어 야한다고 생각했습니다. :)이 솔루션에 문제가 있으면 소리 지르십시오!

기본적으로 StackOverflow, Apple 개발자 포럼 검색, three20, ScrollingMadness, ScrollTestSuite 등의 코드를 살펴 보았습니다 .UIScrollView의 오프셋 및 / 또는 삽입과 함께 UIImageView 프레임을 확대하려고했습니다. ViewController 등에서 아무 효과가 없었습니다 (다른 사람들도 알았 듯이).

그것에 자고 난 후, 나는 몇 가지 대안 각도를 시도했다.

  1. UIImageView를 서브 클래스 화하여 자체 크기를 동적으로 변경합니다. 이는 전혀 효과가 없었습니다.
  2. UIScrollView를 서브 클래스 화하여 자체 contentOffset을 동적으로 변경합니다. 이것이 나에게 승자 인 것 같습니다.

이 서브 클래 싱 UIScrollView 메서드를 사용하면 contentOffset 뮤 테이터를 재정의하므로 이미지가 뷰포트보다 작게 조정될 때 {0,0}으로 설정되지 않습니다. 대신 이미지가 뷰포트의 중앙에 유지되도록 오프셋을 설정합니다. 지금까지는 항상 작동하는 것 같습니다. 넓고, 크고, 작고 큰 이미지로 확인했으며 "작동하지만 최소 확대 / 축소로 핀치가 끊어집니다"문제가 없습니다.

이 솔루션을 사용하는 예제 프로젝트를 github에 업로드했습니다. http://github.com/nyoron/NYOBetterZoom


2
관심있는 사람들을 위해-위의 링크 된 프로젝트를 업데이트하여 '올바른 일'을 수행하는 ViewController에 약간 덜 의존하므로 사용자 정의 UIScrollView 자체가 더 많은 세부 사항을 처리합니다.
Liam Jones

2
리암, 넌 흔들어 나는 이것을 ScrollingMadness의 저자로 말하고 있습니다. 3.2 이상의 iPad에서 BTW 설정 scrollViewDidZoom에서 contentInset 설정 (새로운 3.2+ 델리게이트 방법) Just Works.
Andrey Tarantsov 1

매우 깔끔한 코드입니다. 나는 이것으로 한동안 내 머리를 긁어왔다. handyiphonecode.com에
samvermette

대단하다. 감사합니다. UIStatusBar가 숨겨지는 것에 대해 보상하지 않았으므로 줄 anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0을 다음과 같이 변경 했습니다.anOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot

bi Erdemus가 주어진 해결책은 내 의견으로는 훨씬 간단합니다.
gyozo kudor

23

이 코드는 대부분의 iOS 버전에서 작동해야하며 3.1 이상에서 작동하도록 테스트되었습니다.

포토 콜러 용 Apple WWDC 코드를 기반으로합니다.

아래를 UIScrollView의 하위 클래스에 추가하고 tileContainerView를 이미지 또는 타일이 포함 된 뷰로 바꾸십시오.

- (void)layoutSubviews {
    [super layoutSubviews];

    // center the image as it becomes smaller than the size of the screen
    CGSize boundsSize = self.bounds.size;
    CGRect frameToCenter = tileContainerView.frame;

    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.origin.x = 0;

    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.origin.y = 0;

    tileContainerView.frame = frameToCenter;
}

3
이것은 받아 들여지는 대답이어야합니다. 더 간단합니다. 감사. 당신은 내 생명을 구했습니다 !!!!!! : D
Duck

모든 iOS 3.2+ API 호출을 제거한 후 센터링 로직은 3.1.3이 아닌 iOS 3.2 이상이 설치된 기기에서만 작동하는 것 같습니다 (점프, 깜박임, 임의 오프셋). 나는 3.1.3과 3.2+ 사이의 프레임 원점과 크기의 출력을 비교했고, 그들이 일치하더라도, 어떤 이유로 하위 뷰가 여전히 잘못 배치됩니다. 매우 이상합니다. Liam Jone의 대답만이 나를 위해 일했습니다.
David H

1
두 @Erdemus 및 @JosephH 솔루션 작동하지만, UIScrollView서브 클래스의 접근 방식은 바람직 보인다 뷰가 먼저 + 지속적으로 (반면 줌이 진행되는 동안 표시 될 때 호출 될 scrollViewDidZoom번만 사실 스크롤 당 후에 호출)
SwiftArchitect

22

자동 레이아웃을 사용하는 스크롤보기에 더 적합한 솔루션을 보려면 스크롤보기 하위보기의 프레임을 업데이트하는 대신 스크롤보기의 내용 삽입을 사용하십시오.

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}

1
이것은 나를 위해 일했지만, 어떻게 든 이미지가 더 작 으면이 코드 (직접 호출 할 때)가 스크롤보기를 업데이트하지 않았습니다. 내가해야 할 일은 먼저 UIView그 안에 UIScrollview있는 동일한 센터 ( view.center = scrollview.center;)에 넣고 scrollViewDidZoom세트 view.frame.origin xy0다시 넣었 습니다.
morksinaanab

나에게도 잘 작동하지만 메인 스크롤보기를 호출 dispatch_async하는 메인 큐에 a 를 수행했습니다 . 이렇게하면 뷰가 중앙 이미지로 나타납니다. viewWillAppearscrollViewDidZoom:
Pascal

viewDidLayoutSubviews보기가 처음 표시 될 때 올바르게 설정되도록 이 코드를 호출 하십시오.
카터 메 드린

21

현재 나는에 기반하여 오프셋을 조정하기 위해 서브 클래스 화 UIScrollView및 재정의 중 setContentOffset:입니다 contentSize. 핀치 및 프로그래밍 방식의 확대 / 축소 모두에서 작동합니다.

@implementation HPCenteringScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    const CGSize contentSize = self.contentSize;
    const CGSize scrollViewSize = self.bounds.size;

    if (contentSize.width < scrollViewSize.width)
    {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0;
    }

    if (contentSize.height < scrollViewSize.height)
    {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0;
    }

    [super setContentOffset:contentOffset];
}

@end

이 코드는 짧고 달콤 할뿐만 아니라 @Erdemus 솔루션보다 훨씬 부드러운 확대 / 축소를 생성합니다. RMGallery 데모 에서 실제로 작동하는 것을 볼 수 있습니다 .


6
이 메소드를 구현하기 위해 UIScrollView를 서브 클래스화할 필요는 없습니다. Apple을 사용하면 대리자 메서드에서 스크롤 및 확대 / 축소 이벤트를 볼 수 있습니다. 구체적으로 :-(void) scrollViewDidZoom : (UIScrollView *) scrollView;
Rog

1
내가 본 최고의 솔루션 (제약 조건을 사용할 때도 작동).
Frizlab

12

나는이 문제와 싸우는 데 하루를 보냈고 결국에는 scrollViewDidEndZooming : withView : atScale : 을 다음과 같이 .

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
    CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
    CGFloat viewWidth = view.frame.size.width;
    CGFloat viewHeight = view.frame.size.height;

    CGFloat x = 0;
    CGFloat y = 0;

    if(viewWidth < screenWidth) {
        x = screenWidth / 2;
    }
    if(viewHeight < screenHeight) {
        y = screenHeight / 2 ;
    }

    self.scrollView.contentInset = UIEdgeInsetsMake(y, x, y, x);
}

이렇게하면 이미지가 화면보다 작을 때 이미지 주위에 충분한 공간이있어 원하는 위치에 이미지를 배치 할 수 있습니다.

(UIScrollView에 이미지를 보유 할 UIImageView가 포함되어 있다고 가정)

본질적으로 이것이하는 일은 이미지보기의 너비 / 높이가 화면의 너비 / 높이보다 작은 지 확인하고 그렇다면 화면 너비 / 높이의 절반을 삽입하십시오 (이미지를 원한다면이 크기를 더 크게 만들 수 있습니다) 화면 경계를 벗어납니다).

이것은 UIScrollViewDelegate 메소드이므로 빌드 문제가 발생하지 않도록 뷰 컨트롤러의 선언에 추가하는 것을 잊지 마십시오.


7

애플은 2010 WWDC 세션 비디오를 아이폰 개발자 프로그램의 모든 멤버들에게 공개했다. 논의 된 주제 중 하나는 사진 앱을 만드는 방법입니다 !!! 그들은 매우 유사한 앱을 단계별로 구축하고 모든 코드를 무료로 제공했습니다.

개인 API도 사용하지 않습니다. 비공개 계약으로 인해 여기에 코드를 넣을 수는 없지만 여기에 샘플 코드 다운로드 링크가 있습니다. 액세스하려면 로그인해야 할 것입니다.

http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

다음은 iTunes WWDC 페이지에 대한 링크입니다.

http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj


1
문제의 예는 MyImagePicker이며 유감스럽게도 이와 동일한 문제를 나타냅니다.
콜린 바렛

1
나는 더 분명 했어야했다. 문제의 예는 실제로 "MyImagePicker"가 아니라 "PhotoScroller"입니다. "MyImagePicker"가 제대로 작동하지 않는 것이 맞습니다. 그러나 "PhotoScroller"는 그렇지 않습니다. 시도 해봐.
요나

사진 스크롤러에 대해 토론하는 WWDC 비디오의 제목을 기억하십니까?
Grzegorz Adam Hankiewicz

1
"스크롤보기를 사용하여 앱 디자인"이라고합니다.
요나

2

좋아,이 솔루션은 나를 위해 일하고있다. 나는 그것이 표시 UIScrollView하고있는 UIImageView것에 대한 참조 가있는 서브 클래스를 가지고 있습니다. UIScrollView확대 / 축소 할 때마다 contentSize 속성이 조정됩니다. 세터에서 UIImageView적절하게 스케일 을 조정하고 중앙 위치를 조정합니다.

-(void) setContentSize:(CGSize) size{
CGSize lSelfSize = self.frame.size;
CGPoint mid;
if(self.zoomScale >= self.minimumZoomScale){
    CGSize lImageSize = cachedImageView.initialSize;
    float newHeight = lImageSize.height * self.zoomScale;

    if (newHeight < lSelfSize.height ) {
        newHeight = lSelfSize.height;
    }
    size.height = newHeight;

    float newWidth = lImageSize.width * self.zoomScale;
    if (newWidth < lSelfSize.width ) {
        newWidth = lSelfSize.width;
    }
    size.width = newWidth;
    mid = CGPointMake(size.width/2, size.height/2);

}
else {
    mid = CGPointMake(lSelfSize.width/2, lSelfSize.height/2);
}

cachedImageView.center = mid;
[super  setContentSize:size];
[self printLocations];
NSLog(@"zoom %f setting size %f x %f",self.zoomScale,size.width,size.height);
}

항상 이미지를 UIScrollView크기 조정했습니다. UIScrollView있는 ScrollView에서 또한 내가 만든 사용자 지정 클래스입니다.

-(void) resetSize{
    if (!scrollView){//scroll view is view containing imageview
        return;
    }

    CGSize lSize = scrollView.frame.size;

    CGSize lSelfSize = self.image.size; 
    float lWidth = lSize.width/lSelfSize.width;
    float lHeight = lSize.height/lSelfSize.height;

    // choose minimum scale so image width fits screen
    float factor  = (lWidth<lHeight)?lWidth:lHeight;

    initialSize.height = lSelfSize.height  * factor;
    initialSize.width = lSelfSize.width  * factor;

    [scrollView setContentSize:lSize];
    [scrollView setContentOffset:CGPointZero];
    scrollView.userInteractionEnabled = YES;
}

이 두 가지 방법을 사용하면 사진 앱처럼 동작하는보기를 가질 수 있습니다.


이것은 트릭을 수행하는 것처럼 보이며 상당히 간단한 솔루션입니다. 일부 확대 / 축소 전환이 완벽하지는 않지만 수정할 수 있다고 생각합니다. 좀 더 실험 해 볼게요.
hpique

업데이트 전에 솔루션이 명확했습니다. 이제 약간 혼란 스럽습니다. 당신은 명확히 할 수 있습니까?
hpique

2

내가 한 방법은 계층 구조에 추가 뷰를 추가하는 것입니다.

UIScrollView -> UIView -> UIImageView

당신 부여 UIView하여 같은 화면 비율을 UIScrollView, 그리고 중앙 UIImageView에 그.


고마워요 이것도 시도합니다. 뷰 계층을 구성하는 방법을 보여주기 위해 샘플 코드를 게시하거나 샘플 코드를 변경할 수 있습니까?
hpique

이런 종류의 작품. UIView가 UIScrollView의 크기이기 때문에 이미지가 더 작 으면 (예 : 세로 대신 가로) 이미지의 일부를 화면 밖으로 스크롤 할 수 있습니다. 사진 앱은 이것을 허용하지 않으며 훨씬 멋지게 보입니다.
요나

2

위의 답변이 옳다는 것을 알고 있지만 설명만으로 답변을 드리고 싶습니다. 의견을 통해 왜 우리가 이런 일을하는지 이해할 수 있습니다.

scrollView를 처음로드 할 때 다음 코드를 작성하여 가운데에 놓으십시오. contentOffset먼저 설정 한 다음contentInset

    scrollView.maximumZoomScale = 8
    scrollView.minimumZoomScale = 1

    // set vContent frame
    vContent.frame = CGRect(x: 0,
                            y: 0  ,
                            width: vContentWidth,
                            height: vContentWidth)
    // set scrollView.contentSize
    scrollView.contentSize = vContent.frame.size

    //on the X direction, if contentSize.width > scrollView.bounds.with, move scrollView from 0 to offsetX to make it center(using `scrollView.contentOffset`)
    // if not, don't need to set offset, but we need to set contentInset to make it center.(using `scrollView.contentInset`)
    // so does the Y direction.
    let offsetX = max((scrollView.contentSize.width - scrollView.bounds.width) * 0.5, 0)
    let offsetY = max((scrollView.contentSize.height - scrollView.bounds.height) * 0.5, 0)
    scrollView.contentOffset = CGPoint(x: offsetX, y: offsetY)

    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)

그런 다음 vContent를 꼬집 으면 다음 코드를 작성하여 중앙에 배치합니다.

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    //we just need to ensure that the content is in the center when the contentSize is less than scrollView.size.
    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)
}

2

contentInset다른 것에 필요하지 않은 경우 , 스크롤 뷰의 컨텐츠를 중앙에 배치하는 데 사용할 수 있습니다.

class ContentCenteringScrollView: UIScrollView {

    override var bounds: CGRect {
        didSet { updateContentInset() }
    }

    override var contentSize: CGSize {
        didSet { updateContentInset() }
    }

    private func updateContentInset() {
        var top = CGFloat(0)
        var left = CGFloat(0)
        if contentSize.width < bounds.width {
            left = (bounds.width - contentSize.width) / 2
        }
        if contentSize.height < bounds.height {
            top = (bounds.height - contentSize.height) / 2
        }
        contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left)
    }
}

이 방법이 여전히 contentLayoutGuide스크롤보기 안에 내용을 배치 하는 데 사용할 수 있다면 이점

scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
    imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])

또는 Xcode의 인터페이스 빌더에서 컨텐츠를 끌어다 놓기 만하면됩니다.


1

contentSizeUIScrollView 의 속성을 보고 (키-값 관찰 등을 사용하여) 스크롤보기의 크기보다 작을 contentInset때마다 자동으로 조정합니다 contentSize.


시도 할 것이다. contentSize를 관찰하는 대신 UIScrollViewDelegate 메소드로 할 수 있습니까?
hpique

가능성이 높습니다. 당신이 사용하는 것 - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(에 설명 developer.apple.com/iphone/library/documentation/UIKit/... ),하지만 난 관찰 선호하는 것 contentSize, 그렇지 않으면 당신은 새로운 줌 배율을 폐기하고 어쨌든보기에서 그것을 찾는 바람 때문이다. (여러분의 견해를 기준으로 멋진 수학을 할 수 없다면.)
Tim

고마워 팀 scrollViewDidEndZooming은 내가 사용하는 것입니다. 문제의 코드를 참조하십시오 (첫 번째 회신 후 코드를 추가했습니다). 거의 작동합니다. minimumZoomScale에 도달 한 후 이미지를 꼬 으면 왼쪽 상단으로 돌아갑니다.
hpique

최소 확대 / 축소 배율 이외의 다른 배율에서 손가락을 꼬집는 데 효과가 있고 (이미지 중앙에 있음) 최소 눈금에 도달 한 후에 다시 집 으려고하면 끊어 집니까? 아니면 최소 스케일로 꼬집기에는 전혀 효과가 없습니까?
Tim

첫번째. 최소 확대 / 축소 배율 이외의 다른 배율에서 이미지를 꼬집도록 이미지를 중앙에 배치하고, 최소 배율에 도달하면 다시 집 으려고하면 끊어집니다. 또한 처음으로 최소 확대 / 축소 배율에 도달하면 내용이 맨 위에서 나오는 것처럼 짧게 애니메이션되어 잠시 이상한 효과가 나타납니다. 이것은 적어도 3.0 시뮬레이터에서 발생합니다.
hpique

1

내용을 집중시키는 하나의 우아한 방법 UISCrollView은 이것입니다.

contentSize 에 하나의 관찰자를 추가 UIScrollView하면 내용이 변경 될 때 마다이 메소드가 호출됩니다 ...

[myScrollView addObserver:delegate 
               forKeyPath:@"contentSize"
                  options:(NSKeyValueObservingOptionNew) 
                  context:NULL];

이제 관찰자 방법 :

- (void)observeValueForKeyPath:(NSString *)keyPath   ofObject:(id)object   change:(NSDictionary *)change   context:(void *)context { 

    // Correct Object Class.
    UIScrollView *pointer = object;

    // Calculate Center.
    CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale])  / 2.0 ;
            topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );

    topCorrect = topCorrect - (  pointer.frame.origin.y - imageGallery.frame.origin.y );

    // Apply Correct Center.
    pointer.center = CGPointMake(pointer.center.x,
                                 pointer.center.y + topCorrect ); }
  • 을 변경해야합니다 [pointer viewWithTag:100]. 콘텐츠보기로 교체하십시오 UIView.

    • 또한 imageGallery창 크기를 가리 키도록 변경 하십시오.

크기가 변경 될 때마다 콘텐츠의 중심이 수정됩니다.

참고 : 이 콘텐츠가 제대로 작동하지 않는 유일한 방법은의 표준 확대 / 축소 기능을 사용하는 것 UIScrollView입니다.


이것을 작동시키지 못했습니다. 내용이 아닌 scrollView를 중앙에 배치하는 것 같습니다. 창 크기가 중요한 이유는 무엇입니까? 코드가 x 위치도 수정해서는 안됩니까?
hpique

1

이것은 스크롤 뷰 내부의 모든 종류의 뷰에서 꽤 잘 작동하는 문제에 대한 나의 해결책입니다.

-(void)scrollViewDidZoom:(__unused UIScrollView *)scrollView 
    {
    CGFloat top;
    CGFloat left;
    CGFloat bottom;
    CGFloat right;

    if (_scrollView.contentSize.width < scrollView.bounds.size.width) {
        DDLogInfo(@"contentSize %@",NSStringFromCGSize(_scrollView.contentSize));

        CGFloat width = (_scrollView.bounds.size.width-_scrollView.contentSize.width)/2.0;

        left = width;
        right = width;


    }else {
        left = kInset;
        right = kInset;
    }

    if (_scrollView.contentSize.height < scrollView.bounds.size.height) {

        CGFloat height = (_scrollView.bounds.size.height-_scrollView.contentSize.height)/2.0;

        top = height;
        bottom = height;

    }else {
        top = kInset;
        right = kInset;
    }

    _scrollView.contentInset = UIEdgeInsetsMake(top, left, bottom, right);



  if ([self.tiledScrollViewDelegate respondsToSelector:@selector(tiledScrollViewDidZoom:)])
  {
        [self.tiledScrollViewDelegate tiledScrollViewDidZoom:self];
  }
}

1

여기에는 많은 솔루션이 있지만 여기에 내 솔루션을 넣을 위험이 있습니다. 그것은 두 가지 이유에서 좋습니다 : 진행중인 이미지보기 프레임을 업데이트 할 때 줌 경험을 엉망으로 만들지 않으며 또한 원본 스크롤보기 인세 트 (예 : 반투명 도구 모음 등을 우아하게 처리하기 위해 xib 또는 스토리 보드에 정의 됨)를 존중합니다 .

먼저 작은 도우미를 정의하십시오.

CGSize CGSizeWithAspectFit(CGSize containerSize, CGSize contentSize) {
    CGFloat containerAspect = containerSize.width / containerSize.height,
            contentAspect = contentSize.width / contentSize.height;

    CGFloat scale = containerAspect > contentAspect
                    ? containerSize.height / contentSize.height
                    : containerSize.width / contentSize.width;

    return CGSizeMake(contentSize.width * scale, contentSize.height * scale);
}

원래 삽입물을 유지하려면 정의 된 필드 :

UIEdgeInsets originalScrollViewInsets;

그리고 viewDidLoad의 어딘가에 그것을 채우십시오 :

originalScrollViewInsets = self.scrollView.contentInset;

UIImageView를 UIScrollView에 배치하려면 (UIImage 자체가 loadedImage var에 있다고 가정) :

CGSize containerSize = self.scrollView.bounds.size;
containerSize.height -= originalScrollViewInsets.top + originalScrollViewInsets.bottom;
containerSize.width -= originalScrollViewInsets.left + originalScrollViewInsets.right;

CGSize contentSize = CGSizeWithAspectFit(containerSize, loadedImage.size);

UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect) { CGPointZero, contentSize }];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = loadedImage;

[self.scrollView addSubview:imageView];
self.scrollView.contentSize = contentSize;

[self centerImageViewInScrollView];

scrollViewDidZoom : 해당 스크롤보기에 대한 UIScrollViewDelegate에서 :

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    if (scrollView == self.scrollView) {
        [self centerImageViewInScrollView];
    }
}

마지막으로, 중심 자체 :

- (void)centerImageViewInScrollView {
    CGFloat excessiveWidth = MAX(0.0, self.scrollView.bounds.size.width - self.scrollView.contentSize.width),
            excessiveHeight = MAX(0.0, self.scrollView.bounds.size.height - self.scrollView.contentSize.height),
            insetX = excessiveWidth / 2.0,
            insetY = excessiveHeight / 2.0;

    self.scrollView.contentInset = UIEdgeInsetsMake(
            MAX(insetY, originalScrollViewInsets.top),
            MAX(insetX, originalScrollViewInsets.left),
            MAX(insetY, originalScrollViewInsets.bottom),
            MAX(insetX, originalScrollViewInsets.right)
    );
}

방향 변경을 아직 테스트하지는 않았지만 (즉, UIScrollView 자체의 크기를 조정하기위한 적절한 반응), 비교적 쉽게 고칠 수 있습니다.


1

Erdemus가 게시 한 솔루션이 작동한다는 것을 알 수 있지만 scrollViewDidZoom 메서드가 호출되지 않고 이미지가 왼쪽 상단에 붙어있는 경우가 있습니다. 간단한 해결책은 다음과 같이 이미지를 처음 표시 할 때 메소드를 명시 적으로 호출하는 것입니다.

[self scrollViewDidZoom: scrollView];

대부분의 경우이 방법을 두 번 호출 할 수 있지만이 항목의 다른 답변보다 더 확실한 솔루션입니다.


1

Apple의 사진 스크롤러 예제는 원하는 것을 정확하게 수행합니다. 이것을 UIScrollView 서브 클래스에 넣고 _zoomView를 UIImageView로 변경하십시오.

-(void)layoutSubviews{
  [super layoutSubviews];
  // center the zoom view as it becomes smaller than the size of the screen
  CGSize boundsSize = self.bounds.size;
  CGRect frameToCenter = self.imageView.frame;
  // center horizontally
  if (frameToCenter.size.width < boundsSize.width){
     frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
  }else{
    frameToCenter.origin.x = 0;
  }
  // center vertically
  if (frameToCenter.size.height < boundsSize.height){
     frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
  }else{
    frameToCenter.origin.y = 0;
  }
  self.imageView.frame = frameToCenter; 
}

애플의 사진 스크롤러 샘플 코드


1

애니메이션 흐름을 좋게 만들려면

self.scrollview.bouncesZoom = NO;

이 기능을 사용하십시오 ( 이 답변 의 방법을 사용하여 센터 찾기 )

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
    [UIView animateWithDuration:0.2 animations:^{
        float offsetX = MAX((scrollView.bounds.size.width-scrollView.contentSize.width)/2, 0);
        float offsetY = MAX((scrollView.bounds.size.height-scrollView.contentSize.height)/2, 0);
        self.imageCoverView.center = CGPointMake(scrollView.contentSize.width*0.5+offsetX, scrollView.contentSize.height*0.5+offsetY);
    }];
}

이로 인해 튀는 효과가 발생하지만 미리 갑작스런 움직임은 포함되지 않습니다.


1

승인 된 답변 만 신속하지만 대리자를 사용하여 하위 클래스를 만들지 않습니다.

func centerScrollViewContents(scrollView: UIScrollView) {
    let contentSize = scrollView.contentSize
    let scrollViewSize = scrollView.frame.size;
    var contentOffset = scrollView.contentOffset;

    if (contentSize.width < scrollViewSize.width) {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0
    }

    if (contentSize.height < scrollViewSize.height) {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0
    }

    scrollView.setContentOffset(contentOffset, animated: false)
}

// UIScrollViewDelegate    
func scrollViewDidZoom(scrollView: UIScrollView) {
    centerScrollViewContents(scrollView)
}

1

내부 imageView의 초기 특정 너비 (예 : 300)가 있고 너비를 초기 너비보다 작은 줌 에만 중앙에 맞추려는 경우 에도 도움이 될 수 있습니다.

 func scrollViewDidZoom(scrollView: UIScrollView){
    if imageView.frame.size.width < 300{
        imageView.center.x = self.view.frame.width/2
    }
  }

0

이 작업을 수행하는 현재 방법은 다음과 같습니다. 더 좋지만 여전히 완벽하지는 않습니다. 설정을 시도하십시오.

 myScrollView.bouncesZoom = YES; 

에있을 때 뷰가 중앙에 위치하지 않는 문제를 해결합니다 minZoomScale.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGSize screenSize = [[self view] bounds].size;//[[UIScreen mainScreen] bounds].size;//
CGSize photoSize = [yourImage size];
CGFloat topInset = (screenSize.height - photoSize.height * [myScrollView zoomScale]) / 2.0;
CGFloat sideInset = (screenSize.width - photoSize.width * [myScrollView zoomScale]) / 2.0;

if (topInset < 0.0)
{ topInset = 0.0; }
if (sideInset < 0.0)
{ sideInset = 0.0; } 
[myScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
ApplicationDelegate *appDelegate = (ApplicationDelegate *)[[UIApplication sharedApplication] delegate];

CGFloat scrollViewHeight; //Used later to calculate the height of the scrollView
if (appDelegate.navigationController.navigationBar.hidden == YES) //If the NavBar is Hidden, set scrollViewHeight to 480
{ scrollViewHeight = 480; }
if (appDelegate.navigationController.navigationBar.hidden == NO) //If the NavBar not Hidden, set scrollViewHeight to 360
{ scrollViewHeight = 368; }

imageView.frame = CGRectMake(0, 0, CGImageGetWidth(yourImage)* [myScrollView zoomScale], CGImageGetHeight(yourImage)* [myScrollView zoomScale]);

[imageView setContentMode:UIViewContentModeCenter];
}

또한 축소 후 이미지가 측면에 달라 붙지 않도록 다음을 수행합니다.

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
myScrollView.frame = CGRectMake(0, 0, 320, 420);
 //put the correct parameters for your scroll view width and height above
}

요나 안녕하세요. 우리는 한동안 같은 문제를 다루고있는 것 같습니다. 두 가지 솔루션을 확인하고 곧 답변 드리겠습니다.
hpique

안녕 조나, 나는 당신의 최신 솔루션을 시도했다. 그러나 temporaryImage 란 무엇입니까? temporaryImage = imageView.image을 넣어 보았습니다. 그러나 확대 / 축소하면 이미지가 사라집니다. 감사합니다,
Pannag

Pannag, temporaryImage의 이름이 잘못되었습니다. 사용중인 사진과 마찬가지로 myImage라고합니다. 혼란을 드려 죄송합니다.
요나

0

좋아, 나는이 문제에 대한 꽤 좋은 해결책을 찾았다 고 생각한다. 비결은 지속적으로 imageView's프레임을 다시 조정하는 것 입니다. 내가 지속적으로 조정보다 훨씬 더이 작품을 찾을 수 contentInsets또는contentOffSets . 세로 및 가로 이미지를 모두 수용하기 위해 약간의 추가 코드를 추가해야했습니다.

코드는 다음과 같습니다.

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {

CGSize screenSize = [[self view] bounds].size;

if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
    imageView.frame = [[self view] bounds];
    [myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}

if (myScrollView.zoomScale > initialZoom)
{
    if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
    {
        if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
        }
        if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
        }
    }
    if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
    {
        CGFloat portraitHeight;
        if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
        { portraitHeight = 368;}
        else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}

        if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
        }
        if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
        }
    }
    [myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}

0

페이지 매김을 비활성화하면 제대로 작동합니다.

scrollview.pagingEnabled = NO;

0

나는 똑같은 문제가 있었다. 내가 해결 한 방법은 다음과 같습니다.

이 코드는 다음의 결과로 호출되어야합니다 scrollView:DidScroll:

CGFloat imageHeight = self.imageView.frame.size.width * self.imageView.image.size.height / self.imageView.image.size.width;
BOOL imageSmallerThanContent = (imageHeight < self.scrollview.frame.size.height) ? YES : NO;
CGFloat topOffset = (self.imageView.frame.size.height - imageHeight) / 2;

// If image is not large enough setup content offset in a way that image is centered and not vertically scrollable
if (imageSmallerThanContent) {
     topOffset = topOffset - ((self.scrollview.frame.size.height - imageHeight)/2);
}

self.scrollview.contentInset = UIEdgeInsetsMake(topOffset * -1, 0, topOffset * -1, 0);

0

질문이 조금 오래되었지만 문제는 여전히 존재합니다. I는 그것을 해결 엑스 코드 7 (이 본 경우에는 최상부 항목의 수직 공간 제약함으로써 topLabelsuperViews에)합니다 ( scrollView)는 상단 IBOutlet후마다에게있는 ScrollView의 파단의 높이에 따라 컨텐츠 변경의 상수를 재 계산 ( topLabelbottomLabel).

class MyViewController: UIViewController {

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var topLabel: UILabel!
    @IBOutlet weak var bottomLabel: UILabel!
    @IBOutlet weak var toTopConstraint: NSLayoutConstraint!

    override func viewDidLayoutSubviews() {
        let heightOfScrollViewContents = (topLabel.frame.origin.y + topLabel.frame.size.height - bottomLabel.frame.origin.y)
        // In my case abs() delivers the perfect result, but you could also check if the heightOfScrollViewContents is greater than 0.
        toTopConstraint.constant = abs((scrollView.frame.height - heightOfScrollViewContents) / 2)
    }

    func refreshContents() {
        // Set the label's text …

        self.view.layoutIfNeeded()
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.