Giter VIP home page Giter VIP logo

popupview's Introduction

     

Floaters Toasts Popups Sheets

Popup View

Toasts, alerts and popups library written with SwiftUI

Read Article »

SPM Compatible Cocoapods Compatible Carthage Compatible License: MIT

iOS 18 support

PopupView supports iOS 18 beta, however SwiftUIIntrospect this library is using doesn't support it yet, so .scroll type popup unfortunately doesn't work on iOS 18 for now.

What's new in version 3

  • zoom in/out appear/disappear animations
  • disappearTo parameter to specify disappearing animation direction - can be different from appearFrom

Update to version 3

To include new .zoom type, AppearFrom enum cases were renamed. Instead of:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0
        .type(.floater())
        .appearFrom(.top) // <-- here
}

use:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0
        .type(.floater())
        .appearFrom(.topSlide) // <-- here
}

Update to version 2

Instead of:

.popup(isPresented: $floats.showingTopFirst, type: .floater(), position: .top, animation: .spring(), closeOnTapOutside: true, backgroundColor: .black.opacity(0.5)) {
    FloatTopFirst()
}

use:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0
        .type(.floater())
        .position(.top)
        .animation(.spring())
        .closeOnTapOutside(true)
        .backgroundColor(.black.opacity(0.5))
}

Using this API you can pass parameters in any order you like.

Show over navbar

To display your popup over all other views including navbars please use:

.popup(isPresented: $floats.showingTopFirst) {
    FloatTopFirst()
} customize: {
    $0.isOpaque(true)
}

This will also mean that you won't be able to tap "through" the popup's background on any of the controls "behind it" (that's because this method actually uses transparent fullscreenSheet, which won't pass the touches to underlying view). Opaque popup uses screen size to calculate its position.

Unfortunately, if opaque is false (to allow "through-touches" if you need them), popup - even if forced to be fullscreen, will be displayed under the navbar (if you know how to pass over this restriction, please do let me know in the comments). Please keep in mind that in this case the popup calculates its position using the frame of the view you attach it to, to avoid being under the navbar. So you'll likely want to attach it to the root view of your screen.

Usage

  1. Add a bool to control popup presentation state
  2. Add .popup modifier to your view.
import PopupView

struct ContentView: View {

    @State var showingPopup = false

    var body: some View {
        YourView()
            .popup(isPresented: $showingPopup) {
                Text("The popup")
                    .frame(width: 200, height: 60)
                    .background(Color(red: 0.85, green: 0.8, blue: 0.95))
                    .cornerRadius(30.0)
            } customize: {
                $0.autohideIn(2)
            }
    }
}

Required parameters

isPresented - binding to determine if the popup should be seen on screen or hidden
view - view you want to display on your popup

or

item - binding to item: if item's value is nil - popup is hidden, if non-nil - displayed. Be careful - library makes a copy of your item during dismiss animation!!
view - view you want to display on your popup

Available customizations - optional parameters

use customize closure in popup modifier:

type:

  • default - usual popup in the center of screen
  • toast - fitted to screen i.e. without padding and ignoring safe area
  • floater - has padding and can choose to use or ignore safe area
  • scroll - adds a scroll to your content, if you scroll to top of this scroll - the gesture will continue into popup's drag dismiss.

floater parameters:

  • verticalPadding - padding which will define padding from the relative vertical edge or will be added to safe area if useSafeAreaInset is true
  • horizontalPadding - padding which will define padding from the relative horizontal edge or will be added to safe area if useSafeAreaInset is true
  • useSafeAreaInset - whether to include safe area insets in floater padding

scroll parameters:
headerView - a view on top which won't be a part of the scroll (if you need one)

position - topLeading, top, topTrailing, leading, center, trailing, bottomLeading, bottom, bottomTrailing appearFrom - topSlide, bottomSlide, leftSlide, rightSlide, centerScale: determines the direction of appearing animation. If left empty it copies position parameter: so appears from .top edge, if position is set to .top disappearTo - same as appearFrom, but for disappearing animation. If left empty it copies appearFrom. animation - custom animation for popup sliding onto screen
autohideIn - time after which popup should disappear
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
closeOnTapOutside - false by default: enable/disable closing on tap on outside of popup
backgroundColor - Color.clear by default: change background color of outside area
backgroundView - custom background builder for outside area (if this one is set backgroundColor is ignored)
isOpaque - false by default: if true taps do not pass through popup's background and the popup is displayed on top of navbar. For more see section "Show over navbar"
useKeyboardSafeArea - false by default: if true popup goes up for keyboardHeight when keyboard is displayed dismissCallback - custom callback to call once the popup is dismissed

