You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift5+UIKit实现2-3个UIView间连线绘制、动画及分支功能咨询

iOS 容器视图父子控件连线动画实现方案(基于UIBezierPath+CAShapeLayer)

核心实现逻辑

  • 所有坐标计算统一基于容器视图的坐标系,避免坐标转换错误
  • 连线图层固定添加到容器视图的layer最底层,避免遮挡子视图
  • 每次子视图布局完成后重新计算路径,天然适配不同屏幕尺寸

步骤1:获取关键坐标点

在容器视图的layoutSubviews(自定义View场景)或ViewController的viewDidLayoutSubviews(AutoLayout场景)中获取坐标,所有坐标均为容器自身坐标系下的相对值:

// 筛选视图,默认第一个子视图为顶部视图,剩余为底部视图,可根据实际业务调整筛选规则
guard let topView = subviews.first else { return }
let bottomViews = Array(subviews.dropFirst())

// 顶部起点:顶部视图底部中点
let startPoint = CGPoint(x: topView.frame.midX, y: topView.frame.maxY)
// 分叉点Y坐标:取顶部视图底部和底部视图顶部的中间位置,自动适配不同屏幕高度
let forkY = startPoint.y + ((bottomViews.first?.frame.minY ?? 0) - startPoint.y)/2
// 筛选视图,默认第一个子视图为顶部视图,剩余为底部视图,可根据实际业务调整筛选规则
UIView *topView = self.subviews.firstObject;
if (!topView) return;
NSArray *bottomViews = [self.subviews subarrayWithRange:NSMakeRange(1, self.subviews.count-1)];

// 顶部起点:顶部视图底部中点
CGPoint startPoint = CGPointMake(CGRectGetMidX(topView.frame), CGRectGetMaxY(topView.frame));
// 分叉点Y坐标:取顶部视图底部和底部视图顶部的中间位置,自动适配不同屏幕高度
CGFloat forkY = startPoint.y + (CGRectGetMinY([bottomViews.firstObject frame]) - startPoint.y)/2;

步骤2:根据底部视图数量生成路径

分两种场景处理:

场景1:总共有2个视图(1个顶部+1个底部)

let path = UIBezierPath()
path.move(to: startPoint)
let endPoint = CGPoint(x: bottomViews[0].frame.midX, y: bottomViews[0].frame.minY)
path.addLine(to: endPoint)
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
CGPoint endPoint = CGPointMake(CGRectGetMidX(bottomViews[0].frame), CGRectGetMinY(bottomViews[0].frame));
[path addLineToPoint:endPoint];

场景2:总共有3个视图(1个顶部+2个底部)

let path = UIBezierPath()
path.move(to: startPoint)
// 先画到中间分叉点
let forkPoint = CGPoint(x: startPoint.x, y: forkY)
path.addLine(to: forkPoint)

// 分叉连接第一个底部视图
let endPoint1 = CGPoint(x: bottomViews[0].frame.midX, y: bottomViews[0].frame.minY)
path.addLine(to: endPoint1)

// 回到分叉点连接第二个底部视图
path.move(to: forkPoint)
let endPoint2 = CGPoint(x: bottomViews[1].frame.midX, y: bottomViews[1].frame.minY)
path.addLine(to: endPoint2)
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
// 先画到中间分叉点
CGPoint forkPoint = CGPointMake(startPoint.x, forkY);
[path addLineToPoint:forkPoint];

// 分叉连接第一个底部视图
CGPoint endPoint1 = CGPointMake(CGRectGetMidX(bottomViews[0].frame), CGRectGetMinY(bottomViews[0].frame));
[path addLineToPoint:endPoint1];

// 回到分叉点连接第二个底部视图
[path moveToPoint:forkPoint];
CGPoint endPoint2 = CGPointMake(CGRectGetMidX(bottomViews[1].frame), CGRectGetMinY(bottomViews[1].frame));
[path addLineToPoint:endPoint2];

步骤3:配置CAShapeLayer

提前声明全局CAShapeLayer属性避免重复创建:

lazy var connectLineLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.strokeColor = UIColor.systemBlue.cgColor // 自定义连线颜色
    layer.lineWidth = 2 // 自定义线宽
    layer.fillColor = UIColor.clear.cgColor
    layer.lineCap = .round // 线头顶点圆角
    return layer
}()

// 初始化时添加到容器layer最底层
layer.insertSublayer(connectLineLayer, at: 0)

// 每次路径更新后赋值
connectLineLayer.path = path.cgPath
@property (nonatomic, strong) CAShapeLayer *connectLineLayer;

// 初始化时配置
if (!_connectLineLayer) {
    _connectLineLayer = [CAShapeLayer layer];
    _connectLineLayer.strokeColor = [UIColor systemBlueColor].CGColor; // 自定义连线颜色
    _connectLineLayer.lineWidth = 2; // 自定义线宽
    _connectLineLayer.fillColor = [UIColor clearColor].CGColor;
    _connectLineLayer.lineCap = kCALineCapRound; // 线头顶点圆角
    [self.layer insertSublayer:_connectLineLayer atIndex:0]; // 添加到容器layer最底层
}
// 每次路径更新后赋值
_connectLineLayer.path = path.CGPath;

步骤4:添加路径延伸动画

通过strokeEnd属性实现从无到有的延伸效果:

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 0.8 // 自定义动画时长
animation.fromValue = 0
animation.toValue = 1
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
connectLineLayer.add(animation, forKey: "lineStrokeAnimation")
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration = 0.8; // 自定义动画时长
animation.fromValue = @(0);
animation.toValue = @(1);
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[_connectLineLayer addAnimation:animation forKey:@"lineStrokeAnimation"];

适配注意事项

  • 所有坐标均基于子视图的相对位置计算,没有写死固定数值,4.7英寸到6.2英寸设备只要子视图布局正确,连线会自动适配
  • 子视图布局发生变化(比如屏幕旋转、动态调整子视图位置)时,需要重新调用路径生成代码更新connectLineLayer.path,保证连线对齐
  • 如果需要圆角折线效果,可将addLineToPoint替换为addQuadCurveToPoint实现平滑过渡

内容的提问来源于stack exchange,提问作者rony_y

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 10:45:05