【问题标题】:Scrolling gets "stuck" when using nested scroll views使用嵌套滚动视图时滚动会“卡住”
【发布时间】:2017-10-20 21:20:49
【问题描述】:

问题描述:

我有一个 iOS 项目,用于浏览带有嵌套 UIScrollViews 的图像,其灵感来自著名的 Apple 的 PhotoScroller。 问题是当图像按宽度或高度缩放时,有时滚动只是“卡住”。这是一个示例,说明它在 iPhone 4s 上的外观示例,大小为 935x1400 的图像高度缩放:

(我开始向左拖动,但滚动视图立即放弃此操作并且图像“卡住”)

解决方法:

我通过在缩放后将内部滚动视图的内容大小调整为最接近的整数找到了一种解决方法:

// Inside ImageScrollView.m

- (void)setZoomScale:(CGFloat)zoomScale
{
    [super setZoomScale:zoomScale];
    [self fixContentSizeForScrollingIfNecessary];
}

- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
    [super zoomToRect:rect animated:animated];
    [self fixContentSizeForScrollingIfNecessary];
}

- (void)fixContentSizeForScrollingIfNecessary
{
    if (SYSTEM_VERSION_LESS_THAN(@"10.2"))
    {
        CGSize content = self.contentSize;
        content.width = rint(content.width);
        content.height = rint(content.height);
        self.contentSize = content;
    }
}

但此修复并不完美 - 现在某些图像的侧面显示为 1 像素宽的条纹。例如,在iPhone 6 上,对于大小为690x14300 的图像,它会在底部显示:

另外,奇怪的是,我能够在 iOS 7.0 - 10.1 上重现此问题,但在 iOS 10.2 及更高版本上一切正常。

问题:

那么,我做错了什么?我的修复可以改进吗?

测试项目:

我创建了简单的测试项目来说明所描述的问题 - NestedScrollingProblems。请注意我的 ImageScrollView 版本与 Apple 的版本略有不同,因为我应用了另一个缩放规则。此外,默认情况下,解决方法会被注释掉。 (项目代码有点乱,抱歉)

【问题讨论】:

  • 在您的演示项目中,普通图像工作正常。但大图像只挂第一次。卡在哪里了?
  • 在图像(大的或正常的)之间滚动时会出现卡住,请参阅附件 gif。而且挂在这里是正常的,因为图像是“大”的(对于我的项目我正在使用耕作,但我省略了这部分测试项目无关)

标签: ios objective-c scroll uiscrollview


【解决方案1】:

无法评论帖子(还没有足够的代表)。

但是从它的外观(Apple 的文档)来看,这个项目 deinits 滚动上的图像,然后是 re-inits 当它们将被加载时(参见 UIScrollView.m 中的第 350 行)。而且我还注意到ImageScrollView.m(第 346 行)中的一条注释明确表示该类旨在避免缓存。对于演示来说,这是一种实用的方法,但不适用于生产环境或实际应用程序,这些应用程序考虑到了您想要的 ui 加载速度。

我还注意到,您的应用必须进一步滚动才能进行分页。这可能是代码中的一些错误,或者可能是滞后本身导致主线程无法流畅地运行分页。或者,如果您打算设置如此宽的分页阈值。我建议降低它以获得更好的用户体验,因为现代智能手机的屏幕比 iPhone 4S 的屏幕宽得多。

为了解决这个问题,

我在 SO 上发现了这个 post(如下),它似乎有一个相当不错的 obj-c 方法来缓存,并在应用程序启动后从这样的缓存中获取图像数据。您应该也可以很简单地将其用于发布后方法,甚至可以将其与网络一起使用以从网络下载图像。您只需要确保您的 UIImage 视图正确链接到您使用的 url 字符串,或者通过每个图像视图的一组自定义字符串变量,或者通过将 UImageView 子类化到自定义类中,并将缓存方法添加到它使您的代码看起来更简单。这是来自iOSfleer的帖子中的方法和 NSCahe 类

NSCache 类:

@interface Sample : NSObject

+ (Sample*)sharedInstance;

// set
- (void)cacheImage:(UIImage*)image forKey:(NSString*)key;
// get
- (UIImage*)getCachedImageForKey:(NSString*)key;

@end

#import "Sample.h"

static Sample *sharedInstance;

@interface Sample ()
@property (nonatomic, strong) NSCache *imageCache;
@end

@implementation Sample

+ (Sample*)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[Sample alloc] init];
    });
    return sharedInstance;
}
- (instancetype)init {
    self = [super init];
    if (self) {
        self.imageCache = [[NSCache alloc] init];
    }
    return self;
}