Draggable card - sheet

To implement a sheet (like in 4th gif) enable dragToDismiss on bottom toast (see example project for implementation of the card itself)

.popup(isPresented: $show) {
    // your content 
} customize: {
    $0
        .type (.toast)
        .position(bottom)
        .dragToDismiss(true)
}

Examples

To try PopupView examples:

  • Clone the repo https://github.com/exyte/PopupView.git
  • Open terminal and run cd <PopupViewRepo>/Example/
  • Run pod install to install all dependencies
  • Run open PopupViewExample.xcworkspace/ to open project in the Xcode
  • Try it!

Installation

dependencies: [
    .package(url: "https://github.com/exyte/PopupView.git")
]

To install PopupView, simply add the following line to your Podfile:

pod 'ExytePopupView'

To integrate PopupView into your Xcode project using Carthage, specify it in your Cartfile

github "Exyte/PopupView"

Requirements

  • iOS 15.0+ / macOS 11.0+ / tvOS 14.0+ / watchOS 7.0+
  • Xcode 12+

Our other open source SwiftUI libraries

Grid - The most powerful Grid container
ScalingHeaderScrollView - A scroll view with a sticky header which shrinks as you scroll
AnimatedTabBar - A tabbar with number of preset animations
MediaPicker - Customizable media picker
Chat - Chat UI framework with fully customizable message cells, input view, and a built-in media picker
ConcentricOnboarding - Animated onboarding flow
FloatingButton - Floating button menu
ActivityIndicatorView - A number of animated loading indicators
ProgressIndicatorView - A number of animated progress indicators
SVGView - SVG parser
LiquidSwipe - Liquid navigation animation

popupview's People

Contributors

adusak avatar alex-m-b95 avatar andriizakhliupanyi avatar azaitsev avatar denis-obukhov avatar f3dm76 avatar gly avatar hossinasaadi avatar jeonghi avatar jlz avatar jpangburn avatar kaioelfke avatar mnndnl avatar mudrhs1997 avatar muratbataray avatar richardgroves avatar shipinev avatar villygreen avatar yonabalink avatar yonaharel avatar zapletnev avatar zuinqstudio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

popupview's Issues

Add .popup(item:) variant

There's a need to pass information to the toast UI. SwiftUI handles this for various views by providing an item variant for view modifiers:

    public func popover<Item, Content>(item: Binding<Item?>, attachmentAnchor: PopoverAttachmentAnchor = .rect(.bounds), arrowEdge: Edge = .top, @ViewBuilder content: @escaping (Item) -> Content) -> some View where Item : Identifiable, Content : View

I'm looking at your code and wondering how hard it would be to add the same thing, but I don't quite understand everything you're doing in yours.

Need to get dismiss source

I want to react differently when popup is dismissed because of a tap or because of a drag.

Here is the changes I've made to accomplish this 👇

PopupChanges

Is there another way of achieving what I want ?
I tried adding a click on my view but it doesn't work well since it doesn't take into account your drag handling code.

Do you think that can interest other users ? Do you want me to do a pull request ?

Ignore NavigationBar in Popup

popup with background not ignore navigationview, is it possible to cover all screen while keep using navigationview ?

Screen Shot 2022-04-01 at 16 06 29

Popup blocks touch propagation to List View

When using this library with a List and Navigation links, you have to hold the list cell to activate the navigation link or swipe to delete.

Here is the code I'm using to set up the toast:

.popup(isPresented: $showCopiedToast,
                   type: .toast,
                   position: .bottom,
                   autohideIn: 2,
                   closeOnTap: false) {
                HStack {
                    Text(self.toastText)
                }
                .frame(width: 200, height: 60)
                .background(Color(red: 0.85, green: 0.8, blue: 0.95))
                .cornerRadius(30.0)
                .padding(15)
            }

My guess is the simultaneousGesture stuff is messing with the list view. A workaround or fix would be appreciated.

Is there a way to make the toast show on top of modals?

Ideally we'd like to be able to display a toast and it appear no matter if there was a modal on the screen or not. Right now if there is a modal the toast appears underneath it. We could add the view modifier onto this modal view, but then the toast would go down once the view was dismissed. Is this something that is possible?

closeOnTapOutside doesn't work as intended

I'm using this view like this:

