Swift自定义气泡视图组件:跑步者进度展示实现方案咨询
跑步者进度气泡组件实现方案
问题概述
我需要开发一个以气泡形式展示多名跑步者进度的组件,尝试用Charts库实现但未达到预期效果。当前的Swift实现代码如下:
override func viewDidLoad() { super.viewDidLoad() self.configureChart(raceChartData: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], raceDistance: 10) } func configureChart(raceChartData: [Int], raceDistance: Double) { chartView.chartDescription.enabled = false chartView.dragEnabled = false chartView.pinchZoomEnabled = false chartView.xAxis.enabled = true //chartView.maxVisibleCount = 0 chartView.autoScaleMinMaxEnabled = false chartView.leftAxis.enabled = false chartView.rightAxis.enabled = false chartView.legend.enabled = false chartView.legend.drawInside = false chartView.setScaleEnabled(false) let xAxis: XAxis = chartView.xAxis xAxis.axisLineColor = #colorLiteral(red: 1, green: 0.4823529412, blue: 0.1450980392, alpha: 1).withAlphaComponent(0.1) xAxis.drawAxisLineEnabled = false xAxis.drawLabelsEnabled = false xAxis.gridColor = #colorLiteral(red: 1, green: 0.4823529412, blue: 0.1450980392, alpha: 1).withAlphaComponent(0.3) //xAxis.axisMinimum = 1 //xAxis.axisMaximum = 10 var calculatedChartDistance = 0.0 if raceChartData.count != 0 { var chartdistanceArray = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] if activeRaceFormat == "SPDWRK" { calculatedChartDistance = raceDistance } else { chartView.xAxis.centeredEntries = [0, 0.5, 0.4] chartView.xAxis.centerAxisLabelsEnabled = true calculatedChartDistance = raceDistance } for i in 0...9 { chartdistanceArray[i] = calculatedChartDistance * Double((i + 1)) } let yVals1 = [ BubbleChartDataEntry(x: chartdistanceArray[0], y: 0, size: CGFloat(raceChartData[0])), BubbleChartDataEntry(x: chartdistanceArray[1], y: 0, size: CGFloat(raceChartData[1])), BubbleChartDataEntry(x: chartdistanceArray[2], y: 0, size: CGFloat(raceChartData[2])), BubbleChartDataEntry(x: chartdistanceArray[3], y: 0, size: CGFloat(raceChartData[3])), BubbleChartDataEntry(x: chartdistanceArray[4], y: 0, size: CGFloat(raceChartData[4])), BubbleChartDataEntry(x: chartdistanceArray[5], y: 0, size: CGFloat(raceChartData[5])), BubbleChartDataEntry(x: chartdistanceArray[6], y: 0, size: CGFloat(raceChartData[6])), BubbleChartDataEntry(x: chartdistanceArray[7], y: 0, size: CGFloat(raceChartData[7])), BubbleChartDataEntry(x: chartdistanceArray[8], y: 0, size: CGFloat(raceChartData[8])), BubbleChartDataEntry(x: chartdistanceArray[9], y: 0, size: CGFloat(raceChartData[9])) ] let set1 = BubbleChartDataSet(entries: yVals1) set1.drawIconsEnabled = false set1.setColor(ChartColorTemplates.colorful()[1], alpha: 0.5) set1.drawValuesEnabled = true set1.normalizeSizeEnabled = false let data = [set1] as BubbleChartData data.setDrawValues(true) data.setValueFont(UIFont(name: "HelveticaNeue-Light", size: 7)!) data.setHighlightCircleWidth(0.5) chartView.data = data } }
当前效果

目标效果

需求说明:赛程为10KM,带头像的气泡及标线代表用户自身的跑步进度,其他气泡代表对应距离处的跑步者人数,所有数据来自后端Socket。考虑基于UIView从零开发该组件,以下是具体实现思路:
从零开发UIView组件的实现思路
1. 组件结构拆分
- 进度基线层:绘制横向赛程轴,包含等距刻度线和距离标签(如0KM、2KM...10KM)
- 普通跑步者气泡层:根据各距离点的人数,绘制对应大小的半透明气泡,气泡内显示人数数值
- 用户专属标记层:绘制垂直标线+带头像的气泡,突出用户当前进度位置
2. 核心布局计算
- 坐标映射:将赛程距离(0-10KM)转换为组件内的X坐标,比如组件总宽度为
viewWidth,则nKM处的X坐标为(n / 10) * viewWidth - 气泡大小适配:预设最小/最大气泡直径(如12pt-40pt),根据当前人数与最大人数的比例,计算对应气泡直径,确保视觉比例协调
- 用户位置定位:根据用户当前跑过的距离,计算其在进度轴上的X坐标,垂直标线从基线向上延伸至气泡中心位置
3. 自定义绘制(Core Graphics)
在自定义UIView的draw(_ rect: CGRect)方法中完成所有绘制逻辑:
- 绘制进度基线:用
UIBezierPath绘制横向直线,再添加等距刻度线和距离文本 - 绘制普通气泡:遍历跑步者数据,在对应X坐标处绘制圆形气泡,填充半透明颜色,再用
UILabel或直接绘制文本显示人数 - 绘制用户标记:先绘制垂直标线,再加载用户头像并裁剪为圆形,放在对应X坐标的上方
- 注意:提前缓存计算好的坐标、气泡大小等数据,避免在
draw方法中做重复计算
4. 实时数据更新(Socket驱动)
- 监听后端Socket推送的实时数据,包括各距离点人数、用户当前进度
- 数据更新时,更新组件内的数据源,调用
setNeedsDisplay()触发重绘;可选添加气泡大小/位置的过渡动画,提升流畅度
5. 交互与边界处理
- 可选添加气泡点击交互:给每个气泡添加点击手势,点击时展示该位置的跑步者详情
- 边界情况处理:人数为0时不绘制对应气泡;用户进度超过10KM时,将位置固定在终点
- 性能优化:异步加载用户头像,避免阻塞UI绘制;复用文本绘制的
UIFont、NSParagraphStyle等对象
内容的提问来源于stack exchange,提问作者Wo_0NDeR ᵀᴹ
相关产品推荐
相关产品推荐