- (void)cacheImage:(UIImage*)image forKey:(NSString*)key {
    [self.imageCache setObject:image forKey:key];
}

- (UIImage*)getCachedImageForKey:(NSString*)key {
    return [self.imageCache objectForKey:key];
}

为了不改变你所做的太多,似乎通过将 ImageScrollview.m 中的 displayImageWithInfo 方法更改为以下一个(使用缓存方法),它似乎在之后工作得更好初始负载。如果我是你,我也会更进一步,并在控制器的 viewDidLoad 方法中实现一个循环样式的方法来立即缓存这些图像,以便在启动时更快地加载。但这取决于你。

- (void)displayImageWithInfo:(ImageItem*)imageInfo
{
    CGSize imageSize = (CGSize){.width = imageInfo.width, .height = imageInfo.height};

    // clear the previous imageView
    [self.imageView removeFromSuperview];
    self.imageView = nil;

    // reset our zoomScale to 1.0 before doing any further calculations
    self.zoomScale = 1.0;

    self.imageView = [[UIImageView alloc] initWithFrame:(CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size = imageSize}];

    UIImage *image = [[Sample sharedInstance] getCachedImageForKey:imageInfo.path];
    if(image)
    {
        NSLog(@"This is cached");
        ((UIImageView*)self.imageView).image = image;
    }
    else{

        NSURL *imageURL = [NSURL URLWithString:imageInfo.path];
        UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:imageURL]];

        if(image)
        {
            NSLog(@"Caching ....");
            [[Sample sharedInstance] cacheImage:image forKey:imageInfo.path];
            ((UIImageView*)self.imageView).image = image;
        }

    }


    [self addSubview:self.imageView];

    [self configureForImageSize:imageSize];
}

我还建议在不从滚动的超级视图中删除视图的情况下解决此问题。添加视图是一项非常繁重的任务。再加上图像加载,对于像智能手机这样的小型 CPU 来说可能会非常沉重(因为它们还没有 GPU ......)。为了强调这一点,Apple 甚至提到它不会在 UIImages 显示后重新渲染它们,措辞很微妙here,但它显然没有提到优化的删除然后重新添加和渲染视图后显示一次(例如在这种情况下)。我认为这里的预期用途是显示ImageView,然后在显示控制器后简单地将其更改为image元素。

虽然图像对象支持所有平台原生图像格式,但它 建议您对您的大多数图像使用 PNG 或 JPEG 文件 应用程序。图像对象针对阅读和显示进行了优化 格式,并且这些格式提供比大多数其他格式更好的性能 图像格式。

这就是为什么视图通常在任何可见加载方法(如viewWillAppearviewDidAppear)之前在其超级视图上添加/初始化的原因,或者如果在初始加载后完成,它们很少被取消初始化,它们的内容通常是唯一改变的东西,即便如此,它通常是异步完成的(如果从网络下载),或者它是从缓存中完成的,也可以使用一些初始化程序自动完成(你可以将它添加到我推荐的内容中):

使用 imageNamed:inBundle:compatibleWithTraitCollection: 方法(或 imageNamed: 方法)从图像资产创建图像或 位于应用程序主包中的图像文件(或其他一些已知的 捆)。因为这些方法会自动缓存图像数据, 特别推荐用于您经常使用的图片。

就个人而言,我会尝试采用UICollectionViews 的方法。值得注意的是,当视图滚动出窗口时,它们具有自动处理内容缓存的委托(这正是这个演示的内容)。您也可以向这些方法添加自定义代码,以更好地控制这些视图的滚动效果。一开始它们可能有点难以理解,但我可以证明你在这里尝试完成的工作可以用这个演示使用的一小部分代码来复制。我还认为这个演示是在 2012 年构建的这一事实作为提示.. 这是一个非常古老的演示,并且 UICollectionViews 出现在这个演示最后一次更新的时候。所以我想说这就是苹果一直以来的目标,因为所有面向内容的 UIView 子类都从 UIScrollView 继承了某种类型 (UICollectionView、UITableView、UITextView 等)。值得一看! UICollectionViews.

【讨论】:

  • 感谢您的反馈,但不幸的是,您完全错过了我的问题的全部要点。我对缓存没有任何问题,并且需要重新创建图像以在内存中同时保留 3 个图像以进行优化。所以,正如我已经在问题中描述的那样,我的问题是关于图像之间滚动的特定“卡住”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多