SwiftUI Chart LineMark异常:如何实现两条独立折线?
问题描述
我是iOS开发新手,目前从Firebase获取数据并处理为两个元组数组(后续将扩展为3个),每个数组对应一条折线,元组首个值对应X轴,每个元组代表折线的一个数据点。我使用SwiftUI Chart展示数据,但目前两条折线被错误连接为一条连续线,且数据已按X轴从小到大排序,希望实现两条独立折线。
当前折线显示效果如图:
图表展示代码
Chart { ForEach(listOfEv.indices, id: \.self) { index in LineMark( x: .value("Time", Float(listOfEv[index].time)), y: .value("EV", Float(listOfEv[index].ev)) ).foregroundStyle( .linearGradient( // You can have more colours here depends on your data. colors: [.blue, .red], startPoint: .bottom, endPoint: .top) ) PointMark( x: .value("Time", Float(listOfEv[index].time)), y: .value("EV", Float(listOfEv[index].ev)) ) } ForEach(listOfAv.indices, id: \.self) { index1 in LineMark( x: .value("Time", Float(listOfAv[index1].time)), y: .value("EV", Float(listOfAv[index1].ev)) ).foregroundStyle(.green) PointMark( x: .value("Time", Float(listOfAv[index1].time)), y: .value("EV", Float(listOfAv[index1].ev)) ) } }.foregroundStyle(.green) .frame(width: .infinity,height: 300) .onAppear(perform: { firebaseData.BetTracker(username: "rozzaroo", completion: { EvData, AvData, SpData in listOfAv = AvData listOfSp = SpData listOfEv = EvData }) })
Firebase数据获取代码
func BetTracker(username: String, completion: @escaping ([(Int, Int)], [(Int, Int)], [(Int, Int)]) -> Void) { let ref = Database.database().reference().child("users").child(username.lowercased()).child("bets") ref.observeSingleEvent(of: .value) { snapshot, error in if let error = error { print("Error fetching odds list: \(error)") return } if let data = snapshot.value as? [String: Any] { let keysCount = data.keys.count var processedKeys = 0 var EvCounter = 0 var AvCounter = 0 var time = 0 let sortedKeys = data.keys.sorted { guard let first = data[$0] as? [Any], let second = data[$1] as? [Any], let firstInt = first[1] as? Int, let secondInt = second[1] as? Int else { return false } return firstInt < secondInt } for key in sortedKeys { if let usersBetTrack = data[key] as? [Any] { let refMaster = Database.database().reference().child("Master").child(key) refMaster.observeSingleEvent(of: .value) { caughtBet, error in processedKeys += 1 if let error = error { print("Error fetching data: \(error)") return } if caughtBet.exists() { if let array = caughtBet.value as? [AnyObject], let evMaster = array.first as? String, let userBetAmtTrack = usersBetTrack[2] as? String, let betTimePlace1 = usersBetTrack[1] as? Int { if let userBookOdd = usersBetTrack[3] as? String { var userBookOdds = Float(userBookOdd) var updatedEvMaster = Float(evMaster.dropLast())! / 100 var dollarAmountBet = Float(userBetAmtTrack.dropFirst())! dollarAmountBet *= updatedEvMaster let currentTimeZone = TimeZone.current let gmtOffsetInSeconds = currentTimeZone.secondsFromGMT() var betTimePlace2 = betTimePlace1 + gmtOffsetInSeconds EvCounter = Int(dollarAmountBet) + EvCounter EvCounter = Int(EvCounter) time = 1 + time if let gameResult = array[12] as? String { switch (gameResult) { case "win": var usersStake = Float(userBetAmtTrack.dropFirst()) usersStake = (userBookOdds! - 1) * usersStake! AvCounter = AvCounter + Int(Float(usersStake!)) case "lose": AvCounter = AvCounter - Int(Float(userBetAmtTrack.dropFirst())!) case "pending": AvCounter = AvCounter + 0 default: AvCounter = AvCounter + 0 } } time = time + 1 self.AvList.append((time: Int(time), ev: AvCounter)) self.EvList.append((time: Int(time), ev: EvCounter)) // Check if all keys have been processed, then call the completion handler if processedKeys == keysCount { completion(self.EvList, self.AvList, self.SpList) } } } } else { print("Snapshot does not exist") } } } } } } }
解决方案
问题根源是你给每个数据点单独创建了LineMark,SwiftUI Chart会默认把所有未区分系列的LineMark连接成一条线。要实现独立折线,需要把同一系列的所有数据点归为一组,用.series()修饰符标记不同折线。
修改后的图表代码
Chart { // EV系列折线 ForEach(listOfEv, id: \.time) { item in LineMark( x: .value("Time", Float(item.time)), y: .value("EV", Float(item.ev)) ) PointMark( x: .value("Time", Float(item.time)), y: .value("EV", Float(item.ev)) ) } .foregroundStyle( .linearGradient( colors: [.blue, .red], startPoint: .bottom, endPoint: .top ) ) .series(name: "EV") // AV系列折线 ForEach(listOfAv, id: \.time) { item in LineMark( x: .value("Time", Float(item.time)), y: .value("EV", Float(item.ev)) ) PointMark( x: .value("Time", Float(item.time)), y: .value("EV", Float(item.ev)) ) } .foregroundStyle(.green) .series(name: "AV") } .frame(width: .infinity, height: 300) .onAppear { firebaseData.BetTracker(username: "rozzaroo") { EvData, AvData, SpData in listOfAv = AvData listOfSp = SpData listOfEv = EvData } }
关键修改说明
- 将每个折线的
LineMark和PointMark放在同一个ForEach循环后,用.series()标记系列,Chart会自动把同系列的点连接成独立折线。 - 直接遍历数据元素而非索引,代码更简洁,避免索引相关的潜在bug。
- 移除外层的
.foregroundStyle(.green),防止覆盖系列自定义样式。
额外优化建议
建议把元组替换成自定义Identifiable结构体,简化ForEach的使用:
struct ChartDataPoint: Identifiable { let id = UUID() let time: Int let ev: Int }
之后数据数组可以直接用[ChartDataPoint]类型,ForEach无需手动指定id参数,更符合SwiftUI最佳实践。
内容的提问来源于stack exchange,提问作者Ryan morris
相关产品推荐
相关产品推荐

