The Residence View (or any view with posts populated) could be very gradual, and after debugging, I discovered that the filtering was NOT inflicting the issue. For some cause, it is not gradual on the simulator, however on an precise gadget it is vitally gradual, particularly after posts loading.
I additionally debugged a bunch of occasions, and I am nonetheless unable to determine this downside, nothing within the debugs displaying what’s inflicting it, however my guess is its a bottlenecking downside.
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 non-public var selectedHomeTab: Int = 0
@State non-public var isLoading: Bool = true
@State non-public var lastViewedPostId: String? {
didSet {
if let id = lastViewedPostId {
UserDefaults.normal.set(id, forKey: "lastViewedPostId")
}
}
}
var physique: some View {
VStack {
ToolbarModifier(viewTitle: .fixed("Residence"), appData: appData)
VStack {
HStack(spacing: 87) {
Textual content("For You")
.foregroundStyle(selectedHomeTab == 0 ? Coloration.black : Coloration.grey)
.font(.system(measurement: 15, weight: .semibold))
.offset(y: 10)
.onTapGesture {
withAnimation(.easeIn(length: 0.2)) {
selectedHomeTab = 0
}
}
Textual content("Following")
.foregroundStyle(selectedHomeTab == 1 ? Coloration.black : Coloration.grey)
.font(.system(measurement: 15, weight: .semibold))
.offset(y: 10)
.onTapGesture {
withAnimation(.easeIn(length: 0.2)) {
selectedHomeTab = 1
}
}
}
Rectangle()
.body(width: 50, peak: 5)
.foregroundStyle(Coloration(hex: "#5CE1E6"))
.cornerRadius(3)
.offset(x: selectedHomeTab == 0 ? -77.4 : 70.2, y: 7)
}
.animation(.easeIn(length: 0.1), worth: selectedHomeTab)
.padding(.high, 3)
TabView(choice: $selectedHomeTab) {
ScrollViewReader { scrollProxy in
ScrollView(showsIndicators: false) {
VStack(spacing: 14) {
if viewModel.posts.rely > 0 {
let viewWeight: Double = 0.5
let timestampWeight: Double = 1.2
let likesWeight: Double = 0.9
let cutoffDate = Calendar.present.date(byAdding: .day, worth: -1, to: Date()) ?? Date()
let sortedPosts = viewModel.posts
.lazy
.filter { !$0.consumer.privateProfile && $0.submit.parentId.isEmpty && $0.submit.groupId.isEmpty }
.sorted { (post1, post2) -> Bool in
let likesCount1 = likeViewModel.likes.filter { $0.like.postId == post1.submit.id }.rely
let likesCount2 = likeViewModel.likes.filter { $0.like.postId == post2.submit.id }.rely
let score1 = Double(post1.submit.views) * viewWeight
+ post1.submit.timestamp.timeIntervalSince(cutoffDate) / 3600 * timestampWeight
+ Double(likesCount1) * likesWeight
let score2 = Double(post2.submit.views) * viewWeight
+ post2.submit.timestamp.timeIntervalSince(cutoffDate) / 3600 * timestampWeight
+ Double(likesCount2) * likesWeight
return score1 > score2
}
ForEach(sortedPosts) { submit in
PostCell(postWithUser: submit, appData: appData)
.id(submit.id)
.onAppear {
lastViewedPostId = submit.id
}
}
.padding(.high, 17)
} else {
LoadingModifier()
.padding(.high, 230)
}
}
}
.tag(0)
.onAppear {
viewModel.fetchPosts()
if let savedId = lastViewedPostId {
DispatchQueue.primary.asyncAfter(deadline: .now() + 0.1) {
withAnimation(.easeIn) {
scrollProxy.scrollTo(savedId, anchor: .middle)
}
}
}
}
}
ScrollView(showsIndicators: false) {
if !isLoading {
VStack(spacing: 10) {
if viewModel.posts.rely != 0 {
let followingPosts = viewModel.posts
.filter { $0.submit.parentId.isEmpty && $0.submit.groupId.isEmpty }
.filter { postWithUser in
followingViewModel.followings.accommodates { $0.consumer.id == postWithUser.consumer.id }
}
.sorted(by: { $0.submit.timestamp > $1.submit.timestamp })
if !followingPosts.isEmpty {
ForEach(followingPosts, id: .submit.id) { postWithUser in
PostCell(postWithUser: postWithUser, appData: appData)
}
.padding(.high, 17)
}
} else {
Picture(systemName: "individual.fill")
.resizable()
.scaledToFit()
.body(width: 30, peak: 30)
.fontWeight(.daring)
.foregroundStyle(Coloration(UIColor.systemGray2))
.padding(.high, 120)
Textual content("Your not following anybody but.")
.foregroundStyle(Coloration(UIColor.systemGray2))
.font(.system(measurement: 16, weight: .semibold))
.padding(.backside)
if appData.showFollowRecomendations {
ZStack {
Rectangle()
.foregroundStyle(Coloration(UIColor.systemGray6))
.body(maxWidth: .infinity)
.body(peak: 245)
.cornerRadius(7)
.padding(.horizontal, 16)
VStack(spacing: 7) {
HStack {
Textual content("Continuously adopted")
.foregroundStyle(Coloration(UIColor.systemGray2))
.font(.system(measurement: 14, weight: .common))
Spacer()
Picture(systemName: "xmark")
.resizable()
.scaledToFit()
.body(width: 14, peak: 14)
.fontWeight(.daring)
.foregroundStyle(Coloration(UIColor.systemGray2))
.onTapGesture {
appData.showFollowRecomendations = false
}
}
.padding(.backside, 7)
ForEach(profileViewModel.customers
.sorted(by: { user1, user2 in
let user1FollowerCount = followViewModel.follows.filter { $0.observe.currentUserId == user1.id }.rely
let user2FollowerCount = followViewModel.follows.filter { $0.observe.currentUserId == user2.id }.rely
return user1FollowerCount > user2FollowerCount
}).prefix(3)) { consumer in
if consumer.id != profileViewModel.id {
NavigationLink(vacation spot: UserProfileView(consumer: consumer)) {
ProfileCell(consumer: consumer)
}
} else {
NavigationLink(vacation spot: ProfileView(appData: appData)) {
ProfileCell(consumer: consumer)
}
}
}
}
.padding(.horizontal, 43)
}
}
}
}
} else {
LoadingModifier()
.padding(.high, 80)
}
}
.tag(1)
.onAppear {
followingViewModel.fetchFollowings()
DispatchQueue.primary.asyncAfter(deadline: .now() + 0.2) {
isLoading = false
}
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .by no means))
.refreshable {
viewModel.refreshPosts()
viewModel.fetchPosts()
profileViewModel.fetchUsers()
}
}
.navigationBarBackButtonHidden(true)
.navigationTitle("")
.onAppear {
viewModel.fetchPosts()
profileViewModel.fetchUsers()
likeViewModel.fetchLikes()
appData.showMainNavBar = true
lastViewedPostId = UserDefaults.normal.string(forKey: "lastViewedPostId")
}
}
}
#Preview {
HomeView(appData: AppData())
}
import Basis
import SwiftUI
import Firebase
class PostViewModel: ObservableObject {
@Revealed var posts: [PostWithUser] = []
let db = Firestore.firestore()
@MainActor
func fetchPosts() {
PostService.shared.fetchPosts { lead to
DispatchQueue.primary.async {
swap outcome {
case .success(let posts):
self.posts = posts
case .failure(let error):
print("Did not fetch posts: (error)")
}
}
}
}
func refreshPosts() {
PostService.shared.refreshPosts { lead to
DispatchQueue.primary.async {
swap outcome {
case .success(let posts):
self.posts = posts
case .failure(let error):
print("Did not fetch posts: (error)")
}
}
}
}
non-public func fetchUserProfiles(for posts: [Post]) {
let userIds = Set(posts.map { $0.userId })
UserService.shared.fetchUsers(userIds: Array(userIds)) { lead to
DispatchQueue.primary.async {
swap outcome {
case .success(let customers):
let userDict = Dictionary(uniqueKeysWithValues: customers.map { ($0.id, $0) })
self.posts = posts.map { submit in
if let consumer = userDict[post.userId] {
return PostWithUser(submit: submit, consumer: consumer)
} else {
return PostWithUser(submit: submit, consumer: MockData.mockUser)
}
}
case .failure(let error):
print("Did not fetch consumer profiles: (error)")
}
}
}
}
func deletePost(postId: String) {
PostService.shared.deletePost(postId: postId) { lead to
swap outcome {
case .success:
print("submit deleted")
case .failure(let error):
print("Did not delete submit: (error.localizedDescription)")
}
}
}
}
import Firebase
import FirebaseFirestore
import FirebaseStorage
import SwiftUI
class PostService {
static let shared = PostService()
let db = Firestore.firestore()
let postsRef = Firestore.firestore().assortment("posts")
let usersRef = Firestore.firestore().assortment("customers")
let storageRef = Storage.storage().reference()
non-public var cachedPosts: [PostWithUser] = []
func savePost(textual content: String, picture: UIImage?, videoURL: URL?, isPinned: Bool, parentId: String, groupId: String, completion: @escaping (End result<Void, Error>) -> Void) {
guard let currentUser = Auth.auth().currentUser else {
completion(.failure(NSError(area: "PostService", code: -1, userInfo: nil)))
return
}
let uid = currentUser.uid
let newPostRef = postsRef.doc()
if let picture = picture {
uploadImage(picture, postId: newPostRef.documentID) { lead to
swap outcome {
case .success(let imageUrl):
if let videoURL = videoURL {
self.uploadVideo(videoURL, postId: newPostRef.documentID) { videoResult in
swap videoResult {
case .success(let videoUrl):
self.createPost(textual content: textual content, 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(textual content: textual content, 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) { lead to
swap outcome {
case .success(let videoUrl):
self.createPost(textual content: textual content, 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(textual content: textual content, imageUrl: "", videoUrl: "", isPinned: isPinned, parentId: parentId, postId: newPostRef.documentID, groupId: groupId, uid: uid, completion: completion)
}
}
non-public func uploadImage(_ picture: UIImage, postId: String, completion: @escaping (End result<String, Error>) -> Void) {
guard let imageData = picture.jpegData(compressionQuality: 0.8) else {
completion(.failure(NSError(area: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Image conversion failed."])))
return
}
let imageRef = storageRef.youngster("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(area: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get download URL."])))
}
}
}
}
non-public func uploadVideo(_ videoURL: URL, postId: String, completion: @escaping (End result<String, Error>) -> Void) {
let videoRef = storageRef.youngster("post_videos/(postId).mp4")
print("Importing video to (videoRef.fullPath)")
do {
// Make sure the video file is accessible
let videoData = attempt Knowledge(contentsOf: videoURL)
// Add video utilizing information
videoRef.putData(videoData, metadata: nil) { metadata, error in
if let error = error {
print("Did not add video: (error.localizedDescription)")
completion(.failure(error))
return
}
videoRef.downloadURL { url, error in
if let error = error {
print("Did not get video URL: (error.localizedDescription)")
completion(.failure(error))
} else if let videoUrl = url?.absoluteString {
print("Video uploaded efficiently: (videoUrl)")
completion(.success(videoUrl))
} else {
print("Did not get video URL: Unknown error")
completion(.failure(NSError(area: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get download URL."])))
}
}
}
} catch {
print("Did not entry video file: (error.localizedDescription)")
completion(.failure(error))
}
}
non-public func createPost(textual content: String, imageUrl: String, videoUrl: String, isPinned: Bool, parentId: String, postId: String, groupId: String, uid: String, completion: @escaping (End result<Void, Error>) -> 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.doc(postId).setData(postData) { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
}
func incrementPostViewCount(postId: String) {
let postRef = postsRef.doc(postId)
postRef.updateData(["views": FirebaseFirestore.FieldValue.increment(Int64(1))]) { error in
if let error = error {
print("Error updating views: (error.localizedDescription)")
} else {
print("Views up to date efficiently!")
}
}
}
func fetchPosts(completion: @escaping (End result<[PostWithUser], Error>) -> 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 paperwork = snapshot?.paperwork else {
completion(.success([]))
return
}
let group = DispatchGroup()
var postsWithUsers: [PostWithUser] = []
for doc in paperwork {
group.enter()
let information = doc.information()
guard let userId = information["userId"] as? String,
let parentId = information["parentId"] as? String,
let groupId = information["groupId"] as? String,
let textual content = information["text"] as? String,
let imageUrl = information["imageUrl"] as? String,
let videoUrl = information["videoUrl"] as? String,
let isPinned = information["isPinned"] as? Bool,
let timestamp = information["timestamp"] as? Timestamp,
let views = information["views"] as? Int else {
group.go away()
proceed
}
let submit = Submit(id: doc.documentID, userId: userId, parentId: parentId, groupId: groupId, textual content: textual content, imageUrl: imageUrl, videoUrl: videoUrl, timestamp: timestamp.dateValue(), isPinned: isPinned, likedBy: [], views: views)
self.usersRef.doc(userId).getDocument { userDocument, error in
defer { group.go away() }
if let userDocument = userDocument, let userData = userDocument.information() {
let consumer = Person(
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(submit: submit, consumer: consumer))
} else {
print("Did not fetch consumer information for userId: (userId), error: (String(describing: error))")
}
}
}
group.notify(queue: .primary) {
self.cachedPosts = postsWithUsers
completion(.success(postsWithUsers))
}
}
}
func refreshPosts(completion: @escaping (End result<[PostWithUser], Error>) -> Void) {
cachedPosts = []
fetchPosts { lead to
print("posts refreshed")
}
}
func deletePost(postId: String, completion: @escaping (End result<Void, Error>) -> Void) {
let postRef = postsRef.doc(postId)
postRef.getDocument { doc, error in
if let error = error {
completion(.failure(error))
return
}
guard let doc = doc, doc.exists else {
completion(.failure(NSError(area: "PostService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Post not found."])))
return
}
let information = doc.information()
if information?["imageUrl"] is String {
let imageRef = self.storageRef.youngster("post_images/(postId).jpg")
imageRef.delete { error in
if let error = error {
print("Did not delete picture: (error.localizedDescription)")
}
}
}
postRef.delete { error in
if let error = error {
completion(.failure(error))
} else {
self.cachedPosts.removeAll { $0.submit.id == postId }
completion(.success(()))
}
}
}
}
}
I have been experiencing this subject for hours now, can somebody please assist?