视图裁剪

前言

HttpServerDebug 实现了类似 Xcode Debug View Hierarchy 功能。客户端提供信息,前端绘制并提供交互能力,实现视图调试功能。

视图调试其中一项功能是 Show Clipped Content,虽然不知道 Xcode 的实现方式,但是通过计算我们也可以拿到同样的信息。本文说明 HttpServerDebug 中的实现方案。

效果图

HttpServerDebug 效果截图

Xcode 效果截图

上面一组截图是 HttpServerDebug 运行效果,下面一组截图是 Xcode 中的原生效果。

代码实现

下面代码的目的是计算目标视图的位置和尺寸。

需要注意的是,代码中获取的 CGRect 信息来自于视图的 bounds 属性而不是 frame。bounds 可以理解为目标视图的内容在自己的坐标系统中的位置和尺寸,frame 是目标视图在父视图坐标系统中的位置和尺寸。我们使用了一系列转换函数实现不同坐标系统中的位置和尺寸转换,所以不需要直接获取 frame 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// view:UIView,目标视图
// window:UIWindow,view 属于该 window 视图层级
CGRect tryClippedRect = view.bounds;
UIView *tryView = view;
while (tryView.superview) {
UIView *superview = tryView.superview;

// 目标视图的位置和尺寸转换到父类的坐标系统中
tryClippedRect = [tryView convertRect:tryClippedRect toView:superview];

if (!CGSizeEqualToSize(tryClippedRect.size, CGSizeMake(0, 0)) &&
superview.clipsToBounds) {
// 需要裁剪
CGRect baseRect = superview.bounds;
tryClippedRect = CGRectIntersection(tryClippedRect, baseRect);
tryClippedRect = CGRectIsNull(tryClippedRect) ? CGRectZero : tryClippedRect;
}
tryView = superview;
}
// 在 window 坐标系统中的位置和尺寸
CGRect clippedFrameRoot = tryClippedRect;

上面代码只是计算出了位置和尺寸,调试界面显示还需要对目标视图进行截图,如下面代码所示。

(默认截图会包含目标视图的子视图,否则需要在截图前先移除或隐藏所有的子视图。)

1
2
3
4
5
6
7
8
9
10
11
12
// 目标视图坐标系统中的裁剪位置
CGPoint clippedOrigin = [view convertPoint:clippedFrameRoot.origin fromView:window];

// 截图
UIGraphicsBeginImageContextWithOptions(clippedFrameRoot.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat tx = -clippedOrigin.x;
CGFloat ty = -clippedOrigin.y;
CGContextTranslateCTM(context, tx, ty);
[view.layer renderInContext:context];
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();