UIScrollView 缩小具有 -ve 原点的视图

2024-04-25

我有一个 UIScrollView。在此我有一个 UIView,它的框架具有负原点 - 我需要限制滚动视图,以便您无法滚动整个视图。

我已经在这个滚动视图中实现了缩放。

缩放时,滚动视图将根据比例调整可缩放视图的大小。但它并不能调整原点。

所以如果我有一个带有框架的视图{0, -500}, {1000, 1000}

我缩小到 0.5 的比例,这会给我一个新的框架{0, -500}, {500, 500}

显然这不好,整个视图都从滚动视图中缩小了。我想要的框架是{0, -250}, {500, 500}

我可以通过正确调整原点来修复scrollViewDidZoom方法中的一些问题。这确实有效,但缩放不平滑。此处更改原点会导致它跳跃。

我注意到 UIView 的文档中说(关于框架属性):

警告:如果变换属性不是恒等变换,则 该属性的值未定义,因此应被忽略。

不太确定这是为什么。

我处理这个问题的方法错了吗?修复它的最佳方法是什么?

Thanks


以下是我正在使用的测试应用程序的一些源代码:

在视图控制器中..

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.bigView = [[BigView alloc] initWithFrame: CGRectMake(0, -400, 1000, 1000)];

    [self.bigScroll addSubview: bigView];
    self.bigScroll.delegate = self;
    self.bigScroll.minimumZoomScale = 0.2;
    self.bigScroll.maximumZoomScale = 5;
    self.bigScroll.contentSize = bigView.bounds.size;
}

-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView  {
    return bigView;
}

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {    
//    bigView.frame = CGRectMake(0, -400 * scrollView.zoomScale,
//                               bigView.frame.size.width, bigView.frame.size.height);

    bigView.center = CGPointMake(500 * scrollView.zoomScale, 100 * scrollView.zoomScale);
}

然后在视图中...

- (void)drawRect:(CGRect)rect
{
    // Drawing code
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
    CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
    CGContextFillRect(ctx, CGRectMake(100, 500, 10, 10));

    for (int i = 0; i < 1000; i += 100) {
        CGContextStrokeRect(ctx, CGRectMake(0, i, 1000, 3));        
    }
}

请注意,此处的跳跃在较大缩放比例下更加明显。在我的真实应用程序中,有更多的绘图和处理正在进行,跳跃始终更加明显。


鉴于苹果公司非常坚决的警告,您不必使用frame 属性,也不应该使用。在这种情况下你通常可以使用bounds and center达到你的结果。

在您的情况下,您可以忽略子视图的所有属性。假设你的子视图是viewForZoomingInScrollView你可以使用scrollView的contentOffset and zoomScale特性

- (void) setMinOffsets:(UIScrollView*)scrollView
    {
        CGFloat minOffsetX = MIN_OFFSET_X*scrollView.zoomScale;
        CGFloat minOffsetY = MIN_OFFSET_Y*scrollView.zoomScale;

        if ( scrollView.contentOffset.x < minOffsetX
          || scrollView.contentOffset.y < minOffsetY ) {

            CGFloat offsetX = (scrollView.contentOffset.x > minOffsetX)?
                               scrollView.contentOffset.x : minOffsetX;

            CGFloat offsetY = (scrollView.contentOffset.y > minOffsetY)?
                               scrollView.contentOffset.y : minOffsetY;

            scrollView.contentOffset = CGPointMake(offsetX, offsetY);
        }
    }

从两者调用它scrollViewDidScroll and scrollViewDidZoom在你的scrollView委托中。这应该可以顺利工作,但如果您有疑问,您也可以通过子类化滚动视图并调用它来实现它layoutSubviews。在他们的 PhotoScroller 示例中,Apple 通过覆盖来居中滚动视图的内容layoutSubviews- 尽管令人抓狂的是他们忽略了自己的警告并调整子视图的框架属性来实现这一点。

update