var body: some View {
    ZStack {
        VStack(alignment: .center, spacing: 0, content: {

            Text("Hello")

            Spacer().frame(height: 50)

            Text("Another Text")

            Spacer().frame(height: 30)

            SomeOtherComponent()
            
            Button("Next") {
                print("Button clicked")
            }

            Spacer()

            Image("Official-Logo")
                .resizable()
                .scaledToFit()
                .padding(.vertical, 10)
                .padding(.horizontal, 15)
                .frame(
                    minWidth: 0,
                    maxWidth: .infinity
                )

        }).padding(.top, 50)
    }.popup(isPresented: $showingMessage, closeOnTapOutside: true) {
        VStack(alignment: .leading, spacing: 5, content: {
            Text("Information?")
                .padding(EdgeInsets(top: 10, leading: 10, bottom: 5, trailing: 10))

            Text("Lorem Ipsum Dolor.")
                .padding(EdgeInsets(top: 5, leading: 10, bottom: 20, trailing: 10))
        })
        .frame(width: 320, height: 250)
        .background(Color("darkGray"))
        .cornerRadius(5.0)
    }
}

So in this view, whenever I click outside the popup esp. in the spacers it just fails to dismiss the popup.

Limitation usage

PopouView .popup requires a host view to be a fullscreen. For example, if I put .popup at a view with height =200, it will show a different way.
Is there any better way to allow it to attach to any view and show at the correct position?

Simulator Screen Shot - iPhone 11 Pro Max - 2020-05-21 at 10 02 13

Xcode 12 build failure

I can see this error in new XCode 12

The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions

image

closeOnTapOutside causes issues with SegmentedPicker

Hi,
I have found that if the view being presented by PopupView contains a SegmentedPicker view and closeOnTapOutside is true, the SegmentedPicker does not respond to changes with the usual tap gesture, only to long presses and dragging of segments across it. I believe there are issues with the TapGesture responsible for closeOnTapOutside.

Example with closeOnTapOutside set to true
https://user-images.githubusercontent.com/17231825/127427851-838b4b1c-99ce-490a-9085-29199ddb0b19.mov

Example with closeOnTapOutside set to false

RPReplay_Final1627529799.mov

popup toast doesn't appear if isPresented is .constant(true)

In this code:

            .popup(isPresented: .constant(true),
                   type: .toast,
                   position: .bottom,
                   dragToDismiss: true,
                   closeOnTap: false,
                   closeOnTapOutside: true,
                   dismissCallback: {
                   // on dismiss
                  }) {
                CardView { ... }

PopupView bases the appearAction on isPresented changing:

        .valueChanged(value: isPresented) { isPresented in
            appearAction(isPresented: isPresented)
        }

But if the binding is a .constant(true) since PopupView's inception, it doesn't appear.

Redrawing of views

Changing how the view is shown would prevent reloading of the content.

public func body(content: Content) -> some View {
        Group {
            main(content: content)
        }
        .valueChanged(value: isPresented) { isPresented in
            appearAction(isPresented: isPresented)
        }
    }
    
    private func main(content: Content) -> some View {
        ZStack {
            content
                .frameGetter($presenterContentRect)
            
            if showContent {
                backgroundColor
                    .applyIf(closeOnTapOutside) { view in
                        view.contentShape(Rectangle())
                    }
                    .addTapIfNotTV(if: closeOnTapOutside) {
                        dismiss()
                    }
                    .edgesIgnoringSafeArea(.all)
                    .opacity(currentBackgroundOpacity)
                    .animation(animation)
            }
        }
        .overlay(sheet())
    }

Tap to dismiss doesn't cancel previously scheduled auto hide DispatchWorkItem

I've implemented a queue of toasts, using PopupView for presentation. The issue I'm running into is that when a toast is dismissed via tap and I present the next toast in queue, it gets auto-hidden early. It looks like this is the code with the bug.

if let autohideIn = autohideIn {
    dispatchWorkHolder.work?.cancel()
    dispatchWorkHolder.work = DispatchWorkItem(block: {
        self.isPresented = false
    })
    if isPresented, let work = dispatchWorkHolder.work {
        DispatchQueue.main.asyncAfter(deadline: .now() + autohideIn, execute: work)
    }
}

It seems that since dispatchWorkHolder is a class, it's work property is being passed by reference. Therefore, when I present a second toast, the work property is set to a new DispatchWorkItem that has not been cancelled. So at the time the previously scheduled asyncAfter executes, it is not a cancelled item.

[BUG] dragToDismiss is ignored

Hi,

Many thanks for this great package :)

