Nội dung trung tâm của UIScrollView khi nhỏ hơn


140

Tôi có một cái UIImageViewbên trong UIScrollViewmà tôi sử dụng để phóng to và cuộn. Nếu hình ảnh / nội dung của chế độ xem cuộn lớn hơn chế độ xem cuộn, mọi thứ đều hoạt động tốt. Tuy nhiên, khi hình ảnh trở nên nhỏ hơn chế độ xem cuộn, nó sẽ dính vào góc trên cùng bên trái của chế độ xem cuộn. Tôi muốn giữ nó ở giữa, như ứng dụng Ảnh.

Bất kỳ ý tưởng hoặc ví dụ về việc giữ nội dung của UIScrollViewtrung tâm khi nó nhỏ hơn?

Tôi đang làm việc với iPhone 3.0.

Các mã sau đây gần như hoạt động. Hình ảnh trở về góc trên cùng bên trái nếu tôi chụm nó sau khi đạt mức thu phóng tối thiểu.

- (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)];
}

Bạn đã bao giờ giải quyết vấn đề này hoàn toàn? Tôi đang vật lộn với cùng một vấn đề.
Giô-na

1
Chú ý: Sử dụng giá trị ban đầu để tính toán phần tử nếu nó KHÔNG phải là một (không phóng to). Ví dụ: sử dụng các dòng này: float topInset = (maxSize.height - imageSize.height * initZoom) / 2.0; float sideInset = (maxSize. thong - imageSize. thong * initZoom) / 2.0; và cuối cùng đặt giá trị thu phóng ban đầu [imageScrollView setZoomScale: initZoom];
Felix

Câu trả lời:


228

Tôi đã có giải pháp rất đơn giản! Tất cả những gì bạn cần là cập nhật trung tâm của chế độ xem phụ (lượt xem hình ảnh) trong khi phóng to ScrollViewDelegate. Nếu hình ảnh thu phóng nhỏ hơn scrollview thì điều chỉnh subview.center khác trung tâm là (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
Đối với tôi giải pháp này còn giật hơn cả việc sử dụng NYOBetterZoom của Liam. Có thể nó phụ thuộc vào kích thước hình ảnh, vv Đạo đức; sử dụng giải pháp phù hợp nhất với nhu cầu của bạn
wuf810

2
Stackoverflow GOLD STAR cho điều này. Tôi đã vật lộn với điều này, nhưng giải pháp rất đơn giản.
n13

2
để đơn giản hóa một chút:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R

6
Điều chỉnh này đã giúp tôi thoát ra khi cuộn xem có nội dung: 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
Tôi nhận thấy rằng điều này, giống như nhiều kỹ thuật định tâm khác, dường như gặp vấn đề khi sử dụng zoomToRect:. Sử dụng contentInsetphương pháp hoạt động tốt hơn, nếu bạn tình cờ cần chức năng đó. Xem petersteinberger.com/blog/2013/how-to-center-uiscrollview để biết thêm chi tiết.
Matej Bukovinski

51

Câu trả lời của @ EvelynCordner là câu trả lời hay nhất trong ứng dụng của tôi. Mã ít hơn rất nhiều so với các tùy chọn khác quá.

Đây là phiên bản Swift nếu có ai cần:

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
Điều này làm việc tuyệt vời! Việc xem thậm chí hoạt hình đúng sau khi thu nhỏ.
Carter Medlin

4
Tôi đặt mã này trong một func và cũng gọi nó trong viewDidLayoutSubview để đảm bảo rằng nó được thiết lập đúng lúc đầu.
Carter Medlin

Cuộc gọi tốt @CarterMedlin đã giúp tôi rất nhiều với tải ban đầu Swift 3 của tôi.
AJ Hernandez

Điều này có vẻ hiệu quả, nhưng tôi không thể hiểu tại sao, vì dường như luôn tính toán các phần tử giống nhau: giới hạn của chế độ xem cuộn được cố định, cũng như kích thước nội dung.
Vaddadi Kartick

contentSize thay đổi khi thu phóng cuộn Kích thước xem chỉ cố định
Michał Ziobro

25

Được rồi, tôi đã chiến đấu trong hai ngày qua và cuối cùng đã đi đến một giải pháp khá đáng tin cậy (cho đến nay ...) Tôi nghĩ rằng tôi nên chia sẻ nó và cứu người khác một chút đau đớn. :) Nếu bạn tìm thấy một vấn đề với giải pháp này xin vui lòng hét lên!