上述方法消除了滚动视图达到极限时的“弹跳”。如果你想保留反弹,你可以直接改变视图的center属性:

- (void) setViewCenter:(UIScrollView*)scrollView
    {
        UIView* view = [scrollView subviews][0];
        CGFloat centerX = view.bounds.size.width/2-MIN_OFFSET_X;
        CGFloat centerY = view.bounds.size.height/2-MIN_OFFSET_Y;

        centerX *=scrollView.zoomScale;
        centerY *=scrollView.zoomScale;

        view.center = CGPointMake(centerX, centerY);
    }

update 2

从您更新的问题(带有代码)中,我可以看到这些解决方案都不能解决您的问题。似乎发生的情况是,偏移量越大,变焦运动就会变得越不稳定。偏移量为 100 点时,动作仍然相当流畅,但偏移量为 500 点时,动作就粗糙得令人无法接受。这部分与您的drawRect例程,部分与滚动视图中进行的(太多)重新计算以显示正确的内容有关。所以我有另一个解决方案......

在你的 viewController 中,将你的 customView 的边界/框架原点设置为正常的 (0,0)。我们将使用图层来偏移内容。您需要将 QuartzCore 框架添加到您的项目中,并将其 #import 到您的自定义视图中。

在自定义视图中初始化两个 CAShapeLayers - 一个用于框,另一个用于线条。如果它们共享相同的填充和描边,您只需要一个 CAShapeLayer(在本示例中,我更改了您的填充和描边颜色)。每个 CAShapeLayer 都有它自己的 CGContext,您可以使用颜色、线宽等对每个图层进行一次初始化。然后要使 CAShapelayer 进行绘制,您所要做的就是设置它的path具有 CGPath 的属性。

#import "CustomView.h"
#import <QuartzCore/QuartzCore.h>

@interface CustomView()
@property (nonatomic, strong) CAShapeLayer* shapeLayer1;
@property (nonatomic, strong) CAShapeLayer* shapeLayer2;
@end

@implementation CustomView

    #define MIN_OFFSET_X 100
    #define MIN_OFFSET_Y 500

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initialiseLayers];
    }
    return self;
}


- (void) initialiseLayers
{
    CGRect layerBounds  = CGRectMake( MIN_OFFSET_X,MIN_OFFSET_Y
                          , self.bounds.size.width + MIN_OFFSET_X
                          , self.bounds.size.height+ MIN_OFFSET_Y);

    self.shapeLayer1 = [[CAShapeLayer alloc] init];
    [self.shapeLayer1 setFillColor:[UIColor clearColor].CGColor];
    [self.shapeLayer1 setStrokeColor:[UIColor yellowColor].CGColor];
    [self.shapeLayer1 setLineWidth:1.0f];
    [self.shapeLayer1 setOpacity:1.0f];

    self.shapeLayer1.anchorPoint = CGPointMake(0, 0);
    self.shapeLayer1.bounds = layerBounds;
    [self.layer addSublayer:self.shapeLayer1];

设置界限是关键。与剪切子视图的视图不同,CALayers 将绘制超出其超级图层边界的内容。你要开始画画了MIN_OFFSET_Y位于视图顶部上方的点和MIN_OFFSET_X向左转。这允许您在滚动视图内容视图之外绘制内容,而滚动视图无需执行任何额外的工作。

与视图不同,超级图层不会自动裁剪位于其边界矩形之外的子图层的内容。相反,超级图层默认允许其子图层完整显示.
(Apple 文档,构建层层次结构 http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreAnimation_guide/BuildingaLayerHierarchy/BuildingaLayerHierarchy.html)

    self.shapeLayer2 = [[CAShapeLayer alloc] init];

    [self.shapeLayer2 setFillColor:[UIColor blueColor].CGColor];
    [self.shapeLayer2 setStrokeColor:[UIColor clearColor].CGColor];
    [self.shapeLayer2 setLineWidth:0.0f];
    [self.shapeLayer2 setOpacity:1.0f];

    self.shapeLayer2.anchorPoint = CGPointMake(0, 0);
    self.shapeLayer2.bounds = layerBounds;
    [self.layer addSublayer:self.shapeLayer2];

    [self drawIntoLayer1];
    [self drawIntoLayer2];
}

