From 7b684a1c6ee0e5536918f75fe9be18853f20ab70 Mon Sep 17 00:00:00 2001 From: John Clark Date: Fri, 8 Mar 2024 16:17:19 +0100 Subject: [PATCH] Patch 2.2.3 feat: - Added release-cocoapods.yml fix: - Fixed the problem with memory leaks - Fixed the issue where a drag gesture caused the wrong popup to close - Fixed the rare issue where the popup may not fill the full width of the screen --- .github/workflows/release-cocoapods.yml | 17 ++++++++++++ MijickPopupView.podspec | 2 +- Sources/Internal/Managers/PopupManager.swift | 5 ---- Sources/Internal/Protocols/Popup.swift | 5 ++++ Sources/Internal/Protocols/PopupStack.swift | 6 ++--- .../Internal/Views/PopupBottomStackView.swift | 14 +++++----- .../Internal/Views/PopupTopStackView.swift | 8 +++--- Sources/Internal/Views/PopupView.swift | 27 ++++++++++--------- Sources/Public/Extensions/Public+View.swift | 2 +- 9 files changed, 53 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/release-cocoapods.yml diff --git a/.github/workflows/release-cocoapods.yml b/.github/workflows/release-cocoapods.yml new file mode 100644 index 0000000000..61366210b6 --- /dev/null +++ b/.github/workflows/release-cocoapods.yml @@ -0,0 +1,17 @@ +name: CI +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + build: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + - name: Publish to CocoaPod register + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} + run: | + pod trunk push MijickPopupView.podspec diff --git a/MijickPopupView.podspec b/MijickPopupView.podspec index 061c998c99..7e773f81c6 100644 --- a/MijickPopupView.podspec +++ b/MijickPopupView.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| PopupView is a free and open-source library dedicated for SwiftUI that makes the process of presenting popups easier and much cleaner. DESC - s.version = '2.2.1' + s.version = '2.2.3' s.ios.deployment_target = '14.0' s.osx.deployment_target = '12.0' s.swift_version = '5.0' diff --git a/Sources/Internal/Managers/PopupManager.swift b/Sources/Internal/Managers/PopupManager.swift index 81396bb023..9ee29a9b24 100644 --- a/Sources/Internal/Managers/PopupManager.swift +++ b/Sources/Internal/Managers/PopupManager.swift @@ -18,11 +18,6 @@ public class PopupManager: ObservableObject { static let shared: PopupManager = .init() private init() {} } -extension PopupManager { - var top: [AnyPopup] { views.compactMap { $0 as? AnyPopup } } - var centre: [AnyPopup] { views.compactMap { $0 as? AnyPopup } } - var bottom: [AnyPopup] { views.compactMap { $0 as? AnyPopup } } -} // MARK: - Operations enum StackOperation { diff --git a/Sources/Internal/Protocols/Popup.swift b/Sources/Internal/Protocols/Popup.swift index 0e31cab946..9db184f70e 100644 --- a/Sources/Internal/Protocols/Popup.swift +++ b/Sources/Internal/Protocols/Popup.swift @@ -25,3 +25,8 @@ public extension Popup { func configurePopup(popup: Config) -> Config { popup } } + +// MARK: - Helpers +extension Popup { + func remove() { PopupManager.performOperation(.remove(id: id)) } +} diff --git a/Sources/Internal/Protocols/PopupStack.swift b/Sources/Internal/Protocols/PopupStack.swift index cc465cb4b1..bffff8af71 100644 --- a/Sources/Internal/Protocols/PopupStack.swift +++ b/Sources/Internal/Protocols/PopupStack.swift @@ -14,7 +14,7 @@ protocol PopupStack: View { associatedtype Config: Configurable var items: [AnyPopup] { get } - var heights: [AnyPopup: CGFloat] { get } + var heights: [String: CGFloat] { get } var globalConfig: GlobalConfig { get } var gestureTranslation: CGFloat { get } var translationProgress: CGFloat { get } @@ -28,7 +28,7 @@ protocol PopupStack: View { var tapOutsideClosesPopup: Bool { get } } extension PopupStack { - var heights: [AnyPopup: CGFloat] { [:] } + var heights: [String: CGFloat] { [:] } var gestureTranslation: CGFloat { 0 } var translationProgress: CGFloat { 1 } @@ -112,7 +112,7 @@ extension PopupStack { func getInitialHeight() -> CGFloat { guard let previousView = items.nextToLast else { return 0 } - let height = heights.filter { $0.key == previousView }.first?.value ?? 0 + let height = heights.filter { $0.key == previousView.id }.first?.value ?? 0 return height } } diff --git a/Sources/Internal/Views/PopupBottomStackView.swift b/Sources/Internal/Views/PopupBottomStackView.swift index 5326d0c93e..6ddc14e63e 100644 --- a/Sources/Internal/Views/PopupBottomStackView.swift +++ b/Sources/Internal/Views/PopupBottomStackView.swift @@ -14,7 +14,7 @@ struct PopupBottomStackView: PopupStack { let items: [AnyPopup] let globalConfig: GlobalConfig @State var gestureTranslation: CGFloat = 0 - @State var heights: [AnyPopup: CGFloat] = [:] + @State var heights: [String: CGFloat] = [:] @ObservedObject private var screen: ScreenManager = .shared @ObservedObject private var keyboardManager: KeyboardManager = .shared @@ -69,7 +69,7 @@ private extension PopupBottomStackView { } private extension PopupBottomStackView { func dismissLastItemIfNeeded() { - if translationProgress >= gestureClosingThresholdFactor { items.last?.dismiss() } + if translationProgress >= gestureClosingThresholdFactor { items.last?.remove() } } func resetGestureTranslationOnEnd() { let resetAfter = items.count == 1 && translationProgress >= gestureClosingThresholdFactor ? 0.25 : 0 @@ -79,7 +79,7 @@ private extension PopupBottomStackView { // MARK: - Action Modifiers private extension PopupBottomStackView { - func onScreenChange(_ value: Any) { if let lastItem = items.last { saveHeight(heights[lastItem] ?? .infinity, withAnimation: nil, for: lastItem) }} + func onScreenChange(_ value: Any) { if let lastItem = items.last { saveHeight(heights[lastItem.id] ?? .infinity, withAnimation: nil, for: lastItem) }} } // MARK: - View Modifiers @@ -93,9 +93,9 @@ private extension PopupBottomStackView { func saveHeight(_ height: CGFloat, withAnimation animation: Animation?, for item: AnyPopup) { withAnimation(animation) { let config = item.configurePopup(popup: .init()) - if config.contentFillsEntireScreen { return heights[item] = screen.size.height } - if config.contentFillsWholeHeight { return heights[item] = getMaxHeight() } - return heights[item] = min(height, maxHeight) + if config.contentFillsEntireScreen { return heights[item.id] = screen.size.height } + if config.contentFillsWholeHeight { return heights[item.id] = getMaxHeight() } + return heights[item.id] = min(height, maxHeight) }} func getMaxHeight() -> CGFloat { let basicHeight = screen.size.height - screen.safeArea.top @@ -118,7 +118,7 @@ extension PopupBottomStackView { var popupBottomPadding: CGFloat { lastPopupConfig.popupPadding.bottom } var popupHorizontalPadding: CGFloat { lastPopupConfig.popupPadding.horizontal } var popupShadow: Shadow { globalConfig.bottom.shadow } - var height: CGFloat { heights.first { $0.key == items.last }?.value ?? (lastPopupConfig.contentFillsEntireScreen ? screen.size.height : getInitialHeight()) } + var height: CGFloat { heights.first { $0.key == items.last?.id }?.value ?? (lastPopupConfig.contentFillsEntireScreen ? screen.size.height : getInitialHeight()) } var maxHeight: CGFloat { getMaxHeight() - popupBottomPadding } var distanceFromKeyboard: CGFloat { lastPopupConfig.distanceFromKeyboard ?? globalConfig.bottom.distanceFromKeyboard } var cornerRadius: CGFloat { let cornerRadius = lastPopupConfig.cornerRadius ?? globalConfig.bottom.cornerRadius; return lastPopupConfig.contentFillsEntireScreen ? min(cornerRadius, screen.cornerRadius ?? 0) : cornerRadius } diff --git a/Sources/Internal/Views/PopupTopStackView.swift b/Sources/Internal/Views/PopupTopStackView.swift index 9f86b1ba45..280b20644e 100644 --- a/Sources/Internal/Views/PopupTopStackView.swift +++ b/Sources/Internal/Views/PopupTopStackView.swift @@ -14,7 +14,7 @@ struct PopupTopStackView: PopupStack { let items: [AnyPopup] let globalConfig: GlobalConfig @State var gestureTranslation: CGFloat = 0 - @State var heights: [AnyPopup: CGFloat] = [:] + @State var heights: [String: CGFloat] = [:] @ObservedObject private var screen: ScreenManager = .shared @@ -67,7 +67,7 @@ private extension PopupTopStackView { } private extension PopupTopStackView { func dismissLastItemIfNeeded() { - if translationProgress >= gestureClosingThresholdFactor { items.last?.dismiss() } + if translationProgress >= gestureClosingThresholdFactor { items.last?.remove() } } func resetGestureTranslationOnEnd() { let resetAfter = items.count == 1 && translationProgress >= gestureClosingThresholdFactor ? 0.25 : 0 @@ -84,7 +84,7 @@ private extension PopupTopStackView { } } func getBackgroundColour(for item: AnyPopup) -> Color { getConfig(item).backgroundColour ?? globalConfig.top.backgroundColour } - func saveHeight(_ height: CGFloat, for item: AnyPopup) { heights[item] = height } + func saveHeight(_ height: CGFloat, for item: AnyPopup) { heights[item.id] = height } } // MARK: - Flags & Values @@ -92,7 +92,7 @@ extension PopupTopStackView { var contentTopPadding: CGFloat { lastPopupConfig.contentIgnoresSafeArea ? 0 : max(screen.safeArea.top - popupTopPadding, 0) } var popupTopPadding: CGFloat { lastPopupConfig.popupPadding.top } var popupShadow: Shadow { globalConfig.top.shadow } - var height: CGFloat { heights.first { $0.key == items.last }?.value ?? getInitialHeight() } + var height: CGFloat { heights.first { $0.key == items.last?.id }?.value ?? getInitialHeight() } var cornerRadius: CGFloat { lastPopupConfig.cornerRadius ?? globalConfig.top.cornerRadius } var stackLimit: Int { globalConfig.top.stackLimit } diff --git a/Sources/Internal/Views/PopupView.swift b/Sources/Internal/Views/PopupView.swift index 1f39c697c6..556170f521 100644 --- a/Sources/Internal/Views/PopupView.swift +++ b/Sources/Internal/Views/PopupView.swift @@ -15,7 +15,7 @@ import SwiftUI struct PopupView: View { let globalConfig: GlobalConfig @State private var zIndex: ZIndex = .init() - @ObservedObject private var stack: PopupManager = .shared + @ObservedObject private var popupManager: PopupManager = .shared @ObservedObject private var screenManager: ScreenManager = .shared @@ -27,13 +27,13 @@ struct PopupView: View { struct PopupView: View { let rootView: any View let globalConfig: GlobalConfig - @ObservedObject private var stack: PopupManager = .shared + @ObservedObject private var popupManager: PopupManager = .shared @ObservedObject private var screenManager: ScreenManager = .shared var body: some View { AnyView(rootView) - .disabled(!stack.views.isEmpty) + .disabled(!popupManager.views.isEmpty) .overlay(createBody()) } } @@ -47,7 +47,7 @@ private extension PopupView { .frame(height: screenManager.size.height) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(createOverlay()) - .onChange(of: stack.views.count, perform: onViewsCountChange) + .onChange(of: popupManager.views.count, perform: onViewsCountChange) } } @@ -58,7 +58,7 @@ private extension PopupView { createCentrePopupStackView().zIndex(zIndex.centre) createBottomPopupStackView().zIndex(zIndex.bottom) } - .animation(stackAnimation, value: stack.views.map(\.id)) + .animation(stackAnimation, value: popupManager.views.map(\.id)) } func createOverlay() -> some View { overlayColour @@ -71,28 +71,31 @@ private extension PopupView { private extension PopupView { func createTopPopupStackView() -> some View { - PopupTopStackView(items: stack.top, globalConfig: globalConfig) + PopupTopStackView(items: getViews(AnyPopup.self), globalConfig: globalConfig) } func createCentrePopupStackView() -> some View { - PopupCentreStackView(items: stack.centre, globalConfig: globalConfig) + PopupCentreStackView(items: getViews(AnyPopup.self), globalConfig: globalConfig) } func createBottomPopupStackView() -> some View { - PopupBottomStackView(items: stack.bottom, globalConfig: globalConfig) + PopupBottomStackView(items: getViews(AnyPopup.self), globalConfig: globalConfig) } } +private extension PopupView { + func getViews(_ type: T.Type) -> [T] { popupManager.views.compactMap { $0 as? T } } +} private extension PopupView { - func onViewsCountChange(_ x: Any) { zIndex.reshuffle(stack.views.last) } + func onViewsCountChange(_ count: Int) { zIndex.reshuffle(popupManager.views.last) } } private extension PopupView { var isOverlayActive: Bool { !isStackEmpty && !shouldOverlayBeHiddenForCurrentPopup } - var isStackEmpty: Bool { stack.views.isEmpty } - var shouldOverlayBeHiddenForCurrentPopup: Bool { stack.popupsWithoutOverlay.contains(stack.views.last?.id ?? "") } + var isStackEmpty: Bool { popupManager.views.isEmpty } + var shouldOverlayBeHiddenForCurrentPopup: Bool { popupManager.popupsWithoutOverlay.contains(popupManager.views.last?.id ?? "") } } private extension PopupView { - var stackAnimation: Animation { stack.presenting ? globalConfig.common.animation.entry : globalConfig.common.animation.removal } + var stackAnimation: Animation { popupManager.presenting ? globalConfig.common.animation.entry : globalConfig.common.animation.removal } var overlayColour: Color { globalConfig.common.overlayColour } var overlayAnimation: Animation { .easeInOut(duration: 0.44) } } diff --git a/Sources/Public/Extensions/Public+View.swift b/Sources/Public/Extensions/Public+View.swift index 379e68328f..f2f0bdd97b 100644 --- a/Sources/Public/Extensions/Public+View.swift +++ b/Sources/Public/Extensions/Public+View.swift @@ -15,7 +15,7 @@ public extension View { /// Initialises the library. Use directly with the view in your @main structure func implementPopupView(config: (GlobalConfig) -> GlobalConfig = { $0 }) -> some View { #if os(iOS) || os(macOS) - overlay(PopupView(globalConfig: config(.init()))) + frame(maxWidth: .infinity).overlay(PopupView(globalConfig: config(.init()))) #elseif os(tvOS) PopupView(rootView: self, globalConfig: config(.init())) #endif