Về cơ bản tôi đã trải qua những gì mọi người khác có: tìm kiếm StackOverflow, Diễn đàn nhà phát triển của Apple, đã xem mã cho ba20, ScrollingMadness, ScrollTestSuite, v.v. Tôi đã thử mở rộng khung UIImageView, chơi với phần bù và / hoặc phần trong của UIScrollView từ ViewContoder, v.v. nhưng không có gì hoạt động tốt cả (như mọi người khác cũng đã phát hiện ra).

Sau khi ngủ trên đó, tôi đã thử một vài góc độ khác nhau:

  1. Phân lớp UIImageView để thay đổi kích thước của chính nó một cách linh hoạt - điều này không hoạt động tốt chút nào.
  2. Phân lớp UIScrollView để thay đổi nội dung của chính nó một cách linh hoạt - đây là thứ dường như là một người chiến thắng đối với tôi.

Với phương pháp UIScrollView phân lớp này, tôi ghi đè bộ biến đổi nội dung, do đó, nó không đặt {0,0} khi hình ảnh được thu nhỏ hơn chế độ xem - thay vào đó, nó đặt phần bù sao cho hình ảnh sẽ được giữ ở giữa chế độ xem. Cho đến nay, nó dường như luôn luôn làm việc. Tôi đã kiểm tra nó với các hình ảnh rộng, cao, nhỏ và lớn và không có vấn đề "hoạt động nhưng bị chèn ép ở mức thu phóng tối thiểu".

Tôi đã tải lên một dự án ví dụ cho github sử dụng giải pháp này, bạn có thể tìm thấy nó ở đây: http://github.com/nyoron/NYOBetterZoom


2
Đối với những người quan tâm - Tôi đã cập nhật dự án được liên kết ở trên để nó ít phụ thuộc hơn vào ViewContoder đang làm 'điều đúng đắn', bản thân UIScrollView tùy chỉnh sẽ quan tâm nhiều hơn đến chi tiết.
Liam Jones

2
Liam, bạn đá. Tôi đang nói điều này với tư cách là một tác giả của ScrollingMadness. BTW trên iPad trong 3.2+ cài đặt nội dung Cài đặt trong scrollViewDidZoom (một phương thức ủy nhiệm 3.2+ mới) Chỉ hoạt động.
Andrey Tarantsov

Đoạn mã rất gọn gàng. Tôi đã gãi đầu với điều này trong một thời gian. Đã thêm dự án vào gadiphonecode.com
samvermette

Điều đó thật tuyệt. Cảm ơn bạn. Mặc dù vậy, nó không bù cho UIStatusBar của tôi bị ẩn, vì vậy tôi đã đổi dòng anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0thànhanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot

Theo tôi, giải pháp đưa ra bi Erdemus đơn giản hơn nhiều.
gyozo kudor

23

Mã này sẽ hoạt động trên hầu hết các phiên bản iOS (và đã được thử nghiệm để hoạt động trên 3.1 trở lên).

Nó dựa trên mã Apple WWDC cho trình chụp ảnh.

Thêm phần bên dưới vào lớp con của UIScrollView và thay thế brickContainerView bằng chế độ xem chứa hình ảnh hoặc ô của bạn:

