diff --git a/Shared/Components/RefreshableScrollView.swift b/Shared/Components/RefreshableScrollView.swift index b6c3c4b..a0abf31 100644 --- a/Shared/Components/RefreshableScrollView.swift +++ b/Shared/Components/RefreshableScrollView.swift @@ -3,10 +3,10 @@ import UIKit struct RefreshableScrollView: UIViewRepresentable { var content: Content - var onRefresh: (UIRefreshControl) -> Void + var onRefresh: () -> Void var isRefreshing: Binding - init(isRefreshing: Binding, onRefresh: @escaping (UIRefreshControl) -> Void, @ViewBuilder content: () -> Content) { + init(isRefreshing: Binding, onRefresh: @escaping () -> Void, @ViewBuilder content: () -> Content) { self.content = content() self.onRefresh = onRefresh self.isRefreshing = isRefreshing @@ -15,17 +15,14 @@ struct RefreshableScrollView: UIViewRepresentable { func makeUIView(context: Context) -> UIScrollView { let scrollView = UIScrollView() - // Создаем UIRefreshControl и добавляем его let refreshControl = UIRefreshControl() refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged) scrollView.refreshControl = refreshControl - // Создаем хостинг для нашего SwiftUI контента let hostingController = UIHostingController(rootView: content) hostingController.view.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(hostingController.view) - // Настраиваем Auto Layout NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), @@ -40,14 +37,15 @@ struct RefreshableScrollView: UIViewRepresentable { } func updateUIView(_ uiView: UIScrollView, context: Context) { - // Обновляем состояние индикатора if isRefreshing.wrappedValue { uiView.refreshControl?.beginRefreshing() } else { - uiView.refreshControl?.endRefreshing() + // Отложенное завершение, чтобы избежать цикла обновлений + DispatchQueue.main.async { + uiView.refreshControl?.endRefreshing() + } } - // Обновляем SwiftUI View, если нужно context.coordinator.hostingController?.rootView = content } @@ -63,9 +61,8 @@ struct RefreshableScrollView: UIViewRepresentable { self.parent = parent } - @objc func handleRefresh(_ sender: UIRefreshControl) { - parent.isRefreshing.wrappedValue = true - parent.onRefresh(sender) + @objc func handleRefresh() { + parent.onRefresh() } } } diff --git a/Shared/Views/Tab/HomeTab.swift b/Shared/Views/Tab/HomeTab.swift index aa5215d..5a1e661 100644 --- a/Shared/Views/Tab/HomeTab.swift +++ b/Shared/Views/Tab/HomeTab.swift @@ -11,7 +11,7 @@ struct HomeTab: View { if isLoading { ProgressView("Загрузка ленты...") } else { - RefreshableScrollView(isRefreshing: $isRefreshing, onRefresh: { _ in + RefreshableScrollView(isRefreshing: $isRefreshing, onRefresh: { fetchData() }) { LazyVStack(spacing: 24) { diff --git a/Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift b/Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift index dc21a51..b5f38ea 100644 --- a/Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift +++ b/Shared/Views/Tab/Profile/ProfileContentTabbedGrid.swift @@ -2,12 +2,12 @@ import SwiftUI struct ProfileContentTabbedGrid: View { let isContentLoaded: Bool + @Binding var allPosts: [Post] + @Binding var isLoading: Bool @State private var selectedTabIndex = 0 @State private var selectedCategory = "#все" @State private var searchQuery = "" @State private var selectedSort = "По дате" - @State private var allPosts: [Post] = [] - @State private var isLoading = true var body: some View { VStack(spacing: 0) { @@ -73,15 +73,6 @@ struct ProfileContentTabbedGrid: View { isLoading: isLoading ) } - .onAppear { - if allPosts.isEmpty { - isLoading = true - PostService.shared.fetchAllPosts { result in - self.allPosts = result - self.isLoading = false - } - } - } } // MARK: - Вкладка с меню diff --git a/Shared/Views/Tab/Profile/ProfileTab.swift b/Shared/Views/Tab/Profile/ProfileTab.swift index 78195d5..c598eb2 100644 --- a/Shared/Views/Tab/Profile/ProfileTab.swift +++ b/Shared/Views/Tab/Profile/ProfileTab.swift @@ -2,13 +2,6 @@ import SwiftUI struct ProfileTab: View { @ObservedObject var viewModel: LoginViewModel -// @State private var selectedTabIndex = 0 -// @State private var selectedSort = "По дате" -// @State private var selectedCategory = "#все" -// @State private var searchQuery = "" -// @State private var searchQueryArchive = "" -// @State private var searchQuerySaved = "" -// @State private var searchQueryLiked = "" @State private var isContentLoaded = true @State private var accounts = ["@user1", "@user2", "@user3"] @@ -20,84 +13,93 @@ struct ProfileTab: View { @State private var sheetType: SheetType? = nil enum SheetType: Identifiable { case accountShare + var id: Int { self.hashValue } + } - var id: Int { - switch self { - case .accountShare: return 1 + @State private var allPosts: [Post] = [] + @State private var isLoading = true + @State private var isRefreshing = false + + var body: some View { + NavigationView { + if !isContentLoaded { + SplashScreenView() + } else { + RefreshableScrollView(isRefreshing: $isRefreshing, onRefresh: { + fetchData() + }) { + VStack(spacing: 0) { + header + .frame(minHeight: 300) + .frame(maxWidth: .infinity) + .background(Color(.systemBackground)) + + ProfileContentTabbedGrid( + isContentLoaded: isContentLoaded, + allPosts: $allPosts, + isLoading: $isLoading + ) + } + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + Button(action: { sheetType = .accountShare }) { + HStack(spacing: 4) { + Text("custom_user_name") + .font(.headline) + .foregroundColor(.primary) + Image(systemName: "chevron.down") + .font(.subheadline) + .foregroundColor(.gray) + } + } + } + ToolbarItem(placement: .navigationBarTrailing) { + NavigationLink(destination: SettingsView(viewModel: viewModel)) { + Image(systemName: "wrench") + .imageScale(.large) + } + } + } + .sheet(item: $sheetType) { type in + switch type { + case .accountShare: + AccountShareSheet( + isPresented: Binding( + get: { sheetType != nil }, + set: { if !$0 { sheetType = nil } } + ), + selectedAccount: $selectedAccount, + accounts: accounts + ) + } + } + } + } + .navigationViewStyle(StackNavigationViewStyle()) + .onAppear { + if allPosts.isEmpty { + fetchData(isInitialLoad: true) } } } - var body: some View { - NavigationView { - if !isContentLoaded{ - SplashScreenView() - } else { - GeometryReader { geometry in - VStack(spacing: 0) { - ScrollView { - // ───── Шапка профиля (занимает ~50% экрана) ───── - header - .frame(minHeight: geometry.size.height * 0.5) - .frame(maxWidth: .infinity) - .background(Color(.systemBackground)) - - // ───── Скролл с контентом ───── - - ProfileContentTabbedGrid(isContentLoaded: isContentLoaded) - - } - .frame(maxWidth: .infinity) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .principal) { - Button(action: { - sheetType = .accountShare - }) { - HStack(spacing: 4) { - Text("custom_user_name") - .font(.headline) - .foregroundColor(.primary) - Image(systemName: "chevron.down") - .font(.subheadline) - .foregroundColor(.gray) - } - } - } - - ToolbarItem(placement: .navigationBarTrailing) { - NavigationLink(destination: SettingsView(viewModel: viewModel)) { - Image(systemName: "wrench") - .imageScale(.large) - } - } - } - .sheet(item: $sheetType) { type in - switch type { - case .accountShare: - AccountShareSheet( - isPresented: Binding( - get: { sheetType != nil }, - set: { if !$0 { sheetType = nil } } - ), - selectedAccount: $selectedAccount, - accounts: accounts - ) - } - } + private func fetchData(isInitialLoad: Bool = false) { + if isInitialLoad { + isLoading = true + } else { + isRefreshing = true } + + PostService.shared.fetchAllPosts { fetchedPosts in + self.allPosts = fetchedPosts + + if isInitialLoad { + self.isLoading = false + } + self.isRefreshing = false } - .navigationViewStyle(StackNavigationViewStyle()) -// .onAppear { -// if !isContentLoaded { -// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { -// isContentLoaded = true -// } -// } -// } } // MARK: - Шапка профиля