为每个形状图层设置贝塞尔曲线路径,然后将其传入:

- (void) drawIntoLayer1 {

    UIBezierPath* path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(0,0)];

    for (int i = 0; i < self.bounds.size.height+MIN_OFFSET_Y; i += 100) {
        [path moveToPoint:
                CGPointMake(0,i)];
        [path addLineToPoint:
                CGPointMake(self.bounds.size.width+MIN_OFFSET_X, i)];
        [path addLineToPoint:
                CGPointMake(self.bounds.size.width+MIN_OFFSET_X, i+3)];
        [path addLineToPoint:
                CGPointMake(0, i+3)];
        [path closePath];
    }

    [self.shapeLayer1 setPath:path.CGPath];
}

- (void) drawIntoLayer2 {
    UIBezierPath* path = [UIBezierPath bezierPathWithRect:
            CGRectMake(100+MIN_OFFSET_X, MIN_OFFSET_Y, 10, 10)];
    [self.shapeLayer2 setPath:path.CGPath];
}

这消除了需要drawRect- 如果更改路径属性,您只需要重新绘制图层。即使您确实像调用drawRect那样频繁地更改路径属性,绘图现在也应该显着提高效率。并作为path是一个可动画的属性,如果您需要的话,您还可以免费获得动画。

在你的情况下,我们只需要设置一次路径,所以all的工作在初始化时完成一次。

