diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index b0764b1..0000000 --- a/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/swiftui-introspect", - "state" : { - "revision" : "a08b87f96b41055577721a6e397562b21ad52454", - "version" : "26.0.0" - } - } - ], - "version" : 2 -} diff --git a/Package.swift b/Package.swift index 8729bd1..c2adacc 100644 --- a/Package.swift +++ b/Package.swift @@ -13,15 +13,11 @@ let package = Package( products: [ .library(name: "PopupView", targets: ["PopupView"]), ], - dependencies: [ - .package(url: "https://github.com/siteline/swiftui-introspect", "1.3.0"..<"27.0.0"), - ], + dependencies: [], targets: [ .target( name: "PopupView", - dependencies: [ - .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), - ], + dependencies: [], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency") ] diff --git a/PopupExample/PopupExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PopupExample/PopupExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 0146888..0000000 --- a/PopupExample/PopupExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/swiftui-introspect", - "state" : { - "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", - "version" : "1.3.0" - } - } - ], - "version" : 2 -} diff --git a/PopupExample/PopupExample/ContentView.swift b/PopupExample/PopupExample/ContentView.swift index 7f881f3..a491943 100644 --- a/PopupExample/PopupExample/ContentView.swift +++ b/PopupExample/PopupExample/ContentView.swift @@ -242,7 +242,7 @@ struct ContentView : View { ActionSheetSecond() } customize: { $0 - .type(.scroll(headerView: AnyView(scrollViewHeader()))) + .type(.scroll(headerView: scrollViewHeader())) .position(.bottom) .closeOnTap(false) .closeOnTapOutside(true) diff --git a/README.md b/README.md index 9601995..4e903c2 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,8 @@ scroll parameters: `autohideIn` - time after which popup should disappear `dismissibleIn(Double?, Binding?)` - only allow dismiss after this time passes (forbids closeOnTap, closeOnTapOutside, and drag). Pass a boolean binding if you'd like to track current status `dragToDismiss` - true by default: enable/disable drag to dismiss (upwards for .top popup types, downwards for .bottom and default type) -`closeOnTap` - true by default: enable/disable closing on tap on popup +`closeOnTap` - true by default: enable/disable closing on tap on popup. +NOTE: any gesture or control element you add to popup's body will override tap to close. in this case please close the popup manually if you need it to `closeOnTapOutside` - false by default: enable/disable closing on tap on outside of popup `allowTapThroughBG` - Should allow taps to pass "through" the popup's background down to views "below" it. `.sheet` popup is always allowTapThroughBG = false. False by default `backgroundColor` - Color.clear by default: change background color of outside area diff --git a/Sources/PopupView/PopupScrollViewDelegate.swift b/Sources/PopupView/PopupScrollViewDelegate.swift index aad0b8c..daef003 100644 --- a/Sources/PopupView/PopupScrollViewDelegate.swift +++ b/Sources/PopupView/PopupScrollViewDelegate.swift @@ -36,6 +36,7 @@ final class PopupScrollViewDelegate: ObservableObject { let maxContentOffset = (scrollView?.maxContentOffsetHeight() ?? 0) + keyboardHeightHelper.keyboardHeight if contentOffset - translation.y > 0 { + didReachTop(0) scrollView?.contentOffset.y = min(contentOffset - translation.y, maxContentOffset) gesture.setTranslation(.zero, in: scrollView) } else { diff --git a/Sources/PopupView/PopupView.swift b/Sources/PopupView/PopupView.swift index 50dc48e..abe3cbd 100644 --- a/Sources/PopupView/PopupView.swift +++ b/Sources/PopupView/PopupView.swift @@ -7,9 +7,6 @@ // import SwiftUI -#if os(iOS) -@_spi(Advanced) import SwiftUIIntrospect -#endif public struct Popup: ViewModifier { @@ -381,20 +378,37 @@ public struct Popup: ViewModifier { @ViewBuilder private func contentView() -> some View { #if os(iOS) + let dragGesture = DragGesture() + .updating($dragState) { drag, state, _ in + if !isDragging { + DispatchQueue.main.async { + isDragging = true + } + } + state = .dragging(translation: drag.translation) + } + .onEnded(onDragEnded) + switch type { case .scroll(let headerView): VStack(spacing: 0) { - headerView + scrollHeaderView(view: headerView) .fixedSize(horizontal: false, vertical: true) + .offset(dragOffset()) + .simultaneousGesture(dragGesture) + ScrollView { view() + .background( + ScrollViewResolver { scrollView in + configure(scrollView: scrollView) + } + ) } // no heigher than its contents .frame(maxHeight: scrollViewContentHeight) .frameGetter($scrollViewRect) - } - .introspect(.scrollView, on: .iOS(.v15...)) { scrollView in - configure(scrollView: scrollView) + .offset(dragOffset()) } .offset(CGSize(width: 0, height: scrollViewOffset.height)) @@ -406,6 +420,18 @@ public struct Popup: ViewModifier { #endif } +#if os(iOS) + @ViewBuilder + func scrollHeaderView(view: any View) -> some View { + ZStack { + Color.white + .mask(AnyView(view)) + + AnyView(view) + } + } +#endif + #if swift(>=5.9) /// This is the builder for the sheet content @ViewBuilder @@ -452,7 +478,7 @@ public struct Popup: ViewModifier { .onChange(of: sheetContentRect.size) { sheetContentRect in #if os(iOS) // check if scrollView has already calculated its height, otherwise sheetContentRect is already non-zero but yet incorrect - if case .scroll(_) = type, scrollViewRect.height == 0 { + if case .scroll = type, scrollViewRect.height == 0 { return } #endif @@ -646,3 +672,4 @@ public struct Popup: ViewModifier { } #endif } + diff --git a/Sources/PopupView/PublicAPI.swift b/Sources/PopupView/PublicAPI.swift index 99e2032..9b9bc5e 100644 --- a/Sources/PopupView/PublicAPI.swift +++ b/Sources/PopupView/PublicAPI.swift @@ -23,7 +23,7 @@ extension Popup { case toast case floater(verticalPadding: CGFloat = 10, horizontalPadding: CGFloat = 10, useSafeAreaInset: Bool = true) #if os(iOS) - case scroll(headerView: AnyView = AnyView(Color.clear.frame(height: 1))) + case scroll(headerView: any View = EmptyView()) #endif var defaultPosition: Position { @@ -135,6 +135,7 @@ extension Popup { var dragToDismissDistance: CGFloat? /// Should close on tap - default is `true` + /// NOTE: any gesture or control element you add to popup's body will override tap to close. in this case please close the popup manually if you need it to var closeOnTap: Bool = true /// Should close on tap outside - default is `false` @@ -225,6 +226,7 @@ extension Popup { } /// Should close on tap - default is `true` + /// NOTE: any gesture or control element you add to popup's body will override tap to close. in this case please close the popup manually if you need it to public func closeOnTap(_ closeOnTap: Bool) -> PopupParameters { var params = self params.closeOnTap = closeOnTap diff --git a/Sources/PopupView/Utils.swift b/Sources/PopupView/Utils.swift index d190ef5..09faf4c 100644 --- a/Sources/PopupView/Utils.swift +++ b/Sources/PopupView/Utils.swift @@ -75,7 +75,7 @@ extension View { self #else if condition { - self.simultaneousGesture( + self.gesture( TapGesture().onEnded { onTap() } @@ -281,3 +281,36 @@ extension CGSize { #endif } } + +#if os(iOS) +// MARK: ScrollViewResolver + +struct ScrollViewResolver: UIViewRepresentable { + var onResolve: (UIScrollView) -> Void + + func makeUIView(context: Context) -> UIView { + let view = UIView() + DispatchQueue.main.async { + if let scrollView = view.enclosingScrollView() { + onResolve(scrollView) + } + } + return view + } + + func updateUIView(_ uiView: UIView, context: Context) {} +} + +extension UIView { + func enclosingScrollView() -> UIScrollView? { + var view = self.superview + while view != nil { + if let scroll = view as? UIScrollView { + return scroll + } + view = view?.superview + } + return nil + } +} +#endif