- (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
Đây phải là câu trả lời được chấp nhận, vì nó đơn giản hơn. Cảm ơn. Bạn đã cứu cuộc đời tôi!!!!!! : D
Vịt

Sau khi xóa tất cả các lệnh gọi API iOS 3.2 trở lên, logic định tâm dường như chỉ hoạt động trên các thiết bị có iOS 3.2+ chứ không phải 3.1.3 (tăng vọt, nhấp nháy, được bù ngẫu nhiên). Tôi đã so sánh kết quả đầu ra của nguồn gốc và kích thước khung giữa 3.1.3 và 3.2+ và mặc dù chúng khớp nhau, vì một số lý do, chế độ xem con vẫn được đặt không chính xác. Rất kỳ quặc. Chỉ có câu trả lời của Liam Jone làm việc cho tôi.
David H

1
Cả hai giải pháp @Erdemus và @JosephH đều hoạt động, nhưng UIScrollViewcách tiếp cận lớp con có vẻ thích hợp hơn: nó được gọi khi chế độ xem được hiển thị lần đầu + liên tục trong khi thu phóng đang diễn ra (trong khi scrollViewDidZoomchỉ được gọi một lần trên mỗi cuộn và sau thực tế)
SwiftArchitect

22

Để có giải pháp phù hợp hơn cho các chế độ xem cuộn sử dụng tự động thanh toán, hãy sử dụng nội dung trong chế độ xem cuộn thay vì cập nhật các khung của các lần xem trước của chế độ xem cuộn của bạn.

- (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
Điều này làm việc cho tôi, tuy nhiên nếu hình ảnh nhỏ hơn để bắt đầu bằng cách nào đó thì mã này (khi được gọi trực tiếp) đã không cập nhật cuộn xem. Những gì tôi cần làm cần thiết lần đầu tiên được đặt UIViewđó là bên trong UIScrollviewtrung tâm cùng ( view.center = scrollview.center;) và sau đó trong scrollViewDidZoombộ các view.frame.origin xyđể 0một lần nữa.
morkinaanab

Cũng hoạt động tốt với tôi, nhưng tôi đã thực hiện một dispatch_asynchàng đợi chính trong viewWillAppearđó tôi gọi scrollViewDidZoom:trên chế độ xem cuộn chính của mình. Điều này làm cho chế độ xem xuất hiện với hình ảnh trung tâm.
Pascal

Thực hiện cuộc gọi đến mã này viewDidLayoutSubviewsđể đảm bảo rằng nó được thiết lập đúng khi chế độ xem hiển thị lần đầu tiên.
Carter Medlin

21

Hiện tại tôi đang phân lớp UIScrollViewvà ghi đè setContentOffset:để điều chỉnh bù dựa trên contentSize. Nó hoạt động cả với độ chụm và phóng to theo chương trình.

@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

Ngoài việc ngắn và ngọt ngào, mã này tạo ra khả năng thu phóng mượt mà hơn nhiều so với giải pháp @Erdemus. Bạn có thể thấy nó hoạt động trong bản demo của RMGallery .


6
Bạn không cần phải phân lớp UIScrollView để thực hiện phương pháp này. Apple cho phép bạn xem các sự kiện cuộn và thu phóng trong các phương thức ủy nhiệm. Cụ thể: - (void) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog

1
Giải pháp tốt nhất tôi từng thấy (hoạt động ngay cả khi sử dụng các ràng buộc).
Frizlab

12

Tôi đã dành một ngày để chiến đấu với vấn đề này và cuối cùng đã thực hiện scrollViewDidEndZooming: withView: atScale: như sau:

- (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);
}

Điều này đảm bảo rằng khi hình ảnh nhỏ hơn màn hình, vẫn còn đủ không gian xung quanh nó để bạn có thể đặt nó vào vị trí chính xác mà bạn muốn.

(giả sử rằng UIScrollView của bạn chứa UIImageView để giữ hình ảnh)

Về cơ bản, điều này là kiểm tra xem chiều rộng / chiều cao của chế độ xem hình ảnh của bạn có nhỏ hơn chiều rộng / chiều cao của màn hình hay không, và nếu vậy, hãy tạo một phần nhỏ bằng một nửa chiều rộng / chiều cao của màn hình (bạn có thể làm cho nó lớn hơn nếu bạn muốn hình ảnh đi ra khỏi giới hạn màn hình).

Lưu ý rằng vì đây là phương thức UIScrollViewDelegate , đừng quên thêm nó vào khai báo của trình điều khiển xem của bạn, vì vậy để tránh gặp sự cố xây dựng.


7

Apple đã phát hành video phiên WWDC 2010 cho tất cả các thành viên của chương trình nhà phát triển iphone. Một trong những chủ đề được thảo luận là cách họ tạo ra ứng dụng ảnh !!! Họ từng bước xây dựng một ứng dụng rất giống nhau và đã cung cấp tất cả các mã miễn phí.

Nó cũng không sử dụng api riêng. Tôi không thể đặt bất kỳ mã nào ở đây vì thỏa thuận không tiết lộ, nhưng đây là liên kết đến tải xuống mã mẫu. Bạn có thể sẽ cần phải đăng nhập để có được quyền truy cập.

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

Và, đây là một liên kết đến trang 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
Ví dụ trong câu hỏi là MyImagePicker và nó, vui nhộn, thể hiện vấn đề tương tự.
Colin Barrett

1
Tôi cần phải có được rõ ràng hơn. Ví dụ trong câu hỏi thực sự là "PhotoScroller" chứ không phải "MyImagePicker". Bạn đúng rằng "MyImagePicker" không hoạt động đúng. Nhưng, "PhotoScroller" thì có. Thử nó.
Giô-na

Bạn có nhớ có lẽ tiêu đề của video WWDC nơi họ thảo luận về Photoscroller không?
Grzegorz Adam Hankiewicz

1
Nó được gọi là "Thiết kế ứng dụng với chế độ xem cuộn".
Giô-na

2

Ok, giải pháp này đang làm việc cho tôi. Tôi có một lớp con UIScrollViewvới một tham chiếu đến UIImageViewnó đang hiển thị. Bất cứ khi nào UIScrollViewphóng to, thuộc tính contentSize được điều chỉnh. Đó là trong setter mà tôi chia tỷ lệ UIImageViewthích hợp và cũng điều chỉnh vị trí trung tâm của nó.

-(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);
}

