自定义的Swift气泡视图组件

huangapple go评论89阅读模式
英文:

Swift custom bubble view component

问题

我需要创建一个能够以气泡形式显示许多跑步者进度的组件。我已经尝试使用很棒的Charts库,但没有取得太多成果。

这是代码:

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. self.configureChart(raceChartData: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], raceDistance: 10)
  4. }
  5. func configureChart(raceChartData: [Int], raceDistance: Double) {
  6. // ...(代码中的其他配置)
  7. }

这是结果:

自定义的Swift气泡视图组件

这是我需要创建的真实组件:

自定义的Swift气泡视图组件

假设你需要跑10公里,当前的市场线和带有头像的气泡是你(应用程序用户)以及你已经跑过的距离。其他气泡代表附近的其他人和他们的距离。所有数据来自套接字(后端),我需要以这种形式显示它们。你有任何关于如何做到这一点的想法吗?我在考虑从头开始创建一个组件(基于UIView),但我不知道这是否是一个好计划。

英文:

I need to create a component capable of displaying the progress of many runners in the form of a bubble. I have tried to do it with the Charts library which is great but I have not achieved much.

This is the code:

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. self.configureChart(raceChartData: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], raceDistance: 10)
  4. }
  5. func configureChart(raceChartData: [Int], raceDistance: Double) {
  6. chartView.chartDescription.enabled = false
  7. chartView.dragEnabled = false
  8. chartView.pinchZoomEnabled = false
  9. chartView.xAxis.enabled = true
  10. //chartView.maxVisibleCount = 0
  11. chartView.autoScaleMinMaxEnabled = false
  12. chartView.leftAxis.enabled = false
  13. chartView.rightAxis.enabled = false
  14. chartView.legend.enabled = false
  15. chartView.legend.drawInside = false
  16. chartView.setScaleEnabled(false)
  17. let xAxis: XAxis = chartView.xAxis
  18. xAxis.axisLineColor = #colorLiteral(red: 1, green: 0.4823529412, blue: 0.1450980392, alpha: 1).withAlphaComponent(0.1)
  19. xAxis.drawAxisLineEnabled = false
  20. xAxis.drawLabelsEnabled = false
  21. xAxis.gridColor = #colorLiteral(red: 1, green: 0.4823529412, blue: 0.1450980392, alpha: 1).withAlphaComponent(0.3)
  22. //xAxis.axisMinimum = 1
  23. //xAxis.axisMaximum = 10
  24. var calculatedChartDistance = 0.0
  25. if raceChartData.count != 0 {
  26. var chartdistanceArray = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
  27. if activeRaceFormat == "SPDWRK" {
  28. calculatedChartDistance = raceDistance
  29. } else {
  30. chartView.xAxis.centeredEntries = [0, 0.5, 0.4]
  31. chartView.xAxis.centerAxisLabelsEnabled = true
  32. calculatedChartDistance = raceDistance
  33. }
  34. for i in 0...9 {
  35. chartdistanceArray[i] = calculatedChartDistance * Double((i + 1))
  36. }
  37. let yVals1 = [
  38. BubbleChartDataEntry(x: chartdistanceArray[0], y: 0, size: CGFloat(raceChartData[0])),
  39. BubbleChartDataEntry(x: chartdistanceArray[1], y: 0, size: CGFloat(raceChartData[1])),
  40. BubbleChartDataEntry(x: chartdistanceArray[2], y: 0, size: CGFloat(raceChartData[2])),
  41. BubbleChartDataEntry(x: chartdistanceArray[3], y: 0, size: CGFloat(raceChartData[3])),
  42. BubbleChartDataEntry(x: chartdistanceArray[4], y: 0, size: CGFloat(raceChartData[4])),
  43. BubbleChartDataEntry(x: chartdistanceArray[5], y: 0, size: CGFloat(raceChartData[5])),
  44. BubbleChartDataEntry(x: chartdistanceArray[6], y: 0, size: CGFloat(raceChartData[6])),
  45. BubbleChartDataEntry(x: chartdistanceArray[7], y: 0, size: CGFloat(raceChartData[7])),
  46. BubbleChartDataEntry(x: chartdistanceArray[8], y: 0, size: CGFloat(raceChartData[8])),
  47. BubbleChartDataEntry(x: chartdistanceArray[9], y: 0, size: CGFloat(raceChartData[9]))
  48. ]
  49. let set1 = BubbleChartDataSet(entries: yVals1)
  50. set1.drawIconsEnabled = false
  51. set1.setColor(ChartColorTemplates.colorful()[1], alpha: 0.5)
  52. set1.drawValuesEnabled = true
  53. set1.normalizeSizeEnabled = false
  54. let data = [set1] as BubbleChartData
  55. data.setDrawValues(true)
  56. data.setValueFont(UIFont(name: "HelveticaNeue-Light", size: 7)!)
  57. data.setHighlightCircleWidth(0.5)
  58. chartView.data = data
  59. }
  60. }

