在 SwiftUI 中循环遍历实体数组以基于数组数据中的值构建 tree-structured 视图
Loop through entity array in SwiftUI to build tree-structured view based on values in array data
我是 Swift 的新手。我需要遍历一个数组,并根据数据内容构建一个自定义树结构。我会分享我的代码,但它太笨重太可怕以至于无法在 public 中看到。它甚至离工作还差得很远。所以这里有一些伪代码:
pass in an array of ScheduleCell models (data for a scheduled tv program) to a SwiftUI View.
Each ScheduleCell contains:
Date of broadcast
Channel
Starttime
Program title, subtitle, etc
in the view body, loop over each ScheduleCell
if ScheduleCell is new day then
display new date
go down one line
end
if ScheduleCell is new channel then
display ScheduleCell.channel but do NOT drop down a line
end
display ScheduleCell.title, also on same line as channel
Keep going on the same horizontal line till you hit the next channel
end
最终效果应该是这样的:
11-16-2021
CNN News Program 1 News Program 2 News Program 3 Etc.
NBC Late Show Infomercial Barney Miller Rerun. Etc.
11-17-2021
CNN News Program 1 News Program 2 News Program 3 Etc.
NBC Late Show Infomercial Barney Miller Rerun. Etc.
看来做起来应该很简单。我试过设置树结构,但我在使用 SwiftUI 视图协议时遇到了问题。我曾尝试在视图 body 之外的函数中构建树结构,但我无法弄清楚如何将该结构放入视图 body。
或者,我放弃了所有这些,只是试图通过跟踪数组索引在视图中通过蛮力让它工作,就像这样:
struct ScheduleDisplayView: View {
var schedule: [SchedSlot]
let dfdo = DateFormatter.dateOnly
let dfto = DateFormatter.timeOnly
let chanwidth: CGFloat = 45.0
let fontsize: CGFloat = 10.0
var body: some View {
List {
ForEach(schedule.indices,id:\.self) { idx in
let ssDay: String = dfdo.string(from: schedule[idx].startTime!)
let ssChan: Int32 = schedule[idx].channel!.channelID
if idx == 0 ||
ssDay != dfdo.string(from: schedule[idx-1].startTime!)
{
VStack {
Text(ssDay)
.frame(maxWidth: 200 + chanwidth, alignment: .leading)
}
}
HStack {
if idx == 0 ||
ssChan != schedule[idx-1].channel!.channelID
{
VStack {
Text(String(schedule[idx].channel!.channelID))
.frame(maxWidth: chanwidth, alignment: .center)
//.font(.system(size: fontsize))
Text(schedule[idx].channel!.callSign!)
.frame(maxWidth: chanwidth, alignment: .center)
//.font(.system(size: fontsize))
}
}
Text(schedule[idx].program!.title!)
.frame(maxWidth: 200, alignment: .leading)
.border(Color.black)
}
}
}
}
}
但是上面的方法是行不通的,因为HStack不能让程序标题在同一行。
提前感谢您的任何意见。
额外material:
这是基于以下内容的 CoreData 实体的一些随机切片:
SchedCell
stationID starttime programID duration endtime isUsed channel program
34916 2021-09-29 19:09:00.000 EP000045050088 PT00H09M 2021-09-29 19:18:00.000 0
12131 2021-09-29 19:15:00.000 EP022222100024 PT00H15M 2021-09-29 19:30:00.000 0
34916 2021-09-29 19:18:00.000 EP000045050208 PT00H09M 2021-09-29 19:27:00.000 0
Program
series programID title subtitle fulldescription genre isUsed
EP00000066 EP000000660001 A Pup Named Scooby-Doo Night of the Living Burger After a quarrel, a burgerlike creature haunts Shaggy and Scooby. Children 0
EP00000066 EP000000660002 A Pup Named Scooby-Doo For Letter or Worse The ghost of a long-dead gangster haunts a TV studio. Children 0
EP00000066 EP000000660003 A Pup Named Scooby-Doo A Bicycle Built for Boo! A green monster steals Shaggy's bike. Children 0
EP00000066 EP000000660004 A Pup Named Scooby-Doo The Baby Sitter From Beyond The baby sitter for Shaggy's little sister appears to be a monster. Children 0
Channel
stationID callSign fullName channelID isUsed
15722 WCIX WCIX 2 0
11345 WCIA WCIA 3 0
11278 WAND WAND 4 0
10685 KSDK KSDK 5 0
10269 HSN Home Shopping Network 6 0
11824 WRSP WRSP 7 0
11069 QVC QVC 8 0
至于我尝试在视图之外构建树结构的代码示例,我没有工作代码。这只是产生各种错误消息的片段。这是节点结构。我仍在制定一个例程,将它们 assemble 放入工作树中,一旦我有值得一看的东西,就会 post :
class RootNode {
var children: [DayNode] = []
func add(child: DayNode) {
children.append(child)
child.parent = self
}
}
class DayNode {
var parent: RootNode
var date: String
var children: [ChannelNode] = []
init(date: String) {
self.date = date
}
func add(child: ChannelNode) {
children.append(child)
child.parent = self
}
}
class ChannelNode {
var parent: DayNode
var channel: String
var children: [SchedSlot] = []
init(channel: String) {
self.channel = channel
}
func add(child: SchedSlot) {
children.append(child)
//child.parent = self
}
}
正如我在评论中提到的,我认为这更像是一个数据 organization/sorting 问题,而不是 SwiftUI 布局问题。如果你的数据被正确分组和排序,布局就会变得更简单(例如,你不必因为一个新频道而尝试决定是否换行)。
在下面的示例中,我将大部分代码用于对数据进行分组和排序。然后,布局本身就比较简单了。
重要的是要注意,为了简洁起见,我在这里做了一些不安全的事情,比如使用 first!
你想在实际代码中测试或有意外事件。
struct SchedCell {
var stationID: Int
var startTime: Date
var programID: String
var channel: String
}
func generateSampleCells() -> [SchedCell] {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
return [
("2021-09-29 19:09:00.000",34916,"EP000045050088","PT00H09M"),
("2021-09-29 19:09:00.000",34917,"EP000045050088","PT00H09M"),
("2021-09-29 19:15:00.000",12131,"EP022222100024","PT00H15M"),
("2021-09-29 19:18:00.000",34916,"EP000045050208","PT00H09M"),
("2021-09-30 19:09:00.000",34916,"EP000045050088","PT00H09M"),
("2021-09-30 19:15:00.000",12131,"EP022222100024","PT00H15M"),
("2021-09-30 19:18:00.000",34916,"EP000045050208","PT00H09M"),
("2021-09-30 19:15:00.000",12132,"EP022222100024","PT00H15M"),
("2021-09-29 19:09:00.000",4916,"EP000045050088","PT00H09M"),
("2021-09-29 19:09:00.000",4917,"EP000045050088","PT00H09M"),
("2021-09-29 19:15:00.000",2131,"EP022222100024","PT00H15M"),
].map {
SchedCell(stationID: [=10=].1, startTime: formatter.date(from: [=10=].0)!, programID: [=10=].2, channel: [=10=].3)
}
}
struct ContentView: View {
private var data = generateSampleCells()
private var formatter = DateFormatter()
struct ScheduleDay {
var dateStamp: String
var date : Date
var channels: [ChannelLineup]
}
struct ChannelLineup {
var channelName: String
var programs: [SchedCell]
}
struct DateStampedSchedCell {
var dateStamp: String
var cell: SchedCell
}
var sortedData : [ScheduleDay] {
formatter.dateFormat = "MM-dd-yyyy"
let dateStamped = data
.map { item -> DateStampedSchedCell in
DateStampedSchedCell(dateStamp: formatter.string(from: item.startTime), cell: item)
}
let days = Dictionary(grouping: dateStamped, by: { [=10=].dateStamp} ).values
let channelMappedDays = days.map { day in
ScheduleDay(dateStamp: day.first!.dateStamp,
date: day.first!.cell.startTime,
channels: Dictionary(grouping: day, by: { [=10=].cell.channel }).map { ChannelLineup(channelName: [=10=].key, programs: [=10=].value.map(\.cell))}
)
}.sorted(by: {a,b in a.date < b.date})
return channelMappedDays
}
var body: some View {
ScrollView(.horizontal) {
ForEach(sortedData, id: \.dateStamp) { day in
VStack(alignment: .leading) {
Text("Day: \(day.dateStamp)")
.bold()
ForEach(day.channels, id: \.channelName) { channel in
HStack {
Text("Channel: \(channel.channelName)")
.foregroundColor(.red)
ForEach(channel.programs, id: \.programID) { program in
Text(program.programID)
}
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
我是 Swift 的新手。我需要遍历一个数组,并根据数据内容构建一个自定义树结构。我会分享我的代码,但它太笨重太可怕以至于无法在 public 中看到。它甚至离工作还差得很远。所以这里有一些伪代码:
pass in an array of ScheduleCell models (data for a scheduled tv program) to a SwiftUI View.
Each ScheduleCell contains:
Date of broadcast
Channel
Starttime
Program title, subtitle, etc
in the view body, loop over each ScheduleCell
if ScheduleCell is new day then
display new date
go down one line
end
if ScheduleCell is new channel then
display ScheduleCell.channel but do NOT drop down a line
end
display ScheduleCell.title, also on same line as channel
Keep going on the same horizontal line till you hit the next channel
end
最终效果应该是这样的:
11-16-2021
CNN News Program 1 News Program 2 News Program 3 Etc.
NBC Late Show Infomercial Barney Miller Rerun. Etc.
11-17-2021
CNN News Program 1 News Program 2 News Program 3 Etc.
NBC Late Show Infomercial Barney Miller Rerun. Etc.
看来做起来应该很简单。我试过设置树结构,但我在使用 SwiftUI 视图协议时遇到了问题。我曾尝试在视图 body 之外的函数中构建树结构,但我无法弄清楚如何将该结构放入视图 body。
或者,我放弃了所有这些,只是试图通过跟踪数组索引在视图中通过蛮力让它工作,就像这样:
struct ScheduleDisplayView: View {
var schedule: [SchedSlot]
let dfdo = DateFormatter.dateOnly
let dfto = DateFormatter.timeOnly
let chanwidth: CGFloat = 45.0
let fontsize: CGFloat = 10.0
var body: some View {
List {
ForEach(schedule.indices,id:\.self) { idx in
let ssDay: String = dfdo.string(from: schedule[idx].startTime!)
let ssChan: Int32 = schedule[idx].channel!.channelID
if idx == 0 ||
ssDay != dfdo.string(from: schedule[idx-1].startTime!)
{
VStack {
Text(ssDay)
.frame(maxWidth: 200 + chanwidth, alignment: .leading)
}
}
HStack {
if idx == 0 ||
ssChan != schedule[idx-1].channel!.channelID
{
VStack {
Text(String(schedule[idx].channel!.channelID))
.frame(maxWidth: chanwidth, alignment: .center)
//.font(.system(size: fontsize))
Text(schedule[idx].channel!.callSign!)
.frame(maxWidth: chanwidth, alignment: .center)
//.font(.system(size: fontsize))
}
}
Text(schedule[idx].program!.title!)
.frame(maxWidth: 200, alignment: .leading)
.border(Color.black)
}
}
}
}
}
但是上面的方法是行不通的,因为HStack不能让程序标题在同一行。
提前感谢您的任何意见。
额外material:
这是基于以下内容的 CoreData 实体的一些随机切片:
SchedCell
stationID starttime programID duration endtime isUsed channel program
34916 2021-09-29 19:09:00.000 EP000045050088 PT00H09M 2021-09-29 19:18:00.000 0
12131 2021-09-29 19:15:00.000 EP022222100024 PT00H15M 2021-09-29 19:30:00.000 0
34916 2021-09-29 19:18:00.000 EP000045050208 PT00H09M 2021-09-29 19:27:00.000 0
Program
series programID title subtitle fulldescription genre isUsed
EP00000066 EP000000660001 A Pup Named Scooby-Doo Night of the Living Burger After a quarrel, a burgerlike creature haunts Shaggy and Scooby. Children 0
EP00000066 EP000000660002 A Pup Named Scooby-Doo For Letter or Worse The ghost of a long-dead gangster haunts a TV studio. Children 0
EP00000066 EP000000660003 A Pup Named Scooby-Doo A Bicycle Built for Boo! A green monster steals Shaggy's bike. Children 0
EP00000066 EP000000660004 A Pup Named Scooby-Doo The Baby Sitter From Beyond The baby sitter for Shaggy's little sister appears to be a monster. Children 0
Channel
stationID callSign fullName channelID isUsed
15722 WCIX WCIX 2 0
11345 WCIA WCIA 3 0
11278 WAND WAND 4 0
10685 KSDK KSDK 5 0
10269 HSN Home Shopping Network 6 0
11824 WRSP WRSP 7 0
11069 QVC QVC 8 0
至于我尝试在视图之外构建树结构的代码示例,我没有工作代码。这只是产生各种错误消息的片段。这是节点结构。我仍在制定一个例程,将它们 assemble 放入工作树中,一旦我有值得一看的东西,就会 post :
class RootNode {
var children: [DayNode] = []
func add(child: DayNode) {
children.append(child)
child.parent = self
}
}
class DayNode {
var parent: RootNode
var date: String
var children: [ChannelNode] = []
init(date: String) {
self.date = date
}
func add(child: ChannelNode) {
children.append(child)
child.parent = self
}
}
class ChannelNode {
var parent: DayNode
var channel: String
var children: [SchedSlot] = []
init(channel: String) {
self.channel = channel
}
func add(child: SchedSlot) {
children.append(child)
//child.parent = self
}
}
正如我在评论中提到的,我认为这更像是一个数据 organization/sorting 问题,而不是 SwiftUI 布局问题。如果你的数据被正确分组和排序,布局就会变得更简单(例如,你不必因为一个新频道而尝试决定是否换行)。
在下面的示例中,我将大部分代码用于对数据进行分组和排序。然后,布局本身就比较简单了。
重要的是要注意,为了简洁起见,我在这里做了一些不安全的事情,比如使用 first!
你想在实际代码中测试或有意外事件。
struct SchedCell {
var stationID: Int
var startTime: Date
var programID: String
var channel: String
}
func generateSampleCells() -> [SchedCell] {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
return [
("2021-09-29 19:09:00.000",34916,"EP000045050088","PT00H09M"),
("2021-09-29 19:09:00.000",34917,"EP000045050088","PT00H09M"),
("2021-09-29 19:15:00.000",12131,"EP022222100024","PT00H15M"),
("2021-09-29 19:18:00.000",34916,"EP000045050208","PT00H09M"),
("2021-09-30 19:09:00.000",34916,"EP000045050088","PT00H09M"),
("2021-09-30 19:15:00.000",12131,"EP022222100024","PT00H15M"),
("2021-09-30 19:18:00.000",34916,"EP000045050208","PT00H09M"),
("2021-09-30 19:15:00.000",12132,"EP022222100024","PT00H15M"),
("2021-09-29 19:09:00.000",4916,"EP000045050088","PT00H09M"),
("2021-09-29 19:09:00.000",4917,"EP000045050088","PT00H09M"),
("2021-09-29 19:15:00.000",2131,"EP022222100024","PT00H15M"),
].map {
SchedCell(stationID: [=10=].1, startTime: formatter.date(from: [=10=].0)!, programID: [=10=].2, channel: [=10=].3)
}
}
struct ContentView: View {
private var data = generateSampleCells()
private var formatter = DateFormatter()
struct ScheduleDay {
var dateStamp: String
var date : Date
var channels: [ChannelLineup]
}
struct ChannelLineup {
var channelName: String
var programs: [SchedCell]
}
struct DateStampedSchedCell {
var dateStamp: String
var cell: SchedCell
}
var sortedData : [ScheduleDay] {
formatter.dateFormat = "MM-dd-yyyy"
let dateStamped = data
.map { item -> DateStampedSchedCell in
DateStampedSchedCell(dateStamp: formatter.string(from: item.startTime), cell: item)
}
let days = Dictionary(grouping: dateStamped, by: { [=10=].dateStamp} ).values
let channelMappedDays = days.map { day in
ScheduleDay(dateStamp: day.first!.dateStamp,
date: day.first!.cell.startTime,
channels: Dictionary(grouping: day, by: { [=10=].cell.channel }).map { ChannelLineup(channelName: [=10=].key, programs: [=10=].value.map(\.cell))}
)
}.sorted(by: {a,b in a.date < b.date})
return channelMappedDays
}
var body: some View {
ScrollView(.horizontal) {
ForEach(sortedData, id: \.dateStamp) { day in
VStack(alignment: .leading) {
Text("Day: \(day.dateStamp)")
.bold()
ForEach(day.channels, id: \.channelName) { channel in
HStack {
Text("Channel: \(channel.channelName)")
.foregroundColor(.red)
ForEach(channel.programs, id: \.programID) { program in
Text(program.programID)
}
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}