Evertime tôi đặt hình ảnh trên UIScrollViewtôi thay đổi kích thước nó. Trong UIScrollViewscrollview cũng là một lớp tùy chỉnh mà tôi đã tạo.

-(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;
}

Với hai phương pháp này, tôi có thể có chế độ xem hoạt động giống như ứng dụng ảnh.


Điều này có vẻ là một mẹo nhỏ, và nó là một giải pháp khá đơn giản. Một số chuyển đổi thu phóng không hoàn hảo, nhưng tôi tin rằng nó có thể được sửa chữa. Tôi sẽ thử nghiệm thêm một số.
hpique

Các giải pháp đã rõ ràng trước khi cập nhật. Bây giờ nó hơi khó hiểu. Bạn có thể làm rõ?
hpique

2

Cách tôi đã làm điều này là thêm một chế độ xem bổ sung vào cấu trúc phân cấp:

UIScrollView -> UIView -> UIImageView

Đưa UIViewra tỷ lệ khung hình giống như của bạn UIScrollViewvà tập trung UIImageViewvào đó.


Cảm ơn, mũ lưỡi trai. Sẽ thử điều này là tốt. Bạn có thể đăng mã mẫu hoặc thay đổi mã mẫu của tôi để hiển thị cách bạn xây dựng cấu trúc phân cấp chế độ xem không?
hpique

Đây là loại công trình. Ngoại trừ thực tế là vì UIView có kích thước của UIScrollView, nếu hình ảnh nhỏ hơn (tức là ngang thay vì dọc), bạn có thể cuộn một phần hình ảnh khỏi màn hình. Ứng dụng ảnh không cho phép điều này và trông đẹp hơn nhiều.
Giô-na

2

Tôi biết một số câu trả lời ở trên là đúng, nhưng tôi chỉ muốn đưa ra câu trả lời của mình với một số lời giải thích, các ý kiến ​​sẽ làm cho bạn hiểu tại sao chúng ta làm như vậy.

Khi tôi tải scrollView lần đầu tiên, tôi viết đoạn mã sau để đặt nó ở giữa, xin lưu ý rằng chúng tôi đặt contentOffsettrước, sau đó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)

Sau đó, khi tôi chụm vContent, tôi viết đoạn mã sau để làm cho nó nằm ở giữa.

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

Nếu contentInsetkhông cần thiết cho bất cứ điều gì khác, nó có thể được sử dụng để tập trung vào nội dung của scrollview.

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)
    }
}

Lợi thế nếu cách tiếp cận này là bạn vẫn có thể sử dụng contentLayoutGuideđể đặt nội dung bên trong scrollview

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)
])

hoặc chỉ cần kéo và thả nội dung trong Trình tạo giao diện của Xcode.


1

Bạn có thể xem thuộc contentSizetính của UIScrollView (sử dụng quan sát giá trị khóa hoặc tương tự) và tự động điều chỉnh contentInsetbất cứ khi nào các contentSizethay đổi nhỏ hơn kích thước của chế độ xem cuộn.


Sẽ thử. Điều này có thể thực hiện được với các phương thức UIScrollViewDelegate thay vì quan sát contentSize không?
hpique