I found an issue (or so I think): the dragToDismiss parameter is not been considered. I believe it should be changed in the bellow function:

    /// This is the builder for the sheet content
    func sheet() -> some View {

        // if needed, dispatch autohide and cancel previous one
        if let autohideIn = autohideIn {
            dispatchWorkHolder.work?.cancel()
            
            // Weak reference to avoid the work item capturing the struct,
            // which would create a retain cycle with the work holder itself.
            dispatchWorkHolder.work = DispatchWorkItem(block: { [weak isPresentedRef] in
                isPresentedRef?.value.wrappedValue = false
                dismissCallback()
            })
            if isPresented, let work = dispatchWorkHolder.work {
                DispatchQueue.main.asyncAfter(deadline: .now() + autohideIn, execute: work)
            }
        }

        let sheet = ZStack {
            Group {
                VStack {
                    VStack {
                        self.view()
                            .addTapIfNotTV(if: closeOnTap) {
                                self.dispatchWorkHolder.work?.cancel()
                                self.isPresented = false
                                self.dismissCallback()
                            }
                            .background(
                                GeometryReader { proxy -> AnyView in
                                    let rect = proxy.frame(in: .global)
                                    // This avoids an infinite layout loop
                                    if rect.integral != self.sheetContentRect.integral {
                                        DispatchQueue.main.async {
                                            self.sheetContentRect = rect
                                        }
                                    }
                                    return AnyView(EmptyView())
                                }
                            )
                    }
                }
                .frame(width: screenSize.width)
                .offset(x: 0, y: currentOffset)
                .animation(animation)
            }
        }

        // It should return the view if dragToDismiss is false
        if (!dargToDismiss) { return sheet }

        #if !os(tvOS)
        let drag = DragGesture()
            .updating($dragState) { drag, state, _ in
                state = .dragging(translation: drag.translation)
            }
            .onEnded(onDragEnded)

        return sheet
            .offset(y: dragOffset())
            .simultaneousGesture(drag)
        #else
        return sheet
        #endif
    }

Many thanks in advance.

Help regarding background blur and drop shadow

Hey everyone,

I was wondering if anybody could point me in the right direction on how to modify the PopupView.swift file to add a background blur and a dropshadow to the popup window. I have been trying to adjust the file but I'm unable to blur the view below the popup.

Thanks for your help!

Feature Request: Queue

First off I'm happy with the library, it made showing a popup easy. I'd very much like to queue them and have found that to be somewhat cumbersome on my end. Any chance you could add either some kind of dismiss callback, make the binding value passed in set to false when autohide happens, or just build some nice API for queuing popups?

Popup tap to close & outside tap to close & AutoHide not working on dynamic parent view - Only DragView working

`
//MasterView.swift

import SwiftUI
import CoreData
import Foundation

class DataCenter: ObservableObject {
@published var toastPopupView:AnyView = AnyView(EmptyView())
@published var showtoastPopup:Bool = false
}

struct ContentView: View {

@StateObject var dataCenter = DataCenter()
@State var isLoading:Bool = false
var body: some View {
    ZStack{
       
        Color("launchScreenBackgroundColor").ignoresSafeArea()

        NavigationView{

            MainView(selectedView: ShowView()).environmentObject(dataCenter)
            
        }.navigationViewStyle(StackNavigationViewStyle())
       
            dataCenter.toastPopupView
   
    }.navigationBarHidden(true)
    .navigationBarBackButtonHidden(true)   
}

}
`

`
//ChildView.swift

import SwiftUI

struct DashboardS: View {

var body: some View {
    
    
    
    ZStack(alignment:.top){
        Color("launchScreenBackgroundColor2").ignoresSafeArea()
                                
    
    }.navigationBarHidden(true)
    .navigationBarBackButtonHidden(true)
    .onAppear {

]
dataCenter.showtoastPopup = false
dataCenter.toastPopupView = AnyView(
ZStack{

            }.frame(maxWidth: .infinity)
            .frame(height:120)
            .popup(isPresented:  $dataCenter.showtoastPopup, type: .toast, position: .bottom, animation: .default, autohideIn: 2, dragToDismiss: true, closeOnTap: true, closeOnTapOutside: true) {
                HStack {
                    Text("TEST").foregroundColor(Color.white)
                }
                .frame(maxWidth: .infinity)
                .frame(height:120)
                .background(Color.red)
                .shadow(color: .black, radius: 2)

            }.frame(maxWidth: .infinity)
            .frame(height:120)
            
        )
        DispatchQueue.main.async {
            dataCenter.showtoastPopup = true
        }

// DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// dataCenter.showtoastPopup = false. // this turning to false also not working
//// dataCenter.toastPopupView = AnyView(EmptyView())
// }

    }
  
    
   
}

}

`

this is a short example
but from main to a child there are several views that loads for bottom navigation.
popup comes, drag to close work but tap to close, outside tap to close and autohide not working.

custom tab View
Screenshot 2021-06-18 at 9 25 57 PM

popup load
Screenshot 2021-06-18 at 9 26 43 PM