现在,您可以从scrollView委托方法中删除任何居中代码,不再需要它了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UIScrollView 缩小具有 -ve 原点的视图 的相关文章

  • 如何使用 Objective-C 在 Mac Os X 中模拟 Unicode Char“按键”?

    我想在 Mac OS X 中模拟 unicode 字符发送到前台应用程序 我的意思是我有一个像 a 这样的unicode char 可以包含阿拉伯语 中文等 我想输入它 请注意 我并不是尝试使用虚拟按键或按键代码 只有一个角色 您忠诚的 佩
  • iPhone 拒绝了发布请求 未说明

    iPhone 拒绝了发布请求 内部启动错误 进程启动失败 未指定 这个错误让我抓狂 我似乎无法解决它 我从发现的所有地方都做了以下操作 刷新证书 注销并进入开发者苹果帐户 下载手动证书 删除 Apple 全球证书 重新启动 Mac 和 iP
  • 如何在iOS SDK中使用语音识别? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我知道 SIRI 服务没有公共 API 但是有没有简单的语音识别 API 因此 如果我有一个文本字段并且
  • 如何将LUT png用于CIColorCube滤镜?

    我想使用查找表 png example http nghiatran me wp content uploads 2014 06 FilterMe Part2 ProcessedLUT png 作为颜色立方体数据CIColorCubeSwi
  • 使用 PHP / Javascript 检测应用内浏览器 (WebView)

    我开发了一个适用于 iOS 和 Android 的应用程序 它使用应用程序内浏览器 Webview 从我的网络服务器访问 HTML 文件 我不希望用户可以在不使用该应用程序的情况下访问此文件 是否有可能检测用户是否使用应用程序或直接通过此智
  • 当我的 iPAD 上安装了应用程序的“.ipa”文件时,如何获取 IOS 应用程序的捆绑包 ID

    我正在尝试在安装了我的应用程序的 iPad 上进行自动化测试 我正在使用 Appium 来自动化应用程序 它要求我输入需要测试的应用程序的捆绑 ID 有人可以帮我找到一种方法 从我的 IPAD 上安装的应用程序或从我的 iMAC 上下载的
  • CocoaPods 库中的强dispatch_queue_t

    在可能使用 iOS 5 x OS X 10 7 部署目标或较新的部署目标构建的库中 我在正确定义dispatch queue t财产 大多数情况下我可以按照建议解决它here https stackoverflow com a 248460
  • SwiftUI • 如何实现半屏ShareSheet?

    我在我的项目中实现了一个 ShareSheetSwiftUI App using UIViewControllerRepresentable Code struct ShareView UIViewControllerRepresentab
  • 找不到映射模型(Cocoa 错误 134140)

    基本上 我有一个版本xcdatamodel我正在从 V2 迁移到 V3 我创建了一个Mapping Model具有自定义策略 一旦PersistenStoreCoordinator试图完成它的工作 我打了一个Cocoa错误号134140 找
  • 如何按日期正确对从 CoreData 获取的列表进行分组?

    为了简单起见 假设我想创建一个简单的待办事项应用程序 我的 xcdatamodeld 中有一个实体 Todo 其属性id title and date 以及以下 swiftui 视图 如图所示的示例 import SwiftUI struc
  • clickedButtonAtIndex 方法未被调用

    当用户点击按钮时UIAlertView the clickedButtonAtIndex应该调用方法 但是 它没有 in the h我已经打电话给UIAlertView协议 interface RechercherViewControlle
  • 以编程方式添加 TabBarController

    我想以编程方式制作标签栏控制器和导航控制器 到目前为止 我的代码有效 它在底部显示了一个选项卡栏 但 OptionViewController 在第二个选项卡栏的按钮上没有说任何内容 没有标题 有趣的是 当我单击没有任何内容的按钮时 标题出
  • Alamofire 使用公共键和多个值传递参数?

    我需要在我的项目中执行此操作 如果我手动将字符串附加到 Alamofire 中的 URL 我可以轻松完成此操作 但我不希望这样做 我想要的参数为范围 object 参数的一个公共键中有多个值 我一直在做什么 public func find
  • 如何在 SwiftUI 中管理 AVPlayer 状态

    我有 SwiftUI 中的 URL 列表 当我点击某个项目时 我会呈现一个全屏视频播放器 我有一个 EnvironmentObject它处理一些查看器选项 例如 是否显示时间码 我还有一个显示和隐藏时间码的切换开关 我只在本例中包含了该切换
  • 使用多个 DispatchQueue.main.async 查看冻结

    视图冻结而数据是获取并显示 以我的理解fetchBoard and initUserInfo 不要并行执行 因为视图仅在以下情况下加载fetchBoard 加载板 我担心如果使用DispatchQueue main async多次冻结视图
  • UIImagePickerController 和 iCloud 照片

    切换到 iCloud Photo 后 似乎 UIImagePickerController 返回的一些图像非常模糊 看起来该图像是从 iCloud 照片中获取的 我是否能够检索原始图像 或过滤掉 iCloud 照片图像 或者我是否必须切换到
  • 在 Swift 中将函数作为参数传递

    在 iOS 8 中 我的以下功能按我的预期工作 func showConfirmBox msg String title String firstBtnStr String secondBtnStr String caller UIView
  • 如何为长时间运行的函数设置超时?

    我有一个带有一些代码的函数 可能需要很长时间才能完成 如果该功能花费的时间超过特定时间 我希望该功能停止继续 我怎样才能实现这个 我尝试了下面的解决方案 但它不会停止执行 override func viewDidLoad super vi
  • 长按 UIButton

    我想知道如果有人按住 UIButton 按键的时间过长 我是否可以捕获 UIButton 的事件 通过通知或其他机制 比按一次按钮的时间更长 假设有人按住按钮几秒钟 谢谢 你可以加UILongPressGestureRecognizer h
  • ios - NSObjCRuntime、NSZone 和 NSObject 中的解析问题

    我在用着AddThis http support addthis com customer portal articles 381270 addthis for ios quick start guide UCHnE8iXSs0在我的 iO

随机推荐