TagCloudView.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import Combine
  2. import Foundation
  3. import SwiftUI
  4. import Swinject
  5. struct TagCloudView: View {
  6. var tags: [String]
  7. var shouldParseToMmolL: Bool
  8. @State private var totalHeight
  9. // = CGFloat.zero // << variant for ScrollView/List
  10. = CGFloat.infinity // << variant for VStack
  11. var body: some View {
  12. VStack {
  13. GeometryReader { geometry in
  14. self.generateContent(in: geometry)
  15. }
  16. }
  17. // .frame(height: totalHeight)// << variant for ScrollView/List
  18. .frame(maxHeight: totalHeight) // << variant for VStack
  19. }
  20. private func generateContent(in g: GeometryProxy) -> some View {
  21. var width = CGFloat.zero
  22. var height = CGFloat.zero
  23. return ZStack(alignment: .topLeading) {
  24. ForEach(self.tags, id: \.self) { tag in
  25. self.item(for: tag, isMmolL: shouldParseToMmolL)
  26. .padding([.horizontal, .vertical], 2)
  27. .alignmentGuide(.leading, computeValue: { d in
  28. if abs(width - d.width) > g.size.width
  29. {
  30. width = 0
  31. height -= d.height
  32. }
  33. let result = width
  34. if tag == self.tags.last! {
  35. width = 0 // last item
  36. } else {
  37. width -= d.width
  38. }
  39. return result
  40. })
  41. .alignmentGuide(.top, computeValue: { _ in
  42. let result = height
  43. if tag == self.tags.last! {
  44. height = 0 // last item
  45. }
  46. return result
  47. })
  48. }
  49. }.background(viewHeightReader($totalHeight))
  50. }
  51. // private func item(for textTag: String) -> some View {
  52. // var colorOfTag: Color {
  53. // switch textTag {
  54. // case textTag where textTag.contains("SMB Delivery Ratio:"):
  55. // return .uam
  56. // case textTag where textTag.contains("Bolus"):
  57. // return .green
  58. // case textTag where textTag.contains("TDD:"),
  59. // textTag where textTag.contains("tdd_factor"),
  60. // textTag where textTag.contains("Sigmoid function"),
  61. // textTag where textTag.contains("Logarithmic formula"),
  62. // textTag where textTag.contains("AF:"),
  63. // textTag where textTag.contains("Autosens/Dynamic Limit:"),
  64. // textTag where textTag.contains("Dynamic ISF/CR"),
  65. // textTag where textTag.contains("Basal ratio"),
  66. // textTag where textTag.contains("SMB Ratio"):
  67. // return .zt
  68. // case textTag where textTag.contains("Middleware:"):
  69. // return .red
  70. // case textTag where textTag.contains("SMB Ratio"):
  71. // return .orange
  72. // default:
  73. // return .insulin
  74. // }
  75. // }
  76. //
  77. // return ZStack { Text(textTag)
  78. // .padding(.vertical, 2)
  79. // .padding(.horizontal, 4)
  80. // .font(.subheadline)
  81. // .background(colorOfTag.opacity(0.8))
  82. // .foregroundColor(Color.white)
  83. // .cornerRadius(2) }
  84. // }
  85. private func item(for textTag: String, isMmolL: Bool) -> some View {
  86. var colorOfTag: Color {
  87. switch textTag {
  88. case textTag where textTag.contains("SMB Delivery Ratio:"):
  89. return .uam
  90. case textTag where textTag.contains("Bolus"):
  91. return .green
  92. case textTag where textTag.contains("TDD:"),
  93. textTag where textTag.contains("tdd_factor"),
  94. textTag where textTag.contains("Sigmoid function"),
  95. textTag where textTag.contains("Logarithmic formula"),
  96. textTag where textTag.contains("AF:"),
  97. textTag where textTag.contains("Autosens/Dynamic Limit:"),
  98. textTag where textTag.contains("Dynamic ISF/CR"),
  99. textTag where textTag.contains("Basal ratio"),
  100. textTag where textTag.contains("SMB Ratio"):
  101. return .zt
  102. case textTag where textTag.contains("Middleware:"):
  103. return .red
  104. case textTag where textTag.contains("SMB Ratio"):
  105. return .orange
  106. default:
  107. return .insulin
  108. }
  109. }
  110. func formattedTextTag(for tag: String) -> String {
  111. // List of glucose-related tags
  112. let glucoseTags = ["ISF:", "Target:", "minPredBG", "minGuardBG", "IOBpredBG", "COBpredBG", "UAMpredBG", "Dev:"]
  113. var updatedTag = tag
  114. // Apply conversion if necessary
  115. for glucoseTag in glucoseTags {
  116. if glucoseTag == "ISF:" {
  117. // Handle the special ISF case with the arrow
  118. if let range = updatedTag.range(of: "\(glucoseTag)\\s*\\d+→\\d+", options: .regularExpression) {
  119. let glucoseValueString = updatedTag[range]
  120. let values = glucoseValueString.components(separatedBy: "→")
  121. if let firstValue = Double(
  122. values[0]
  123. .components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
  124. ),
  125. let secondValue = Double(
  126. values[1]
  127. .components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
  128. )
  129. {
  130. let formattedFirstValue = isMmolL ? Double(firstValue.asMmolL) : firstValue
  131. let formattedSecondValue = isMmolL ? Double(secondValue.asMmolL) : secondValue
  132. let formattedGlucoseValueString =
  133. "\(glucoseTag) \(formattedFirstValue)→\(formattedSecondValue)"
  134. updatedTag = updatedTag.replacingOccurrences(
  135. of: glucoseValueString,
  136. with: formattedGlucoseValueString
  137. )
  138. }
  139. }
  140. } else {
  141. // General case for other glucose tags
  142. if let range = updatedTag.range(of: "\(glucoseTag)\\s*\\d+", options: .regularExpression) {
  143. let glucoseValueString = updatedTag[range]
  144. if let glucoseValue = Double(
  145. glucoseValueString
  146. .components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
  147. ) {
  148. let formattedValue = isMmolL ? Double(glucoseValue.asMmolL) : glucoseValue
  149. updatedTag = updatedTag.replacingOccurrences(
  150. of: glucoseValueString,
  151. with: "\(glucoseTag) \(formattedValue)"
  152. )
  153. }
  154. }
  155. }
  156. }
  157. return updatedTag
  158. }
  159. let formattedTextTag = formattedTextTag(for: textTag)
  160. return ZStack {
  161. Text(formattedTextTag)
  162. .padding(.vertical, 2)
  163. .padding(.horizontal, 4)
  164. .font(.subheadline)
  165. .background(colorOfTag.opacity(0.8))
  166. .foregroundColor(Color.white)
  167. .cornerRadius(2)
  168. }
  169. }
  170. private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
  171. GeometryReader { geometry -> Color in
  172. let rect = geometry.frame(in: .local)
  173. DispatchQueue.main.async {
  174. binding.wrappedValue = rect.size.height
  175. }
  176. return .clear
  177. }
  178. }
  179. }
  180. struct TestTagCloudView: View {
  181. var body: some View {
  182. VStack {
  183. Text("Header").font(.largeTitle)
  184. TagCloudView(
  185. tags: ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4"],
  186. shouldParseToMmolL: false
  187. )
  188. Text("Some other text")
  189. Divider()
  190. Text("Some other cloud")
  191. TagCloudView(tags: ["Apple", "Google", "Amazon", "Microsoft", "Oracle", "Facebook"], shouldParseToMmolL: false)
  192. }
  193. }
  194. }