View layers
Screenshot 2021-06-18 at 9 27 50 PM

Thanks

Popup view should be lazy loaded

Here is an example

The current implemention is even showSystemAlertNotification = false, popup body is still executed. This could bring some problems in reality:

data struct is set a valid value only if showSystemAlertNotification= true. Since it's not lazy-loaded, a crash happens since self.systemAlertNotification = nil

.popup(isPresented: $showSystemAlertNotification, type: .floater(), position: .top, animation: Animation.spring()) {
                SystemAlert(alert: self.data!)
            }

Keep getting "Invalid frame dimension" error

When dismissing the popup view, got error message often, anyone know how to resolve it?

2021-04-14 12:51:35.864282+0800 gikiapp[923:160335] [SwiftUI] Invalid frame dimension (negative or non-finite).
2021-04-14 12:51:35.883202+0800 gikiapp[923:160335] [SwiftUI] Invalid frame dimension (negative or non-finite).

Popup view makes the child view navigate to top

Xcode. 13.1
iOS 15.0

At the end of a long list I want to trigger a popup view. When the user triggers it the scrollview is reset to beginning.

struct ChildView: View {
    
    @Binding var show: Bool
    let text: String
    
    var body: some View {
        
        ScrollView(.vertical, showsIndicators: false) {
            ForEach(0..<50) { _ in
                Text("Hello World")
                    .padding()
            }
            
            VStack {
                Button {
                    show.toggle()
                } label: {
                    Text(text)
                }
            }
        }
        .clipped()

    }
}

struct TabsView: View {
    
    @State var show = false
    
    var body: some View {
        
        ZStack {
            TabView {
                ChildView(show: $show, text: "FirstView")
                    .tabItem({
                        Label("First", systemImage: "greetingcard")
                    })
            }
        }
        .popup(isPresented: $show, type: .default, dragToDismiss: true, closeOnTap: false, closeOnTapOutside: true, backgroundColor: .black.opacity(0.5)) {
            VStack {
                Text("👻")
                Text("Hello World.")
                Button {
                    show.toggle()
                } label: {
                    Text("Dismiss ")
                }
            }
            .padding(.vertical, 20)
            .padding(.horizontal, 40)
            .frame(width: 300, height: 300)
            .background(.red)
            .cornerRadius(10)
        }
    }
}

Popup not showing up

The popup is not working anymore, it doesn't appear 🤔 this code was working in v0.0.3 but not on the latest v0.0.4

struct ContentView: View {
    
    @State var name: String = ""
    @State var isPopupPresented = false
    
    var body: some View {
        ZStack {
            VStack {
                Text("Test")
                    .padding()
                    .overlay(RoundedRectangle(cornerRadius: 8).stroke().foregroundColor(.green))
                
                Button(action: {
                    self.isPopupPresented.toggle()
                }, label: { Text("Deploy").padding() })
            }.padding()
            
        }.popup(isPresented: $isPopupPresented, type: .toast, autohideIn: 3.0, view: {
            HStack {
                Text("The popup")
            }
            .frame(width: 200, height: 60)
            .background(Color(red: 0.85, green: 0.8, blue: 0.95))
            .cornerRadius(8.0)
            .padding(.bottom, 25)
        })
    }
}

Suggestion: Add opacity to body content

Hello there.

When I use both ColorPicker & PopupView, ColorPicker tap gesture get blocked by PopupView.

I added these code to body content ⬇️ fixed this problem:

.opacity(isPresented ? 1 : 0)

截屏2021-07-18 下午6 29 59

NavigationView makes the app crash with SIGABRT

I've tested the Popup with this simple View

import SwiftUI
import PopupView

struct ContentView: View {
    
    @State var name: String = ""
    @State var isPopupPresented = false
    
    var body: some View {
        NavigationView {
            ZStack {
                Text("Test")
                    .padding()
                    .overlay(RoundedRectangle(cornerRadius: 8).stroke().foregroundColor(.green))
                
            }.popup(isPresented: $isPopupPresented, view: {
                HStack {
                    Text("The popup")
                }
                .frame(width: 200, height: 60)
                .background(Color(red: 0.85, green: 0.8, blue: 0.95))
                .cornerRadius(30.0)
            })
            
            .navigationBarTitle("Large Title")
        }
    }
}

but as soon as I deploy it on the simulator the view crashes with this log

2020-06-11 12:07:41.661664+0200 Test[2114:73858] precondition failure: invalid input index: 4
(lldb) 

Screenshot 2020-06-11 at 12 10 05

Top Floater is showing a line after dismissed