Nhiều khả năng; bạn sẽ sử dụng - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(được mô tả tại developer.apple.com/iphone/lcontentSize Library / document / UIKit / Giả ), nhưng tôi thích quan sát hơn vì nếu không, bạn sẽ loại bỏ tỷ lệ thu phóng mới và tìm thấy nó từ chế độ xem. (Trừ khi bạn có thể rút ra một số phép toán tuyệt vời dựa trên kích thước tương đối của lượt xem của mình.)
Tim

Cảm ơn, Tim. scrollViewDidEndZooming là những gì tôi đang sử dụng. Xem mã trong câu hỏi (Tôi đã thêm nó sau lần trả lời đầu tiên của bạn). Nó gần như hoạt động. Vấn đề duy nhất còn lại nếu nếu tôi chụm hình ảnh sau khi đạt đến mức tối thiểu, nó sẽ quay trở lại góc trên cùng bên trái.
hpique

Bạn có nghĩa là nó hoạt động (căn giữa hình ảnh) để chụm từ bất kỳ tỷ lệ nào ngoài tỷ lệ thu phóng tối thiểu và chỉ bị vỡ nếu bạn cố gắng ghim lại một lần nữa khi bạn ở tỷ lệ tối thiểu? Hoặc nó không hoạt động để chèn ép đến quy mô tối thiểu ở tất cả?
Tim

Cái đầu tiên. Nó căn giữa hình ảnh để chụm từ bất kỳ tỷ lệ nào ngoài tỷ lệ thu phóng tối thiểu và nó sẽ bị vỡ nếu bạn cố gắng chụm lại một lần nữa khi bạn ở tỷ lệ tối thiểu. Ngoài ra, khi lần đầu tiên đạt đến tỷ lệ thu phóng tối thiểu, nội dung được tạo hình động ngắn như xuất phát từ trên xuống, điều này gây ra hiệu ứng kỳ lạ ngắn. Điều này xảy ra trên trình giả lập 3.0 ít nhất.
hpique

1

Một cách thanh lịch để tập trung vào nội dung UISCrollViewlà đây.

Thêm một người quan sát vào nội dung Kích thước của bạn UIScrollView, vì vậy phương thức này sẽ được gọi mỗi khi nội dung thay đổi ...

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

Bây giờ trên phương pháp quan sát của bạn:

- (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 ); }
  • Bạn nên thay đổi [pointer viewWithTag:100]. Thay thế bằng cách xem nội dung của bạn UIView.

    • Cũng thay đổi imageGallerychỉ vào kích thước cửa sổ của bạn.

Điều này sẽ sửa trung tâm của nội dung mỗi khi thay đổi kích thước của anh ấy.

LƯU Ý: Cách duy nhất nội dung này không hoạt động tốt là với chức năng thu phóng tiêu chuẩn của UIScrollView.


Không thể làm điều này để làm việc. Có vẻ như bạn đang tập trung vào scrollView chứ không phải nội dung của nó. Tại sao kích thước cửa sổ quan trọng? Không phải mã cũng đúng vị trí x sao?
hpique

1

Đây là giải pháp của tôi cho vấn đề đó hoạt động khá tốt đối với bất kỳ loại chế độ xem nào trong chế độ xem cuộn.

-(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

Có rất nhiều giải pháp ở đây, nhưng tôi có nguy cơ đặt ở đây của riêng mình. Điều này tốt cho hai lý do: nó không gây rối trải nghiệm thu phóng, cũng như tiến hành cập nhật khung nhìn hình ảnh và cũng tôn trọng các phần tử xem cuộn ban đầu (giả sử, được xác định trong xib hoặc bảng phân cảnh để xử lý các thanh công cụ bán trong suốt một cách duyên dáng, v.v.) .

Đầu tiên, xác định một người trợ giúp nhỏ:

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);
}

Để giữ lại các bản gốc, trường được xác định:

UIEdgeInsets originalScrollViewInsets;

Và một nơi nào đó trong viewDidLoad điền vào nó:

originalScrollViewInsets = self.scrollView.contentInset;

Để đặt UIImageView vào UIScrollView (giả sử chính UIImage nằm trong tệp varImage):

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: từ UIScrollViewDelegate cho chế độ xem cuộn đó:

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

Cuối cùng, tập trung vào chính nó:

- (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)
    );
}

