top bar test

This commit is contained in:
cheykrym 2025-07-25 01:58:34 +03:00
parent 975802dc88
commit 2052492912
4 changed files with 58 additions and 21 deletions

View File

@ -4,17 +4,20 @@ import UIKit
struct RefreshableScrollView<Content: View>: UIViewRepresentable { struct RefreshableScrollView<Content: View>: UIViewRepresentable {
var content: Content var content: Content
var onRefresh: () -> Void var onRefresh: () -> Void
var onScroll: ((CGPoint) -> Void)?
var isRefreshing: Binding<Bool> var isRefreshing: Binding<Bool>
init(isRefreshing: Binding<Bool>, onRefresh: @escaping () -> Void, @ViewBuilder content: () -> Content) { init(isRefreshing: Binding<Bool>, onRefresh: @escaping () -> Void, onScroll: ((CGPoint) -> Void)? = nil, @ViewBuilder content: () -> Content) {
self.content = content() self.content = content()
self.onRefresh = onRefresh self.onRefresh = onRefresh
self.onScroll = onScroll
self.isRefreshing = isRefreshing self.isRefreshing = isRefreshing
} }
func makeUIView(context: Context) -> UIScrollView { func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView() let scrollView = UIScrollView()
scrollView.delaysContentTouches = false scrollView.delaysContentTouches = false
scrollView.delegate = context.coordinator
let refreshControl = UIRefreshControl() let refreshControl = UIRefreshControl()
refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged) refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged)
@ -41,7 +44,6 @@ struct RefreshableScrollView<Content: View>: UIViewRepresentable {
if isRefreshing.wrappedValue { if isRefreshing.wrappedValue {
uiView.refreshControl?.beginRefreshing() uiView.refreshControl?.beginRefreshing()
} else { } else {
// Отложенное завершение, чтобы избежать цикла обновлений
DispatchQueue.main.async { DispatchQueue.main.async {
uiView.refreshControl?.endRefreshing() uiView.refreshControl?.endRefreshing()
} }
@ -54,7 +56,7 @@ struct RefreshableScrollView<Content: View>: UIViewRepresentable {
Coordinator(self) Coordinator(self)
} }
class Coordinator: NSObject { class Coordinator: NSObject, UIScrollViewDelegate {
var parent: RefreshableScrollView var parent: RefreshableScrollView
var hostingController: UIHostingController<Content>? var hostingController: UIHostingController<Content>?
@ -65,5 +67,9 @@ struct RefreshableScrollView<Content: View>: UIViewRepresentable {
@objc func handleRefresh() { @objc func handleRefresh() {
parent.onRefresh() parent.onRefresh()
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) {
parent.onScroll?(scrollView.contentOffset)
}
} }
} }

View File

@ -5,14 +5,18 @@ struct HomeTab: View {
@State private var isLoading = true @State private var isLoading = true
@State private var isRefreshing = false @State private var isRefreshing = false
var onScroll: ((CGPoint) -> Void)?
var body: some View { var body: some View {
VStack { VStack {
if isLoading { if isLoading {
ProgressView("Загрузка ленты...") ProgressView("Загрузка ленты...")
} else { } else {
RefreshableScrollView(isRefreshing: $isRefreshing, onRefresh: { RefreshableScrollView(
fetchData() isRefreshing: $isRefreshing,
}) { onRefresh: { fetchData() },
onScroll: onScroll
) {
LazyVStack(spacing: 24) { LazyVStack(spacing: 24) {
ForEach(posts) { post in ForEach(posts) { post in
PostDetailView(post: post) PostDetailView(post: post)

View File

@ -11,11 +11,15 @@ struct MainView: View {
@ObservedObject var viewModel: LoginViewModel @ObservedObject var viewModel: LoginViewModel
@State private var selectedTab: Int = 0 @State private var selectedTab: Int = 0
// Состояния для TopBarView, когда активна вкладка Profile // Состояния для TopBarView
@State private var selectedAccount = "@user1" @State private var selectedAccount = "@user1"
@State private var accounts = ["@user1", "@user2", "@user3"] @State private var accounts = ["@user1", "@user2", "@user3"]
@State private var sheetType: ProfileTab.SheetType? = nil @State private var sheetType: ProfileTab.SheetType? = nil
// Состояния для скрытия TopBar
@State private var topBarVisible = true
@State private var lastScrollY: CGFloat = 0
private var tabTitle: String { private var tabTitle: String {
switch selectedTab { switch selectedTab {
case 0: case 0:
@ -32,7 +36,7 @@ struct MainView: View {
} }
var body: some View { var body: some View {
NavigationView { // NavigationView нужен здесь для NavigationLink в TopBarView NavigationView {
VStack(spacing: 0) { VStack(spacing: 0) {
TopBarView( TopBarView(
title: tabTitle, title: tabTitle,
@ -41,38 +45,38 @@ struct MainView: View {
accounts: accounts, accounts: accounts,
viewModel: viewModel viewModel: viewModel
) )
.offset(y: topBarVisible ? 0 : -100)
.animation(.spring(response: 0.4, dampingFraction: 0.8), value: topBarVisible)
ZStack { ZStack {
switch selectedTab { switch selectedTab {
case 0: case 0:
HomeTab() HomeTab(onScroll: handleScroll)
case 1: case 1:
SearchTab() SearchTab() // SearchTab не имеет скролла, поэтому onScroll не нужен
case 2: case 2:
ChatsTab() ChatsTab() // ChatsTab тоже
case 3: case 3:
// Передаем состояния в ProfileTab
ProfileTab( ProfileTab(
viewModel: viewModel, viewModel: viewModel,
sheetType: $sheetType, sheetType: $sheetType,
selectedAccount: $selectedAccount, selectedAccount: $selectedAccount,
accounts: $accounts accounts: $accounts,
onScroll: handleScroll
) )
default: default:
HomeTab() HomeTab(onScroll: handleScroll)
} }
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
CustomTabBar(selectedTab: $selectedTab) { CustomTabBar(selectedTab: $selectedTab) {
// Действие для кнопки "Создать"
print("Create button tapped") print("Create button tapped")
} }
} }
.ignoresSafeArea(edges: .bottom) .ignoresSafeArea(edges: .bottom)
.navigationBarHidden(true) // Скрываем стандартный NavigationBar .navigationBarHidden(true)
.sheet(item: $sheetType) { type in .sheet(item: $sheetType) { type in
// Обработка sheet, перенесенная из ProfileTab
switch type { switch type {
case .accountShare: case .accountShare:
AccountShareSheet( AccountShareSheet(
@ -86,7 +90,26 @@ struct MainView: View {
} }
} }
} }
.navigationViewStyle(StackNavigationViewStyle()) // Важно для корректной работы .navigationViewStyle(StackNavigationViewStyle())
}
private func handleScroll(offset: CGPoint) {
let currentY = offset.y
// Показываем бар, если скроллим вверх или дошли до верха
if currentY < lastScrollY || currentY <= 0 {
if !topBarVisible {
topBarVisible = true
}
}
// Скрываем, если скроллим вниз и отступили от верха
else if currentY > lastScrollY && currentY > 50 {
if topBarVisible {
topBarVisible = false
}
}
lastScrollY = currentY
} }
} }

View File

@ -9,6 +9,8 @@ struct ProfileTab: View {
@Binding var selectedAccount: String @Binding var selectedAccount: String
@Binding var accounts: [String] @Binding var accounts: [String]
var onScroll: ((CGPoint) -> Void)?
let followers = ["@alice", "@bob", "@charlie"] let followers = ["@alice", "@bob", "@charlie"]
let following = ["@dev", "@design", "@ios"] let following = ["@dev", "@design", "@ios"]
@ -47,9 +49,11 @@ struct ProfileTab: View {
) )
} }
RefreshableScrollView(isRefreshing: $isRefreshing, onRefresh: { RefreshableScrollView(
fetchData() isRefreshing: $isRefreshing,
}) { onRefresh: { fetchData() },
onScroll: onScroll
) {
VStack(spacing: 12) { VStack(spacing: 12) {
header header
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)