I just cloned the repository and tried the example but it seems top floater is leaving a line after it gets dismissed.
All other popup types doesn't show it.

I am using Xcode 13.3.
In between thanks for making this great library.

show popup on tabbar page

Hello there.
I want to show pop ups on tabbar page. I want to show the views I have created in subpages but. I want to send views via shared class ObservableObject. I couldn't solve it anyway. Can you help me with this?


class sharedValues: ObservableObject {
    @Published var selectedItemIndex : Int = 1
    @Published var showTabBar : Bool = true
    @Published var blurTabBar : Bool = false
    
    
    
    @Published var showingPopup = false

    var sharePopupView ?
}




struct TabPage: View {
    @EnvironmentObject var share : sharedValues

    var body: some View {
        VStack(spacing:0) {

            NavigationView{
                
                switch share.selectedItemIndex {
                case 0: Shop()
                case 1: ConversationsView()

                case 2: GameHome()
                case 3: GameHome()
                default: GameHome()
                }
            }
            
            Tabbar()
                
        }
        .popup(isPresented: $share.showingPopup, type: .`default`,animation: .spring(), closeOnTapOutside: true) {
            share.sharePopupView
        }

    }
    
}


struct ConversationsView: View {
    @ViewBuilder private func sunpage1() -> some View {
        HStack(){
                Button(action: {
                    share.showingPopup = true
                    share.sharePopupView = {
                        Some view items ????
                    }
                }) {
                    VStack {
                        Image(systemName: "trash.slash")                        
                        Text("delete all")
                    }
                }
}
}

Position .center?

Hi there. Thanks for this library. Could we also get a position .center? Thanks!

[Bug]Fix retain cycle issue

First, thanks for your PopupView library❤️.

Issue

ContentView's @StateObject variable never deinit

Minimal Example

class Test: ObservableObject {
    var loadState: EMLoadState = .loading
    var groupEntityArr: [ListGroupEntity] = []
    var groupStructArr: [ListGroupStruct] = []
    @Published var groupUIArr: [ListGroupUI] = []
    
    deinit {
        print("deinit will never be executed") // 《=========
    }
}
struct TestView: View {
    @StateObject var viewModel = Test()
    @State var show: Bool = false
    @State var text: String = ""
    var body: some View {
        ZStack {
            
        }
        .popup(isPresented: $show, autohideIn: 2, dismissCallback: {
            temp()
        }, view: {
            Text(text)
        })
    }
    
    func temp() {
        print(self.text)
    }
}

Solution

if let autohideIn = autohideIn {
            dispatchWorkHolder.work?.cancel()
            
            let block = dismissCallback // add this code
            dispatchWorkHolder.work = DispatchWorkItem(block: { [weak isPresentedRef] in
                isPresentedRef?.value.wrappedValue = false
                dismissCallback() // remove this code, which cause @StateObject never deinit
                block() // add this code
            })
            if isPresented, let work = dispatchWorkHolder.work {
                DispatchQueue.main.asyncAfter(deadline: .now() + autohideIn, execute: work)
            }
        }

Animates in wrong direction for top / bottom

Currently, when I go for a toast or a float, if I pass .top, it animates from the top bot goes all the way to the bottom. If I pass in .bottom, it animates from the bottom but goes all the way to the top

Can not hide on Mac Catalyst

.popup(isPresented: $showAlert, type: .floater() ,position: .top, animation: Animation.spring(), autohideIn: 2) {
            HStack(spacing: 10) {
                Image(systemName: "xmark.circle.fill")
                    .foregroundColor(.white)
                    .font(.system(size: 20))
                Text("No data".localized)
                    .font(.headline)
                    .foregroundColor(.white)
            }
            .frame(width: 150, height: 40)
            .background(Color.red)
            .cornerRadius(30.0)
            .shadow(radius: 5)
        }

image

autoHide might be failed in Xcode 13

Description

  • autohideIn is not working: the dismissCallback will be called continuously, and the dialog is not dismissed
.popup(isPresented: $viewModel.infoToastShow,
                       type: .floater(verticalPadding: 15),
                       position: .top,
                       autohideIn: 2,
                       dismissCallback: viewModel.infoToastCallback) {
                           // UI
                       }

Xcode

Screen Shot 2021-10-06 at 11 38 15 PM

how to pass custom Text to pop up

Hey all, I have Button which calls a ScannerView and receives the scanned barcode. I would like to pass the scanned String to a popup view and can't figure out how to do it properly. The pop up is always empty

That's my simplified code

struct WorkflowView: View {

    @State private var isShowingScanner = false
    @State private var scannedCode: String?
    @State private var isShowingScannerBanner = false

