From 680de16d7644c10af63fcb4b704817e2bd4882b2 Mon Sep 17 00:00:00 2001 From: Nikita Vasilev Date: Mon, 26 Aug 2024 09:45:08 +0400 Subject: [PATCH] Integrate header and footer views into the list view --- .../ViewModifiers/LoadingViewModifier.swift | 2 +- .../Views/PaginatorListView.swift | 61 ++++++++++++++++--- .../Presentation/Views/PaginatorView.swift | 33 ++++++++++ 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/Sources/BladeTCA/Classes/Presentation/ViewModifiers/LoadingViewModifier.swift b/Sources/BladeTCA/Classes/Presentation/ViewModifiers/LoadingViewModifier.swift index ae29da8..c428494 100644 --- a/Sources/BladeTCA/Classes/Presentation/ViewModifiers/LoadingViewModifier.swift +++ b/Sources/BladeTCA/Classes/Presentation/ViewModifiers/LoadingViewModifier.swift @@ -13,7 +13,7 @@ struct LoadingViewModifier: ViewModifier { // MARK: ViewModifier func body(content: Content) -> some View { - VStack { + Group { content if isLoading { diff --git a/Sources/BladeTCA/Classes/Presentation/Views/PaginatorListView.swift b/Sources/BladeTCA/Classes/Presentation/Views/PaginatorListView.swift index 9ff9cca..63ac507 100644 --- a/Sources/BladeTCA/Classes/Presentation/Views/PaginatorListView.swift +++ b/Sources/BladeTCA/Classes/Presentation/Views/PaginatorListView.swift @@ -7,10 +7,14 @@ import BladeTCA import ComposableArchitecture import SwiftUI +// MARK: - PaginatorListView + public struct PaginatorListView< State: Equatable & Identifiable, Action: Equatable, + Header: View, Body: View, + Footer: View, PositionType: Equatable, Request: Equatable >: View { @@ -18,30 +22,73 @@ public struct PaginatorListView< private typealias StoreType = ViewStoreOf> + @SwiftUI.State private var isLoading = false + // MARK: Properties public let store: Store, PaginatorAction> + public let header: () -> Header public let content: (State) -> Body + public let footer: () -> Footer public init( store: Store, PaginatorAction>, - content: @escaping (State) -> Body + @ViewBuilder header: @escaping () -> Header, + @ViewBuilder content: @escaping (State) -> Body, + @ViewBuilder footer: @escaping () -> Footer ) { self.store = store + self.header = header self.content = content + self.footer = footer } // MARK: View public var body: some View { - WithViewStore(store, observe: { $0 }) { (viewStore: StoreType) in - List(viewStore.items) { item in - content(item) - .onAppear { - viewStore.send(.itemAppeared(item.id)) + List { + Section { + header() + } + + WithViewStore(store, observe: { $0 }) { (viewStore: StoreType) in + Section(content: { + ForEach(viewStore.items) { item in + content(item) + .onAppear { + viewStore.send(.itemAppeared(item.id)) + } } + }) + } + + Section { + footer() + } + + Section { + EmptyView() + .modifier(LoadingViewModifier(isLoading: isLoading)) } - .modifier(LoadingViewModifier(isLoading: viewStore.isLoading && !viewStore.items.isEmpty)) } } } + +public extension PaginatorListView where Header == EmptyView, Footer == EmptyView { + init( + store: Store, PaginatorAction>, + @ViewBuilder content: @escaping (State) -> Body + ) { + self.init(store: store, header: { EmptyView() }, content: content, footer: { EmptyView() }) + } +} + +public extension PaginatorListView where Header: View, Footer == EmptyView { + init( + store: Store, PaginatorAction>, + header: @escaping () -> Header, + @ViewBuilder content: @escaping (State) -> Body + ) { + self.init(store: store, header: header, content: content, footer: { EmptyView() }) + } +} diff --git a/Sources/BladeTCA/Classes/Presentation/Views/PaginatorView.swift b/Sources/BladeTCA/Classes/Presentation/Views/PaginatorView.swift index fadfde8..904f12d 100644 --- a/Sources/BladeTCA/Classes/Presentation/Views/PaginatorView.swift +++ b/Sources/BladeTCA/Classes/Presentation/Views/PaginatorView.swift @@ -14,7 +14,9 @@ public struct PaginatorView< Action: Equatable, PositionType: Equatable, Request: Equatable, + Header: View, Body: View, + Footer: View, RowContent: View >: View { // MARK: Types @@ -24,18 +26,24 @@ public struct PaginatorView< // MARK: Properties public let store: Store, PaginatorAction> + public let header: () -> Header public let content: ([State], @escaping (State) -> AnyView) -> Body + public let footer: () -> Footer public let rowContent: (State) -> RowContent // MARK: Initialization public init( store: Store, PaginatorAction>, + @ViewBuilder header: @escaping () -> Header, @ViewBuilder content: @escaping ([State], @escaping (State) -> AnyView) -> Body, + @ViewBuilder footer: @escaping () -> Footer, @ViewBuilder rowContent: @escaping (State) -> RowContent ) { self.store = store + self.header = header self.content = content + self.footer = footer self.rowContent = rowContent } @@ -43,16 +51,41 @@ public struct PaginatorView< public var body: some View { WithViewStore(store, observe: { $0 }) { (viewStore: StoreType) in + header() + content(viewStore.items.elements) { item in rowContent(item) .onAppear { viewStore.send(.itemAppeared(item.id)) } .any } .modifier(LoadingViewModifier(isLoading: viewStore.isLoading && !viewStore.items.isEmpty)) + + footer() } } } +public extension PaginatorView where Header == EmptyView, Footer == EmptyView { + init( + store: Store, PaginatorAction>, + @ViewBuilder content: @escaping ([State], @escaping (State) -> AnyView) -> Body, + @ViewBuilder rowContent: @escaping (State) -> RowContent + ) { + self.init(store: store, header: { EmptyView() }, content: content, footer: { EmptyView() }, rowContent: rowContent) + } +} + +public extension PaginatorView where Header: View, Footer == EmptyView { + init( + store: Store, PaginatorAction>, + header: @escaping () -> Header, + @ViewBuilder content: @escaping ([State], @escaping (State) -> AnyView) -> Body, + @ViewBuilder rowContent: @escaping (State) -> RowContent + ) { + self.init(store: store, header: header, content: content, footer: { EmptyView() }, rowContent: rowContent) + } +} + // MARK: Extension private extension View {