Tôi chưa thử nghiệm thay đổi hướng (nghĩa là phản ứng thích hợp để thay đổi kích thước chính UIScrollView), nhưng việc khắc phục điều đó tương đối dễ dàng.


1

Bạn sẽ thấy rằng giải pháp được đăng bởi Erdemus có hiệu quả, nhưng có một số trường hợp phương thức scrollViewDidZoom không được gọi và hình ảnh của bạn bị kẹt ở góc trên cùng bên trái. Một giải pháp đơn giản là gọi phương thức một cách rõ ràng khi ban đầu bạn hiển thị một hình ảnh, như thế này:

[self scrollViewDidZoom: scrollView];

Trong nhiều trường hợp, bạn có thể gọi phương thức này hai lần, nhưng đây là một giải pháp sạch hơn một số câu trả lời khác trong chủ đề này.


1

Ví dụ về máy quét ảnh của Apple thực hiện chính xác những gì bạn đang tìm kiếm. Đặt cái này trong Lớp con UIScrollView của bạn và thay đổi _zoomView thành UIImageView của bạn.

-(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; 
}

Mã mẫu của Apple


1

Để làm cho hình ảnh động trôi chảy, hãy đặt

self.scrollview.bouncesZoom = NO;

và sử dụng chức năng này (tìm trung tâm bằng phương pháp tại câu trả lời này )

- (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);
    }];
}

Điều này tạo ra hiệu ứng nảy nhưng không liên quan đến bất kỳ chuyển động đột ngột nào trước đó.


1

Chỉ là câu trả lời được phê duyệt nhanh chóng, nhưng không có phân lớp bằng cách sử dụng đại biểu

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

Trong trường hợp imageView bên trong của bạn có chiều rộng cụ thể ban đầu (ví dụ 300) và bạn chỉ muốn căn giữa chiều rộng của nó chỉ khi phóng to nhỏ hơn chiều rộng ban đầu, điều này cũng có thể giúp bạn.

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

0

Đây là cách hiện tại tôi đang làm cho công việc này. Nó tốt hơn nhưng vẫn không hoàn hảo. Thử cài đặt:

 myScrollView.bouncesZoom = YES; 

để khắc phục sự cố với chế độ xem không định tâm khi tại 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];
}

Ngoài ra, tôi làm như sau để ngăn hình ảnh dính vào một bên sau khi thu nhỏ.

- (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
}

Xin chào Giô-na. Có vẻ như chúng tôi đã xử lý cùng một vấn đề trong một thời gian. Sẽ kiểm tra hai giải pháp của bạn và trả lời sớm.
hpique

Xin chào Jonah, tôi đã thử giải pháp mới nhất của bạn. Tuy nhiên, tạm thời là gì? Tôi đã thử đặt tạm thời = imageView.image; tuy nhiên, khi tôi phóng to, hình ảnh sẽ biến mất. Cảm ơn, Pannag
psanketi

Pannag, tạm thời được đặt tên kém. Nó nên được gọi là myImage vì nó chỉ là bất kỳ hình ảnh nào bạn đang sử dụng. Xin lỗi vì sự nhầm lẫn.
Giô-na

0

Được rồi, tôi nghĩ rằng tôi đã tìm thấy một giải pháp khá tốt cho vấn đề này. Bí quyết là liên tục điều chỉnh imageView'skhung. Tôi thấy điều này hoạt động tốt hơn nhiều so với việc liên tục điều chỉnh contentInsetshoặc contentOffSets. Tôi đã phải thêm một chút mã bổ sung để chứa cả hình ảnh dọc và ngang.

Đây là mã:

- (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

Chỉ cần vô hiệu hóa phân trang, vì vậy nó sẽ hoạt động tốt:

scrollview.pagingEnabled = NO;

0

Tôi cũng có chính xác vấn đề đấy. Đây là cách tôi giải quyết

Mã này sẽ được gọi là kết quả của 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

Mặc dù câu hỏi hơi cũ nhưng vấn đề vẫn tồn tại. Tôi đã giải quyết nó trong Xcode 7 bằng cách thực hiện ràng buộc không gian theo chiều dọc của mục trên cùng (trong trường hợp này là topLabel) đối với superViews ( scrollViewđỉnh) IBOutletvà sau đó tính toán lại hằng số của nó mỗi khi nội dung thay đổi tùy thuộc vào chiều cao của các lần xem trước của 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()
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.