    var body: some View {
        ZStack{ //need to trigger pop up views
            VStack {
                    Button(action: {
                        self.isShowingScanner = true
                    }, label: {
                        Text("Scan barcode")
                            .font(.title)
                            .padding(5)
                            .background(Color.orange)
                            .cornerRadius(10)
                    })
            }
            .sheet(isPresented: $isShowingScanner) {
                    CodeScannerView(codeTypes: [.ean8, .ean13, .upce, .code39, .code93, .code128, .code39Mod43, .interleaved2of5, .qr]) { response in
                        if case let .success(result) = response {
                            scannedCode = result.string
                            isShowingScanner = false
                            isShowingScannerBanner = true
                        }
                    }
                }
        }
        .popup(isPresented: $isShowingScannerBanner, type: .floater(verticalPadding: 90), position: .top, autohideIn: 2) {
                    createSuccessBanner(message: scannedCode ?? "")
                }
}

add default value for floater's vertical padding

When I use the type .floater(verticalPadding:) in case of a NavigationView and the position is top, the popup view appears on top of the notch in iPhone X and later, which requires me to calculate safeAreaInsets.top and then add it to the padding, this can be done using GeometryReader which's not suitable in some situations and has side effects.
I believe adding a default value for the padding to include top safe area insets would be a good idea.

Close Modal When Click to Outside

Hello, thanks for this useful pod :)

I recommend if you add a feature for hiding modal when users click to the outside of modals, it will be so useful :)

Thanks in advance.

Improvement: Fix sheet layout bugs & NavigationBarItem layout bugs

Hello there!

I modified the source code, and fixed these issues:

1、Wrong layout in presented view(present via sheet)
2、NavigationBarItem bounce when pull down a presented view(view.isModalInPresentation = true)
3、Popup block ColorPicker tap gesture when popup view is invisible.

Which I didn't implement:

1、Have not implement drag gesture
2、Have not test on Mac、Watch、TV

Apologize for no time to pull a request.
Thanks for your open source.

// Usage
struct TestPage: View {
    @State var show: Bool = false
    var body: some View {
        VStack {
            Text("1")
                .onTapGesture {
                    show.toggle()
                }
            Spacer()
        }
        .em_popup(isPresented: $show,
                  position: .top(padding: 44),
                  duration: 2,
                  animation: .spring(),
                  ignoreEdges: nil,
                  closeOnTap: true,
                  closeOnTapOutside: true,
                  popupContent: {
                    Color.red.frame(width: 200, height: 100)
                  }, onDismiss: {
                    print("dismissed")
                  })
    }
}
// Source Code
import SwiftUI

extension View {
    
    public func em_popup<EMPopupContent: View>(isPresented: Binding<Bool>,
                                               position: EMPopupPosition,
                                               duration: Double? = nil,
                                               animation: Animation? = .spring(),
                                               ignoreEdges: Edge.Set?,
                                               closeOnTap: Bool = true,
                                               closeOnTapOutside: Bool = true,
                                               popupContent: @escaping () -> EMPopupContent,
                                               onDismiss: @escaping() -> ()) -> some View {
        self.modifier(
            EMPopupView(isPresented: isPresented,
                        position: position,
                        duration: duration,
                        animation: animation,
                        ignoreEdges: ignoreEdges,
                        closeOnTap: closeOnTap,
                        closeOnTapOutside: closeOnTapOutside,
                        popupContent: popupContent,
                        onDismiss: onDismiss)
        )
    }
    
    @ViewBuilder
    func em_apply_if<T: View>(_ condition: Bool, apply: (Self) -> T) -> some View {
        if condition {
            apply(self)
        } else {
            self
        }
    }
}


public enum EMPopupPosition: Equatable {
    case top(padding: CGFloat = 0)
    case bottom(padding: CGFloat = 0)
    
    var padding: CGFloat {
        switch self {
        case let .top(padding):
            return padding
        case let .bottom(padding):
            return padding
        }
    }
    
    static public func == (lhs: EMPopupPosition, rhs: EMPopupPosition) -> Bool {
        switch lhs {
        case .top:
            switch rhs {
            case .top:
                return true
            case .bottom:
                return false
            }
        case .bottom:
            switch rhs {
            case .top:
                return false
            case .bottom:
                return true
            }
        }
    }
}


public struct EMPopupView<EMPopupContent: View>: ViewModifier {
    
    @Binding var isPresented: Bool
    
    var position: EMPopupPosition
    
    var duration: Double?
    
    var animation: Animation?

    var ignoreEdges: Edge.Set?
    
    var closeOnTap: Bool
    
    var closeOnTapOutside: Bool

