Anonymous
При переходе к домашнему представлению загрузка занимает много времени, а при переходе обратно из FullPostCell на перехо
Сообщение
Anonymous » 21 июн 2024, 18:53
Главное представление (или любое представление с заполненными сообщениями) работает очень медленно, и после отладки я обнаружил, что фильтрация НЕ является причиной проблемы. По какой-то причине на симуляторе он не медленный, но на реальном устройстве он очень медленный, особенно после загрузки постов.
Я тоже отлаживал кучу раз, но до сих пор не могу в этом разобраться проблема, в отладках ничего не указывает на ее причину, но я думаю, что это проблема с узким местом.
Код: Выделить всё
import SwiftUI
struct HomeView: View {
@EnvironmentObject var authViewModel: AuthViewModel
@StateObject var viewModel = PostViewModel()
@StateObject var followingViewModel = FollowingViewModel()
@StateObject var followViewModel = FollowViewModel()
@StateObject var profileViewModel = ProfileViewModel()
@StateObject var likeViewModel = LikeViewModel()
@StateObject var appData: AppData
@State private var selectedHomeTab: Int = 0
@State private var isLoading: Bool = true
@State private var lastViewedPostId: String? {
didSet {
if let id = lastViewedPostId {
UserDefaults.standard.set(id, forKey: "lastViewedPostId")
}
}
}
var body: some View {
VStack {
ToolbarModifier(viewTitle: .constant("Home"), appData: appData)
VStack {
HStack(spacing: 87) {
Text("For You")
.foregroundStyle(selectedHomeTab == 0 ? Color.black : Color.gray)
.font(.system(size: 15, weight: .semibold))
.offset(y: 10)
.onTapGesture {
withAnimation(.easeIn(duration: 0.2)) {
selectedHomeTab = 0
}
}
Text("Following")
.foregroundStyle(selectedHomeTab == 1 ? Color.black : Color.gray)
.font(.system(size: 15, weight: .semibold))
.offset(y: 10)
.onTapGesture {
withAnimation(.easeIn(duration: 0.2)) {
selectedHomeTab = 1
}
}
}
Rectangle()
.frame(width: 50, height: 5)
.foregroundStyle(Color(hex: "#5CE1E6"))
.cornerRadius(3)
.offset(x: selectedHomeTab == 0 ? -77.4 : 70.2, y: 7)
}
.animation(.easeIn(duration: 0.1), value: selectedHomeTab)
.padding(.top, 3)
TabView(selection: $selectedHomeTab) {
ScrollViewReader { scrollProxy in
ScrollView(showsIndicators: false) {
VStack(spacing: 14) {
if viewModel.posts.count > 0 {
let viewWeight: Double = 0.5
let timestampWeight: Double = 1.2
let likesWeight: Double = 0.9
let cutoffDate = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()
let sortedPosts = viewModel.posts
.lazy
.filter { !$0.user.privateProfile && $0.post.parentId.isEmpty && $0.post.groupId.isEmpty }
.sorted { (post1, post2) -> Bool in
let likesCount1 = likeViewModel.likes.filter { $0.like.postId == post1.post.id }.count
let likesCount2 = likeViewModel.likes.filter { $0.like.postId == post2.post.id }.count
let score1 = Double(post1.post.views) * viewWeight
+ post1.post.timestamp.timeIntervalSince(cutoffDate) / 3600 * timestampWeight
+ Double(likesCount1) * likesWeight
let score2 = Double(post2.post.views) * viewWeight
+ post2.post.timestamp.timeIntervalSince(cutoffDate) / 3600 * timestampWeight
+ Double(likesCount2) * likesWeight
return score1 > score2
}
ForEach(sortedPosts) { post in
PostCell(postWithUser: post, appData: appData)
.id(post.id)
.onAppear {
lastViewedPostId = post.id
}
}
.padding(.top, 17)
} else {
LoadingModifier()
.padding(.top, 230)
}
}
}
.tag(0)
.onAppear {
viewModel.fetchPosts()
if let savedId = lastViewedPostId {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation(.easeIn) {
scrollProxy.scrollTo(savedId, anchor: .center)
}
}
}
}
}
ScrollView(showsIndicators: false) {
if !isLoading {
VStack(spacing: 10) {
if viewModel.posts.count != 0 {
let followingPosts = viewModel.posts
.filter { $0.post.parentId.isEmpty && $0.post.groupId.isEmpty }
.filter { postWithUser in
followingViewModel.followings.contains { $0.user.id == postWithUser.user.id }
}
.sorted(by: { $0.post.timestamp > $1.post.timestamp })
if !followingPosts.isEmpty {
ForEach(followingPosts, id: \.post.id) { postWithUser in
PostCell(postWithUser: postWithUser, appData: appData)
}
.padding(.top, 17)
}
} else {
Image(systemName: "person.fill")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.fontWeight(.bold)
.foregroundStyle(Color(UIColor.systemGray2))
.padding(.top, 120)
Text("Your not following anyone yet.")
.foregroundStyle(Color(UIColor.systemGray2))
.font(.system(size: 16, weight: .semibold))
.padding(.bottom)
if appData.showFollowRecomendations {
ZStack {
Rectangle()
.foregroundStyle(Color(UIColor.systemGray6))
.frame(maxWidth: .infinity)
.frame(height: 245)
.cornerRadius(7)
.padding(.horizontal, 16)
VStack(spacing: 7) {
HStack {
Text("Frequently followed")
.foregroundStyle(Color(UIColor.systemGray2))
.font(.system(size: 14, weight: .regular))
Spacer()
Image(systemName: "xmark")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
.fontWeight(.bold)
.foregroundStyle(Color(UIColor.systemGray2))
.onTapGesture {
appData.showFollowRecomendations = false
}
}
.padding(.bottom, 7)
ForEach(profileViewModel.users
.sorted(by: { user1, user2 in
let user1FollowerCount = followViewModel.follows.filter { $0.follow.currentUserId == user1.id }.count
let user2FollowerCount = followViewModel.follows.filter { $0.follow.currentUserId == user2.id }.count
return user1FollowerCount > user2FollowerCount
}).prefix(3)) { user in
if user.id != profileViewModel.id {
NavigationLink(destination: UserProfileView(user: user)) {
ProfileCell(user: user)
}
} else {
NavigationLink(destination: ProfileView(appData: appData)) {
ProfileCell(user: user)
}
}
}
}
.padding(.horizontal, 43)
}
}
}
}
} else {
LoadingModifier()
.padding(.top, 80)
}
}
.tag(1)
.onAppear {
followingViewModel.fetchFollowings()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
isLoading = false
}
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.refreshable {
viewModel.refreshPosts()
viewModel.fetchPosts()
profileViewModel.fetchUsers()
}
}
.navigationBarBackButtonHidden(true)
.navigationTitle("")
.onAppear {
viewModel.fetchPosts()
profileViewModel.fetchUsers()
likeViewModel.fetchLikes()
appData.showMainNavBar = true
lastViewedPostId = UserDefaults.standard.string(forKey: "lastViewedPostId")
}
}
}
#Preview {
HomeView(appData: AppData())
}
Код: Выделить всё
import Foundation
import SwiftUI
import Firebase
class PostViewModel: ObservableObject {
@Published var posts: [PostWithUser] = []
let db = Firestore.firestore()
@MainActor
func fetchPosts() {
PostService.shared.fetchPosts { result in
DispatchQueue.main.async {
switch result {
case .success(let posts):
self.posts = posts
case .failure(let error):
print("Failed to fetch posts: \(error)")
}
}
}
}
func refreshPosts() {
PostService.shared.refreshPosts { result in
DispatchQueue.main.async {
switch result {
case .success(let posts):
self.posts = posts
case .failure(let error):
print("Failed to fetch posts: \(error)")
}
}
}
}
private func fetchUserProfiles(for posts: [Post]) {
let userIds = Set(posts.map { $0.userId })
UserService.shared.fetchUsers(userIds: Array(userIds)) { result in
DispatchQueue.main.async {
switch result {
case .success(let users):
let userDict = Dictionary(uniqueKeysWithValues: users.map { ($0.id, $0) })
self.posts = posts.map { post in
if let user = userDict[post.userId] {
return PostWithUser(post: post, user: user)
} else {
return PostWithUser(post: post, user: MockData.mockUser)
}
}
case .failure(let error):
print("Failed to fetch user profiles: \(error)")
}
}
}
}
func deletePost(postId: String) {
PostService.shared.deletePost(postId: postId) { result in
switch result {
case .success:
print("post deleted")
case .failure(let error):
print("Failed to delete post: \(error.localizedDescription)")
}
}
}
}
Код: Выделить всё
import Firebase
import FirebaseFirestore
import FirebaseStorage
import SwiftUI
class PostService {
static let shared = PostService()
let db = Firestore.firestore()
let postsRef = Firestore.firestore().collection("posts")
let usersRef = Firestore.firestore().collection("users")
let storageRef = Storage.storage().reference()
private var cachedPosts: [PostWithUser] = []
func savePost(text: String, image: UIImage?, videoURL: URL?, isPinned: Bool, parentId: String, groupId: String, completion: @escaping (Result) -> Void) {
guard let currentUser = Auth.auth().currentUser else {
completion(.failure(NSError(domain: "PostService", code: -1, userInfo: nil)))
return
}
let uid = currentUser.uid
let newPostRef = postsRef.document()
if let image = image {
uploadImage(image, postId: newPostRef.documentID) { result in
switch result {
case .success(let imageUrl):
if let videoURL = videoURL {
self.uploadVideo(videoURL, postId: newPostRef.documentID) { videoResult in
switch videoResult {
case .success(let videoUrl):
self.createPost(text: text, imageUrl: imageUrl, videoUrl: videoUrl, isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
} else {
self.createPost(text: text, imageUrl: imageUrl, videoUrl: "", isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion)
}
case .failure(let error):
completion(.failure(error))
}
}
} else if let videoURL = videoURL {
uploadVideo(videoURL, postId: newPostRef.documentID) { result in
switch result {
case .success(let videoUrl):
self.createPost(text: text, imageUrl: "", videoUrl: videoUrl, isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
} else {
self.createPost(text: text, imageUrl: "", videoUrl: "", isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion)
}
}
private func uploadImage(_ image: UIImage, postId: String, completion: @escaping (Result) -> Void) {
guard let imageData = image.jpegData(compressionQuality: 0.8) else {
completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Image conversion failed."])))
return
}
let imageRef = storageRef.child("post_images/\(postId).jpg")
imageRef.putData(imageData, metadata: nil) { metadata, error in
if let error = error {
completion(.failure(error))
return
}
imageRef.downloadURL { url, error in
if let error = error {
completion(.failure(error))
} else if let imageUrl = url?.absoluteString {
completion(.success(imageUrl))
} else {
completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get download URL."])))
}
}
}
}
private func uploadVideo(_ videoURL: URL, postId: String, completion: @escaping (Result) -> Void) {
let videoRef = storageRef.child("post_videos/\(postId).mp4")
print("Uploading video to \(videoRef.fullPath)")
do {
// Ensure the video file is accessible
let videoData = try Data(contentsOf: videoURL)
// Upload video using data
videoRef.putData(videoData, metadata: nil) { metadata, error in
if let error = error {
print("Failed to upload video: \(error.localizedDescription)")
completion(.failure(error))
return
}
videoRef.downloadURL { url, error in
if let error = error {
print("Failed to get video URL: \(error.localizedDescription)")
completion(.failure(error))
} else if let videoUrl = url?.absoluteString {
print("Video uploaded successfully: \(videoUrl)")
completion(.success(videoUrl))
} else {
print("Failed to get video URL: Unknown error")
completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get download URL."])))
}
}
}
} catch {
print("Failed to access video file: \(error.localizedDescription)")
completion(.failure(error))
}
}
private func createPost(text: String, imageUrl: String, videoUrl: String, isPinned: Bool, parentId: String, postId: String, groupId: String, uid: String, completion: @escaping (Result) -> Void) {
let postData: [String: Any] = [
"userId": uid,
"text": text,
"imageUrl": imageUrl,
"videoUrl": videoUrl,
"parentId": parentId,
"groupId": groupId,
"isPinned": isPinned,
"timestamp": FieldValue.serverTimestamp(),
"views": 0
]
postsRef.document(postId).setData(postData) { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
}
func incrementPostViewCount(postId: String) {
let postRef = postsRef.document(postId)
postRef.updateData(["views": FirebaseFirestore.FieldValue.increment(Int64(1))]) { error in
if let error = error {
print("Error updating views: \(error.localizedDescription)")
} else {
print("Views updated successfully!")
}
}
}
func fetchPosts(completion: @escaping (Result) -> Void) {
if !cachedPosts.isEmpty {
completion(.success(cachedPosts))
return
}
postsRef.order(by: "timestamp", descending: true).getDocuments { snapshot, error in
if let error = error {
completion(.failure(error))
return
}
guard let documents = snapshot?.documents else {
completion(.success([]))
return
}
let group = DispatchGroup()
var postsWithUsers: [PostWithUser] = []
for document in documents {
group.enter()
let data = document.data()
guard let userId = data["userId"] as? String,
let parentId = data["parentId"] as? String,
let groupId = data["groupId"] as? String,
let text = data["text"] as? String,
let imageUrl = data["imageUrl"] as? String,
let videoUrl = data["videoUrl"] as? String,
let isPinned = data["isPinned"] as? Bool,
let timestamp = data["timestamp"] as? Timestamp,
let views = data["views"] as? Int else {
group.leave()
continue
}
let post = Post(id: document.documentID, userId: userId, parentId: parentId, groupId: groupId, text: text, imageUrl: imageUrl, videoUrl: videoUrl, timestamp: timestamp.dateValue(), isPinned: isPinned, likedBy: [], views: views)
self.usersRef.document(userId).getDocument { userDocument, error in
defer { group.leave() }
if let userDocument = userDocument, let userData = userDocument.data() {
let user = User(
id: userId,
username: userData["username"] as? String ?? "",
bio: userData["bio"] as? String ?? "",
profilePictureUrl: userData["profileImageUrl"] as? String ?? "",
privateProfile: userData["privateProfile"] as? Bool ?? false,
privateFollowerList: userData["privateFollowerList"] as? Bool ?? false,
privateFollowingList: userData["privateFollowingList"] as? Bool ?? false,
privateReplies: userData["privateReplies"] as? Bool ?? false,
privateLikes: userData["privateLikes"] as? Bool ?? false
)
postsWithUsers.append(PostWithUser(post: post, user: user))
} else {
print("Failed to fetch user data for userId: \(userId), error: \(String(describing: error))")
}
}
}
group.notify(queue: .main) {
self.cachedPosts = postsWithUsers
completion(.success(postsWithUsers))
}
}
}
func refreshPosts(completion: @escaping (Result) -> Void) {
cachedPosts = []
fetchPosts { result in
print("posts refreshed")
}
}
func deletePost(postId: String, completion: @escaping (Result) -> Void) {
let postRef = postsRef.document(postId)
postRef.getDocument { document, error in
if let error = error {
completion(.failure(error))
return
}
guard let document = document, document.exists else {
completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Post not found."])))
return
}
let data = document.data()
if data?["imageUrl"] is String {
let imageRef = self.storageRef.child("post_images/\(postId).jpg")
imageRef.delete { error in
if let error = error {
print("Failed to delete image: \(error.localizedDescription)")
}
}
}
postRef.delete { error in
if let error = error {
completion(.failure(error))
} else {
self.cachedPosts.removeAll { $0.post.id == postId }
completion(.success(()))
}
}
}
}
}
Я уже несколько часов сталкиваюсь с этой проблемой. Может кто-нибудь помочь?
Подробнее здесь:
https://stackoverflow.com/questions/786 ... en-navigat
1718985184
Anonymous
Главное представление (или любое представление с заполненными сообщениями) работает очень медленно, и после отладки я обнаружил, что фильтрация НЕ является причиной проблемы. По какой-то причине на симуляторе он не медленный, но на реальном устройстве он очень медленный, особенно после загрузки постов. Я тоже отлаживал кучу раз, но до сих пор не могу в этом разобраться проблема, в отладках ничего не указывает на ее причину, но я думаю, что это проблема с узким местом. [code]import SwiftUI struct HomeView: View { @EnvironmentObject var authViewModel: AuthViewModel @StateObject var viewModel = PostViewModel() @StateObject var followingViewModel = FollowingViewModel() @StateObject var followViewModel = FollowViewModel() @StateObject var profileViewModel = ProfileViewModel() @StateObject var likeViewModel = LikeViewModel() @StateObject var appData: AppData @State private var selectedHomeTab: Int = 0 @State private var isLoading: Bool = true @State private var lastViewedPostId: String? { didSet { if let id = lastViewedPostId { UserDefaults.standard.set(id, forKey: "lastViewedPostId") } } } var body: some View { VStack { ToolbarModifier(viewTitle: .constant("Home"), appData: appData) VStack { HStack(spacing: 87) { Text("For You") .foregroundStyle(selectedHomeTab == 0 ? Color.black : Color.gray) .font(.system(size: 15, weight: .semibold)) .offset(y: 10) .onTapGesture { withAnimation(.easeIn(duration: 0.2)) { selectedHomeTab = 0 } } Text("Following") .foregroundStyle(selectedHomeTab == 1 ? Color.black : Color.gray) .font(.system(size: 15, weight: .semibold)) .offset(y: 10) .onTapGesture { withAnimation(.easeIn(duration: 0.2)) { selectedHomeTab = 1 } } } Rectangle() .frame(width: 50, height: 5) .foregroundStyle(Color(hex: "#5CE1E6")) .cornerRadius(3) .offset(x: selectedHomeTab == 0 ? -77.4 : 70.2, y: 7) } .animation(.easeIn(duration: 0.1), value: selectedHomeTab) .padding(.top, 3) TabView(selection: $selectedHomeTab) { ScrollViewReader { scrollProxy in ScrollView(showsIndicators: false) { VStack(spacing: 14) { if viewModel.posts.count > 0 { let viewWeight: Double = 0.5 let timestampWeight: Double = 1.2 let likesWeight: Double = 0.9 let cutoffDate = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date() let sortedPosts = viewModel.posts .lazy .filter { !$0.user.privateProfile && $0.post.parentId.isEmpty && $0.post.groupId.isEmpty } .sorted { (post1, post2) -> Bool in let likesCount1 = likeViewModel.likes.filter { $0.like.postId == post1.post.id }.count let likesCount2 = likeViewModel.likes.filter { $0.like.postId == post2.post.id }.count let score1 = Double(post1.post.views) * viewWeight + post1.post.timestamp.timeIntervalSince(cutoffDate) / 3600 * timestampWeight + Double(likesCount1) * likesWeight let score2 = Double(post2.post.views) * viewWeight + post2.post.timestamp.timeIntervalSince(cutoffDate) / 3600 * timestampWeight + Double(likesCount2) * likesWeight return score1 > score2 } ForEach(sortedPosts) { post in PostCell(postWithUser: post, appData: appData) .id(post.id) .onAppear { lastViewedPostId = post.id } } .padding(.top, 17) } else { LoadingModifier() .padding(.top, 230) } } } .tag(0) .onAppear { viewModel.fetchPosts() if let savedId = lastViewedPostId { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { withAnimation(.easeIn) { scrollProxy.scrollTo(savedId, anchor: .center) } } } } } ScrollView(showsIndicators: false) { if !isLoading { VStack(spacing: 10) { if viewModel.posts.count != 0 { let followingPosts = viewModel.posts .filter { $0.post.parentId.isEmpty && $0.post.groupId.isEmpty } .filter { postWithUser in followingViewModel.followings.contains { $0.user.id == postWithUser.user.id } } .sorted(by: { $0.post.timestamp > $1.post.timestamp }) if !followingPosts.isEmpty { ForEach(followingPosts, id: \.post.id) { postWithUser in PostCell(postWithUser: postWithUser, appData: appData) } .padding(.top, 17) } } else { Image(systemName: "person.fill") .resizable() .scaledToFit() .frame(width: 30, height: 30) .fontWeight(.bold) .foregroundStyle(Color(UIColor.systemGray2)) .padding(.top, 120) Text("Your not following anyone yet.") .foregroundStyle(Color(UIColor.systemGray2)) .font(.system(size: 16, weight: .semibold)) .padding(.bottom) if appData.showFollowRecomendations { ZStack { Rectangle() .foregroundStyle(Color(UIColor.systemGray6)) .frame(maxWidth: .infinity) .frame(height: 245) .cornerRadius(7) .padding(.horizontal, 16) VStack(spacing: 7) { HStack { Text("Frequently followed") .foregroundStyle(Color(UIColor.systemGray2)) .font(.system(size: 14, weight: .regular)) Spacer() Image(systemName: "xmark") .resizable() .scaledToFit() .frame(width: 14, height: 14) .fontWeight(.bold) .foregroundStyle(Color(UIColor.systemGray2)) .onTapGesture { appData.showFollowRecomendations = false } } .padding(.bottom, 7) ForEach(profileViewModel.users .sorted(by: { user1, user2 in let user1FollowerCount = followViewModel.follows.filter { $0.follow.currentUserId == user1.id }.count let user2FollowerCount = followViewModel.follows.filter { $0.follow.currentUserId == user2.id }.count return user1FollowerCount > user2FollowerCount }).prefix(3)) { user in if user.id != profileViewModel.id { NavigationLink(destination: UserProfileView(user: user)) { ProfileCell(user: user) } } else { NavigationLink(destination: ProfileView(appData: appData)) { ProfileCell(user: user) } } } } .padding(.horizontal, 43) } } } } } else { LoadingModifier() .padding(.top, 80) } } .tag(1) .onAppear { followingViewModel.fetchFollowings() DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { isLoading = false } } } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) .refreshable { viewModel.refreshPosts() viewModel.fetchPosts() profileViewModel.fetchUsers() } } .navigationBarBackButtonHidden(true) .navigationTitle("") .onAppear { viewModel.fetchPosts() profileViewModel.fetchUsers() likeViewModel.fetchLikes() appData.showMainNavBar = true lastViewedPostId = UserDefaults.standard.string(forKey: "lastViewedPostId") } } } #Preview { HomeView(appData: AppData()) } [/code] [code]import Foundation import SwiftUI import Firebase class PostViewModel: ObservableObject { @Published var posts: [PostWithUser] = [] let db = Firestore.firestore() @MainActor func fetchPosts() { PostService.shared.fetchPosts { result in DispatchQueue.main.async { switch result { case .success(let posts): self.posts = posts case .failure(let error): print("Failed to fetch posts: \(error)") } } } } func refreshPosts() { PostService.shared.refreshPosts { result in DispatchQueue.main.async { switch result { case .success(let posts): self.posts = posts case .failure(let error): print("Failed to fetch posts: \(error)") } } } } private func fetchUserProfiles(for posts: [Post]) { let userIds = Set(posts.map { $0.userId }) UserService.shared.fetchUsers(userIds: Array(userIds)) { result in DispatchQueue.main.async { switch result { case .success(let users): let userDict = Dictionary(uniqueKeysWithValues: users.map { ($0.id, $0) }) self.posts = posts.map { post in if let user = userDict[post.userId] { return PostWithUser(post: post, user: user) } else { return PostWithUser(post: post, user: MockData.mockUser) } } case .failure(let error): print("Failed to fetch user profiles: \(error)") } } } } func deletePost(postId: String) { PostService.shared.deletePost(postId: postId) { result in switch result { case .success: print("post deleted") case .failure(let error): print("Failed to delete post: \(error.localizedDescription)") } } } } [/code] [code]import Firebase import FirebaseFirestore import FirebaseStorage import SwiftUI class PostService { static let shared = PostService() let db = Firestore.firestore() let postsRef = Firestore.firestore().collection("posts") let usersRef = Firestore.firestore().collection("users") let storageRef = Storage.storage().reference() private var cachedPosts: [PostWithUser] = [] func savePost(text: String, image: UIImage?, videoURL: URL?, isPinned: Bool, parentId: String, groupId: String, completion: @escaping (Result) -> Void) { guard let currentUser = Auth.auth().currentUser else { completion(.failure(NSError(domain: "PostService", code: -1, userInfo: nil))) return } let uid = currentUser.uid let newPostRef = postsRef.document() if let image = image { uploadImage(image, postId: newPostRef.documentID) { result in switch result { case .success(let imageUrl): if let videoURL = videoURL { self.uploadVideo(videoURL, postId: newPostRef.documentID) { videoResult in switch videoResult { case .success(let videoUrl): self.createPost(text: text, imageUrl: imageUrl, videoUrl: videoUrl, isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion) case .failure(let error): completion(.failure(error)) } } } else { self.createPost(text: text, imageUrl: imageUrl, videoUrl: "", isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion) } case .failure(let error): completion(.failure(error)) } } } else if let videoURL = videoURL { uploadVideo(videoURL, postId: newPostRef.documentID) { result in switch result { case .success(let videoUrl): self.createPost(text: text, imageUrl: "", videoUrl: videoUrl, isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion) case .failure(let error): completion(.failure(error)) } } } else { self.createPost(text: text, imageUrl: "", videoUrl: "", isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion) } } private func uploadImage(_ image: UIImage, postId: String, completion: @escaping (Result) -> Void) { guard let imageData = image.jpegData(compressionQuality: 0.8) else { completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Image conversion failed."]))) return } let imageRef = storageRef.child("post_images/\(postId).jpg") imageRef.putData(imageData, metadata: nil) { metadata, error in if let error = error { completion(.failure(error)) return } imageRef.downloadURL { url, error in if let error = error { completion(.failure(error)) } else if let imageUrl = url?.absoluteString { completion(.success(imageUrl)) } else { completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get download URL."]))) } } } } private func uploadVideo(_ videoURL: URL, postId: String, completion: @escaping (Result) -> Void) { let videoRef = storageRef.child("post_videos/\(postId).mp4") print("Uploading video to \(videoRef.fullPath)") do { // Ensure the video file is accessible let videoData = try Data(contentsOf: videoURL) // Upload video using data videoRef.putData(videoData, metadata: nil) { metadata, error in if let error = error { print("Failed to upload video: \(error.localizedDescription)") completion(.failure(error)) return } videoRef.downloadURL { url, error in if let error = error { print("Failed to get video URL: \(error.localizedDescription)") completion(.failure(error)) } else if let videoUrl = url?.absoluteString { print("Video uploaded successfully: \(videoUrl)") completion(.success(videoUrl)) } else { print("Failed to get video URL: Unknown error") completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get download URL."]))) } } } } catch { print("Failed to access video file: \(error.localizedDescription)") completion(.failure(error)) } } private func createPost(text: String, imageUrl: String, videoUrl: String, isPinned: Bool, parentId: String, postId: String, groupId: String, uid: String, completion: @escaping (Result) -> Void) { let postData: [String: Any] = [ "userId": uid, "text": text, "imageUrl": imageUrl, "videoUrl": videoUrl, "parentId": parentId, "groupId": groupId, "isPinned": isPinned, "timestamp": FieldValue.serverTimestamp(), "views": 0 ] postsRef.document(postId).setData(postData) { error in if let error = error { completion(.failure(error)) } else { completion(.success(())) } } } func incrementPostViewCount(postId: String) { let postRef = postsRef.document(postId) postRef.updateData(["views": FirebaseFirestore.FieldValue.increment(Int64(1))]) { error in if let error = error { print("Error updating views: \(error.localizedDescription)") } else { print("Views updated successfully!") } } } func fetchPosts(completion: @escaping (Result) -> Void) { if !cachedPosts.isEmpty { completion(.success(cachedPosts)) return } postsRef.order(by: "timestamp", descending: true).getDocuments { snapshot, error in if let error = error { completion(.failure(error)) return } guard let documents = snapshot?.documents else { completion(.success([])) return } let group = DispatchGroup() var postsWithUsers: [PostWithUser] = [] for document in documents { group.enter() let data = document.data() guard let userId = data["userId"] as? String, let parentId = data["parentId"] as? String, let groupId = data["groupId"] as? String, let text = data["text"] as? String, let imageUrl = data["imageUrl"] as? String, let videoUrl = data["videoUrl"] as? String, let isPinned = data["isPinned"] as? Bool, let timestamp = data["timestamp"] as? Timestamp, let views = data["views"] as? Int else { group.leave() continue } let post = Post(id: document.documentID, userId: userId, parentId: parentId, groupId: groupId, text: text, imageUrl: imageUrl, videoUrl: videoUrl, timestamp: timestamp.dateValue(), isPinned: isPinned, likedBy: [], views: views) self.usersRef.document(userId).getDocument { userDocument, error in defer { group.leave() } if let userDocument = userDocument, let userData = userDocument.data() { let user = User( id: userId, username: userData["username"] as? String ?? "", bio: userData["bio"] as? String ?? "", profilePictureUrl: userData["profileImageUrl"] as? String ?? "", privateProfile: userData["privateProfile"] as? Bool ?? false, privateFollowerList: userData["privateFollowerList"] as? Bool ?? false, privateFollowingList: userData["privateFollowingList"] as? Bool ?? false, privateReplies: userData["privateReplies"] as? Bool ?? false, privateLikes: userData["privateLikes"] as? Bool ?? false ) postsWithUsers.append(PostWithUser(post: post, user: user)) } else { print("Failed to fetch user data for userId: \(userId), error: \(String(describing: error))") } } } group.notify(queue: .main) { self.cachedPosts = postsWithUsers completion(.success(postsWithUsers)) } } } func refreshPosts(completion: @escaping (Result) -> Void) { cachedPosts = [] fetchPosts { result in print("posts refreshed") } } func deletePost(postId: String, completion: @escaping (Result) -> Void) { let postRef = postsRef.document(postId) postRef.getDocument { document, error in if let error = error { completion(.failure(error)) return } guard let document = document, document.exists else { completion(.failure(NSError(domain: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Post not found."]))) return } let data = document.data() if data?["imageUrl"] is String { let imageRef = self.storageRef.child("post_images/\(postId).jpg") imageRef.delete { error in if let error = error { print("Failed to delete image: \(error.localizedDescription)") } } } postRef.delete { error in if let error = error { completion(.failure(error)) } else { self.cachedPosts.removeAll { $0.post.id == postId } completion(.success(())) } } } } } [/code] Я уже несколько часов сталкиваюсь с этой проблемой. Может кто-нибудь помочь? Подробнее здесь: [url]https://stackoverflow.com/questions/78653216/when-navigating-to-the-home-view-it-takes-a-long-time-to-load-and-when-navigat[/url]