ios - مركز محتوى UIScrollView عندما أصغر




objective-c cocoa-touch (16)

أصدرت شركة Apple مقاطع فيديو حول جلسات WWDC لعام 2010 لجميع أعضاء برنامج مطوري iPhone. أحد المواضيع التي تمت مناقشتها هو كيفية إنشاء تطبيق الصور !!! إنهم ينشئون خطوة مشابهة جدًا للتطبيق خطوة بخطوة وقد جعلوا جميع الرموز متاحة مجانًا.

لا يستخدم 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

لدي UIImageView داخل 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)];
}

إحدى الطرق الأنيقة لتوسيط محتوى UISCrollView هو هذا.

إضافة مراقب واحد إلى المحتوىالحجم الخاص بك 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 .


حسنا ، هذا الحل يعمل بالنسبة لي. لدي فئة فرعية من UIScrollView مع الإشارة إلى UIImageView يتم عرضه. كلما كان التكبير UIScrollView ، يتم ضبط خاصية contentSize. هو في UIImageView أنا مقياس 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);
}

Evertime I set the image on the UIScrollView I resize it. يعتبر 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;
}

باستخدام هاتين الطريقتين ، يمكنني الحصول على عرض يتصرف تمامًا مثل تطبيق الصور.


حسنًا ، لقد قاتلت هذا طوال اليومين الماضيين وإيقافهم وبعد أن توصلنا في النهاية إلى حل موثوق به جدًا (حتى الآن ...) اعتقدت أنني يجب أن أشاركه وأنقذ الآخرين بعض الألم. :) إذا كنت لا تجد مشكلة مع هذا الحل يرجى الصراخ!

لقد مررت بشكل أساسي بما لدى الجميع: البحث في ، منتديات Apple Developer ، نظر في الشفرة الخاصة بـ three20 ، ScrollingMadness ، ScrollTestSuite ، إلخ. لقد حاولت توسيع إطار UIImageView ، اللعب باستخدام إزاحة UIScrollView و / أو إدخالات من ViewController ، إلخ. ولكن لا شيء يعمل بشكل رائع (كما اكتشف الجميع أيضًا).

بعد النوم عليه ، جربت بضعة زوايا بديلة:

  1. وضع فئة فرعية في UIImageView بحيث يغير حجمها بشكل ديناميكي - وهذا لم يعمل بشكل جيد على الإطلاق.
  2. وضع فئة فرعية في UIScrollView بحيث يغير المحتوى الخاص به بشكل تلقائي ديناميكي - هذا هو الذي يبدو أنه الفائز بالنسبة لي.

باستخدام طريقة UIScrollView الفرعية هذه ، أقوم بتجاوز mutator contentOffset بحيث لا يتم تعيين {0،0} عندما يكون حجم الصورة أصغر من إطار العرض - بدلاً من ذلك يتم تعيين الإزاحة بحيث تظل الصورة متمركزة في إطار العرض. حتى الآن ، يبدو دائما أن العمل. لقد تحققت من الصور الواسعة والكبيرة والصغيرة الحجم ولا تحتوي على "الأعمال ولكن قرصة الحد الأدنى لكسر الصورة".

لقد قمت بتحميل مثال لمشروع على جيثب يستخدم هذا الحل ، يمكنك العثور عليه هنا: http://github.com/nyoron/NYOBetterZoom


في الوقت الحالي ، أقوم setContentOffset: فئة فرعية من setContentOffset: وتجاوز 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 .


في حالة عرض imageView الداخلي الخاص بك (على سبيل المثال 300) وكنت ترغب فقط في توسيط عرضه فقط على تكبير أصغر من عرضه الأولي ، فقد يساعدك هذا أيضًا.

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

لجعل تدفق الرسوم المتحركة جيد ، قم بتعيين

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

هذا يخلق تأثير كذاب ولكن لا ينطوي على أي تحركات مفاجئة مسبقا.


لدي حل بسيط جدا! كل ما تحتاجه هو تحديث مركز العرض الفرعي (imageview) أثناء التكبير في ScrollViewDelegate. إذا كانت الصورة التي تم تكبيرها أصغر من scrollview ، فاضبط centview.center else 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);
}

للحصول على حل يتناسب بشكل أفضل مع طرق العرض التمرير التي تستخدم autolayout ، استخدم عناصر عرض المحتوى الخاصة بالعرض التمرير بدلاً من تحديث الإطارات للعرضين الموجودين في طريقة العرض التمرير.

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

مثال Scroll Photo لـ 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; 
}

الصورة الرمزية آبل سكرولر نموذج


ها هي الطريقة الحالية لأقوم بهذا العمل. من الأفضل لكن لا يزال غير مثالي. حاول الإعداد:

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

Also, I do the following to prevent the image from sticking a the side after zooming out.

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

هذا هو الحل لهذه المشكلة التي تعمل بشكل جيد لأي نوع من العرض داخل scrollview.

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

يجب أن يعمل هذا الرمز على معظم إصدارات iOS (وقد تم اختباره للعمل على 3.1 إلى أعلى).

يعتمد على كود Apple WWDC الخاص بـ photoscoller.

أضف أدناه إلى الفئة الفرعية الخاصة بك من 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;
}

يمكنك مشاهدة contentSize خاصية UIScrollView (باستخدام مراقبة قيمة المفتاح أو ما شابه) ، وضبط contentInset تلقائيًا عند تغيير حجم contentSize ليكون أقل من حجم عرض التمرير.


Although the question is a bit old yet the problem still exists. I solved it in Xcode 7 by making the vertical space constraint of the uppermost item (in this case the topLabel ) to the superViews (the scrollView ) top an IBOutlet and then recalculating its constant every time the content changes depending on the height of the scrollView's subviews ( topLabel and bottomLabel ).

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

Just disable the pagination, so it'll work fine:

scrollview.pagingEnabled = NO;






uiimageview