    var popupContent: () -> EMPopupContent
    
    var onDismiss: () -> ()
    

    @State private var presenterContentRect: CGRect = .zero
    
    @State private var popupContentRect: CGRect = .zero

    @State private var topPadding: CGFloat = 0

    private var dispatchWorkHolder: DispatchWorkHolder = DispatchWorkHolder()

    // Handle dispatch capture self
    private var isPresentedRef: ClassReference<Binding<Bool>>?
    
    private var hideOffset: CGFloat {
        if case EMPopupPosition.top(_) = position {
            return -popupContentRect.height
        } else {
            return presenterContentRect.height
        }
    }
    
    init(isPresented: Binding<Bool>,
         position: EMPopupPosition,
         duration: Double?,
         animation: Animation?,
         ignoreEdges: Edge.Set?,
         closeOnTap: Bool,
         closeOnTapOutside: Bool,
         popupContent: @escaping () -> EMPopupContent,
         onDismiss: @escaping() -> ()) {
        self._isPresented = isPresented
        self.position = position
        self.duration = duration
        self.animation = animation
        self.ignoreEdges = ignoreEdges
        self.closeOnTap = closeOnTap
        self.closeOnTapOutside = closeOnTapOutside
        self.popupContent = popupContent
        self.onDismiss = onDismiss
        self.isPresentedRef = ClassReference(self.$isPresented)
    }
    
    public func body(content: Content) -> some View {
        content
            .background(
                // get presenter' rect
                GeometryReader { proxy -> AnyView in
                    let rect = proxy.frame(in: .global)
                    if rect.integral != self.presenterContentRect.integral {
                        DispatchQueue.main.async {
                            self.presenterContentRect = rect
                        }
                    }
                    return AnyView(EmptyView())
                }
            )
            .overlay(makePopupContent())
    }
    
    private func makePopupContent() -> some View {
        if duration != nil {
            dispatchWorkHolder.work?.cancel()
            dispatchWorkHolder.work = DispatchWorkItem(block: { [weak isPresentedRef] in
                isPresentedRef?.value.wrappedValue = false
                onDismiss()
            })
            if isPresented && dispatchWorkHolder.work != nil {
                DispatchQueue.main.asyncAfter(deadline: .now() + duration!, execute: dispatchWorkHolder.work!)
            }
        }
        
        let popup = ZStack {
            // background
            Color.clear
                .onTapGesture {
                    if closeOnTapOutside {
                        self.dispatchWorkHolder.work?.cancel()
                        isPresented = false
                        self.onDismiss()
                    }
                }

            // pop up content
            VStack(spacing: 0) {
                if position == EMPopupPosition.bottom(padding: 0) { Spacer() }
                HStack(spacing: 0) {
                    popupContent()
                        .padding(.top, position == EMPopupPosition.top(padding: 0) ? position.padding : 0)
                        .padding(.bottom, position == EMPopupPosition.bottom(padding: 0) ? position.padding : 0)
                        .background(
                            GeometryReader { proxy -> AnyView in
                                let rect = proxy.frame(in: .global)
                                if rect.integral != self.popupContentRect.integral {
                                    DispatchQueue.main.async {
                                        self.popupContentRect = rect
                                    }
                                }
                                return AnyView(EmptyView().frame(width: popupContentRect.width).animation(.none))
                            }
                        )
                        .onTapGesture {
                            if closeOnTap {
                                self.dispatchWorkHolder.work?.cancel()
                                isPresented = false
                                self.onDismiss()
                            }
                        }
                }
                
                if position == EMPopupPosition.top(padding: 0) { Spacer() }
            }
        }
        .em_apply_if(ignoreEdges != nil, apply: { view in
            view.edgesIgnoringSafeArea(ignoreEdges!)
        })
        .opacity(isPresented ? 1 : 0)
        .offset(y: isPresented ? 0 : hideOffset)
        .animation(animation)

        return popup
            
    }
}

// Handle dispatch capture self
fileprivate class DispatchWorkHolder {
    var work: DispatchWorkItem?
}

// Handle dispatch capture self
fileprivate final class ClassReference<T> {
    var value: T
    
    init(_ value: T) {
        self.value = value
    }
}

Improvement: Drag to dismiss

Hi ! Thanks for your work and for sharing with us this library. I'm coming with a suggestion for improving the UX regarding the options that a user has in order to dismiss the popup. It would be cool if the user would be able to dismiss it by dragging down. Thanks a lot !

Prevent the tap event bubbling

Hi, Thanks for your works on this cool project, I have question that when we enable closeOnTapOutside , how can enable prevent the outside view object receiving the tap event?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.