From 98265c72d6c1e5f76ecb8ad0d33d4aee906735f3 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Mon, 14 Apr 2025 17:00:54 +0800 Subject: [PATCH] fix index view --- dimensionhub/Core/API.swift | 4 +- dimensionhub/Views/FlexImage.swift | 17 ++- dimensionhub/Views/Index/IndexMainView.swift | 135 ++++++++----------- dimensionhub/Views/Index/IndexModel.swift | 84 ++++++++---- 4 files changed, 124 insertions(+), 116 deletions(-) diff --git a/dimensionhub/Core/API.swift b/dimensionhub/Core/API.swift index 2f907dd..86501b8 100644 --- a/dimensionhub/Core/API.swift +++ b/dimensionhub/Core/API.swift @@ -118,8 +118,8 @@ struct API { } print("request url: \(request.url!.absoluteString)") - let x = String(data: data, encoding: .utf8)! - print("url: \(request.url!.path()), data is: \(x)") +// let x = String(data: data, encoding: .utf8)! +// print("url: \(request.url!.path()), data is: \(x)") do { let result = try JSONDecoder().decode(APISuccessResponse.self, from: data) return .result(result.result) diff --git a/dimensionhub/Views/FlexImage.swift b/dimensionhub/Views/FlexImage.swift index ba9401b..62a53f7 100644 --- a/dimensionhub/Views/FlexImage.swift +++ b/dimensionhub/Views/FlexImage.swift @@ -58,11 +58,18 @@ struct FlexImage: View { } } case .local(let data): - Image(uiImage: UIImage(data: data)!) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: width, height: height) - .clipped() + if let uiImage = UIImage(data: data) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: width, height: height) + .clipped() + } else { + Image(placeholder) + .resizable() + .aspectRatio(contentMode: .fill) + .clipped() + } } } diff --git a/dimensionhub/Views/Index/IndexMainView.swift b/dimensionhub/Views/Index/IndexMainView.swift index 3576198..2d30570 100644 --- a/dimensionhub/Views/Index/IndexMainView.swift +++ b/dimensionhub/Views/Index/IndexMainView.swift @@ -14,6 +14,7 @@ struct IndexMainView: View { @AppStorage("userId") private var userId: String = Utils.defaultUserId() @State var indexModel = IndexModel() + @State private var scrollID: Int? = 0 // 提示信息 @State var showPrompt: Bool = false @@ -62,30 +63,27 @@ struct IndexMainView: View { // 基于日期的更新列表 LazyVStack(alignment: .center, spacing: 10) { - ForEach(indexModel.updateDramaGroups, id: \.group_id) { group in - DramaGroupView(group: group, model: indexModel) { - selectGroupId = group.group_id - indexModel.selectedDate = group.group_id - showDateNavPopover = true + ForEach(Array(indexModel.dramaGroupElements.enumerated()), id: \.offset) { idx, item in + switch item { + case .label(groupId: let groupId, groupName: let groupName): + DramaGroupLabelView(group_name: groupName) { + selectGroupId = groupId + indexModel.selectedDate = groupId + showDateNavPopover = true + } + case .item(groupId: let groupId, item: let item): + DramaGroupItemView(groupId: groupId, item: item) + .id(item.id) } - .contentShape(Rectangle()) } } + .scrollTargetLayout() ProgressView() - .background(GeometryReader { geometry in - let minY = geometry.frame(in: .global).minY - Color.clear.preference(key: FooterOffsetPreferenceKey.self, value: Int(minY)) - }) } .frame(width: 370) .coordinateSpace(name: "indexScrollView") - .onPreferenceChange(FooterOffsetPreferenceKey.self) { offset in - // 延迟到当前帧结束更新state的值,避免导致循环更新的警告 - DispatchQueue.main.async { - indexModel.loadMorePublisher.send((userId, offset)) - } - } + .scrollPosition(id: $scrollID) .refreshable { guard !self.showDateNavPopover && !self.headerRefreshing else { return @@ -100,6 +98,11 @@ struct IndexMainView: View { } } } + .onChange(of: scrollID) { _, newValue in + if let newValue { + indexModel.scrollIDPublisher.send((userId, newValue)) + } + } .overlay(alignment: .topTrailing) { HStack(alignment: .center) { Button(action: { @@ -139,9 +142,6 @@ struct IndexMainView: View { .task { await self.indexModel.loadData(userId: self.userId) } - .onPreferenceChange(DramaGroupElementPreferenceKey.self) { frames in - indexModel.visiblePublisher.send(frames) - } } } @@ -149,92 +149,63 @@ struct IndexMainView: View { extension IndexMainView { // 显示分组信息 - struct DramaGroupView: View { - @EnvironmentObject var appNav: AppNavigation - - let group: IndexModel.UpdateDramaGroup - let model: IndexModel - + struct DramaGroupLabelView: View { + let group_name: String var onTap: () -> Void var body: some View { VStack(alignment: .center, spacing: 10) { HStack { Spacer() - Text(group.group_name) + Text(group_name) .font(.system(size: 18)) .fontWeight(.regular) .onTapGesture { onTap() } } - - ForEach(group.items, id: \.id) { item in - Button(action: { - appNav.append(dest: .detail(id: item.id)) - }) { - FlexImage(urlString: item.thumb, width: 370, height: 180, placeholder: "ph_img_big") - .frame(width: 370, height: 180) - .overlay(alignment: .topLeading) { - VStack(alignment: .leading, spacing: 8) { - Text(item.name) - .font(.system(size: 16)) - .foregroundColor(.white) - .lineLimit(1) - - Text(item.status) - .font(.system(size: 12)) - .foregroundColor(.white) - .lineLimit(1) - } - .padding(5) - .background( - Color.black.opacity(0.6) - ) - .cornerRadius(5) - .padding(8) - } - .background(GeometryReader { geometry in - let height = geometry.size.height - let minY = geometry.frame(in: .named("indexScrollView")).minY - let y = minY >= 0 ? minY : minY + height - - Color.clear - .preference(key: DramaGroupElementPreferenceKey.self, value: [ group.group_id : y]) - }) - } - } } } } - -} + // 显示具体的item + struct DramaGroupItemView: View { + @EnvironmentObject var appNav: AppNavigation -// MARK: PreferenceKey -extension IndexMainView { - - // 元素的坐标的距离变化, [groupId : minY] - struct DramaGroupElementPreferenceKey: PreferenceKey { - static var defaultValue: [String: CGFloat] = [:] + let groupId: String + let item: IndexModel.UpdateDramaGroup.Item - static func reduce(value: inout [String: CGFloat], nextValue: () -> [String: CGFloat]) { - value.merge(nextValue()) { $1 } - } - } - - // 底部的元素的坐标信息变化 - struct FooterOffsetPreferenceKey: PreferenceKey { - static var defaultValue: Int = 0 - - static func reduce(value: inout Int, nextValue: () -> Int) { - value += nextValue() + var body: some View { + FlexImage(urlString: item.thumb, width: 370, height: 180, placeholder: "ph_img_big") + .frame(width: 370, height: 180) + .overlay(alignment: .topLeading) { + VStack(alignment: .leading, spacing: 8) { + Text(item.name) + .font(.system(size: 16)) + .foregroundColor(.white) + .lineLimit(1) + + Text(item.status) + .font(.system(size: 12)) + .foregroundColor(.white) + .lineLimit(1) + } + .padding(5) + .background( + Color.black.opacity(0.6) + ) + .cornerRadius(5) + .padding(8) + } + .contentShape(Rectangle()) + .onTapGesture { + appNav.append(dest: .detail(id: item.id)) + } } } } - #Preview { IndexMainView() } diff --git a/dimensionhub/Views/Index/IndexModel.swift b/dimensionhub/Views/Index/IndexModel.swift index dce9f79..4576a2b 100644 --- a/dimensionhub/Views/Index/IndexModel.swift +++ b/dimensionhub/Views/Index/IndexModel.swift @@ -49,11 +49,19 @@ final class IndexModel { let update_dramas: [UpdateDramaGroup] let follow_num: Int } - + + enum GroupElement { + case label(groupId: String, groupName: String) + case item(groupId: String, item: UpdateDramaGroup.Item) + } + var selectedDate: String // 保存原始的更新数据 var updateDramaGroups: [UpdateDramaGroup] = [] + + var dramaGroupElements: [GroupElement] = [] + var follow_num: String = "0" // 用来显示固定栏目的group_name @@ -73,13 +81,10 @@ final class IndexModel { // 用来追踪最近的更新时间 @ObservationIgnored private var updateInterval = Date() - - // 用来记录追踪当前footer的offset的变化 + + // 用来追踪scrollID的变化 @ObservationIgnored - var loadMorePublisher = PassthroughSubject<(String, Int), Never>() - - @ObservationIgnored - var visiblePublisher = PassthroughSubject<[String: CGFloat], Never>() + var scrollIDPublisher = PassthroughSubject<(String, Int), Never>() @ObservationIgnored private var bag = Set() @@ -87,16 +92,35 @@ final class IndexModel { init() { self.selectedDate = "" - self.loadMorePublisher - .debounce(for: 0.2, scheduler: RunLoop.main) - .dropFirst() - .sink { (userId, offset) in + self.scrollIDPublisher + .sink { userId, scrollID in + + self.dramaGroupElements.forEach { element in + switch element { + case .label(let groupId, let groupName): + () + case .item(let groupId, let item): + if item.id == scrollID { + DispatchQueue.main.async { + self.setFixedDrameGroup(groupId: groupId) + } + } + } + } + + let ids = self.dramaGroupElements.suffix(6).compactMap { element -> Int? in + switch element { + case .label(_, _): + return nil + case .item(groupId: _, item: let item): + return item.id + } + } + // 判断更新周期 let timeDistance = self.updateInterval.distance(to: Date()) // 滑动停止的时候,检测是否到达了底部 - let height = Int(UIScreen.main.bounds.height) - let distance = height - offset - if timeDistance > 1.0 && (distance >= 0 && distance < 50) { + if timeDistance > 1.0 && ids.contains(scrollID) { Task { await self.loadMoreUpdateDramasTask(userId: userId, mode: .next) } @@ -104,19 +128,6 @@ final class IndexModel { } } .store(in: &bag) - - self.visiblePublisher - .debounce(for: 0.05, scheduler: RunLoop.main) - .dropFirst() - .sink { frames in - let visibleFrames = frames.filter { $0.value >= 0} - if let minFrame = visibleFrames.min(by: { $0.value <= $1.value}) { - DispatchQueue.main.async { - self.setFixedDrameGroup(groupId: minFrame.key) - } - } - } - .store(in: &bag) } func loadData(userId: String) async { @@ -133,6 +144,8 @@ final class IndexModel { await MainActor.run { self.updateDramaGroups = result.update_dramas + self.dramaGroupElements = transformUpdateDramaGroups(groups: result.update_dramas) + self.fixedDramaGroup = result.update_dramas.first self.follow_num = result.follow_num >= 100 ? "99+" : "\(result.follow_num)" } @@ -169,6 +182,7 @@ final class IndexModel { displayDramaGroups(self.updateDramaGroups, label: "before") await MainActor.run { self.updateDramaGroups = preappendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups) + self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups) } displayDramaGroups(self.updateDramaGroups, label: "after") } @@ -186,6 +200,7 @@ final class IndexModel { displayDramaGroups(self.updateDramaGroups, label: "before") await MainActor.run { self.updateDramaGroups = appendMergeDramaGroups(groups: self.updateDramaGroups, mergeGroups: groups) + self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups) } displayDramaGroups(self.updateDramaGroups, label: "after") @@ -197,15 +212,30 @@ final class IndexModel { } } + // 转换成显示的元素信息 + private func transformUpdateDramaGroups(groups: [UpdateDramaGroup]) -> [GroupElement] { + var groupElements: [GroupElement] = [] + for group in groups { + groupElements.append(.label(groupId: group.group_id, groupName: group.group_name)) + for item in group.items { + groupElements.append(.item(groupId: group.group_id, item: item)) + } + } + return groupElements + } + // 指定日期,并更新日期下对应的数据 func loadDateUpdateDramas(userId: String, date: String) async { self.updateDramaGroups.removeAll() + self.dramaGroupElements.removeAll() + let response = await API.loadDateUpdateDramas(userId: userId, date: date, as: [UpdateDramaGroup].self) if case let .result(groups) = response { preloadGroupImages(groups: groups) await MainActor.run { self.updateDramaGroups = groups + self.dramaGroupElements = transformUpdateDramaGroups(groups: self.updateDramaGroups) self.fixedDramaGroup = groups.first } }