And this is the result:

自定义的Swift气泡视图组件

Here is the real component I need to create:

自定义的Swift气泡视图组件

Suppose you have to run 10 KM, the current market line and the bubble with avatar are you (the app user) and the distance you have running. The other bubbles is the amount of people near one from other and their distance. All the data comes from socket (backend) and I need to display in that form. Any idea of how I can do this?, I'm thinking in do a component from scratch (based on UIView) but I don't know if this is a good plan at all.

答案1

得分: 0

以下是翻译好的部分:

  1. // 以下是Swift代码的注释部分,无需翻译
  2. // Una clase para representar un gráfico de burbujas con varios corredores
  3. class DiyBubbleChartView: UIView {
  4. // Un arreglo para almacenar los corredores
  5. var currentRunners: [Runner] = []
  6. // Una constante para la distancia total a recorrer
  7. let totalDistance = 100.0
  8. // Un método para actualizar corredores en el gráfico
  9. func updateRunners(_ runners: [Runner]) {
  10. self.currentRunners.removeAll()
  11. self.currentRunners = runners
  12. self.setNeedsDisplay() // Llama al método draw para actualizar el gráfico
  13. }
  14. // Un método para agregar un corredor al gráfico
  15. func addRunner(_ runner: Runner) {
  16. self.currentRunners.append(runner)
  17. self.setNeedsDisplay() // Llama al método draw para actualizar el gráfico
  18. }
  19. // Un方法para dibujar el gráfico de burbujas
  20. override func draw(_ rect: CGRect) {
  21. super.draw(rect)
  22. // Obtiene el contexto gráfico actual
  23. guard let context = UIGraphicsGetCurrentContext() else { return }
  24. self.drawGradientBackground()
  25. self.drawGridLines()
  26. // Define el color y el ancho de las líneas
  27. context.setStrokeColor(UIColor.black.cgColor)
  28. context.setLineWidth(0.1)
  29. // Agrupa los corredores que están juntos en un diccionario con la distancia como clave y el número de corredores como valor
  30. var groups: [Double: Int] = [:]
  31. for runner in currentRunners {
  32. groups[runner.distance, default: 0] += 1
  33. }
  34. // Para cada grupo de corredores, dibuja una burbuja con un radio proporcional al número de corredores y una posición proporcional a la distancia
  35. for (distance, count) in groups {
  36. var radius = CGFloat(count) // El radio de la burbuja es 5 veces el número de corredores
  37. // Verifica si el radio es mayor que la mitad de la altura de la vista
  38. if radius > (bounds.height / 2) - 20.0 {
  39. // Si lo es, reduce el radio a ese valor (20.0 es el margen)
  40. radius = (bounds.height / 2) - 20.0
  41. }
  42. var x = 10 + CGFloat(distance / totalDistance) * (bounds.width - 20) // La posición x de la burbuja es proporcional a la distancia recorrida
  43. let y = bounds.height / 2 // La posición y de la burbuja es la mitad de la altura del gráfico
  44. // Verifica si la posición x más el radio es mayor que el ancho de la vista
  45. if x + radius > bounds.width {
  46. // Si lo es, resta la diferencia al valor de x para mover la burbuja hacia la izquierda
  47. x -= (x + radius) - bounds.width
  48. }
  49. // Dibuja un círculo con el centro y el radio dados
  50. // Establece el color de relleno del contexto gráfico
  51. context.setFillColor(UIColor.red.withAlphaComponent(0.3).cgColor)
  52. context.addArc(center: CGPoint(x: x, y: y), radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
  53. context.fillPath()
  54. context.strokePath()
  55. }
  56. self.drawBubbleWithUserAvatar(withGroups: groups)
  57. }
  58. // Crea una instancia de CAGradientLayer y le asigna un tamaño igual al de la vista
  59. func drawGradientBackground() {
  60. // Remover todas las sublayers para evitar problemas
  61. self.layer.sublayers?.removeAll()
  62. let gradientLayer = CAGradientLayer()
  63. gradientLayer.frame = self.bounds
  64. // Define los colores para el degradado
  65. let firstColor = UIColor(named: "firstColor")!.withAlphaComponent(0.3)
  66. let secondColor = UIColor(named: "secondColor")!.withAlphaComponent(0.3)
  67. // Define los colores del degradado como un arreglo de CGColor
  68. gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
  69. // Define los puntos de inicio y fin del degradado como CGPoint
  70. gradientLayer.startPoint = CGPoint(x: 0, y: 0)
  71. gradientLayer.endPoint = CGPoint(x: 1, y: 1)
  72. // Agrega el degradado como una subcapa de la vista
  73. self.layer.insertSublayer(gradientLayer, at: 0)
  74. }
  75. // Dibuja las líneas horizontales de la gráfica
  76. func drawGridLines() {
  77. // Obtiene el contexto gráfico actual
  78. guard let context = UIGraphicsGetCurrentContext() else { return }
  79. let lineColor = #colorLiteral(red: 1, green: 0.4823529412, blue: 0.1450980392, alpha: 1).withAlphaComponent(0.3)
  80. // Define una constante para la distancia entre las líneas
  81. let lineSpacing = 32.0
  82. context.setStrokeColor(lineColor.cgColor)
  83. context.setLineWidth(1.0)
  84. // Define una constante para el número de líneas a dibujar
  85. let numberOfLines = Int((bounds.width - 20) / lineSpacing)
  86. // Para cada línea, calcula su posición x y dibuja una línea vertical desde el borde superior hasta el borde inferior de la vista
  87. for i in 0...numberOfLines {
  88. let x = 10 + CGFloat(i) * lineSpacing // La posición x de la línea es igual al margen izquierdo más el espaciado multiplicado por el índice de la línea
  89. context.move(to: CGPoint(x: x, y: 0)) // Mueve el punto inicial al borde superior de la vista
  90. context.addLine(to: CGPoint(x: x, y: bounds.height)) // Agrega una línea al borde inferior de la vista
  91. context.strokePath() // Dibuja la línea
  92. }
  93. }
  94. // 以下是未翻译的部分,如需要继续翻译,请提供具体内容
  95. // ...
  96. }
  1. // 以下是测试用的Swift代码的注释部分,无需翻译
  2. // To test, in some view controller:
  3. let chart = DiyBubbleChartView(frame: CGRect(x: 0, y: 0, width: 300, height: 110))
  4. var runners: [Runner] = []
  5. var distances = [Double]()
  6. // Rellenar las distancias posibles desde 1 hasta 100
  7. for i in 1...100 {
  8. distances.append
  9. <details>
  10. <summary>英文:</summary>
  11. I have ended done this so far:
  12. ```swift
  13. //
  14. // DiyBubbleChartView.swift
  15. // Test Charts
  16. //
  17. // Created by Fidel Hern&#225;ndez Salazar on 5/29/23.
  18. //
  19. import Foundation
  20. import UIKit
  21. // Una clase para representar un gr&#225;fico de burbujas con varios corredores
  22. class DiyBubbleChartView: UIView {
  23. // Un arreglo para almacenar los corredores
  24. var currentRunners: [Runner] = []
  25. // Una constante para la distancia total a recorrer
  26. let totalDistance = 100.0
  27. // Un m&#233;todo para actualizar corredores en el gr&#225;fico
  28. func updateRunners(_ runners: [Runner]) {
  29. self.currentRunners.removeAll()
  30. self.currentRunners = runners
  31. self.setNeedsDisplay() // Llama al m&#233;todo draw para actualizar el gr&#225;fico
  32. }
  33. // Un m&#233;todo para agregar un corredor al gr&#225;fico
  34. func addRunner(_ runner: Runner) {
  35. self.currentRunners.append(runner)
  36. self.setNeedsDisplay() // Llama al m&#233;todo draw para actualizar el gr&#225;fico
  37. }
  38. // Un m&#233;todo para dibujar el gr&#225;fico de burbujas
  39. override func draw(_ rect: CGRect) {
  40. super.draw(rect)
  41. // Obtiene el contexto gr&#225;fico actual
  42. guard let context = UIGraphicsGetCurrentContext() else { return }
  43. self.drawGradientBackground()
  44. self.drawGridLines()
  45. // Define el color y el ancho de las l&#237;neas
  46. context.setStrokeColor(UIColor.black.cgColor)
  47. context.setLineWidth(0.1)
  48. // // Dibuja una l&#237;nea horizontal para representar la distancia total
  49. // context.move(to: CGPoint(x: 10, y: bounds.height / 2))
  50. // context.addLine(to: CGPoint(x: bounds.width - 10, y: bounds.height / 2))
  51. // context.strokePath()
  52. // // Dibuja una etiqueta con la distancia total
  53. // let label = UILabel(frame: CGRect(x: bounds.width - 50, y: bounds.height / 2 - 20, width: 40, height: 20))
  54. // label.text = &quot;\(totalDistance)&quot;
  55. // label.textColor = .black
  56. // label.font = .systemFont(ofSize: 12)
  57. // addSubview(label)
  58. // Agrupa los corredores que est&#225;n juntos en un diccionario con la distancia como clave y el n&#250;mero de corredores como valor
  59. var groups: [Double: Int] = [:]
  60. for runner in currentRunners {
  61. groups[runner.distance, default: 0] += 1
  62. }
  63. // Para cada grupo de corredores, dibuja una burbuja con un radio proporcional al n&#250;mero de corredores y una posici&#243;n proporcional a la distancia
  64. for (distance, count) in groups {
  65. var radius = CGFloat(count) // El radio de la burbuja es 5 veces el n&#250;mero de corredores
  66. // Verifica si el radio es mayor que la mitad de la altura de la vista
  67. if radius &gt; (bounds.height / 2) - 20.0 {
  68. // Si lo es, reduce el radio a ese valor (20.0 es el margin)
  69. radius = (bounds.height / 2) - 20.0
  70. }
  71. var x = 10 + CGFloat(distance / totalDistance) * (bounds.width - 20) // La posici&#243;n x de la burbuja es proporcional a la distancia recorrida
  72. let y = bounds.height / 2 // La posici&#243;n y de la burbuja es la mitad de la altura del gr&#225;fico
  73. // Verifica si la posici&#243;n x m&#225;s el radio es mayor que el ancho de la vista
  74. if x + radius &gt; bounds.width {
  75. // Si lo es, resta la diferencia al valor de x para mover la burbuja hacia la izquierda
  76. x -= (x + radius) - bounds.width
  77. }
  78. // Dibuja un c&#237;rculo con el centro y el radio dados
  79. // Establece el color de relleno del contexto gr&#225;fico
  80. context.setFillColor(UIColor.red.withAlphaComponent(0.3).cgColor)
  81. context.addArc(center: CGPoint(x: x, y: y), radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
  82. context.fillPath()
  83. context.strokePath()
  84. // // Dibuja una etiqueta con el n&#250;mero de corredores dentro de la burbuja
  85. // let label = UILabel(frame: CGRect(x: x - radius, y: y - radius, width: radius * 2, height: radius * 2))
  86. // label.text = &quot;\(count)&quot;
  87. // label.textColor = .black
  88. // label.font = UIFont(name: &quot;HelveticaNeue-Light&quot;, size: 7)! // .systemFont(ofSize: 12)
  89. // label.textAlignment = .center
  90. // addSubview(label)
  91. }
  92. self.drawBubbleWithUserAvatar(withGroups: groups)
  93. }
  94. // Crea una instancia de CAGradientLayer y le asigna un tama&#241;o igual al de la vista
  95. func drawGradientBackground() {
  96. // Remover todas las sublayers para evitar problemas
  97. self.layer.sublayers?.removeAll()
  98. let gradientLayer = CAGradientLayer()
  99. gradientLayer.frame = self.bounds
  100. // Define los colores para el degradado
  101. let firstColor = UIColor(named: &quot;firstColor&quot;)!.withAlphaComponent(0.3)
  102. let secondColor = UIColor(named: &quot;secondColor&quot;)!.withAlphaComponent(0.3)
  103. // Define los colores del degradado como un arreglo de CGColor
  104. gradientLayer.colors = [firstColor.cgColor, secondColor.cgColor]
  105. // Define los puntos de inicio y fin del degradado como CGPoint
  106. gradientLayer.startPoint = CGPoint(x: 0, y: 0)
  107. gradientLayer.endPoint = CGPoint(x: 1, y: 1)
  108. // Agrega el degradado como una subcapa de la vista
  109. self.layer.insertSublayer(gradientLayer, at: 0)
  110. }
  111. // Dibuja las lineas horizontales de la grafica
  112. func drawGridLines() {
  113. // Obtiene el contexto gr&#225;fico actual
  114. guard let context = UIGraphicsGetCurrentContext() else { return }
  115. let lineColor = #colorLiteral(red: 1, green: 0.4823529412, blue: 0.1450980392, alpha: 1).withAlphaComponent(0.3)
  116. // Define una constante para la distancia entre las l&#237;neas
  117. let lineSpacing = 32.0
  118. context.setStrokeColor(lineColor.cgColor)
  119. context.setLineWidth(1.0)
  120. // Define una constante para el n&#250;mero de l&#237;neas a dibujar
  121. let numberOfLines = Int((bounds.width - 20) / lineSpacing)
  122. // Para cada l&#237;nea, calcula su posici&#243;n x y dibuja una l&#237;nea vertical desde el borde superior hasta el borde inferior de la vista
  123. for i in 0...numberOfLines {
  124. let x = 10 + CGFloat(i) * lineSpacing // La posici&#243;n x de la l&#237;nea es igual al margen izquierdo m&#225;s el espaciado multiplicado por el &#237;ndice de la l&#237;nea
  125. context.move(to: CGPoint(x: x, y: 0)) // Mueve el punto inicial al borde superior de la vista
  126. context.addLine(to: CGPoint(x: x, y: bounds.height)) // Agrega una l&#237;nea al borde inferior de la vista
  127. context.strokePath() // Dibuja la l&#237;nea
  128. }
  129. }
  130. func drawBubbleWithUserAvatar(withGroups: [Double: Int]) {
  131. // Define una constante con el nombre del corredor principal
  132. let mainRunnerName = &quot;User 1&quot;
  133. // Obtiene la distancia recorrida por el corredor principal
  134. let mainRunnerDistance = currentRunners.first(where: { $0.name == mainRunnerName })!.distance
  135. // Obtiene el n&#250;mero de corredores en el mismo grupo que el corredor principal
  136. let mainRunnerCount = withGroups[mainRunnerDistance]!
  137. // Calcula el radio y la posici&#243;n x de la burbuja del corredor principal usando las mismas f&#243;rmulas que antes
  138. let mainRunnerRadius = CGFloat(mainRunnerCount * 10)
  139. let mainRunnerX = 10 + CGFloat(mainRunnerDistance / totalDistance) * (bounds.width - 20)
  140. // Crea una instancia de UIImageView con una imagen de prueba (puedes cambiarla por la imagen real del perfil del usuario)
  141. let imageView = UIImageView(image: UIImage(named: &quot;user-avatar&quot;))
  142. // Calcula el radio de la imagen como el mismo que el de la burbuja del usuario
  143. var imageRadius = mainRunnerRadius
  144. // Verifica si el radio es mayor que la mitad de la altura de la vista
  145. if imageRadius &gt; (bounds.height / 2) - 20.0 {
  146. // Si lo es, reduce el tama&#241;o de la imagen a ese valor
  147. imageRadius = (bounds.height / 2) - 20.0
  148. }
  149. // // Le asigna un tama&#241;o y una posici&#243;n iguales a los de la burbuja del corredor principal
  150. // imageView.frame = CGRect(x: mainRunnerX - mainRunnerRadius, y: bounds.height / 2 - mainRunnerRadius, width: mainRunnerRadius * 2, height: mainRunnerRadius * 2)
  151. // Le asigna un tama&#241;o y una posici&#243;n iguales a los de la burbuja del usuario
  152. imageView.frame = CGRect(x: mainRunnerX - imageRadius, y: bounds.height / 2 - imageRadius, width: imageRadius * 2, height: imageRadius * 2)
  153. // Le da una forma circular recortando las esquinas
  154. //imageView.layer.cornerRadius = mainRunnerRadius
  155. imageView.layer.cornerRadius = imageRadius
  156. imageView.clipsToBounds = true
  157. // Agrega la imagen como una subvista de la vista
  158. addSubview(imageView)
  159. // Obtiene el contexto gr&#225;fico actual
  160. guard let context = UIGraphicsGetCurrentContext() else { return }
  161. context.setStrokeColor(UIColor.init(named: &quot;firstColor&quot;)!.cgColor)
  162. context.setLineWidth(1.0)
  163. // Calcula las posiciones y del inicio y fin de la l&#237;nea como el borde superior e inferior de la vista principal
  164. let startY = 0.0
  165. let endY = bounds.height
  166. // Dibuja una l&#237;nea vertical con los puntos dados
  167. context.move(to: CGPoint(x: mainRunnerX, y: startY))
  168. context.addLine(to: CGPoint(x: mainRunnerX, y: endY))
  169. context.strokePath()
  170. /// .......
  171. // Crea una instancia de CALayer y le asigna un tama&#241;o y una posici&#243;n que cubran el &#225;rea deseada
  172. let backgroundLayer = CALayer()
  173. backgroundLayer.frame = CGRect(x: 0, y: 0, width: mainRunnerX, height: bounds.height)
  174. // Define el color del fondo como un CGColor
  175. backgroundLayer.backgroundColor = UIColor.init(named: &quot;secondColor&quot;)!.withAlphaComponent(0.2).cgColor
  176. // Agrega el fondo como una subcapa de la vista
  177. self.layer.insertSublayer(backgroundLayer, at: 1)
  178. }
  179. }

To test, in some view controller:

  1. let chart = DiyBubbleChartView(frame: CGRect(x: 0, y: 0, width: 300, height: 110))
  2. var runners: [Runner] = []
  3. var distances = [Double]()
  4. // Rellenar las distancias posibles desde 1 hasta 100
  5. for i in 1...100 {
  6. distances.append(Double(i))
  7. }
  8. for i in 1...100 {
  9. self.runners.append(Runner(name: &quot;User \(i)&quot;, distance: distances.randomElement() ?? 1.0, isUser: false))
  10. }
  11. self.updateBubbleView(withRunners: self.runners)

And the updateBubbleView func

  1. func updateBubbleView(withRunners: [Runner]) {
  2. // Crea una instancia del gr&#225;fico de burbujas y le agrega algunos corredores de prueba
  3. self.chart.updateRunners(withRunners)
  4. }

huangapple
  • 本文由 发表于 2023年5月29日 09:49:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/76354254.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定