├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .travis.yml ├── Example ├── JFPopup.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── JFPopup-Example.xcscheme ├── JFPopup.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── JFPopup │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── DrawerView.swift │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── face.imageset │ │ │ ├── Contents.json │ │ │ └── face@2x.png │ ├── Info.plist │ ├── JFPopup_Example-Bridging-Header.h │ ├── OCViewController.h │ ├── OCViewController.m │ ├── PopupInViewController.swift │ ├── PresentVCModeViewController.swift │ ├── TestCustomViewController.swift │ ├── UIViewController+JFPopupObjc.swift │ └── ViewController.swift ├── Podfile ├── Podfile.lock ├── Pods │ ├── JRBaseKit │ │ ├── LICENSE │ │ └── Sources │ │ │ ├── JF.swift │ │ │ ├── Size+Extenison.swift │ │ │ ├── UIColor+Extension.swift │ │ │ ├── UIImage+JFColor.swift │ │ │ └── UIView+JFRect.swift │ ├── Local Podspecs │ │ └── JFPopup.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ └── project.pbxproj │ └── Target Support Files │ │ ├── JFPopup │ │ ├── JFPopup-Info.plist │ │ ├── JFPopup-dummy.m │ │ ├── JFPopup-prefix.pch │ │ ├── JFPopup-umbrella.h │ │ ├── JFPopup.debug.xcconfig │ │ ├── JFPopup.modulemap │ │ ├── JFPopup.release.xcconfig │ │ └── ResourceBundle-JFPopup-JFPopup-Info.plist │ │ ├── JRBaseKit │ │ ├── JRBaseKit-Info.plist │ │ ├── JRBaseKit-dummy.m │ │ ├── JRBaseKit-prefix.pch │ │ ├── JRBaseKit-umbrella.h │ │ ├── JRBaseKit.debug.xcconfig │ │ ├── JRBaseKit.modulemap │ │ └── JRBaseKit.release.xcconfig │ │ ├── Pods-JFPopup_Example │ │ ├── Pods-JFPopup_Example-Info.plist │ │ ├── Pods-JFPopup_Example-acknowledgements.markdown │ │ ├── Pods-JFPopup_Example-acknowledgements.plist │ │ ├── Pods-JFPopup_Example-dummy.m │ │ ├── Pods-JFPopup_Example-frameworks.sh │ │ ├── Pods-JFPopup_Example-umbrella.h │ │ ├── Pods-JFPopup_Example.debug.xcconfig │ │ ├── Pods-JFPopup_Example.modulemap │ │ └── Pods-JFPopup_Example.release.xcconfig │ │ └── Pods-JFPopup_Tests │ │ ├── Pods-JFPopup_Tests-Info.plist │ │ ├── Pods-JFPopup_Tests-acknowledgements.markdown │ │ ├── Pods-JFPopup_Tests-acknowledgements.plist │ │ ├── Pods-JFPopup_Tests-dummy.m │ │ ├── Pods-JFPopup_Tests-umbrella.h │ │ ├── Pods-JFPopup_Tests.debug.xcconfig │ │ ├── Pods-JFPopup_Tests.modulemap │ │ └── Pods-JFPopup_Tests.release.xcconfig └── Tests │ ├── Info.plist │ └── Tests.swift ├── JFActionSheet.podspec ├── JFPopup.podspec ├── JFToast.podspec ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── JFPopup │ ├── Classes │ ├── .gitkeep │ ├── Core │ │ ├── JFPopup.swift │ │ ├── JFPopupAnimation.swift │ │ ├── JFPopupController.swift │ │ ├── JFPopupView.swift │ │ ├── UIView+JFPopup.swift │ │ └── UIViewContoller+JFPopup.swift │ └── General │ │ ├── ActionSheet │ │ ├── JFPopupAction.swift │ │ └── JFPopupActionSheetView.swift │ │ ├── Alert │ │ ├── JFAlertAction.swift │ │ ├── JFAlertView+PopupView.swift │ │ ├── JFAlertView+UIViewController.swift │ │ └── JFAlertView.swift │ │ └── Toast │ │ ├── JFToastQueueTask.swift │ │ ├── JFToastView+Objc.swift │ │ └── JFToastView.swift │ └── Resources │ ├── .gitkeep │ ├── fail@2x.png │ ├── jf_loading@2x.png │ └── success@2x.png └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | .build/ 22 | 23 | # Bundler 24 | .bundle 25 | 26 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 27 | # Carthage/Checkouts 28 | 29 | Carthage/Build 30 | 31 | # We recommend against adding the Pods directory to your .gitignore. However 32 | # you should judge for yourself, the pros and cons are mentioned at: 33 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 34 | # 35 | # Note: if you ignore the Pods directory, make sure to uncomment 36 | # `pod install` in .travis.yml 37 | # 38 | # Pods/ 39 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/JFPopup.xcworkspace -scheme JFPopup-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Example/JFPopup.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/JFPopup.xcodeproj/xcshareddata/xcschemes/JFPopup-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /Example/JFPopup.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/JFPopup.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/JFPopup/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JFPopup 4 | // 5 | // Created by fanjiaorng919 on 10/10/2021. 6 | // Copyright (c) 2021 fanjiaorng919. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/JFPopup/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/JFPopup/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/JFPopup/DrawerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawerView.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/10. 6 | // 7 | 8 | import UIKit 9 | 10 | class DrawerView: UIView { 11 | 12 | var closeHandle: (() -> ())? 13 | 14 | lazy var closeBtn: UIButton = { 15 | var btn = UIButton(type: .system) 16 | if #available(iOS 13.0, *) { 17 | btn = UIButton(type: .close) 18 | } else { 19 | btn.setTitle("关闭", for: .normal) 20 | btn.setTitleColor(.black, for: .normal) 21 | } 22 | btn.jf.right = self.jf_width - 60 23 | btn.jf.top = 15 + CGFloat.jf.statusBarHeight() 24 | btn.jf.size = CGSize(width: 45, height: 45) 25 | btn.addTarget(self, action: #selector(closeAction), for: .touchUpInside) 26 | return btn 27 | }() 28 | 29 | @objc func closeAction() { 30 | self.closeHandle?() 31 | } 32 | 33 | override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | self.backgroundColor = UIColor.jf.rgb(0x7e7eff) 36 | self.addSubview(self.closeBtn) 37 | } 38 | 39 | required init?(coder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/JFPopup/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/JFPopup/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/JFPopup/Images.xcassets/face.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "face@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Example/JFPopup/Images.xcassets/face.imageset/face@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryFans/JFPopup/8b0227215e754218280667cd656e22abc4d62821/Example/JFPopup/Images.xcassets/face.imageset/face@2x.png -------------------------------------------------------------------------------- /Example/JFPopup/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/JFPopup/JFPopup_Example-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | #import "OCViewController.h" 5 | -------------------------------------------------------------------------------- /Example/JFPopup/OCViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCViewController.h 3 | // JFPopup_Example 4 | // 5 | // Created by 逸风 on 2021/10/10. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface OCViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/JFPopup/OCViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // OCViewController.m 3 | // JFPopup_Example 4 | // 5 | // Created by 逸风 on 2021/10/10. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | #import "OCViewController.h" 10 | #import "JFPopup_Example-Swift.h" 11 | 12 | @interface OCViewController () 13 | @property (nonatomic, strong) UIButton *button; 14 | @property (nonatomic, strong) UIButton *button1; 15 | @end 16 | 17 | @implementation OCViewController 18 | 19 | - (UIButton *)button { 20 | if (!_button) { 21 | _button = [UIButton buttonWithType:UIButtonTypeCustom]; 22 | [_button setTitle:@"不停点我" forState:UIControlStateNormal]; 23 | [_button setBackgroundColor:UIColor.redColor]; 24 | } 25 | return _button; 26 | } 27 | 28 | - (UIButton *)button1 { 29 | if (!_button1) { 30 | _button1 = [UIButton buttonWithType:UIButtonTypeCustom]; 31 | [_button1 setTitle:@"Dismiss" forState:UIControlStateNormal]; 32 | [_button1 setBackgroundColor:UIColor.blueColor]; 33 | } 34 | return _button1; 35 | } 36 | 37 | - (void)viewDidLoad { 38 | [super viewDidLoad]; 39 | self.title = @"OC"; 40 | self.view.backgroundColor = UIColor.whiteColor; 41 | [self popup_actionSheetWith:YES actions:^NSArray * _Nonnull{ 42 | return @[[[JFPopupAction alloc] initWith:@"拍摄" subTitle:@"照片或视频照片" autoDismiss:YES clickActionCallBack:^{ 43 | 44 | }]]; 45 | }]; 46 | [JFToastView toastWithHit:@"你好我兼容Object-C"]; 47 | [self.view addSubview:self.button]; 48 | self.button.frame = CGRectMake(50, 150, 150, 150); 49 | [self.button addTarget:self action:@selector(clickMe) forControlEvents:UIControlEventTouchUpInside]; 50 | 51 | [self.view addSubview:self.button1]; 52 | self.button1.frame = CGRectMake(50, 315, 150, 150); 53 | [self.button1 addTarget:self action:@selector(clickMe1) forControlEvents:UIControlEventTouchUpInside]; 54 | // Do any additional setup after loading the view. 55 | } 56 | 57 | - (void)clickMe1 { 58 | [self dismissViewControllerAnimated:YES completion:^{ 59 | 60 | }]; 61 | } 62 | 63 | - (void)clickMe { 64 | //暂时只兼容 这三种, 后续有issue 可以继续支持 65 | int random = arc4random() % 3; 66 | if (random == 0) { 67 | [JFToastView toastWithHit:@"你好我兼容Object-C"]; 68 | } else if (random == 1) { 69 | [JFToastView toastWithIcon:JFToastObjcAssetTypeImageName imageName:@"face"]; 70 | } else { 71 | [JFToastView toastWithHit:@"支付成功" type:JFToastObjcAssetTypeSuccess imageName:nil]; 72 | } 73 | } 74 | 75 | /* 76 | #pragma mark - Navigation 77 | 78 | // In a storyboard-based application, you will often want to do a little preparation before navigation 79 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 80 | // Get the new view controller using [segue destinationViewController]. 81 | // Pass the selected object to the new view controller. 82 | } 83 | */ 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /Example/JFPopup/PopupInViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopupInViewController.swift 3 | // JFPopup_Example 4 | // 5 | // Created by 逸风 on 2021/10/19. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JFPopup 11 | 12 | class PopupInViewController: UIViewController { 13 | 14 | var count = 0 15 | let frame = CGRect(x: 15, y: 0, width: CGSize.jf.screenWidth() - 30, height: itemHeight) 16 | var originY: CGFloat = 0 17 | 18 | var scrollView: UIScrollView = { 19 | let scrollView = UIScrollView() 20 | scrollView.contentSize = CGSize(width: CGSize.jf.screenWidth(), height: 0) 21 | scrollView.alwaysBounceVertical = true 22 | scrollView.bounces = true 23 | return scrollView 24 | }() 25 | 26 | @discardableResult private func buildLabel(withTitle title: String) -> UILabel { 27 | var label = UILabel() 28 | label.font = UIFont.systemFont(ofSize: 16, weight: .semibold) 29 | label.text = title + ":" 30 | label.textColor = UIColor.black 31 | self.scrollView.addSubview(label) 32 | label.frame = frame 33 | label.numberOfLines = 1 34 | label.sizeToFit() 35 | label.jf.height = label.jf.height + 30 36 | label.jf.origin.y = originY 37 | originY += label.jf.height 38 | return label 39 | } 40 | 41 | @discardableResult private func buildSubLabel(withTitle title: String) -> UILabel { 42 | var label = UILabel() 43 | label.text = title 44 | label.textColor = UIColor.gray 45 | label.font = UIFont.systemFont(ofSize: 13) 46 | self.scrollView.addSubview(label) 47 | label.frame = frame 48 | label.numberOfLines = 2 49 | label.sizeToFit() 50 | label.jf.origin.y = originY 51 | originY += label.jf.height 52 | return label 53 | } 54 | 55 | private func buildButton(withTitle title: String) -> UIButton { 56 | var btn = UIButton(type: .custom) 57 | btn.setTitle(title, for: .normal) 58 | btn.setTitleColor(.black, for: .normal) 59 | btn.titleLabel?.font = UIFont.systemFont(ofSize: 15) 60 | btn.contentHorizontalAlignment = .left 61 | btn.setBackgroundImage(UIImage.jf.color(0x000000,alpha: 0.1), for: .highlighted) 62 | self.scrollView.addSubview(btn) 63 | btn.frame = frame 64 | btn.jf.origin.y = originY 65 | addBottomLine(with: btn) 66 | originY += btn.jf.height 67 | return btn 68 | } 69 | 70 | private func addBottomLine(with actionItem: UIView) { 71 | var lineView = UIView() 72 | lineView.backgroundColor = UIColor.init(red: 221 / 255.0, green: 221 / 255.0, blue: 221 / 255.0, alpha: 1) 73 | actionItem.addSubview(lineView) 74 | lineView.jf.left = 0 75 | lineView.jf.top = actionItem.jf.height - 1 76 | lineView.jf.height = 0.5 77 | lineView.jf.width = actionItem.jf.width 78 | } 79 | 80 | override func viewDidLoad() { 81 | super.viewDidLoad() 82 | 83 | var t: CGFloat = 0 84 | if #available(iOS 11.0, *) { 85 | t = UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 0 86 | } 87 | print("safe area top: " + "\(t)") 88 | self.title = "Popup From UIView" 89 | self.view.backgroundColor = .white 90 | self.view.addSubview(self.scrollView) 91 | self.scrollView.frame = self.view.frame 92 | 93 | self.buildLabel(withTitle: "通用组件示例") 94 | self.buildSubLabel(withTitle: "所有Popup type") 95 | self.buildSubLabel(withTitle: "self.view.popup.xxx, add 到当前 view") 96 | self.buildSubLabel(withTitle: "JFPopupView.popup.xxx add 到 window") 97 | 98 | let btn3 = self.buildButton(withTitle: "Drawer") 99 | btn3.addTarget(self, action: #selector(clickAction3), for: .touchUpInside) 100 | 101 | self.buildLabel(withTitle: "ActionSheet") 102 | let btn4 = self.buildButton(withTitle: "add to current view") 103 | btn4.addTarget(self, action: #selector(clickAction4), for: .touchUpInside) 104 | 105 | let btn5 = self.buildButton(withTitle: "add to window view") 106 | btn5.addTarget(self, action: #selector(clickAction5), for: .touchUpInside) 107 | 108 | self.buildLabel(withTitle: "Toast Usage (v1.1 add)") 109 | 110 | let btn6 = self.buildButton(withTitle: "默认toast,支持灵动岛否则默认剧中") 111 | btn6.addTarget(self, action: #selector(clickAction6), for: .touchUpInside) 112 | 113 | let btn7 = self.buildButton(withTitle: "自定义参数") 114 | btn7.addTarget(self, action: #selector(clickAction7), for: .touchUpInside) 115 | 116 | let btn8 = self.buildButton(withTitle: "默认Icon") 117 | btn8.addTarget(self, action: #selector(clickAction8), for: .touchUpInside) 118 | 119 | let btn9 = self.buildButton(withTitle: "自定义Icon,可以没文本") 120 | btn9.addTarget(self, action: #selector(clickAction9), for: .touchUpInside) 121 | 122 | self.buildLabel(withTitle: "Loading Usage (v1.3 add)") 123 | 124 | let btn10 = self.buildButton(withTitle: "常规不带文字") 125 | btn10.addTarget(self, action: #selector(clickAction10), for: .touchUpInside) 126 | 127 | let btn11 = self.buildButton(withTitle: "带文字") 128 | btn11.addTarget(self, action: #selector(clickAction11), for: .touchUpInside) 129 | 130 | let btn12 = self.buildButton(withTitle: "loading in view (灵动岛会强制keywindow,因为不在最顶层会被遮挡)") 131 | btn12.addTarget(self, action: #selector(clickAction12), for: .touchUpInside) 132 | 133 | self.buildLabel(withTitle: "Alert View Usage (v1.4 add)") 134 | 135 | let btn13 = self.buildButton(withTitle: "默认风格,自带取消按钮") 136 | btn13.addTarget(self, action: #selector(clickAction13), for: .touchUpInside) 137 | 138 | let btn14 = self.buildButton(withTitle: "Title 和 SubTitle可以二选一,单个按钮") 139 | btn14.addTarget(self, action: #selector(clickAction14), for: .touchUpInside) 140 | 141 | let btn15 = self.buildButton(withTitle: "完全自定义") 142 | btn15.addTarget(self, action: #selector(clickAction15), for: .touchUpInside) 143 | 144 | let btn16 = self.buildButton(withTitle: "从VC弹出也行") 145 | btn16.addTarget(self, action: #selector(clickAction16), for: .touchUpInside) 146 | 147 | self.scrollView.contentSize = CGSize(width: CGSize.jf.screenWidth(), height: originY + itemHeight) 148 | } 149 | 150 | @objc func clickAction16() { 151 | self.popup.alert { 152 | [.title("我是从VC Present 出来的"), 153 | .subTitle("用法和View一致,只是一个是self.popup.alert(self是UIViewControll),一个是JFPopupView.popup.alert"), 154 | .confirmAction([ 155 | .text("知道了"), 156 | .tapActionCallback({ 157 | JFPopupView.popup.toast(hit: "知道了") 158 | }) 159 | ]) 160 | ] 161 | } 162 | } 163 | 164 | @objc func clickAction15() { 165 | JFPopupView.popup.alert {[ 166 | .title("不同标题颜色"), 167 | .titleColor(.red), 168 | .withoutAnimation(true), 169 | .subTitle("我是完全自定义的,标题颜色,action颜色,文本都支持修改,不带动画"), 170 | .subTitleColor(.black), 171 | .cancelAction([.textColor(.blue),.text("我是取消超出文本裁切"),.tapActionCallback({ 172 | JFPopupView.popup.toast(hit: "点击了取消") 173 | })]), 174 | .confirmAction([ 175 | .text("我是确定"), 176 | .textColor(.red), 177 | .tapActionCallback({ 178 | JFPopupView.popup.toast(hit: "点击了确定") 179 | }) 180 | ]) 181 | ]} 182 | } 183 | 184 | @objc func clickAction14() { 185 | JFPopupView.popup.alert {[ 186 | .subTitle("我是Title 和 SubTitle可以二选一,单个按钮"), 187 | .showCancel(false), 188 | .confirmAction([ 189 | .text("知道了"), 190 | .tapActionCallback({ 191 | JFPopupView.popup.toast(hit: "我知道了") 192 | }) 193 | ]) 194 | ]} 195 | } 196 | 197 | @objc func clickAction13() { 198 | JFPopupView.popup.alert {[ 199 | .title("温馨提示"), 200 | .subTitle("我是默认风格,自带取消按钮"), 201 | .confirmAction([ 202 | .text("知道了"), 203 | .tapActionCallback({ 204 | JFPopupView.popup.toast(hit: "我知道了") 205 | }) 206 | ]) 207 | ]} 208 | } 209 | 210 | @objc func clickAction10() { 211 | DispatchQueue.main.async { 212 | JFPopupView.popup.loading() 213 | } 214 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 215 | JFPopupView.popup.hideLoading() 216 | JFPopupView.popup.toast(hit: "刷新成功") 217 | } 218 | } 219 | 220 | @objc func clickAction11() { 221 | JFPopupView.popup.loading(hit: "正在载入视频") 222 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 223 | JFPopupView.popup.hideLoading() 224 | JFPopupView.popup.toast(hit: "加载成功", icon: .success) 225 | } 226 | } 227 | 228 | @objc func clickAction12() { 229 | //只支持 controller.view, 默认keywindow 230 | JFPopupView.popup.loading(hit: "加载中", inView: self.view) 231 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 232 | JFPopupView.popup.hideLoading() 233 | JFPopupView.popup.toast(hit: "加载失败", icon: .fail) 234 | } 235 | } 236 | 237 | @objc func clickAction3() { 238 | JFPopupView.popup.drawer { mainContainer in 239 | let view = DrawerView(frame: CGRect(x: 0, y: 0, width: CGSize.jf.screenWidth() - 100, height: CGSize.jf.screenHeight())) 240 | view.backgroundColor = UIColor.jf.rgb(0x7e7eff) 241 | view.closeHandle = { [weak mainContainer] in 242 | mainContainer?.dismissPopupView(completion: { isFinished in 243 | 244 | }) 245 | } 246 | return view 247 | } onDismissPopupView: { mainContainer in 248 | print("is close from tap bg \(String(describing: mainContainer?.isClosedFromTapBackground))") 249 | JFPopupView.popup.toast(hit: "Drawer 消失", icon: .success) 250 | } 251 | } 252 | 253 | @objc func clickAction6() { 254 | JFPopupView.popup.toast(hit: "默认toast,支持灵动岛") 255 | } 256 | 257 | @objc func clickAction8() { 258 | let random = arc4random() % 3 259 | if random == 0 { 260 | JFPopupView.popup.toast(hit: "支付成功", icon: .success) 261 | } else if random == 1 { 262 | JFPopupView.popup.toast(hit: "支付失败", icon: .fail) 263 | } else { 264 | JFPopupView.popup.toast(hit: "自定义", icon: .imageName(name: "face")) 265 | } 266 | } 267 | 268 | @objc func clickAction7() { 269 | // mainContainer 在当前view 弹出, 默认 keywindow 270 | // mainContainer in current view popup, default is keywindow 271 | // withoutAnimation 不用动画 272 | // enableUserInteraction 如果 true 相当于mask遮挡superview手势 不能触发 273 | JFPopupView.popup.toast { 274 | [ 275 | .hit("不响应super view,带背景色,加大时长,不用动画,在当前view弹出,position top"), 276 | .enableUserInteraction(true), 277 | .bgColor(UIColor.jf.rgb(0x000000,alpha: 0.3)), 278 | .autoDismissDuration(.seconds(value: 3)), 279 | .mainContainer(self.view), 280 | .withoutAnimation(true), 281 | .position(.top) 282 | ] 283 | } 284 | } 285 | 286 | @objc func clickAction9() { 287 | var options: [JFToastOption] = [.icon(.imageName(name: "face"))] 288 | let random = arc4random() % 2 289 | if random == 0 { 290 | options += [.hit("Hello Word !")] 291 | } 292 | JFPopupView.popup.toast { options } 293 | } 294 | 295 | @objc func clickAction5() { 296 | self.view.popup.actionSheet { 297 | [ 298 | JFPopupAction(with: "拍摄", subTitle: "照片或视频照片", clickActionCallBack: { [weak self] in 299 | self?.pushVC() 300 | }), 301 | JFPopupAction(with: "从手机相册选择", subTitle: nil, clickActionCallBack: { 302 | 303 | }), 304 | JFPopupAction(with: "用秒剪制作视频", subTitle: nil, clickActionCallBack: { 305 | 306 | }), 307 | ] 308 | } 309 | } 310 | 311 | @objc func clickAction4() { 312 | let highlightAttribute: [NSAttributedString.Key : Any] = [.font: UIFont.systemFont(ofSize: 14), .foregroundColor: UIColor.red] 313 | let subTitle = NSAttributedString(string: "照片或视频照片", attributes: highlightAttribute) 314 | JFPopupView.popup.actionSheet { 315 | [ 316 | JFPopupAction(with: "拍摄", subAttributedTitle: subTitle, clickActionCallBack: { [weak self] in 317 | self?.pushVC() 318 | }), 319 | JFPopupAction(with: "从手机相册选择", subTitle: nil, clickActionCallBack: { 320 | 321 | }), 322 | JFPopupAction(with: "用秒剪制作视频", subTitle: nil, clickActionCallBack: { 323 | 324 | }), 325 | ] 326 | } 327 | } 328 | 329 | @objc func pushVC() { 330 | let vc = OCViewController() 331 | self.navigationController?.pushViewController(vc, animated: true) 332 | } 333 | 334 | } 335 | -------------------------------------------------------------------------------- /Example/JFPopup/PresentVCModeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentVCModeViewController.swift 3 | // JFPopup_Example 4 | // 5 | // Created by 逸风 on 2021/10/19. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JFPopup 11 | 12 | //自定义实现动画 13 | extension PresentVCModeViewController: JFPopupAnimationProtocol { 14 | func present(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView, completion: ((Bool) -> ())?) { 15 | var contianerView = contianerView 16 | contianerView.frame.origin.y = -contianerView.jf.height 17 | contianerView.jf.centerX = CGSize.jf.screenWidth() / 2 18 | contianerView.layoutIfNeeded() 19 | UIView.animate(withDuration: 0.25, animations: { 20 | contianerView.jf.centerY = CGSize.jf.screenHeight() / 2 21 | contianerView.layoutIfNeeded() 22 | }) { (finished) in 23 | transitonContext?.completeTransition(true) 24 | completion?(finished) 25 | } 26 | } 27 | 28 | func dismiss(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView?, completion: ((Bool) -> ())?) { 29 | guard let contianerView = contianerView else { 30 | transitonContext?.completeTransition(true) 31 | completion?(false) 32 | return 33 | } 34 | UIView.animate(withDuration: 0.25, animations: { 35 | contianerView.superview?.alpha = 0 36 | contianerView.frame.origin.y = -contianerView.jf.height 37 | contianerView.layoutIfNeeded() 38 | }) { (finished) in 39 | transitonContext?.completeTransition(true) 40 | completion?(finished) 41 | } 42 | } 43 | } 44 | 45 | class PresentVCModeViewController: UIViewController { 46 | 47 | let frame = CGRect(x: 15, y: 0, width: CGSize.jf.screenWidth() - 30, height: itemHeight) 48 | var originY: CGFloat = 0 49 | 50 | @discardableResult private func buildLabel(withTitle title: String) -> UILabel { 51 | var label = UILabel() 52 | label.font = UIFont.systemFont(ofSize: 16, weight: .semibold) 53 | label.text = title + ":" 54 | label.textColor = UIColor.black 55 | self.scrollView.addSubview(label) 56 | label.frame = frame 57 | label.numberOfLines = 1 58 | label.sizeToFit() 59 | label.jf.height = label.jf.height + 30 60 | label.jf.origin.y = originY 61 | originY += label.jf.height 62 | return label 63 | } 64 | 65 | @discardableResult private func buildSubLabel(withTitle title: String) -> UILabel { 66 | var label = UILabel() 67 | label.text = title 68 | label.textColor = UIColor.gray 69 | label.font = UIFont.systemFont(ofSize: 13) 70 | self.scrollView.addSubview(label) 71 | label.frame = frame 72 | label.numberOfLines = 2 73 | label.sizeToFit() 74 | label.jf.origin.y = originY 75 | originY += label.jf.height 76 | return label 77 | } 78 | 79 | private func buildButton(withTitle title: String) -> UIButton { 80 | var btn = UIButton(type: .custom) 81 | btn.setTitle(title, for: .normal) 82 | btn.setTitleColor(.black, for: .normal) 83 | btn.titleLabel?.font = UIFont.systemFont(ofSize: 15) 84 | btn.contentHorizontalAlignment = .left 85 | btn.setBackgroundImage(UIImage.jf.color(0x000000,alpha: 0.1), for: .highlighted) 86 | self.scrollView.addSubview(btn) 87 | btn.frame = frame 88 | btn.jf.origin.y = originY 89 | addBottomLine(with: btn) 90 | originY += btn.jf.height 91 | return btn 92 | } 93 | 94 | var scrollView: UIScrollView = { 95 | let scrollView = UIScrollView() 96 | scrollView.contentSize = CGSize(width: CGSize.jf.screenWidth(), height: 0) 97 | scrollView.alwaysBounceVertical = true 98 | scrollView.bounces = true 99 | return scrollView 100 | }() 101 | 102 | private func addBottomLine(with actionItem: UIView) { 103 | var lineView = UIView() 104 | lineView.backgroundColor = UIColor.init(red: 221 / 255.0, green: 221 / 255.0, blue: 221 / 255.0, alpha: 1) 105 | actionItem.addSubview(lineView) 106 | lineView.jf.left = 0 107 | lineView.jf.top = actionItem.jf.height - 1 108 | lineView.jf.height = 0.5 109 | lineView.jf.width = actionItem.jf.width 110 | } 111 | 112 | override func viewDidLoad() { 113 | super.viewDidLoad() 114 | self.title = "Present In VC Mode" 115 | self.view.backgroundColor = .white 116 | self.view.addSubview(self.scrollView) 117 | self.scrollView.frame = self.view.frame 118 | 119 | self.buildLabel(withTitle: "快速创建模式示例(闭包)") 120 | self.buildSubLabel(withTitle: "支持4种模式,左抽屉,右抽屉,底部Sheet,对话框,皆支持自定义View") 121 | 122 | let btn = self.buildButton(withTitle: "Drawer Right") 123 | btn.addTarget(self, action: #selector(clickAction), for: .touchUpInside) 124 | 125 | let btn1 = self.buildButton(withTitle: "Dialog") 126 | btn1.addTarget(self, action: #selector(clickAction1), for: .touchUpInside) 127 | 128 | let btn2 = self.buildButton(withTitle: "BottomSheet") 129 | btn2.addTarget(self, action: #selector(clickAction2), for: .touchUpInside) 130 | 131 | let btn3 = self.buildButton(withTitle: "Drawer Left") 132 | btn3.addTarget(self, action: #selector(clickAction3), for: .touchUpInside) 133 | 134 | self.buildLabel(withTitle: "通用组件示例") 135 | 136 | let btn4 = self.buildButton(withTitle: "微信ActionSheet 自带取消") 137 | btn4.addTarget(self, action: #selector(clickAction4), for: .touchUpInside) 138 | 139 | let btn6 = self.buildButton(withTitle: "微信ActionSheet 不带取消") 140 | btn6.addTarget(self, action: #selector(clickAction6), for: .touchUpInside) 141 | 142 | let btn7 = self.buildButton(withTitle: "微信ActionSheet 点击action不退出") 143 | btn7.addTarget(self, action: #selector(clickAction7), for: .touchUpInside) 144 | 145 | let btn13 = self.buildButton(withTitle: "微信ActionSheet Attributed SubTitle") 146 | btn13.addTarget(self, action: #selector(clickAction13), for: .touchUpInside) 147 | 148 | let btn12 = self.buildButton(withTitle: "通用 Alert View From Present VC") 149 | btn12.addTarget(self, action: #selector(clickAction12), for: .touchUpInside) 150 | 151 | self.buildLabel(withTitle: "兼容OC写法") 152 | self.buildSubLabel(withTitle: "见UIViewController+JFPopupObjc.swift") 153 | 154 | let btn5 = self.buildButton(withTitle: "OC Usage(自行写Extension)") 155 | btn5.addTarget(self, action: #selector(pushVC), for: .touchUpInside) 156 | 157 | self.buildLabel(withTitle: "VC模式创建") 158 | 159 | let btn8 = self.buildButton(withTitle: "继承JFPopupController,点击背景不允许退出") 160 | btn8.addTarget(self, action: #selector(clickAction8), for: .touchUpInside) 161 | 162 | let btn9 = self.buildButton(withTitle: "直接初始化方法创建") 163 | btn9.addTarget(self, action: #selector(clickAction9), for: .touchUpInside) 164 | 165 | 166 | self.buildLabel(withTitle: "自定义") 167 | let btn10 = self.buildButton(withTitle: "扩展自定义动画") 168 | btn10.addTarget(self, action: #selector(clickAction10), for: .touchUpInside) 169 | 170 | let btn11 = self.buildButton(withTitle: "自定义配置") 171 | btn11.addTarget(self, action: #selector(clickAction11), for: .touchUpInside) 172 | 173 | self.scrollView.contentSize = CGSize(width: CGSize.jf.screenWidth(), height: originY + itemHeight) 174 | } 175 | 176 | @objc func clickAction13() { 177 | let highlightAttribute: [NSAttributedString.Key : Any] = [.font: UIFont.systemFont(ofSize: 14), .foregroundColor: UIColor.red] 178 | let subTitle = NSAttributedString(string: "照片或视频照片", attributes: highlightAttribute) 179 | self.popup.actionSheet { 180 | [ 181 | JFPopupAction(with: "拍摄", subAttributedTitle: subTitle, clickActionCallBack: { [weak self] in 182 | self?.pushVC() 183 | }), 184 | ] 185 | } 186 | } 187 | 188 | @objc func clickAction12() { 189 | self.popup.alert {[ 190 | .title("从VC弹出alertView"), 191 | .subTitle("也支持从UIView弹出,更多用法请看《从UIView弹出》示例"), 192 | .confirmAction([ 193 | .text("过去看"), 194 | .tapActionCallback({ [weak self] in 195 | self?.navigationController?.pushViewController(PopupInViewController(), animated: true) 196 | }) 197 | ]) 198 | ]} 199 | } 200 | 201 | @objc func clickAction11() { 202 | var config = JFPopupConfig.dialog 203 | config.absoluteRect = .init(x: 20, y: 150, width: 200, height: 200) 204 | config.bgColor = UIColor.jf.rgb(0x7e7eff,alpha: 0.5) 205 | self.popup.custom(with: config) { 206 | let view: UIView = { 207 | let view = UIView() 208 | view.frame = CGRect(x: 0, y: 0, width: 200, height: 200) 209 | view.layer.cornerRadius = 12 210 | view.backgroundColor = .black 211 | let label = UILabel() 212 | label.text = "完全自定义Frame" 213 | label.textColor = .white 214 | view.addSubview(label) 215 | label.sizeToFit() 216 | label.center = view.center 217 | return view 218 | }() 219 | return view 220 | } 221 | } 222 | 223 | @objc func clickAction10() { 224 | var config = JFPopupConfig.dialog 225 | config.bgColor = .clear 226 | let vc = JFPopupController(with: config, popupProtocol: self) { 227 | let view: UIView = { 228 | let view = UIView() 229 | view.frame = CGRect(x: 0, y: 0, width: 200, height: 200) 230 | view.layer.cornerRadius = 12 231 | view.backgroundColor = .black 232 | return view 233 | }() 234 | return view 235 | } 236 | vc.show(with: self) 237 | } 238 | 239 | @objc func clickAction9() { 240 | var config = JFPopupConfig.dialog 241 | config.bgColor = .clear 242 | let vc = JFPopupController(with: config) { 243 | let view: UIView = { 244 | let view = UIView() 245 | view.frame = CGRect(x: 0, y: 0, width: 200, height: 200) 246 | view.layer.cornerRadius = 12 247 | view.backgroundColor = .black 248 | return view 249 | }() 250 | return view 251 | } 252 | vc.show(with: self) 253 | } 254 | 255 | @objc func clickAction8() { 256 | var config = JFPopupConfig.bottomSheet 257 | config.isDismissible = false 258 | let vc = TestCustomViewController(with: config) 259 | vc.show(with: self) 260 | } 261 | 262 | @objc func clickAction7() { 263 | self.popup.actionSheet { 264 | [ 265 | JFPopupAction(with: "从手机相册选择", subTitle: nil, autoDismiss: false, clickActionCallBack: { [weak self] in 266 | print("我没退出") 267 | JFPopupView.popup.toast(hit: "3s后退出") 268 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 269 | self?.popup.dismissPopup() 270 | } 271 | }), 272 | ] 273 | } 274 | } 275 | 276 | @objc func clickAction6() { 277 | self.popup.actionSheet(with: false) { 278 | [ 279 | JFPopupAction(with: "拍摄", subTitle: "照片或视频照片", clickActionCallBack: { [weak self] in 280 | self?.pushVC() 281 | }), 282 | JFPopupAction(with: "从手机相册选择", subTitle: nil, clickActionCallBack: { 283 | 284 | }), 285 | JFPopupAction(with: "用秒剪制作视频", subTitle: nil, clickActionCallBack: { 286 | 287 | }), 288 | ] 289 | } 290 | } 291 | 292 | @objc func clickAction4() { 293 | self.popup.actionSheet { 294 | [ 295 | JFPopupAction(with: "拍摄", subTitle: "照片或视频照片", clickActionCallBack: { [weak self] in 296 | self?.pushVC() 297 | }), 298 | JFPopupAction(with: "从手机相册选择", subTitle: nil, clickActionCallBack: { 299 | 300 | }), 301 | JFPopupAction(with: "用秒剪制作视频", subTitle: nil, clickActionCallBack: { 302 | 303 | }), 304 | ] 305 | } 306 | } 307 | 308 | @objc func clickAction3() { 309 | //default left 310 | self.popup.drawer { 311 | let v = DrawerView(frame: CGRect(x: 0, y: 0, width: CGSize.jf.screenWidth(), height: CGSize.jf.screenHeight())) 312 | v.closeHandle = { [weak self] in 313 | self?.popup.dismissPopup() 314 | } 315 | return v 316 | } 317 | } 318 | 319 | @objc func pushVC() { 320 | let vc = OCViewController() 321 | self.navigationController?.pushViewController(vc, animated: true) 322 | } 323 | 324 | @objc func clickAction2() { 325 | 326 | self.popup.bottomSheet { 327 | let v = UIView(frame: CGRect(x: 0, y: 0, width: CGSize.jf.screenWidth(), height: 300)) 328 | v.backgroundColor = .red 329 | return v 330 | } 331 | } 332 | 333 | @objc func clickAction1() { 334 | self.popup.dialog { 335 | let v = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) 336 | v.backgroundColor = .red 337 | return v 338 | } 339 | } 340 | 341 | @objc func clickAction() { 342 | self.popup.drawer(with: .right) { 343 | let v = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: CGSize.jf.screenHeight())) 344 | v.backgroundColor = .red 345 | return v 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /Example/JFPopup/TestCustomViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestCustomViewController.swift 3 | // JFPopup_Example 4 | // 5 | // Created by 逸风 on 2021/10/11. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JFPopup 11 | 12 | class TestCustomViewController: JFPopupController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | @objc func clickBtnAction() { 20 | self.closeVC(with: nil) 21 | } 22 | 23 | override func viewForContainer() -> UIView? { 24 | let view = UIView() 25 | view.backgroundColor = .black 26 | view.frame = CGRect(x: 0, y: 0, width: CGSize.jf.screenWidth(), height: 300) 27 | var btn = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 100)) 28 | btn.setTitle("点击才能退出", for: .normal) 29 | btn.addTarget(self, action: #selector(clickBtnAction), for: .touchUpInside) 30 | btn.setTitleColor(.white, for: .normal) 31 | view.addSubview(btn) 32 | btn.jf.centerY = view.jf.centerY 33 | btn.jf.centerX = view.jf.centerX 34 | return view 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Example/JFPopup/UIViewController+JFPopupObjc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Test.swift 3 | // JFPopup_Example 4 | // 5 | // Created by 逸风 on 2021/10/10. 6 | // Copyright © 2021 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import JFPopup 11 | 12 | @objc public extension UIViewController { 13 | 14 | @objc func popup_dismiss() { 15 | self.popup.dismissPopup() 16 | } 17 | 18 | @objc func popup_actionSheet(with autoCancelAction: Bool = true, actions: (() -> [JFPopupAction])) { 19 | self.popup.actionSheet(with: autoCancelAction, actions: actions) 20 | } 21 | 22 | @objc func popup_bottomSheet(with isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 23 | self.popup.bottomSheet(with: isDismissible, enableDrag: enableDrag, bgColor: bgColor, container: container) 24 | } 25 | 26 | func popup_drawer(with direction: JFPopupAnimationDirection = .left, isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 27 | self.popup.drawer(with: direction, isDismissible: isDismissible, enableDrag: enableDrag, bgColor: bgColor, container: container) 28 | } 29 | 30 | func popup_dialog(with isDismissible: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 31 | self.popup.dialog(with: isDismissible, bgColor: bgColor, container: container) 32 | } 33 | 34 | func popup_custom(with animationType: JFPopupAnimationType = .dialog, isDismissible: Bool = true, enableDrag: Bool = true, direction: JFPopupAnimationDirection = .left, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 35 | self.popup.objc_custom(with: animationType, isDismissible: isDismissible, enableDrag: enableDrag, direction: direction, bgColor: bgColor, container: container) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Example/JFPopup/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JFPopup 4 | // 5 | // Created by fanjiaorng919 on 10/10/2021. 6 | // Copyright (c) 2021 fanjiaorng919. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JFPopup 11 | 12 | let itemHeight: CGFloat = 50 13 | 14 | class ViewController: UIViewController { 15 | 16 | let frame = CGRect(x: 15, y: 0, width: CGSize.jf.screenWidth() - 30, height: itemHeight) 17 | var originY: CGFloat = 0 18 | 19 | var scrollView: UIScrollView = { 20 | let scrollView = UIScrollView() 21 | scrollView.contentSize = CGSize(width: CGSize.jf.screenWidth(), height: 0) 22 | scrollView.alwaysBounceVertical = true 23 | scrollView.bounces = true 24 | return scrollView 25 | }() 26 | 27 | @discardableResult private func buildLabel(withTitle title: String) -> UILabel { 28 | var label = UILabel() 29 | label.text = title + ":" 30 | label.font = UIFont.systemFont(ofSize: 16, weight: .semibold) 31 | label.textColor = UIColor.black 32 | self.scrollView.addSubview(label) 33 | label.frame = frame 34 | label.numberOfLines = 1 35 | label.sizeToFit() 36 | label.jf.height = label.jf.height + 30 37 | label.jf.origin.y = originY 38 | originY += label.jf.height 39 | return label 40 | } 41 | 42 | @discardableResult private func buildSubLabel(withTitle title: String) -> UILabel { 43 | var label = UILabel() 44 | label.text = title 45 | label.textColor = UIColor.gray 46 | label.font = UIFont.systemFont(ofSize: 13) 47 | self.scrollView.addSubview(label) 48 | label.frame = frame 49 | label.numberOfLines = 2 50 | label.sizeToFit() 51 | label.jf.origin.y = originY 52 | originY += label.jf.height 53 | return label 54 | } 55 | 56 | private func buildButton(withTitle title: String) -> UIButton { 57 | var btn = UIButton(type: .custom) 58 | btn.setTitle(title, for: .normal) 59 | btn.setTitleColor(.black, for: .normal) 60 | btn.titleLabel?.font = UIFont.systemFont(ofSize: 15) 61 | btn.contentHorizontalAlignment = .left 62 | btn.setBackgroundImage(UIImage.jf.color(0x000000,alpha: 0.1), for: .highlighted) 63 | self.scrollView.addSubview(btn) 64 | btn.frame = frame 65 | btn.jf.origin.y = originY 66 | addBottomLine(with: btn) 67 | originY += btn.jf.height 68 | return btn 69 | } 70 | 71 | private func addBottomLine(with actionItem: UIView) { 72 | var lineView = UIView() 73 | lineView.backgroundColor = UIColor.init(red: 221 / 255.0, green: 221 / 255.0, blue: 221 / 255.0, alpha: 1) 74 | actionItem.addSubview(lineView) 75 | lineView.jf.left = 0 76 | lineView.jf.top = actionItem.jf.height - 1 77 | lineView.jf.height = 0.5 78 | lineView.jf.width = actionItem.jf.width 79 | } 80 | 81 | override func viewDidLoad() { 82 | super.viewDidLoad() 83 | self.title = "Example" 84 | self.view.addSubview(self.scrollView) 85 | self.scrollView.frame = self.view.frame 86 | self.buildLabel(withTitle: "示例代码") 87 | self.buildSubLabel(withTitle: "支持从Controller 弹出, 也支持从View 弹出") 88 | self.buildSubLabel(withTitle: "V1.1 add ToastView, Usage请看(从UIView弹出)") 89 | let btn = self.buildButton(withTitle: "从Controller弹出") 90 | btn.addTarget(self, action: #selector(clickAction), for: .touchUpInside) 91 | 92 | let btn1 = self.buildButton(withTitle: "从UIView弹出") 93 | btn1.addTarget(self, action: #selector(clickAction1), for: .touchUpInside) 94 | 95 | let btn2 = self.buildButton(withTitle: "Objc兼容") 96 | btn2.addTarget(self, action: #selector(clickAction2), for: .touchUpInside) 97 | 98 | self.scrollView.contentSize = CGSize(width: CGSize.jf.screenWidth(), height: originY + itemHeight) 99 | } 100 | 101 | @objc func clickAction2() { 102 | // self.navigationController?.pushViewController(OCViewController(), animated: true) 103 | let vc = OCViewController() 104 | self.present(vc, animated: true) 105 | } 106 | 107 | @objc func clickAction1() { 108 | self.navigationController?.pushViewController(PopupInViewController(), animated: true) 109 | } 110 | 111 | @objc func clickAction() { 112 | self.navigationController?.pushViewController(PresentVCModeViewController(), animated: true) 113 | } 114 | 115 | } 116 | 117 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '9.0' 4 | 5 | target 'JFPopup_Example' do 6 | pod 'JFPopup', :path => '../' 7 | 8 | post_install do |installer| 9 | installer.generated_projects.each do |project| 10 | project.targets.each do |target| 11 | target.build_configurations.each do |config| 12 | config.build_settings['CODE_SIGN_IDENTITY'] = '' 13 | end 14 | end 15 | end 16 | end 17 | 18 | target 'JFPopup_Tests' do 19 | inherit! :search_paths 20 | 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - JFPopup (1.5.4): 3 | - JRBaseKit (~> 1.1.1) 4 | - JRBaseKit (1.1.1) 5 | 6 | DEPENDENCIES: 7 | - JFPopup (from `../`) 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - JRBaseKit 12 | 13 | EXTERNAL SOURCES: 14 | JFPopup: 15 | :path: "../" 16 | 17 | SPEC CHECKSUMS: 18 | JFPopup: c3ada13bbda3cfe85bd71228e20a0b622eab72af 19 | JRBaseKit: e817f5516d9427f9dd249c99263f95fa260c3246 20 | 21 | PODFILE CHECKSUM: 3040b8d466bd38b0257fda44b4645a864d4520f5 22 | 23 | COCOAPODS: 1.11.3 24 | -------------------------------------------------------------------------------- /Example/Pods/JRBaseKit/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 JerryFans 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Example/Pods/JRBaseKit/Sources/JF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JF.swift 3 | // Example 4 | // 5 | // Created by JerryFans on 2021/8/4. 6 | // 7 | import UIKit 8 | public struct JF { 9 | let base: Base 10 | init(_ base: Base) { 11 | self.base = base 12 | } 13 | } 14 | 15 | public protocol JFCompatible {} 16 | public extension JFCompatible { 17 | static var jf: JF.Type { 18 | set {} 19 | get { JF.self } 20 | } 21 | var jf: JF { 22 | set {} 23 | get { JF(self) } 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/Pods/JRBaseKit/Sources/Size+Extenison.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Size+Extenison.swift 3 | // Example 4 | // 5 | // Created by JerryFans on 2021/8/4. 6 | // 7 | 8 | import UIKit 9 | extension Bool: JFCompatible {} 10 | public extension JF where Base == Bool { 11 | static func isBangsiPhone() -> Bool { 12 | var isBangs = false 13 | if #available(iOS 11.0, *) { 14 | isBangs = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0 > 0.0 15 | } 16 | return isBangs 17 | } 18 | } 19 | 20 | extension CGFloat: JFCompatible {} 21 | public extension JF where Base == CGFloat { 22 | 23 | static func navigationBarHeight() -> CGFloat { 24 | return self.safeAreaInsets().top + 44.0 25 | } 26 | 27 | static func statusBarHeight() -> CGFloat { 28 | return self.safeAreaInsets().top 29 | } 30 | 31 | static func safeAreaBottomHeight() -> CGFloat { 32 | return self.safeAreaInsets().bottom 33 | } 34 | 35 | static func safeAreaInsets() -> UIEdgeInsets { 36 | if #available(iOS 11.0, *) { 37 | return UIApplication.shared.windows.first?.safeAreaInsets ?? .zero 38 | } else { 39 | return .zero 40 | } 41 | } 42 | } 43 | 44 | extension CGSize: JFCompatible {} 45 | public extension JF where Base == CGSize { 46 | 47 | static func screenBounds() -> CGRect { 48 | return UIScreen.main.bounds 49 | } 50 | 51 | static func screenSize() -> CGSize { 52 | return UIScreen.main.bounds.size 53 | } 54 | 55 | static func screenWidth() -> CGFloat { 56 | return UIScreen.main.bounds.size.width 57 | } 58 | 59 | static func screenHeight() -> CGFloat { 60 | return UIScreen.main.bounds.size.height 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Example/Pods/JRBaseKit/Sources/UIColor+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extension.swift 3 | // Example 4 | // 5 | // Created by JerryFans on 2021/8/4. 6 | // 7 | 8 | import UIKit 9 | extension UIColor: JFCompatible {} 10 | public extension JF where Base: UIColor { 11 | //0xRRGGBB 12 | static func rgb(_ hex:UInt32, alpha:CGFloat = 1.0) -> UIColor { 13 | let divisor = CGFloat(255) 14 | let red = CGFloat((hex & 0xFF0000) >> 16) / divisor 15 | let green = CGFloat((hex & 0x00FF00) >> 8) / divisor 16 | let blue = CGFloat( hex & 0x0000FF ) / divisor 17 | return UIColor.init(red: red, green: green, blue: blue, alpha: alpha) 18 | } 19 | 20 | //0xRRGGBBAA 21 | static func rgba(_ hex:UInt32) -> UIColor { 22 | let divisor = CGFloat(255) 23 | let red = CGFloat((hex & 0xFF000000) >> 24) / divisor 24 | let green = CGFloat((hex & 0x00FF0000) >> 16) / divisor 25 | let blue = CGFloat((hex & 0x0000FF00) >> 8) / divisor 26 | let alpha = CGFloat( hex & 0x000000FF ) / divisor 27 | return UIColor.init(red: red, green: green, blue: blue, alpha: alpha == 0 ? 1 : alpha) 28 | } 29 | 30 | static func argb(_ hex:UInt32) -> UIColor { 31 | let divisor = CGFloat(255) 32 | let alpha = CGFloat((hex & 0xFF000000) >> 24) / divisor 33 | let red = CGFloat((hex & 0x00FF0000) >> 16) / divisor 34 | let green = CGFloat((hex & 0x0000FF00) >> 8) / divisor 35 | let blue = CGFloat( hex & 0x000000FF ) / divisor 36 | return UIColor.init(red: red, green: green, blue: blue, alpha: alpha == 0 ? 1 : alpha) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/Pods/JRBaseKit/Sources/UIImage+JFColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+JFColor.swift 3 | // JRBaseKit 4 | // 5 | // Created by 逸风 on 2021/10/10. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIImage: JFCompatible {} 11 | public extension JF where Base: UIImage { 12 | static func color(_ hex:UInt32, alpha:CGFloat = 1.0) -> UIImage { 13 | return UIImage(customColor: UIColor.jf.rgb(hex, alpha: alpha)) 14 | } 15 | } 16 | 17 | public extension UIImage { 18 | @objc convenience init(customColor: UIColor,size: CGSize = CGSize(width: 1, height: 1)) { 19 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 20 | UIGraphicsBeginImageContext(rect.size) 21 | let context = UIGraphicsGetCurrentContext(); 22 | context?.setFillColor(customColor.cgColor); 23 | context?.fill(rect) 24 | let image = UIGraphicsGetImageFromCurrentImageContext() 25 | UIGraphicsEndImageContext() 26 | guard let cgImage = image?.cgImage else { 27 | self.init() 28 | return 29 | } 30 | self.init(cgImage: cgImage) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/Pods/JRBaseKit/Sources/UIView+JFRect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+JFRect.swift 3 | // JRBaseKit 4 | // 5 | // Created by 逸风 on 2021/10/10. 6 | // 7 | import UIKit 8 | 9 | extension UIView: JFCompatible {} 10 | public extension JF where Base: UIView { 11 | 12 | var top: CGFloat { 13 | get { return base.jf_top } 14 | set { base.jf_top = newValue } 15 | } 16 | 17 | var left: CGFloat { 18 | get { return base.jf_left } 19 | set { base.jf_left = newValue } 20 | } 21 | 22 | var bottom: CGFloat { 23 | get { return base.jf_bottom } 24 | set { base.jf_bottom = newValue } 25 | } 26 | 27 | var right: CGFloat { 28 | get { return base.jf_right } 29 | set { base.jf_right = newValue } 30 | } 31 | 32 | var centerX: CGFloat { 33 | get { return base.jf_centerX } 34 | set { base.jf_centerX = newValue } 35 | } 36 | 37 | var centerY: CGFloat { 38 | get { return base.jf_centerY } 39 | set { base.jf_centerY = newValue } 40 | } 41 | 42 | var width: CGFloat { 43 | get { return base.jf_width } 44 | set { base.jf_width = newValue } 45 | } 46 | 47 | var height: CGFloat { 48 | get { return base.jf_height } 49 | set { base.jf_height = newValue } 50 | } 51 | 52 | var origin: CGPoint { 53 | get { return base.jf_origin } 54 | set { base.jf_origin = newValue } 55 | } 56 | 57 | var size: CGSize { 58 | get { return base.jf_size } 59 | set { base.jf_size = newValue } 60 | } 61 | 62 | @available(iOS 10.0, *) 63 | static func shake() { 64 | UISelectionFeedbackGenerator().selectionChanged() 65 | } 66 | 67 | @available(iOS 10.0, *) 68 | func shake() { 69 | UISelectionFeedbackGenerator().selectionChanged() 70 | } 71 | 72 | } 73 | 74 | //MARK: - For OC 75 | public extension UIView { 76 | @objc var jf_top: CGFloat { 77 | get { 78 | return self.frame.origin.y 79 | } 80 | set { 81 | var frame:CGRect = self.frame 82 | frame.origin.y = newValue 83 | self.frame = frame 84 | } 85 | } 86 | 87 | @objc var jf_left: CGFloat { 88 | get { 89 | return self.frame.origin.x 90 | } 91 | set { 92 | var frame:CGRect = self.frame 93 | frame.origin.x = newValue 94 | self.frame = frame 95 | } 96 | } 97 | 98 | @objc var jf_bottom: CGFloat { 99 | get { 100 | return self.frame.origin.y + self.frame.size.height 101 | } 102 | set { 103 | var frame:CGRect = self.frame 104 | frame.origin.y = newValue - frame.size.height 105 | self.frame = frame 106 | } 107 | } 108 | 109 | @objc var jf_right: CGFloat { 110 | get { 111 | return self.frame.origin.x + self.frame.size.width 112 | } 113 | set { 114 | var frame:CGRect = self.frame 115 | frame.origin.x = newValue - frame.size.width 116 | self.frame = frame 117 | } 118 | } 119 | 120 | @objc var jf_centerX: CGFloat { 121 | get { 122 | return self.center.x 123 | } 124 | set { 125 | self.center = .init(x: newValue, y: self.center.y) 126 | } 127 | } 128 | 129 | @objc var jf_centerY: CGFloat { 130 | get { 131 | return self.center.y 132 | } 133 | set { 134 | self.center = .init(x: self.center.x, y: newValue) 135 | } 136 | } 137 | 138 | @objc var jf_width: CGFloat { 139 | get { 140 | return self.bounds.width 141 | } 142 | set { 143 | var frame:CGRect = self.frame 144 | frame.size.width = newValue 145 | self.frame = frame 146 | } 147 | } 148 | 149 | @objc var jf_height: CGFloat { 150 | get { 151 | return self.bounds.height 152 | } 153 | set { 154 | var frame:CGRect = self.frame 155 | frame.size.height = newValue 156 | self.frame = frame 157 | } 158 | } 159 | 160 | @objc var jf_origin: CGPoint { 161 | get { 162 | return self.frame.origin 163 | } 164 | set { 165 | var frame:CGRect = self.frame 166 | frame.origin = newValue 167 | self.frame = frame 168 | } 169 | } 170 | 171 | @objc var jf_size: CGSize { 172 | get { 173 | return self.frame.size 174 | } 175 | set { 176 | var frame:CGRect = self.frame 177 | frame.size = newValue 178 | self.frame = frame 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/JFPopup.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JFPopup", 3 | "version": "1.5.4", 4 | "summary": "A Swift Popup Module help you popup your custom view easily", 5 | "description": "*JFPopup can help you popup your custom view with any way*\n*Like popup a Drawer, a dialog, a bottomSheet,*\n*Also support Objc, but you should writeJFPopup extension with youself, usage see example.\n*Support many General Kit:\n*Version 1.0.0 support a Wechat Style ActionSheet\n*Version 1.2.0 support a ToastView\n*Version 1.3.0 support a LodingView\n*Version 1.4.0 support a AlertView\n*In the feature, will support more popup view", 6 | "homepage": "https://github.com/JerryFans/JFPopup", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "JerryFans": "fanjiarong_haohao@163.com" 13 | }, 14 | "source": { 15 | "git": "https://github.com/JerryFans/JFPopup.git", 16 | "tag": "1.5.4" 17 | }, 18 | "platforms": { 19 | "ios": "9.0" 20 | }, 21 | "source_files": "Sources/JFPopup/Classes/**/*", 22 | "swift_versions": [ 23 | "4.0" 24 | ], 25 | "dependencies": { 26 | "JRBaseKit": [ 27 | "~> 1.1.1" 28 | ] 29 | }, 30 | "resource_bundles": { 31 | "JFPopup": [ 32 | "Sources/JFPopup/Resources/*.png" 33 | ] 34 | }, 35 | "swift_version": "4.0" 36 | } 37 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - JFPopup (1.5.4): 3 | - JRBaseKit (~> 1.1.1) 4 | - JRBaseKit (1.1.1) 5 | 6 | DEPENDENCIES: 7 | - JFPopup (from `../`) 8 | 9 | SPEC REPOS: 10 | trunk: 11 | - JRBaseKit 12 | 13 | EXTERNAL SOURCES: 14 | JFPopup: 15 | :path: "../" 16 | 17 | SPEC CHECKSUMS: 18 | JFPopup: c3ada13bbda3cfe85bd71228e20a0b622eab72af 19 | JRBaseKit: e817f5516d9427f9dd249c99263f95fa260c3246 20 | 21 | PODFILE CHECKSUM: 3040b8d466bd38b0257fda44b4645a864d4520f5 22 | 23 | COCOAPODS: 1.11.3 24 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/JFPopup-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.5.4 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/JFPopup-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_JFPopup : NSObject 3 | @end 4 | @implementation PodsDummy_JFPopup 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/JFPopup-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/JFPopup-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double JFPopupVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char JFPopupVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/JFPopup.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JFPopup 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -framework "JRBaseKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/JFPopup.modulemap: -------------------------------------------------------------------------------- 1 | framework module JFPopup { 2 | umbrella header "JFPopup-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/JFPopup.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JFPopup 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -framework "JRBaseKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JFPopup/ResourceBundle-JFPopup-JFPopup-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.5.4 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JRBaseKit/JRBaseKit-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JRBaseKit/JRBaseKit-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_JRBaseKit : NSObject 3 | @end 4 | @implementation PodsDummy_JRBaseKit 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JRBaseKit/JRBaseKit-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JRBaseKit/JRBaseKit-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double JRBaseKitVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char JRBaseKitVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JRBaseKit/JRBaseKit.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/JRBaseKit 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JRBaseKit/JRBaseKit.modulemap: -------------------------------------------------------------------------------- 1 | framework module JRBaseKit { 2 | umbrella header "JRBaseKit-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/JRBaseKit/JRBaseKit.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/JRBaseKit 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## JFPopup 5 | 6 | Copyright (c) 2021 JerryFans 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## JRBaseKit 28 | 29 | Copyright (c) 2021 JerryFans 30 | 31 | Permission is hereby granted, free of charge, to any person obtaining a copy 32 | of this software and associated documentation files (the "Software"), to deal 33 | in the Software without restriction, including without limitation the rights 34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software, and to permit persons to whom the Software is 36 | furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in 39 | all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 47 | THE SOFTWARE. 48 | 49 | Generated by CocoaPods - https://cocoapods.org 50 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2021 JerryFans <fanjiarong_haohao@163.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | JFPopup 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Copyright (c) 2021 JerryFans 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | 66 | License 67 | MIT 68 | Title 69 | JRBaseKit 70 | Type 71 | PSGroupSpecifier 72 | 73 | 74 | FooterText 75 | Generated by CocoaPods - https://cocoapods.org 76 | Title 77 | 78 | Type 79 | PSGroupSpecifier 80 | 81 | 82 | StringsTable 83 | Acknowledgements 84 | Title 85 | Acknowledgements 86 | 87 | 88 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_JFPopup_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_JFPopup_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/JFPopup/JFPopup.framework" 180 | install_framework "${BUILT_PRODUCTS_DIR}/JRBaseKit/JRBaseKit.framework" 181 | fi 182 | if [[ "$CONFIGURATION" == "Release" ]]; then 183 | install_framework "${BUILT_PRODUCTS_DIR}/JFPopup/JFPopup.framework" 184 | install_framework "${BUILT_PRODUCTS_DIR}/JRBaseKit/JRBaseKit.framework" 185 | fi 186 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 187 | wait 188 | fi 189 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_JFPopup_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_JFPopup_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup/JFPopup.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit/JRBaseKit.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "JFPopup" -framework "JRBaseKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_JFPopup_Example { 2 | umbrella header "Pods-JFPopup_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Example/Pods-JFPopup_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup/JFPopup.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit/JRBaseKit.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "JFPopup" -framework "JRBaseKit" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_JFPopup_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_JFPopup_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_JFPopup_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_JFPopup_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup/JFPopup.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit/JRBaseKit.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "JFPopup" -framework "JRBaseKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_JFPopup_Tests { 2 | umbrella header "Pods-JFPopup_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-JFPopup_Tests/Pods-JFPopup_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/JFPopup/JFPopup.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JRBaseKit/JRBaseKit.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "JFPopup" -framework "JRBaseKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import JFPopup 3 | 4 | class Tests: XCTestCase { 5 | 6 | override func setUp() { 7 | super.setUp() 8 | // Put setup code here. This method is called before the invocation of each test method in the class. 9 | } 10 | 11 | override func tearDown() { 12 | // Put teardown code here. This method is called after the invocation of each test method in the class. 13 | super.tearDown() 14 | } 15 | 16 | func testExample() { 17 | // This is an example of a functional test case. 18 | XCTAssert(true, "Pass") 19 | } 20 | 21 | func testPerformanceExample() { 22 | // This is an example of a performance test case. 23 | self.measure() { 24 | // Put the code you want to measure the time of here. 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /JFActionSheet.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint JFToast.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'JFActionSheet' 11 | s.version = '1.3.0' 12 | s.summary = 'JFActionSheet is a part of JFPopup Module help you popup a wechat style ActionSheet view easily In Swift' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | *support title and sub title* 22 | *support auto fill cancel button* 23 | *suport auto dismiss when you click* 24 | DESC 25 | 26 | s.homepage = 'https://github.com/JerryFans/JFPopup' 27 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 28 | s.license = { :type => 'MIT', :file => 'LICENSE' } 29 | s.author = { 'JerryFans' => 'fanjiarong_haohao@163.com' } 30 | s.source = { :git => 'https://github.com/JerryFans/JFPopup.git', :tag => s.version.to_s } 31 | # s.social_media_url = 'https://twitter.com/' 32 | 33 | s.ios.deployment_target = '9.0' 34 | 35 | s.source_files = 'JFPopup/Classes/Core/*','JFPopup/Classes/General/ActionSheet/*' 36 | 37 | s.swift_version = ['4.0'] 38 | 39 | s.dependency 'JRBaseKit', '~> 0.9.0' 40 | 41 | # s.resource_bundles = { 42 | # 'JFPopup' => ['JFPopup/Assets/*.png'], 43 | # } 44 | 45 | # s.public_header_files = 'Pod/Classes/**/*.h' 46 | # s.frameworks = 'UIKit', 'MapKit' 47 | 48 | end 49 | -------------------------------------------------------------------------------- /JFPopup.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint JFPopup.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'JFPopup' 11 | s.version = '1.5.7' 12 | s.summary = 'A Swift Popup Module help you popup your custom view easily' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | *JFPopup can help you popup your custom view with any way* 22 | *Like popup a Drawer, a dialog, a bottomSheet,* 23 | *Also support Objc, but you should writeJFPopup extension with youself, usage see example. 24 | *Support many General Kit: 25 | *Version 1.0.0 support a Wechat Style ActionSheet 26 | *Version 1.2.0 support a ToastView 27 | *Version 1.3.0 support a LodingView 28 | *Version 1.4.0 support a AlertView 29 | *In the feature, will support more popup view 30 | DESC 31 | 32 | s.homepage = 'https://github.com/JerryFans/JFPopup' 33 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 34 | s.license = { :type => 'MIT', :file => 'LICENSE' } 35 | s.author = { 'JerryFans' => 'fanjiarong_haohao@163.com' } 36 | s.source = { :git => 'https://github.com/JerryFans/JFPopup.git', :tag => s.version.to_s } 37 | # s.social_media_url = 'https://twitter.com/' 38 | 39 | s.ios.deployment_target = '9.0' 40 | 41 | s.source_files = 'Sources/JFPopup/Classes/**/*' 42 | 43 | s.swift_version = ['4.0'] 44 | 45 | s.dependency 'JRBaseKit', '~> 1.1.1' 46 | 47 | s.resource_bundles = { 48 | 'JFPopup' => ['Sources/JFPopup/Resources/*.png'], 49 | } 50 | 51 | # s.public_header_files = 'Pod/Classes/**/*.h' 52 | # s.frameworks = 'UIKit', 'MapKit' 53 | 54 | end 55 | -------------------------------------------------------------------------------- /JFToast.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint JFToast.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'JFToast' 11 | s.version = '1.2.0' 12 | s.summary = 'JFToast is a part of JFPopup Module help you popup toast view easily In Swift' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | *support three position, top center and bottom* 22 | *support only hint* 23 | *support only icon* 24 | *support hint + icon* 25 | DESC 26 | 27 | s.homepage = 'https://github.com/JerryFans/JFPopup' 28 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 29 | s.license = { :type => 'MIT', :file => 'LICENSE' } 30 | s.author = { 'JerryFans' => 'fanjiarong_haohao@163.com' } 31 | s.source = { :git => 'https://github.com/JerryFans/JFPopup.git', :tag => s.version.to_s } 32 | # s.social_media_url = 'https://twitter.com/' 33 | 34 | s.ios.deployment_target = '9.0' 35 | 36 | s.source_files = 'Sources/JFPopup/Classes/Core/*','JFPopup/Classes/General/Toast/*' 37 | 38 | s.swift_version = ['4.0'] 39 | 40 | s.dependency 'JRBaseKit', '~> 1.1.1' 41 | 42 | s.resource_bundles = { 43 | 'JFPopup' => ['Sources/JFPopup/Resources/*.png'], 44 | } 45 | 46 | # s.public_header_files = 'Pod/Classes/**/*.h' 47 | # s.frameworks = 'UIKit', 'MapKit' 48 | 49 | end 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 JerryFans 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "jrbasekit", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/JerryFans/JRBaseKit.git", 7 | "state" : { 8 | "revision" : "38674789638f65db94778e18ac01b1134573d788", 9 | "version" : "1.1.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "JFPopup", 8 | platforms: [.iOS(.v11)], 9 | products: [ 10 | .library( 11 | name: "JFPopup", 12 | targets: ["JFPopup"]), 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/JerryFans/JRBaseKit.git", from: "1.1.0"), 16 | ], 17 | targets: [ 18 | .target( 19 | name: "JFPopup", 20 | dependencies: [ 21 | .product(name: "JRBaseKit", package: "JRBaseKit") 22 | ], 23 | resources: [.process("Resources")]) 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JFPopup 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/JFPopup.svg?style=flat)](https://cocoapods.org/pods/JFPopup) 4 | [![License](https://img.shields.io/cocoapods/l/JFPopup.svg?style=flat)](https://cocoapods.org/pods/JFPopup) 5 | [![Platform](https://img.shields.io/cocoapods/p/JFPopup.svg?style=flat)](https://cocoapods.org/pods/JFPopup) 6 | [![Language](https://img.shields.io/badge/language-Swift-DE5C43.svg?style=flat)](https://cocoapods.org/pods/JFPopup) 7 | [![Support](http://img.shields.io/badge/support-ObjC-brightgreen.svg?style=flat)](https://cocoapods.org/pods/JFPopup) 8 | 9 | JFPopup is a Swift Module help you popup your custom view easily. 10 | 11 | Support 3 way to popup, Drawer, Dialog and BottomSheet. 12 | 13 | ## Installation 14 | 15 | ### cocoapods 16 | 17 | ```ruby 18 | pod 'JFPopup', '1.5.4' 19 | ``` 20 | 21 | ### Swift Package Manager 22 | 23 | After 1.5.4 24 | 25 | ```ruby 26 | 27 | https://github.com/JerryFans/JFPopup.git 28 | 29 | ``` 30 | 31 | 32 | ## Example 33 | 34 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 35 | 36 | Toast Style Support iPhone 14 Pro+ Dynamic Island(灵动岛) 37 | 38 | ![](http://image.jerryfans.com/dynamic_island_toast.gif) 39 | 40 | ## Quick Create your popup view 41 | 42 | ### Dialog 43 | 44 | 对话框模式,类似UIAlertConroller, 你也可以编写你的自定义AlertView 45 | 46 | ``` 47 | self.popup.dialog { 48 | let v = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) 49 | v.backgroundColor = .red 50 | return v 51 | } 52 | ``` 53 | 54 | ![](http://image.jerryfans.com/dialog.gif) 55 | ![](http://image.jerryfans.com/dynamic_island_toast.gif) 56 | 57 | ### Drawer 58 | 抽屉模式,支持左右抽屉,宽度自定义,最大可以全屏, 59 | 60 | ``` 61 | //default left 62 | self.popup.drawer { 63 | let v = DrawerView(frame: CGRect(x: 0, y: 0, width: CGSize.jf.screenWidth(), height: CGSize.jf.screenHeight())) 64 | v.closeHandle = { [weak self] in 65 | self?.popup.dismiss() 66 | } 67 | return v 68 | } 69 | 70 | self.popup.drawer(with: .right) { 71 | let v = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: CGSize.jf.screenHeight())) 72 | v.backgroundColor = .red 73 | return v 74 | } 75 | ``` 76 | 77 | ![](http://image.jerryfans.com/drawer.gif) 78 | 79 | ### Bottomsheet 80 | 81 | 类似Flutter Bottomsheet, 底部往上弹出一个容器。 你也可以给予此创建你的个人自定义风格UIActionSheet. 底下有微信风格的组件已封装 82 | 83 | ``` 84 | self.popup.bottomSheet { 85 | let v = UIView(frame: CGRect(x: 0, y: 0, width: CGSize.jf.screenWidth(), height: 300)) 86 | v.backgroundColor = .red 87 | return v 88 | } 89 | ``` 90 | 91 | ![](http://image.jerryfans.com/bottom_sheet.gif) 92 | 93 | ### 通用组件 94 | 95 | - v1.0,暂时只有一款微信风格ActionSheet, 基于上面bottomSheet打造,后续会基于上面基础popup,打造更多基础组件 96 | 97 | - v1.1 新增JFToastView, 支持多种Toast 98 | 99 | - v1.3 新增Loading 样式弹窗, 支持多种格式,详情看下面 100 | - v1.4 新增微信 AlertView 样式弹窗, 支持多种格式,详情看下面 101 | - v1.5 JFToastView 支持iPhone14 Pro系列 灵动岛 (Toast Style Support iPhone14 Pro + Dynamic Island) 102 | - v1.5.2 修复偶现可能无法获取顶层keywindow导致的问题 103 | - v1.5.3 修复连续触发Loading show and hide偶现无法移除View的问题 104 | 105 | ### AlertView 106 | 107 | 1、默认风格,自带取消按钮 108 | 109 | ``` 110 | JFPopupView.popup.alert {[ 111 | .title("温馨提示"), 112 | .subTitle("我是默认风格,自带取消按钮"), 113 | .confirmAction([ 114 | .text("知道了"), 115 | .tapActionCallback({ 116 | JFPopupView.popup.toast(hit: "我知道了") 117 | }) 118 | ]) 119 | ]} 120 | ``` 121 | 122 | 2、Title 和 SubTitle可以二选一,不要Cancel 123 | 124 | ``` 125 | JFPopupView.popup.alert {[ 126 | .subTitle("我是Title 和 SubTitle可以二选一,单个按钮"), 127 | .showCancel(false), 128 | .confirmAction([ 129 | .text("知道了"), 130 | .tapActionCallback({ 131 | JFPopupView.popup.toast(hit: "我知道了") 132 | }) 133 | ]) 134 | ]} 135 | ``` 136 | 137 | 3、自定义颜色、不用动画等 138 | 139 | ``` 140 | JFPopupView.popup.alert {[ 141 | .title("不同标题颜色"), 142 | .titleColor(.red), 143 | .withoutAnimation(true), 144 | .subTitle("我是完全自定义的,标题颜色,action颜色,文本都支持修改,不带动画"), 145 | .subTitleColor(.black), 146 | .cancelAction([.textColor(.blue),.text("我是取消超出文本裁切"),.tapActionCallback({ 147 | JFPopupView.popup.toast(hit: "点击了取消") 148 | })]), 149 | .confirmAction([ 150 | .text("我是确定"), 151 | .textColor(.red), 152 | .tapActionCallback({ 153 | JFPopupView.popup.toast(hit: "点击了确定") 154 | }) 155 | ]) 156 | ]} 157 | ``` 158 | 159 | 4、也可以Present到VC 效果一样 160 | 161 | ``` 162 | //self 是 UIViewController 163 | self.popup.alert { 164 | [.title("我是从VC Present 出来的"), 165 | .subTitle("用法和View一致,只是一个是self.popup.alert(self是UIViewControll),一个是JFPopupView.popup.alert"), 166 | .confirmAction([ 167 | .text("知道了"), 168 | .tapActionCallback({ 169 | JFPopupView.popup.toast(hit: "知道了") 170 | }) 171 | ]) 172 | ] 173 | } 174 | ``` 175 | 176 | ![](http://image.jerryfans.com/alert.gif) 177 | 178 | ### Loading 179 | 180 | ``` 181 | 1、only loading icon 182 | 183 | JFPopupView.popup.loading() 184 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 185 | JFPopupView.popup.hideLoading() 186 | } 187 | 188 | 2、 loading + hit 189 | 190 | JFPopupView.popup.loading(hit: "正在载入视频") 191 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 192 | JFPopupView.popup.hideLoading() 193 | } 194 | 195 | 3、 loading in view (default in keywindow) 196 | 197 | //只支持 controller.view, 默认keywindow 198 | JFPopupView.popup.loading(hit: "加载中", inView: self.view) 199 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 200 | JFPopupView.popup.hideLoading() 201 | } 202 | 203 | 204 | ``` 205 | 206 | ![](http://image.jerryfans.com/loading1.gif) 207 | 208 | ### Toast 209 | 210 | 已适配iPhone 14 Pro系列 灵动岛 (support dynamic island toast) 211 | 212 | ``` 213 | 1、only hit 214 | 215 | JFPopupView.popup.toast(hit: "普通toast,默认superview可以响应") 216 | 217 | 2、 hit + icon (内置success和fail, 支持自定义) 218 | 219 | JFPopupView.popup.toast(hit: "支付成功", icon: .success) 220 | 221 | 222 | JFPopupView.popup.toast(hit: "自定义Icon", icon: .imageName(name: "face")) 223 | 224 | 3、完全自定义 225 | 226 | JFPopupView.popup.toast { 227 | [ 228 | .hit("不响应super view,带背景色,加大时长,不用动画,在当前view弹出,position top"), 229 | .enableUserInteraction(true), 230 | .bgColor(UIColor.jf.rgb(0x000000,alpha: 0.3)), 231 | .autoDismissDuration(.seconds(value: 3)), 232 | .mainContainer(self.view), 233 | .withoutAnimation(true), 234 | .position(.top) 235 | ] 236 | } 237 | 238 | ``` 239 | 240 | ![](http://image.jerryfans.com/toast.gif) 241 | ![](http://image.jerryfans.com/dynamic_island_toast.gif) 242 | 243 | 244 | ### ActionSheet 245 | 246 | ``` 247 | self.popup.actionSheet { 248 | [ 249 | JFPopupAction(with: "拍摄", subTitle: "照片或视频照片", clickActionCallBack: { [weak self] in 250 | self?.pushVC() 251 | }), 252 | JFPopupAction(with: "从手机相册选择", subTitle: nil, clickActionCallBack: { 253 | 254 | }), 255 | JFPopupAction(with: "用秒剪制作视频", subTitle: nil, clickActionCallBack: { 256 | 257 | }), 258 | ] 259 | } 260 | ``` 261 | 262 | ![](http://image.jerryfans.com/wechat_sheet.gif) 263 | 264 | ## VC模式创建 265 | 266 | 此方法推荐兼容OC情况下使用,或者你的popup View代码量非常大 需要一个vc来管理。 267 | 268 | 继承写法 (类似继承UITableView,dataSoure写在内部) 269 | 270 | ``` 271 | var config = JFPopupConfig.bottomSheet 272 | config.isDismissible = false 273 | let vc = TestCustomViewController(with: config) 274 | vc.show(with: self) 275 | ``` 276 | 闭包写法 277 | 278 | ``` 279 | var config = JFPopupConfig.dialog 280 | config.bgColor = .clear 281 | let vc = JFPopupController(with: config, popupProtocol: self) { 282 | let view: UIView = { 283 | let view = UIView() 284 | view.frame = CGRect(x: 0, y: 0, width: 200, height: 200) 285 | view.layer.cornerRadius = 12 286 | view.backgroundColor = .black 287 | return view 288 | }() 289 | return view 290 | } 291 | vc.show(with: self) 292 | ``` 293 | 294 | ## Author 295 | 296 | JerryFans, fanjiarong_haohao@163.com 297 | 298 | ## License 299 | 300 | JFPopup is available under the MIT license. See the LICENSE file for more info. 301 | 302 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryFans/JFPopup/8b0227215e754218280667cd656e22abc4d62821/Sources/JFPopup/Classes/.gitkeep -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/Core/JFPopup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Popup.swift 3 | // PopupKit 4 | // 5 | // Created by 逸风 on 2021/10/9. 6 | // 7 | import JRBaseKit 8 | import UIKit 9 | 10 | fileprivate let supportDynamicIsLandList = [ 11 | "iPhone 14 Pro", 12 | "iPhone 14 Pro Max" 13 | ] 14 | 15 | public var JFIsSupportDynamicIsLand: Bool { 16 | get { 17 | var t: CGFloat = 0 18 | if #available(iOS 11.0, *) { 19 | t = UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 0 20 | } 21 | return t == 59.0 22 | } 23 | } 24 | 25 | public struct JFPopup { 26 | public let base: Base 27 | init(_ base: Base) { 28 | self.base = base 29 | } 30 | } 31 | 32 | public protocol JFPopupCompatible {} 33 | public extension JFPopupCompatible { 34 | static var popup: JFPopup.Type { 35 | set {} 36 | get { JFPopup.self } 37 | } 38 | var popup: JFPopup { 39 | set {} 40 | get { JFPopup(self) } 41 | } 42 | } 43 | 44 | public protocol JFPopupProtocol { 45 | var dataSource: JFPopupDataSource? { get set } 46 | var popupProtocol: JFPopupAnimationProtocol? { get set } 47 | var container: UIView? { get set } 48 | var config: JFPopupConfig { get set } 49 | func autoDismissHandle() 50 | func dismissPopupView(completion: @escaping ((_ isFinished: Bool) -> ())) 51 | } 52 | 53 | @objc public enum JFPopupAnimationDirection: Int { 54 | case unowned 55 | case left 56 | case right 57 | } 58 | 59 | @objc public enum JFPopupAnimationType: Int { 60 | case dialog 61 | case bottomSheet 62 | case drawer 63 | } 64 | 65 | @objc public protocol JFPopupDataSource: AnyObject { 66 | @objc func viewForContainer() -> UIView? 67 | } 68 | 69 | public protocol JFPopupAnimationProtocol: AnyObject { 70 | func present(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView, completion: ((_ isFinished: Bool) -> ())?) 71 | func dismiss(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView?, completion: ((_ isFinished: Bool) -> ())?) 72 | } 73 | 74 | public enum JFTimerDuration { 75 | 76 | ///纳秒 77 | case nanoseconds(value: UInt32) 78 | ///微妙 79 | case microseconds(value: UInt32) 80 | ///毫秒 81 | case milliseconds(value: UInt32) 82 | ///秒 83 | case seconds(value: UInt32) 84 | ///分钟 85 | case minutes(value: UInt32) 86 | 87 | public func timeDuration() -> DispatchTimeInterval { 88 | switch self { 89 | case .nanoseconds(value: let value): 90 | return .nanoseconds(Int(value)) 91 | case .microseconds(value: let value): 92 | return .microseconds(Int(value)) 93 | case .milliseconds(value: let value): 94 | return .milliseconds(Int(value)) 95 | case .seconds(value: let value): 96 | return .seconds(Int(value)) 97 | case .minutes(value: let value): 98 | return .seconds(Int(value) * 60) 99 | } 100 | } 101 | } 102 | 103 | public enum JFToastPosition { 104 | case center 105 | case top 106 | case bottom 107 | case dynamicIsland //新增灵动岛位置动画 108 | } 109 | 110 | public struct JFPopupConfig { 111 | 112 | ///background view colod 113 | public var bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4) 114 | ///enableUserInteraction, if true, your gesture can not transmit to super view 115 | public var enableUserInteraction = true 116 | ///popup style 117 | public var animationType: JFPopupAnimationType = .dialog 118 | ///popup the view without animation 119 | public var withoutAnimation = false 120 | ///if trie tap background can dismiss popup view 121 | public var isDismissible = true 122 | /// enable drag gesture 123 | public var enableDrag = true 124 | /// use in drawer type , from left or right direction 125 | public var direction: JFPopupAnimationDirection = .unowned 126 | 127 | ///if true will auto dismiss popup view, use duration, default is false 128 | public var enableAutoDismiss = false 129 | ///auto dismiss duration, default is 2 seconds 130 | public var autoDismissDuration: JFTimerDuration = .seconds(value: 2) 131 | 132 | ///toast view position 133 | public var toastPosition: JFToastPosition = JFIsSupportDynamicIsLand ? .dynamicIsland : .center 134 | 135 | ///show with a absolute rect , only use in dialog mode popup 136 | public var absoluteRect: CGRect? 137 | 138 | /// static style config 139 | public static var dialog = JFPopupConfig(enableDrag: false) 140 | public static var bottomSheet = JFPopupConfig(animationType: .bottomSheet) 141 | public static var drawer = JFPopupConfig(animationType: .drawer, direction: .left) 142 | } 143 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/Core/JFPopupAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFPopupAnimation.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/9. 6 | // 7 | 8 | import UIKit 9 | import QuartzCore 10 | 11 | class JFPopupAnimation: NSObject { 12 | 13 | static func present(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView, completion: ((_ isFinished: Bool) -> ())?) { 14 | let type = config.animationType 15 | switch type { 16 | case .bottomSheet: 17 | do { 18 | contianerView.frame.origin.y = CGSize.jf.screenSize().height 19 | contianerView.layoutIfNeeded() 20 | UIView.animate(withDuration: 0.25, animations: { 21 | contianerView.frame.origin.y = CGSize.jf.screenSize().height - contianerView.frame.size.height 22 | contianerView.layoutIfNeeded() 23 | }) { (finished) in 24 | transitonContext?.completeTransition(true) 25 | completion?(finished) 26 | } 27 | } 28 | break 29 | case .dialog: 30 | do { 31 | let originSize = contianerView.jf.size 32 | if config.toastPosition == .dynamicIsland { 33 | contianerView.jf_size = CGSize(width: 120, height: 34) 34 | contianerView.center = CGPoint(x: CGSize.jf.screenSize().width / 2, y: 27) 35 | } 36 | contianerView.layoutIfNeeded() 37 | let updateV = { 38 | contianerView.center = CGPoint(x: CGSize.jf.screenSize().width / 2, y: CGSize.jf.screenSize().height / 2) 39 | if let absoluteRect = config.absoluteRect { 40 | contianerView.frame = absoluteRect 41 | } else if config.toastPosition == .top { 42 | contianerView.jf_top = CGFloat.jf.navigationBarHeight() + 15 43 | } else if config.toastPosition == .bottom { 44 | contianerView.jf_bottom = CGSize.jf.screenHeight() - CGFloat.jf.safeAreaBottomHeight() - 15 45 | } else if config.toastPosition == .dynamicIsland { 46 | contianerView.jf_size = originSize 47 | contianerView.center = CGPoint(x: CGSize.jf.screenSize().width / 2, y: originSize.height / 2 + 10) 48 | } 49 | contianerView.layoutIfNeeded() 50 | } 51 | if config.toastPosition == .dynamicIsland && config.absoluteRect == nil { 52 | UIView.animate(withDuration: 0.25) { 53 | updateV() 54 | } completion: { finished in 55 | transitonContext?.completeTransition(true) 56 | completion?(finished) 57 | } 58 | return 59 | } 60 | updateV() 61 | contianerView.alpha = 0 62 | UIView.animate(withDuration: config.withoutAnimation ? 0 : 0.25, delay: 0, options: .curveEaseInOut) { 63 | contianerView.alpha = 1 64 | } completion: { finished in 65 | transitonContext?.completeTransition(true) 66 | completion?(true) 67 | } 68 | } 69 | break 70 | case .drawer: 71 | do { 72 | var originY: CGFloat = -contianerView.frame.size.width 73 | var targetY: CGFloat = 0 74 | if config.direction == .right { 75 | originY = CGSize.jf.screenWidth() 76 | targetY = CGSize.jf.screenWidth() - contianerView.frame.size.width 77 | } 78 | contianerView.frame.origin.x = originY 79 | contianerView.center.y = CGSize.jf.screenHeight() / 2 80 | contianerView.layoutIfNeeded() 81 | UIView.animate(withDuration: 0.25, animations: { 82 | contianerView.frame.origin.x = targetY 83 | contianerView.layoutIfNeeded() 84 | }) { (finished) in 85 | transitonContext?.completeTransition(true) 86 | completion?(finished) 87 | } 88 | } 89 | break 90 | 91 | } 92 | } 93 | 94 | static func dismiss(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView?, completion: ((_ isFinished: Bool) -> ())?) { 95 | let type = config.animationType 96 | switch type { 97 | case .bottomSheet: 98 | do { 99 | guard let _ = contianerView else { 100 | transitonContext?.completeTransition(true) 101 | completion?(true) 102 | return 103 | } 104 | UIView.animate(withDuration: 0.25, animations: { 105 | contianerView?.superview?.alpha = 0 106 | contianerView?.frame.origin.y = CGSize.jf.screenSize().height 107 | contianerView?.layoutIfNeeded() 108 | }) { (finished) in 109 | transitonContext?.completeTransition(true) 110 | completion?(finished) 111 | } 112 | } 113 | break 114 | case .dialog: 115 | do { 116 | UIView.animate(withDuration: 0.25, animations: { 117 | if config.toastPosition == .dynamicIsland && config.absoluteRect == nil { 118 | contianerView?.layer.cornerRadius = 17 119 | contianerView?.jf_size = CGSize(width: 120, height: 34) 120 | contianerView?.center = CGPoint(x: CGSize.jf.screenSize().width / 2, y: 27) 121 | } 122 | contianerView?.subviews.forEach({ v in 123 | if config.toastPosition == .dynamicIsland && config.absoluteRect == nil { 124 | v.isHidden = true 125 | } else { 126 | v.alpha = 0 127 | } 128 | }) 129 | contianerView?.alpha = 0 130 | }) { (finished) in 131 | transitonContext?.completeTransition(true) 132 | completion?(finished) 133 | } 134 | } 135 | break 136 | case .drawer: 137 | do { 138 | guard let view = contianerView else { 139 | transitonContext?.completeTransition(true) 140 | completion?(true) 141 | return 142 | } 143 | var targetY: CGFloat = -view.frame.size.width 144 | if config.direction == .right { 145 | targetY = CGSize.jf.screenWidth() 146 | } 147 | UIView.animate(withDuration: 0.25, animations: { 148 | contianerView?.superview?.alpha = 0 149 | contianerView?.frame.origin.x = targetY 150 | contianerView?.layoutIfNeeded() 151 | }) { (finished) in 152 | transitonContext?.completeTransition(true) 153 | completion?(finished) 154 | } 155 | } 156 | break 157 | } 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/Core/JFPopupController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFPopupController.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/9. 6 | // 7 | 8 | import UIKit 9 | 10 | extension JFPopupController: JFPopupProtocol { 11 | 12 | public func dismissPopupView(completion: ((Bool) -> ())) { 13 | self.closeVC(with: nil) 14 | } 15 | 16 | public func autoDismissHandle() { 17 | guard self.config.enableAutoDismiss else { return } 18 | DispatchQueue.main.asyncAfter(deadline: .now() + self.config.autoDismissDuration.timeDuration()) { 19 | self.dismissPopupView { isFinished in 20 | 21 | } 22 | } 23 | } 24 | 25 | 26 | 27 | } 28 | 29 | open class JFPopupController: UIViewController,JFPopupDataSource { 30 | 31 | //JFPopupProtocol 32 | public weak var dataSource: JFPopupDataSource? 33 | public weak var popupProtocol: JFPopupAnimationProtocol? 34 | public var container: UIView? 35 | public var config: JFPopupConfig = .dialog 36 | //JFPopupProtocol 37 | 38 | 39 | //JFPopupDataSource 40 | @objc open func viewForContainer() -> UIView? { 41 | return nil 42 | } 43 | //JFPopupDataSource 44 | 45 | weak var transitionContext: UIViewControllerAnimatedTransitioning? 46 | var isShow = false 47 | var beginTouchPoint: CGPoint = .zero 48 | var beginFrame: CGRect = .zero 49 | 50 | deinit { 51 | print("JFPopupController dealloc") 52 | } 53 | 54 | public init(with config: JFPopupConfig) { 55 | super.init(nibName: nil, bundle: nil) 56 | self.transitionContext = self 57 | self.config = config 58 | } 59 | 60 | public init(with config: JFPopupConfig, container: (() -> UIView)?) { 61 | super.init(nibName: nil, bundle: nil) 62 | self.container = container?() 63 | self.transitionContext = self 64 | self.config = config 65 | } 66 | 67 | public init(with config: JFPopupConfig, popupProtocol: JFPopupAnimationProtocol? = nil, container: (() -> UIView)?) { 68 | super.init(nibName: nil, bundle: nil) 69 | self.popupProtocol = popupProtocol 70 | self.container = container?() 71 | self.transitionContext = self 72 | self.config = config 73 | } 74 | 75 | required public init?(coder: NSCoder) { 76 | fatalError("init(coder:) has not been implemented") 77 | } 78 | 79 | open override func viewDidLoad() { 80 | super.viewDidLoad() 81 | self.navigationController?.setNavigationBarHidden(true, animated: false) 82 | if self.popupProtocol == nil { 83 | self.popupProtocol = self 84 | } 85 | if let container = self.container { 86 | self.view.addSubview(container) 87 | } else { 88 | if self.dataSource == nil { 89 | self.dataSource = self 90 | } 91 | if let v = self.dataSource?.viewForContainer() { 92 | self.view.addSubview(v) 93 | self.container = v 94 | } 95 | } 96 | self.view.backgroundColor = self.config.bgColor 97 | let panGest: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(onPan(gest:))) 98 | panGest.delegate = self 99 | self.view.addGestureRecognizer(panGest) 100 | let tapGest = UITapGestureRecognizer(target: self, action: #selector(tapBGAction)) 101 | tapGest.delegate = self 102 | self.view.addGestureRecognizer(tapGest) 103 | } 104 | 105 | @objc open func show(with vc: UIViewController) { 106 | let navi = UINavigationController.init(rootViewController: self) 107 | navi.transitioningDelegate = self 108 | navi.modalPresentationStyle = .custom 109 | self.isShow = true 110 | vc.present(navi, animated: true) { 111 | } 112 | } 113 | 114 | @objc func tapBGAction() { 115 | guard self.config.isDismissible else { return } 116 | self.closeVC(with: nil) 117 | } 118 | 119 | @objc open func closeVC(with completion: (() -> Void)?) { 120 | self.dismiss(animated: true, completion: completion) 121 | } 122 | 123 | open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { 124 | self.isShow = false 125 | super.dismiss(animated: flag, completion: completion) 126 | } 127 | 128 | @objc private func onPan(gest: UIPanGestureRecognizer) { 129 | guard self.config.enableDrag else { 130 | self.tapBGAction() 131 | return 132 | } 133 | guard let container = self.container else { return } 134 | switch gest.state { 135 | case .began: 136 | self.beginFrame = container.frame 137 | self.beginTouchPoint = gest.location(in: self.view) 138 | break 139 | case .changed: 140 | guard self.config.animationType != .dialog else { break } 141 | self.container?.frame = self.getRectForPan(pan: gest) 142 | break 143 | case .ended,.cancelled: 144 | guard self.config.animationType != .dialog else { 145 | self.tapBGAction() 146 | break 147 | } 148 | self.container?.frame = self.getRectForPan(pan: gest) 149 | let isClosed: Bool = self.checkGestToClose(gest: gest) 150 | if isClosed == true { 151 | self.tapBGAction() 152 | } else { 153 | UIView.animate(withDuration: 0.2) { 154 | self.container?.frame = self.beginFrame 155 | } 156 | } 157 | break 158 | 159 | default: 160 | break 161 | } 162 | } 163 | 164 | private func checkGestToClose(gest: UIPanGestureRecognizer) -> Bool { 165 | if self.config.animationType == .drawer { 166 | if self.config.direction == .left { 167 | return gest.velocity(in: container).x < 0 168 | } else { 169 | return gest.velocity(in: container).x > 0 170 | } 171 | } else if self.config.animationType == .bottomSheet { 172 | return gest.velocity(in: container).y > 0 173 | } 174 | return false 175 | } 176 | 177 | private func getRectForPan(pan: UIPanGestureRecognizer) -> CGRect { 178 | guard let view = self.container else { return .zero } 179 | var rect: CGRect = view.frame 180 | let currentTouch = pan.location(in: self.view) 181 | if self.config.animationType == .drawer { 182 | let xRate = (self.beginTouchPoint.x - self.beginFrame.origin.x) / self.beginFrame.size.width 183 | let currentTouchDeltaX = xRate * view.jf.width 184 | var x = currentTouch.x - currentTouchDeltaX 185 | if x < self.beginFrame.origin.x && self.config.direction == .right { 186 | x = self.beginFrame.origin.x 187 | } else if x > self.beginFrame.origin.x && self.config.direction == .left { 188 | x = self.beginFrame.origin.x 189 | } 190 | 191 | rect.origin.x = x 192 | } else if self.config.animationType == .bottomSheet { 193 | let yRate = (self.beginTouchPoint.y - self.beginFrame.origin.y) / self.beginFrame.size.height 194 | let currentTouchDeltaY = yRate * view.jf.height 195 | var y = currentTouch.y - currentTouchDeltaY 196 | if y < self.beginFrame.origin.y { 197 | y = self.beginFrame.origin.y 198 | } 199 | rect.origin.y = y 200 | } 201 | return rect 202 | } 203 | } 204 | 205 | extension JFPopupController: UIViewControllerTransitioningDelegate,UIViewControllerAnimatedTransitioning,UIGestureRecognizerDelegate { 206 | 207 | public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 208 | let isTapGest = gestureRecognizer is UITapGestureRecognizer 209 | let point = gestureRecognizer.location(in: gestureRecognizer.view) 210 | let rect = self.container?.frame ?? .zero 211 | let inContianer = rect.contains(point) 212 | if isTapGest { 213 | if inContianer { 214 | return false 215 | } 216 | if self.config.isDismissible == false { 217 | return false 218 | } 219 | } else { 220 | if self.config.enableDrag == false || self.config.isDismissible == false { 221 | return false 222 | } 223 | } 224 | return true 225 | } 226 | 227 | public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 228 | 0.25 229 | } 230 | 231 | public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 232 | return self.transitionContext 233 | } 234 | 235 | public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 236 | return self.transitionContext 237 | } 238 | 239 | public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 240 | if self.isShow == true { 241 | self.present(transitonContext: transitionContext) 242 | } else { 243 | self.dismiss(transitonContext: transitionContext) 244 | } 245 | } 246 | 247 | func present(transitonContext: UIViewControllerContextTransitioning) { 248 | let toNavi: UINavigationController = transitonContext.viewController(forKey:.to) as! UINavigationController 249 | let containerView = transitonContext.containerView 250 | containerView.addSubview(toNavi.view) 251 | guard let contianerView = self.container else { 252 | transitonContext.completeTransition(true) 253 | return 254 | } 255 | self.popupProtocol?.present(with: transitonContext, config: self.config, contianerView: contianerView, completion: { [weak self] isFinished in 256 | self?.autoDismissHandle() 257 | }) 258 | } 259 | 260 | func dismiss(transitonContext: UIViewControllerContextTransitioning) { 261 | self.popupProtocol?.dismiss(with: transitonContext, config: self.config, contianerView: self.container, completion: nil) 262 | } 263 | 264 | } 265 | 266 | extension JFPopupController: JFPopupAnimationProtocol { 267 | 268 | public func dismiss(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView?, completion: ((Bool) -> ())?) { 269 | JFPopupAnimation.dismiss(with: transitonContext, config: self.config, contianerView: contianerView, completion: completion) 270 | } 271 | 272 | public func present(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView, completion: ((Bool) -> ())?) { 273 | JFPopupAnimation.present(with: transitonContext, config: self.config, contianerView: contianerView, completion: completion) 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/Core/JFPopupView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFPopupView.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/18. 6 | // 7 | 8 | import UIKit 9 | 10 | /// popup va custom view use in view, not controller 11 | public extension JFPopup where Base: JFPopupView { 12 | 13 | /// popup a bottomSheet with your custom view 14 | /// - Parameters: 15 | /// - isDismissible: default true, will tap bg auto dismiss 16 | /// - enableDrag: default true, will enable drag animate 17 | /// - bgColor: background view color 18 | /// - container: your custom view 19 | /// - onDismissPopupView: call when popup view dismissed (dealloc) 20 | @discardableResult static func bottomSheet(with isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), yourView: UIView? = nil, container: @escaping (_ mainContainer: JFPopupView?) -> UIView, onDismissPopupView: ((_ mainContainer: JFPopupView?) -> Void)? = nil) -> JFPopupView? { 21 | var config: JFPopupConfig = .bottomSheet 22 | config.isDismissible = isDismissible 23 | config.enableDrag = enableDrag 24 | config.bgColor = bgColor 25 | return self.custom(with: config, yourView: yourView) { mainContainer in 26 | container(mainContainer) 27 | } onDismissPopupView: { mainContainer in 28 | onDismissPopupView?(mainContainer) 29 | } 30 | } 31 | 32 | 33 | /// popup a drawer style view with your custom view 34 | /// - Parameters: 35 | /// - direction: left or right 36 | /// - isDismissible: default true, will tap bg auto dismiss 37 | /// - enableDrag: default true, will enable drag animate 38 | /// - bgColor: background view color 39 | /// - container: your custom view 40 | /// - onDismissPopupView: call when popup view dismissed (dealloc) 41 | @discardableResult static func drawer(with direction: JFPopupAnimationDirection = .left, isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), yourView: UIView? = nil, container: @escaping (_ mainContainer: JFPopupView?) -> UIView, onDismissPopupView: ((_ mainContainer: JFPopupView?) -> Void)? = nil) -> JFPopupView? { 42 | var config: JFPopupConfig = .drawer 43 | config.direction = direction 44 | config.isDismissible = isDismissible 45 | config.enableDrag = enableDrag 46 | config.bgColor = bgColor 47 | return self.custom(with: config, yourView: yourView) { mainContainer in 48 | container(mainContainer) 49 | } onDismissPopupView: { mainContainer in 50 | onDismissPopupView?(mainContainer) 51 | } 52 | } 53 | 54 | 55 | /// popup a dialog style view with your custom view 56 | /// - Parameters: 57 | /// - isDismissible: default true, will tap bg auto dismiss 58 | /// - bgColor: background view color 59 | /// - container: your custom view 60 | /// - onDismissPopupView: call when popup view dismissed (dealloc) 61 | @discardableResult static func dialog(with isDismissible: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), yourView: UIView? = nil, container: @escaping (_ mainContainer: JFPopupView?) -> UIView, onDismissPopupView: ((_ mainContainer: JFPopupView?) -> Void)? = nil) -> JFPopupView? { 62 | var config: JFPopupConfig = .dialog 63 | config.isDismissible = isDismissible 64 | config.bgColor = bgColor 65 | config.toastPosition = .center 66 | return self.custom(with: config, yourView: yourView) { mainContainer in 67 | container(mainContainer) 68 | } onDismissPopupView: { mainContainer in 69 | onDismissPopupView?(mainContainer) 70 | } 71 | } 72 | 73 | /// popup a custom view with custom config 74 | /// - Parameters: 75 | /// - config: popup config 76 | /// - container: your custom view 77 | /// - onDismissPopupView: call when popup view dismissed (dealloc) 78 | @discardableResult static func custom(with config: JFPopupConfig, yourView: UIView? = nil, container: @escaping (_ mainContainer: JFPopupView?) -> UIView?, onDismissPopupView: ((_ mainContainer: JFPopupView?) -> Void)? = nil) -> JFPopupView? { 79 | if Thread.current != Thread.main { 80 | return DispatchQueue.main.sync { 81 | let v = JFPopupView(with: config) { mainContainer in 82 | container(mainContainer) 83 | } 84 | v.dismissedHanlde = { [weak v] in 85 | onDismissPopupView?(v) 86 | } 87 | v.popup(into: yourView) 88 | return v 89 | } 90 | } else { 91 | let v = JFPopupView(with: config) { mainContainer in 92 | container(mainContainer) 93 | } 94 | v.dismissedHanlde = { [weak v] in 95 | onDismissPopupView?(v) 96 | } 97 | v.popup(into: yourView) 98 | return v 99 | } 100 | } 101 | } 102 | 103 | extension JFPopupView: JFPopupProtocol { 104 | 105 | public func dismissPopupView(completion: @escaping ((Bool) -> ())) { 106 | self.popupProtocol?.dismiss(with: nil, config: self.config, contianerView: self.container, completion: { [weak self] isFinished in 107 | self?.removeFromSuperview() 108 | completion(isFinished) 109 | self?.dismissedHanlde?() 110 | }) 111 | } 112 | 113 | public func autoDismissHandle() { 114 | DispatchQueue.main.asyncAfter(deadline: .now() + self.config.autoDismissDuration.timeDuration()) { 115 | self.dismissPopupView { isFinished in 116 | 117 | } 118 | } 119 | } 120 | 121 | 122 | } 123 | 124 | public class JFPopupView: UIView { 125 | 126 | var beginTouchPoint: CGPoint = .zero 127 | var beginFrame: CGRect = .zero 128 | 129 | public weak var dataSource: JFPopupDataSource? 130 | 131 | public weak var popupProtocol: JFPopupAnimationProtocol? 132 | 133 | public var container: UIView? 134 | 135 | public var config: JFPopupConfig = .dialog 136 | 137 | //use in onDismiss CallBack, some one want to know which is dismiss from tap background 138 | /* 139 | 140 | onDismissPopupView: { mainContainer in 141 | if mainContainer?.isClosedFromTapBackground { 142 | //xxx 143 | } 144 | } 145 | 146 | */ 147 | public var isClosedFromTapBackground = false 148 | 149 | typealias DismissedHanlde = () -> Void 150 | var dismissedHanlde: DismissedHanlde? 151 | 152 | deinit { 153 | print("JFPopupView dealloc") 154 | } 155 | 156 | public init(with config: JFPopupConfig, container: ((_ mainContainer: JFPopupView?) -> UIView?)?) { 157 | super.init(frame: UIScreen.main.bounds) 158 | self.container = container?(self) 159 | self.config = config 160 | self.configSubview() 161 | self.configGest() 162 | } 163 | 164 | public init(with config: JFPopupConfig, popupProtocol: JFPopupAnimationProtocol? = nil, container: ((_ mainContainer: JFPopupView?) -> UIView?)?) { 165 | super.init(frame: UIScreen.main.bounds) 166 | self.popupProtocol = popupProtocol 167 | self.container = container?(self) 168 | self.config = config 169 | self.configSubview() 170 | self.configGest() 171 | } 172 | 173 | func configSubview() { 174 | self.isUserInteractionEnabled = self.config.enableUserInteraction 175 | self.backgroundColor = self.config.bgColor 176 | if self.popupProtocol == nil { 177 | self.popupProtocol = self 178 | } 179 | if let container = self.container { 180 | self.addSubview(container) 181 | } else { 182 | if self.dataSource == nil { 183 | self.dataSource = self 184 | } 185 | if let v = self.dataSource?.viewForContainer() { 186 | self.addSubview(v) 187 | self.container = v 188 | } 189 | } 190 | } 191 | 192 | func configGest() { 193 | let panGest: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(onPan(gest:))) 194 | panGest.delegate = self 195 | self.addGestureRecognizer(panGest) 196 | let tapGest = UITapGestureRecognizer(target: self, action: #selector(tapBGAction)) 197 | tapGest.delegate = self 198 | self.addGestureRecognizer(tapGest) 199 | } 200 | 201 | @objc func tapBGAction() { 202 | guard self.config.isDismissible else { return } 203 | self.isClosedFromTapBackground = true 204 | self.dismissPopupView { [weak self] isFinished in 205 | 206 | } 207 | } 208 | 209 | func popup(into yourView: UIView? = nil) { 210 | guard let view = self.container else { return } 211 | guard let window = UIApplication.shared.windows.first(where: \.isKeyWindow) else { return } 212 | if let view = yourView { 213 | view.addSubview(self) 214 | } else { 215 | window.addSubview(self) 216 | } 217 | self.popupProtocol?.present(with: nil, config: self.config, contianerView: view, completion: { [weak self] isFinished in 218 | guard self?.config.enableAutoDismiss == true else { return } 219 | self?.autoDismissHandle() 220 | }) 221 | } 222 | 223 | override init(frame: CGRect) { 224 | super.init(frame: frame) 225 | } 226 | 227 | required init?(coder: NSCoder) { 228 | fatalError("init(coder:) has not been implemented") 229 | } 230 | 231 | } 232 | 233 | extension JFPopupView: UIGestureRecognizerDelegate { 234 | 235 | @objc private func onPan(gest: UIPanGestureRecognizer) { 236 | guard self.config.enableDrag else { 237 | self.tapBGAction() 238 | return 239 | } 240 | guard let container = self.container else { return } 241 | switch gest.state { 242 | case .began: 243 | self.beginFrame = container.frame 244 | self.beginTouchPoint = gest.location(in: self) 245 | break 246 | case .changed: 247 | guard self.config.animationType != .dialog else { break } 248 | self.container?.frame = self.getRectForPan(pan: gest) 249 | break 250 | case .ended,.cancelled: 251 | guard self.config.animationType != .dialog else { 252 | self.tapBGAction() 253 | break 254 | } 255 | self.container?.frame = self.getRectForPan(pan: gest) 256 | let isClosed: Bool = self.checkGestToClose(gest: gest) 257 | if isClosed == true { 258 | self.tapBGAction() 259 | } else { 260 | UIView.animate(withDuration: 0.2) { 261 | self.container?.frame = self.beginFrame 262 | } 263 | } 264 | break 265 | 266 | default: 267 | break 268 | } 269 | } 270 | 271 | private func checkGestToClose(gest: UIPanGestureRecognizer) -> Bool { 272 | if self.config.animationType == .drawer { 273 | if self.config.direction == .left { 274 | return gest.velocity(in: container).x < 0 275 | } else { 276 | return gest.velocity(in: container).x > 0 277 | } 278 | } else if self.config.animationType == .bottomSheet { 279 | return gest.velocity(in: container).y > 0 280 | } 281 | return false 282 | } 283 | 284 | private func getRectForPan(pan: UIPanGestureRecognizer) -> CGRect { 285 | guard let view = self.container else { return .zero } 286 | var rect: CGRect = view.frame 287 | let currentTouch = pan.location(in: self) 288 | if self.config.animationType == .drawer { 289 | let xRate = (self.beginTouchPoint.x - self.beginFrame.origin.x) / self.beginFrame.size.width 290 | let currentTouchDeltaX = xRate * view.jf.width 291 | var x = currentTouch.x - currentTouchDeltaX 292 | if x < self.beginFrame.origin.x && self.config.direction == .right { 293 | x = self.beginFrame.origin.x 294 | } else if x > self.beginFrame.origin.x && self.config.direction == .left { 295 | x = self.beginFrame.origin.x 296 | } 297 | 298 | rect.origin.x = x 299 | } else if self.config.animationType == .bottomSheet { 300 | let yRate = (self.beginTouchPoint.y - self.beginFrame.origin.y) / self.beginFrame.size.height 301 | let currentTouchDeltaY = yRate * view.jf.height 302 | var y = currentTouch.y - currentTouchDeltaY 303 | if y < self.beginFrame.origin.y { 304 | y = self.beginFrame.origin.y 305 | } 306 | rect.origin.y = y 307 | } 308 | return rect 309 | } 310 | 311 | public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 312 | let isTapGest = gestureRecognizer is UITapGestureRecognizer 313 | let point = gestureRecognizer.location(in: gestureRecognizer.view) 314 | let rect = self.container?.frame ?? .zero 315 | let inContianer = rect.contains(point) 316 | if isTapGest { 317 | if inContianer { 318 | return false 319 | } 320 | if self.config.isDismissible == false { 321 | return false 322 | } 323 | } else { 324 | if self.config.enableDrag == false || self.config.isDismissible == false { 325 | return false 326 | } 327 | } 328 | return true 329 | } 330 | } 331 | 332 | extension JFPopupView: JFPopupDataSource { 333 | @objc public func viewForContainer() -> UIView? { 334 | return nil 335 | } 336 | } 337 | 338 | extension JFPopupView: JFPopupAnimationProtocol { 339 | public func dismiss(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView?, completion: ((Bool) -> ())?) { 340 | JFPopupAnimation.dismiss(with: transitonContext, config: self.config, contianerView: contianerView, completion: completion) 341 | } 342 | 343 | public func present(with transitonContext: UIViewControllerContextTransitioning?, config: JFPopupConfig, contianerView: UIView, completion: ((Bool) -> ())?) { 344 | JFPopupAnimation.present(with: transitonContext, config: self.config, contianerView: contianerView, completion: completion) 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/Core/UIView+JFPopup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+JFPopup.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/19. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView: JFPopupCompatible {} 11 | public extension JFPopup where Base: UIView { 12 | 13 | /// popup a bottomSheet with your custom view 14 | /// - Parameters: 15 | /// - isDismissible: default true, will tap bg auto dismiss 16 | /// - enableDrag: default true, will enable drag animate 17 | /// - bgColor: background view color 18 | /// - container: your custom view 19 | @discardableResult func bottomSheet(with isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: @escaping (_ mainContainer: JFPopupView?) -> UIView) -> JFPopupView? { 20 | return JFPopupView.popup.bottomSheet(with: isDismissible, enableDrag: enableDrag, bgColor: bgColor, yourView: base) { mainContainer in 21 | container(mainContainer) 22 | } 23 | } 24 | 25 | 26 | /// popup a drawer style view with your custom view 27 | /// - Parameters: 28 | /// - direction: left or right 29 | /// - isDismissible: default true, will tap bg auto dismiss 30 | /// - enableDrag: default true, will enable drag animate 31 | /// - bgColor: background view color 32 | /// - container: your custom view 33 | @discardableResult func drawer(with direction: JFPopupAnimationDirection = .left, isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: @escaping (_ mainContainer: JFPopupView?) -> UIView) -> JFPopupView? { 34 | return JFPopupView.popup.drawer(with: direction, isDismissible: isDismissible, enableDrag: enableDrag, bgColor: bgColor, yourView: base) { mainContainer in 35 | container(mainContainer) 36 | } 37 | } 38 | 39 | 40 | /// popup a dialog style view with your custom view 41 | /// - Parameters: 42 | /// - isDismissible: default true, will tap bg auto dismiss 43 | /// - bgColor: background view color 44 | /// - container: your custom view 45 | @discardableResult func dialog(with isDismissible: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: @escaping (_ mainContainer: JFPopupView?) -> UIView) -> JFPopupView? { 46 | return JFPopupView.popup.dialog(with: isDismissible, bgColor: bgColor, yourView: base) { mainContainer in 47 | container(mainContainer) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/Core/UIViewContoller+JFPopup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewContoller+Popup.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/9. 6 | // 7 | 8 | import UIKit 9 | extension UIViewController: JFPopupCompatible {} 10 | 11 | public extension JFPopup where Base: UIViewController { 12 | 13 | func dismissPopup() { 14 | if let navi = base.presentedViewController as? UINavigationController, let vc = navi.viewControllers.first as? JFPopupController { 15 | vc.closeVC(with: nil) 16 | } 17 | } 18 | 19 | /// popup a bottomSheet with your custom view 20 | /// - Parameters: 21 | /// - isDismissible: default true, will tap bg auto dismiss 22 | /// - enableDrag: default true, will enable drag animate 23 | /// - bgColor: background view color 24 | /// - container: your custom view 25 | func bottomSheet(with isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 26 | var config: JFPopupConfig = .bottomSheet 27 | config.isDismissible = isDismissible 28 | config.enableDrag = enableDrag 29 | config.bgColor = bgColor 30 | let view = container() 31 | let vc = JFPopupController(with: config) { 32 | view 33 | } 34 | vc.show(with: base) 35 | } 36 | 37 | 38 | /// popup a drawer style view with your custom view 39 | /// - Parameters: 40 | /// - direction: left or right 41 | /// - isDismissible: default true, will tap bg auto dismiss 42 | /// - enableDrag: default true, will enable drag animate 43 | /// - bgColor: background view color 44 | /// - container: your custom view 45 | func drawer(with direction: JFPopupAnimationDirection = .left, isDismissible: Bool = true, enableDrag: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 46 | var config: JFPopupConfig = .drawer 47 | config.direction = direction 48 | config.isDismissible = isDismissible 49 | config.enableDrag = enableDrag 50 | config.bgColor = bgColor 51 | self.custom(with: config, container: container) 52 | } 53 | 54 | 55 | /// popup a dialog style view with your custom view 56 | /// - Parameters: 57 | /// - isDismissible: default true, will tap bg auto dismiss 58 | /// - bgColor: background view color 59 | /// - container: your custom view 60 | func dialog(with isDismissible: Bool = true, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 61 | var config: JFPopupConfig = .dialog 62 | config.isDismissible = isDismissible 63 | config.bgColor = bgColor 64 | config.toastPosition = .center 65 | self.custom(with: config, container: container) 66 | } 67 | 68 | /// popup a custom view with custom config 69 | /// - Parameters: 70 | /// - config: popup config 71 | /// - container: your custom view 72 | func custom(with config: JFPopupConfig, container: () -> UIView?) { 73 | guard let view = container() else { 74 | return 75 | } 76 | let vc = JFPopupController(with: config) { 77 | view 78 | } 79 | vc.show(with: base) 80 | } 81 | 82 | // adapt objc extension 83 | func objc_custom(with animationType: JFPopupAnimationType = .dialog, isDismissible: Bool = true, enableDrag: Bool = true, direction: JFPopupAnimationDirection = .left, bgColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4), container: () -> UIView) { 84 | var config = JFPopupConfig() 85 | config.animationType = animationType 86 | config.isDismissible = isDismissible 87 | config.enableDrag = enableDrag 88 | config.direction = direction 89 | config.bgColor = bgColor 90 | self.custom(with: config, container: container) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/ActionSheet/JFPopupAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFPopupAction.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/9. 6 | // 7 | 8 | import UIKit 9 | 10 | @objc public class JFPopupAction: NSObject { 11 | 12 | var title: String? 13 | var subTitle: String? 14 | var subAttributedTitle: NSAttributedString? 15 | var clickActionCallBack: (() -> ())? 16 | var autoDismiss: Bool = true //auto dismiss when you click action 17 | 18 | @objc public convenience init(with title: String?, subTitle: String?, autoDismiss: Bool = true, clickActionCallBack: @escaping (() -> ())) { 19 | self.init() 20 | self.title = title 21 | self.subTitle = subTitle 22 | self.autoDismiss = autoDismiss 23 | self.clickActionCallBack = clickActionCallBack 24 | } 25 | 26 | @objc public convenience init(with title: String?, subAttributedTitle: NSAttributedString?, autoDismiss: Bool = true, clickActionCallBack: @escaping (() -> ())) { 27 | self.init() 28 | self.title = title 29 | self.subAttributedTitle = subAttributedTitle 30 | self.autoDismiss = autoDismiss 31 | self.clickActionCallBack = clickActionCallBack 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/ActionSheet/JFPopupActionSheetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFPopupActionSheetView.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/9. 6 | // 7 | 8 | import UIKit 9 | 10 | public extension JFPopup where Base: UIViewController { 11 | /// popup a actionSheet 12 | /// - Parameters: 13 | /// - autoCancelAction: is true, will add cancel action in the last 14 | /// - actions: the actions item 15 | func actionSheet(with autoCancelAction: Bool = true, actions: (() -> [JFPopupAction])) { 16 | self.bottomSheet(with: true, enableDrag: false) { 17 | let v = JFPopupActionSheetView(with: actions(), autoCancelAction: autoCancelAction) 18 | v.autoDismissHandle = { 19 | dismissPopup() 20 | } 21 | return v 22 | } 23 | } 24 | } 25 | 26 | public extension JFPopup where Base: UIView { 27 | /// popup a actionSheet 28 | /// - Parameters: 29 | /// - autoCancelAction: is true, will add cancel action in the last 30 | /// - actions: the actions item 31 | @discardableResult func actionSheet(with autoCancelAction: Bool = true, actions: @escaping (() -> [JFPopupAction])) -> JFPopupView? { 32 | return JFPopupView.popup.bottomSheet(with: true, enableDrag: false, yourView: base) { mainContainer in 33 | let v = JFPopupActionSheetView(with: actions(), autoCancelAction: autoCancelAction) 34 | v.autoDismissHandle = { [weak mainContainer] in 35 | mainContainer?.dismissPopupView(completion: { isFinished in 36 | 37 | }) 38 | } 39 | return v 40 | } 41 | } 42 | } 43 | 44 | public extension JFPopup where Base: JFPopupView { 45 | /// popup a actionSheet 46 | /// - Parameters: 47 | /// - autoCancelAction: is true, will add cancel action in the last 48 | /// - actions: the actions item 49 | @discardableResult static func actionSheet(with autoCancelAction: Bool = true, yourView: UIView? = nil, actions: @escaping (() -> [JFPopupAction])) -> JFPopupView? { 50 | return self.bottomSheet(with: true, enableDrag: false, yourView: yourView) { mainContainer in 51 | let v = JFPopupActionSheetView(with: actions(), autoCancelAction: autoCancelAction) 52 | v.autoDismissHandle = { [weak mainContainer] in 53 | mainContainer?.dismissPopupView(completion: { isFinished in 54 | 55 | }) 56 | } 57 | return v 58 | } 59 | } 60 | } 61 | 62 | class JFPopupActionView: UIView { 63 | 64 | var clickActionHandle: (() -> ())? 65 | 66 | let titleLabel: UILabel = { 67 | let label = UILabel() 68 | label.textColor = UIColor.init(red: 19 / 255.0, green: 20 / 255.0, blue: 19 / 255.0, alpha: 1) 69 | label.font = UIFont.systemFont(ofSize: 16) 70 | return label 71 | }() 72 | 73 | let subTitleLabel: UILabel = { 74 | let label = UILabel() 75 | label.textColor = UIColor.init(red: 135 / 255.0, green: 135 / 255.0, blue: 135 / 255.0, alpha: 1) 76 | label.font = UIFont.systemFont(ofSize: 12) 77 | return label 78 | }() 79 | 80 | lazy var verStackView: UIStackView = { 81 | let stackView = UIStackView(arrangedSubviews: [self.titleLabel,self.subTitleLabel]) 82 | stackView.alignment = .center 83 | stackView.spacing = 5 84 | stackView.distribution = .fillProportionally 85 | stackView.axis = .vertical 86 | return stackView 87 | }() 88 | 89 | var lineView: UIView = { 90 | let view = UIView() 91 | view.backgroundColor = UIColor.init(red: 221 / 255.0, green: 221 / 255.0, blue: 221 / 255.0, alpha: 1) 92 | return view 93 | }() 94 | 95 | lazy var clickBtn: UIButton = { 96 | let btn = UIButton(type: .custom) 97 | btn.addTarget(self, action: #selector(clickAction), for: .touchUpInside) 98 | btn.setBackgroundImage(UIImage.jf.color(0x000000,alpha: 0.1), for: .highlighted) 99 | return btn 100 | }() 101 | 102 | @objc func clickAction() { 103 | self.clickActionHandle?() 104 | } 105 | 106 | override init(frame: CGRect) { 107 | super.init(frame: frame) 108 | self.backgroundColor = .white 109 | self.addSubview(self.verStackView) 110 | self.verStackView.translatesAutoresizingMaskIntoConstraints = false 111 | self.addConstraints( 112 | [ 113 | NSLayoutConstraint(item: self.verStackView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0), 114 | NSLayoutConstraint(item: self.verStackView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0), 115 | NSLayoutConstraint(item: self.verStackView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0) 116 | ] 117 | ) 118 | self.addSubview(self.lineView) 119 | self.lineView.translatesAutoresizingMaskIntoConstraints = false 120 | self.addConstraints( 121 | [ 122 | NSLayoutConstraint(item: self.lineView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0), 123 | NSLayoutConstraint(item: self.lineView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0), 124 | NSLayoutConstraint(item: self.lineView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0), 125 | NSLayoutConstraint(item: self.lineView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 0.5) 126 | ] 127 | ) 128 | self.addSubview(self.clickBtn) 129 | self.clickBtn.translatesAutoresizingMaskIntoConstraints = false 130 | self.addConstraints([ 131 | NSLayoutConstraint(item: self.clickBtn, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0), 132 | NSLayoutConstraint(item: self.clickBtn, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0), 133 | NSLayoutConstraint(item: self.clickBtn, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0), 134 | NSLayoutConstraint(item: self.clickBtn, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0) 135 | ]) 136 | } 137 | 138 | required init?(coder: NSCoder) { 139 | fatalError("init(coder:) has not been implemented") 140 | } 141 | } 142 | 143 | public class JFPopupActionSheetView: UIView { 144 | 145 | var actions: [JFPopupAction]? 146 | var autoDismissHandle: (() -> ())? 147 | var autoCancelAction: Bool = true 148 | 149 | public convenience init(with actions: [JFPopupAction], autoCancelAction: Bool = true) { 150 | self.init(frame: .zero) 151 | self.autoCancelAction = autoCancelAction 152 | self.actions = actions 153 | self.configSubview() 154 | } 155 | 156 | override init(frame: CGRect) { 157 | super.init(frame: frame) 158 | } 159 | 160 | required init(coder: NSCoder) { 161 | fatalError("init(coder:) has not been implemented") 162 | } 163 | 164 | func configSubview() { 165 | self.backgroundColor = UIColor.init(red: 245 / 255.0, green: 245 / 255.0, blue: 245 / 255.0, alpha: 1) 166 | guard let actions = self.actions, actions.count > 0 else { return } 167 | var originY: CGFloat = 0 168 | var views: [UIView] = [] 169 | for element in actions.enumerated() { 170 | let action = element.element 171 | var actionHeight: CGFloat = 0 172 | let view = JFPopupActionView() 173 | view.clickActionHandle = { [weak self] in 174 | if action.autoDismiss { 175 | self?.autoDismissHandle?() 176 | } 177 | action.clickActionCallBack?() 178 | } 179 | if let title = action.title { 180 | view.titleLabel.text = title 181 | view.titleLabel.isHidden = false 182 | view.titleLabel.sizeToFit() 183 | actionHeight += view.titleLabel.frame.size.height 184 | } 185 | if let subTitle = action.subTitle { 186 | view.subTitleLabel.text = subTitle 187 | view.subTitleLabel.isHidden = false 188 | view.subTitleLabel.sizeToFit() 189 | actionHeight += 5 190 | actionHeight += view.subTitleLabel.frame.size.height 191 | } else if let subAttTitle = action.subAttributedTitle { 192 | view.subTitleLabel.attributedText = subAttTitle 193 | view.subTitleLabel.isHidden = false 194 | view.subTitleLabel.sizeToFit() 195 | actionHeight += 5 196 | actionHeight += view.subTitleLabel.frame.size.height 197 | } 198 | view.lineView.isHidden = element.offset == (actions.count - 1) 199 | actionHeight += 30 200 | view.frame = CGRect(origin: CGPoint(x: 0, y: originY), size: CGSize(width: CGSize.jf.screenWidth(), height: actionHeight)) 201 | originY += actionHeight 202 | self.addSubview(view) 203 | views.append(view) 204 | } 205 | 206 | if var view = views.last, self.autoCancelAction == false { 207 | view.jf.height += CGFloat.jf.safeAreaBottomHeight() 208 | self.frame.size.height = view.jf.bottom 209 | } else { 210 | var view = JFPopupActionView() 211 | view.titleLabel.text = "取消" 212 | view.clickActionHandle = { [weak self] in 213 | self?.autoDismissHandle?() 214 | } 215 | view.titleLabel.sizeToFit() 216 | view.jf.left = 0 217 | view.jf.top = originY + 5 218 | view.jf.width = CGSize.jf.screenWidth() 219 | view.jf.height = view.titleLabel.jf.height + 30 + CGFloat.jf.safeAreaBottomHeight() 220 | for constraint in view.constraints { 221 | if let first = constraint.firstItem, first.isEqual(view.verStackView) && constraint.firstAttribute == .centerY { 222 | constraint.constant = -CGFloat.jf.safeAreaBottomHeight() / 2 223 | break 224 | } 225 | } 226 | self.addSubview(view) 227 | self.frame.size.height = view.jf.bottom 228 | } 229 | self.frame.size.width = CGSize.jf.screenWidth() 230 | 231 | let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 12, height: 12)) 232 | let maskLayer = CAShapeLayer() 233 | maskLayer.frame = self.bounds 234 | maskLayer.path = path.cgPath 235 | self.layer.mask = maskLayer 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/Alert/JFAlertAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFAlertAction.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/22. 6 | // 7 | 8 | import UIKit 9 | 10 | public typealias TapActionCallBack = () -> () 11 | 12 | let JFAlertCancelColor = UIColor(red: 20 / 255.0, green: 20 / 255.0, blue: 20 / 255.0, alpha: 1) 13 | let JFAlertSureColor = UIColor(red: 69 / 255.0, green: 85 / 255.0, blue: 130 / 255.0, alpha: 1) 14 | 15 | public enum JFAlertActionOption { 16 | case textColor(UIColor) 17 | case text(String) 18 | case tapActionCallback(TapActionCallBack) 19 | 20 | static var cancel: [JFAlertActionOption] = [ 21 | .text("取消"),.textColor(JFAlertCancelColor), 22 | ] 23 | } 24 | 25 | class JFAlertAction: NSObject { 26 | 27 | var options: [JFAlertActionOption] = [] 28 | var clickBtnCallBack: (() -> ())? 29 | var tapActionCallback: TapActionCallBack? 30 | var defaultColor: UIColor = UIColor(red: 20 / 255.0, green: 20 / 255.0, blue: 20 / 255.0, alpha: 1) 31 | 32 | convenience init(with options: [JFAlertActionOption], defaultColor: UIColor) { 33 | self.init() 34 | self.options = options 35 | self.defaultColor = defaultColor 36 | } 37 | 38 | override init() { 39 | super.init() 40 | } 41 | 42 | deinit { 43 | print("JFAlertAction dealloc") 44 | } 45 | 46 | @objc func clickAction() { 47 | self.clickBtnCallBack?() 48 | self.tapActionCallback?() 49 | } 50 | 51 | func buildActionButton() -> UIButton? { 52 | var color = self.defaultColor 53 | var text: String? 54 | for option in options { 55 | switch option { 56 | case .textColor(let uIColor): 57 | color = uIColor 58 | case .text(let t): 59 | text = t 60 | case .tapActionCallback(let tap): 61 | self.tapActionCallback = tap 62 | } 63 | } 64 | guard let text = text else { 65 | return nil 66 | } 67 | let button: UIButton = { 68 | let btn = UIButton(type: .custom) 69 | btn.setTitle(text, for: .normal) 70 | btn.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium) 71 | btn.setTitleColor(color, for: .normal) 72 | btn.setBackgroundImage(UIImage.jf.color(0xffffff), for: .normal) 73 | btn.setBackgroundImage(UIImage.jf.color(0x000000,alpha: 0.1), for: .highlighted) 74 | btn.addTarget(self, action: #selector(clickAction), for: .touchUpInside) 75 | return btn 76 | }() 77 | return button 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/Alert/JFAlertView+PopupView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFAlertView+PopupView.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/22. 6 | // 7 | 8 | import UIKit 9 | 10 | public extension JFPopup where Base: JFPopupView { 11 | 12 | @discardableResult static func alert(options: () -> [JFAlertOption]) -> JFPopupView? { 13 | let allOptions = options() 14 | var config: JFPopupConfig = .dialog 15 | var alertConfig = JFAlertConfig() 16 | config.enableUserInteraction = true 17 | config.enableAutoDismiss = false 18 | config.isDismissible = false 19 | config.toastPosition = .center 20 | for option in allOptions { 21 | switch option { 22 | case .title(let string): 23 | alertConfig.title = string 24 | case .titleColor(let uIColor): 25 | alertConfig.titleColor = uIColor 26 | case .subTitle(let string): 27 | alertConfig.subTitle = string 28 | case .subTitleColor(let uIColor): 29 | alertConfig.subTitleColor = uIColor 30 | case .showCancel(let bool): 31 | alertConfig.showCancel = bool 32 | case .cancelAction(let actions): 33 | alertConfig.cancelAction = actions 34 | case .confirmAction(let actions): 35 | alertConfig.confirmAction = actions 36 | case .withoutAnimation(let bool): 37 | config.withoutAnimation = bool 38 | } 39 | } 40 | guard alertConfig.title != nil || alertConfig.subTitle != nil else { 41 | assert(alertConfig.title != nil || alertConfig.subTitle != nil, "title or subTitle only can one value nil") 42 | return nil 43 | } 44 | let popupView = self.custom(with: config, yourView: nil) { mainContainer in 45 | JFAlertView(with: alertConfig) 46 | } 47 | return popupView 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/Alert/JFAlertView+UIViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFAlertView+UIViewController.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/22. 6 | // 7 | 8 | import UIKit 9 | 10 | public extension JFPopup where Base: UIViewController { 11 | func alert(options: () -> [JFAlertOption]) { 12 | let allOptions = options() 13 | var config: JFPopupConfig = .dialog 14 | var alertConfig = JFAlertConfig() 15 | config.enableUserInteraction = true 16 | config.enableAutoDismiss = false 17 | config.isDismissible = false 18 | for option in allOptions { 19 | switch option { 20 | case .title(let string): 21 | alertConfig.title = string 22 | case .titleColor(let uIColor): 23 | alertConfig.titleColor = uIColor 24 | case .subTitle(let string): 25 | alertConfig.subTitle = string 26 | case .subTitleColor(let uIColor): 27 | alertConfig.subTitleColor = uIColor 28 | case .showCancel(let bool): 29 | alertConfig.showCancel = bool 30 | case .cancelAction(let actions): 31 | alertConfig.cancelAction = actions 32 | case .confirmAction(let actions): 33 | alertConfig.confirmAction = actions 34 | case .withoutAnimation(let bool): 35 | config.withoutAnimation = bool 36 | } 37 | } 38 | guard alertConfig.title != nil || alertConfig.subTitle != nil else { 39 | assert(alertConfig.title != nil || alertConfig.subTitle != nil, "title or subTitle only can one value nil") 40 | return 41 | } 42 | guard let alertView = JFAlertView(with: alertConfig) else { return } 43 | alertView.clickActionHandle = { 44 | dismissPopup() 45 | } 46 | self.dialog(with: false, bgColor: config.bgColor) { 47 | alertView 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/Alert/JFAlertView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFAlertView.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/21. 6 | // 7 | 8 | import UIKit 9 | 10 | public enum JFAlertOption { 11 | case title(String) 12 | case titleColor(UIColor) 13 | case subTitle(String) 14 | case subTitleColor(UIColor) 15 | case showCancel(Bool) 16 | case cancelAction([JFAlertActionOption]) 17 | case confirmAction([JFAlertActionOption]) 18 | case withoutAnimation(Bool) 19 | } 20 | 21 | public struct JFAlertConfig { 22 | var title: String? 23 | var titleColor: UIColor = UIColor(red: 20 / 255.0, green: 20 / 255.0, blue: 20 / 255.0, alpha: 1) 24 | var subTitle: String? 25 | var subTitleColor: UIColor = UIColor(red: 94 / 255.0, green: 94 / 255.0, blue: 94 / 255.0, alpha: 1) 26 | var showCancel = true 27 | var cancelAction: [JFAlertActionOption]? 28 | var confirmAction: [JFAlertActionOption]? 29 | var itemSpacing: CGFloat = 20 30 | var contentInset = UIEdgeInsets.init(top: 30, left: 20, bottom: 30, right: 20) 31 | 32 | } 33 | 34 | class JFAlertView: UIView { 35 | 36 | let margin: CGFloat = 15 * UIScreen.main.scale 37 | 38 | var cancelAction: JFAlertAction? 39 | var confirmAction: JFAlertAction? 40 | 41 | lazy var titleLabel: UILabel = { 42 | let label = UILabel() 43 | label.textColor = self.config.titleColor 44 | label.numberOfLines = 0 45 | label.textAlignment = .center 46 | label.font = UIFont.systemFont(ofSize: 18, weight: .medium) 47 | label.isHidden = true 48 | return label 49 | }() 50 | 51 | lazy var subTitleLabel: UILabel = { 52 | let label = UILabel() 53 | label.textColor = self.config.subTitleColor 54 | label.numberOfLines = 0 55 | label.textAlignment = .center 56 | label.font = UIFont.systemFont(ofSize: 16) 57 | label.isHidden = true 58 | return label 59 | }() 60 | 61 | lazy var verStackView: UIStackView = { 62 | let stackView = UIStackView(arrangedSubviews: [self.titleLabel,self.subTitleLabel]) 63 | stackView.alignment = .center 64 | stackView.spacing = self.config.itemSpacing 65 | stackView.axis = .vertical 66 | stackView.distribution = .fill 67 | return stackView 68 | }() 69 | 70 | let bottomView: UIView = { 71 | let view = UIView() 72 | view.backgroundColor = UIColor.init(red: 221 / 255.0, green: 221 / 255.0, blue: 221 / 255.0, alpha: 1) 73 | return view 74 | }() 75 | 76 | var config: JFAlertConfig = JFAlertConfig() 77 | var clickActionHandle: (() -> ())? 78 | 79 | public convenience init?(with config: JFAlertConfig) { 80 | //subTitle or title must have one value 81 | guard config.subTitle != nil || config.title != nil else { 82 | return nil 83 | } 84 | self.init(frame: .zero) 85 | self.config = config 86 | self.configSubview() 87 | } 88 | 89 | override init(frame: CGRect) { 90 | super.init(frame: CGRect(x: CGSize.jf.screenWidth(), y: CGSize.jf.screenHeight(), width: CGSize.jf.screenWidth(), height: CGSize.jf.screenHeight())) 91 | self.layer.cornerRadius = 10 92 | self.layer.masksToBounds = true 93 | self.backgroundColor = .white 94 | } 95 | 96 | func configAutolayout() { 97 | self.bottomView.translatesAutoresizingMaskIntoConstraints = false 98 | self.addSubview(self.bottomView) 99 | self.addConstraints([ 100 | NSLayoutConstraint(item: self.bottomView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0), 101 | NSLayoutConstraint(item: self.bottomView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0), 102 | NSLayoutConstraint(item: self.bottomView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0), 103 | NSLayoutConstraint(item: self.bottomView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 55), 104 | ]) 105 | 106 | self.addSubview(self.verStackView) 107 | self.verStackView.translatesAutoresizingMaskIntoConstraints = false 108 | self.verStackView.addConstraints([ 109 | NSLayoutConstraint(item: self.titleLabel, attribute: .width, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .width, multiplier: 1, constant: CGSize.jf.screenWidth() - (margin * 2 + self.config.contentInset.left + self.config.contentInset.right)), 110 | NSLayoutConstraint(item: self.subTitleLabel, attribute: .width, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .width, multiplier: 1, constant: CGSize.jf.screenWidth() - (margin * 2 + self.config.contentInset.left + self.config.contentInset.right)), 111 | ]) 112 | self.addConstraints( 113 | [ 114 | NSLayoutConstraint(item: self.verStackView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0), 115 | NSLayoutConstraint(item: self.verStackView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: -27.5), 116 | ] 117 | ) 118 | } 119 | 120 | func configSubview() { 121 | 122 | self.configAutolayout() 123 | var cancelAction: [JFAlertActionOption]? = JFAlertActionOption.cancel 124 | if let cancel = self.config.cancelAction { 125 | cancelAction = cancel 126 | } 127 | if self.config.showCancel == false { 128 | cancelAction = nil 129 | } 130 | var arrangedSubviews: [UIView] = [] 131 | if let cancel = cancelAction { 132 | let action = JFAlertAction(with: cancel, defaultColor: JFAlertCancelColor) 133 | action.clickBtnCallBack = { [weak self] in 134 | if let supV = self?.superview as? JFPopupView { 135 | supV.dismissPopupView { isFinished in 136 | 137 | } 138 | } else { 139 | self?.clickActionHandle?() 140 | } 141 | } 142 | self.cancelAction = action 143 | if let btn = action.buildActionButton() { 144 | arrangedSubviews.append(btn) 145 | } 146 | } 147 | if let confirm = config.confirmAction { 148 | let action = JFAlertAction(with: confirm, defaultColor: JFAlertSureColor) 149 | action.clickBtnCallBack = { [weak self] in 150 | if let supV = self?.superview as? JFPopupView { 151 | supV.dismissPopupView { isFinished in 152 | 153 | } 154 | } else { 155 | self?.clickActionHandle?() 156 | } 157 | } 158 | self.confirmAction = action 159 | if let btn = action.buildActionButton() { 160 | arrangedSubviews.append(btn) 161 | } 162 | } 163 | 164 | if arrangedSubviews.count > 0 { 165 | if let btn1 = arrangedSubviews.first, let btn2 = arrangedSubviews.last { 166 | let stackView = UIStackView(arrangedSubviews: arrangedSubviews) 167 | stackView.backgroundColor = .clear 168 | stackView.alignment = .bottom 169 | stackView.spacing = 1 170 | stackView.axis = .horizontal 171 | stackView.distribution = .fillEqually 172 | 173 | var constraints: [NSLayoutConstraint] = [] 174 | constraints.append(NSLayoutConstraint(item: btn1, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 54)) 175 | if btn1 != btn2 { 176 | constraints.append(NSLayoutConstraint(item: btn2, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 54)) 177 | } 178 | self.bottomView.addSubview(stackView) 179 | stackView.translatesAutoresizingMaskIntoConstraints = false 180 | stackView.addConstraints(constraints) 181 | 182 | self.addConstraints([ 183 | NSLayoutConstraint(item: stackView, attribute: .left, relatedBy: .equal, toItem: self.bottomView, attribute: .left, multiplier: 1, constant: 0), 184 | NSLayoutConstraint(item: stackView, attribute: .right, relatedBy: .equal, toItem: self.bottomView, attribute: .right, multiplier: 1, constant: 0), 185 | NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: self.bottomView, attribute: .bottom, multiplier: 1, constant: 0), 186 | NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self.bottomView, attribute: .top, multiplier: 1, constant: 1), 187 | ]) 188 | } 189 | } 190 | 191 | if let title = self.config.title { 192 | self.titleLabel.text = title 193 | self.titleLabel.isHidden = false 194 | } 195 | 196 | if let subTitle = self.config.subTitle { 197 | self.subTitleLabel.text = subTitle 198 | self.subTitleLabel.isHidden = false 199 | } 200 | self.layoutIfNeeded() 201 | let titleSize = self.titleLabel.frame.size 202 | let subTitleSize = self.subTitleLabel.frame.size 203 | var height: CGFloat = self.config.contentInset.bottom + self.config.contentInset.top 204 | let width = CGSize.jf.screenWidth() - margin * 2 205 | 206 | if titleSize != .zero { 207 | height += titleSize.height 208 | } 209 | 210 | if subTitleSize != .zero { 211 | height += titleSize != .zero ? self.config.itemSpacing : 0 212 | height += subTitleSize.height 213 | } 214 | height += 55 215 | self.frame = CGRect(x: 0, y: 0, width: width, height: height) 216 | 217 | } 218 | 219 | required init?(coder: NSCoder) { 220 | fatalError("init(coder:) has not been implemented") 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/Toast/JFToastQueueTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFToastQueueTask.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class JFToastQueueTask: NSObject { 11 | 12 | var config: JFPopupConfig? 13 | var toastConfig: JFToastConfig? 14 | weak var mainContainer: UIView? 15 | weak var popupView: JFPopupView? 16 | 17 | override init() { 18 | super.init() 19 | } 20 | 21 | convenience init(with config: JFPopupConfig, toastConfig: JFToastConfig?, mainContainer: UIView?, popupView: JFPopupView?) { 22 | self.init() 23 | self.config = config 24 | self.toastConfig = toastConfig 25 | self.mainContainer = mainContainer 26 | self.popupView = popupView 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/Toast/JFToastView+Objc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFToastView+Objc.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/19. 6 | // 7 | 8 | import UIKit 9 | 10 | @objc public enum JFToastObjcAssetType: Int { 11 | case unknow 12 | case success 13 | case fail 14 | case imageName 15 | } 16 | 17 | public extension JFToastView { 18 | 19 | @objc class func toast(hit: String) { 20 | JFToastView.toast(hit: hit, type: .unknow, imageName: nil) 21 | } 22 | 23 | @objc class func toast(icon: JFToastObjcAssetType, imageName: String?) { 24 | JFToastView.toast(hit: nil, type: icon, imageName: imageName) 25 | } 26 | 27 | @objc class func toast(hit: String?, type: JFToastObjcAssetType, imageName: String?) { 28 | var options: [JFToastOption] = [] 29 | if let hit = hit { 30 | options += [.hit(hit)] 31 | } 32 | if type != .unknow { 33 | var iconType: JFToastAssetIconType? 34 | if type == .success { 35 | iconType = .success 36 | } else if type == .fail { 37 | iconType = .fail 38 | } else if let name = imageName, type == .imageName { 39 | iconType = .imageName(name: name, imageType: "png") 40 | } 41 | if let icon = iconType { 42 | options += [.icon(icon)] 43 | } 44 | } 45 | JFPopupView.popup.toast { 46 | options 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/JFPopup/Classes/General/Toast/JFToastView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JFToastView.swift 3 | // JFPopup 4 | // 5 | // Created by 逸风 on 2021/10/16. 6 | // 7 | 8 | import UIKit 9 | 10 | //only loading style have queue 11 | private var JFLoadingViewsQueue: [JFToastQueueTask] = [] 12 | 13 | public enum JFToastOption { 14 | case hit(String) 15 | case icon(JFToastAssetIconType) 16 | case enableAutoDismiss(Bool) 17 | case enableUserInteraction(Bool) 18 | case autoDismissDuration(JFTimerDuration) 19 | case bgColor(UIColor) 20 | case mainContainer(UIView) 21 | case withoutAnimation(Bool) 22 | case position(JFToastPosition) 23 | case enableRotation(Bool) 24 | case contentInset(UIEdgeInsets) 25 | case itemSpacing(CGFloat) 26 | } 27 | 28 | public enum JFToastAssetIconType { 29 | 30 | case success 31 | case fail 32 | case imageName(name: String, imageType: String = "png") 33 | 34 | func getImageName() -> (name: String, imageType: String)? { 35 | switch self { 36 | case .success: 37 | return ("success","png") 38 | case .fail: 39 | return ("fail","png") 40 | case .imageName(name: let name, imageType: let imageType): 41 | return (name,imageType) 42 | } 43 | } 44 | } 45 | 46 | public struct JFToastConfig { 47 | var title: String? 48 | var assetIcon: JFToastAssetIconType? 49 | var enableDynamicIsLand: Bool = false 50 | var enableRotation: Bool = false 51 | var contentInset: UIEdgeInsets = .init(top: 12, left: 25, bottom: 12, right: 25) 52 | var itemSpacing: CGFloat = 5.0 53 | } 54 | 55 | public class JFToastView: UIView { 56 | 57 | let titleLabel: UILabel = { 58 | let label = UILabel() 59 | label.textColor = .white 60 | label.numberOfLines = 0 61 | label.textAlignment = .center 62 | label.font = UIFont.systemFont(ofSize: 15) 63 | label.isHidden = true 64 | return label 65 | }() 66 | 67 | let iconImgView: UIImageView = { 68 | let imageView = UIImageView() 69 | imageView.contentMode = .scaleAspectFit 70 | imageView.isHidden = true 71 | return imageView 72 | }() 73 | 74 | lazy var verStackView: UIStackView = { 75 | let stackView = UIStackView(arrangedSubviews: [self.iconImgView,self.titleLabel]) 76 | stackView.alignment = .center 77 | stackView.spacing = self.config.itemSpacing 78 | stackView.axis = .vertical 79 | stackView.distribution = .fill 80 | return stackView 81 | }() 82 | 83 | var config: JFToastConfig = JFToastConfig() 84 | 85 | public convenience init?(with config: JFToastConfig) { 86 | //assetIcon or title must have one value 87 | guard config.assetIcon != nil || config.title != nil else { 88 | return nil 89 | } 90 | self.init(frame: .zero) 91 | self.config = config 92 | self.configSubview() 93 | } 94 | 95 | override init(frame: CGRect) { 96 | super.init(frame: CGRect(x: CGSize.jf.screenWidth(), y: CGSize.jf.screenHeight(), width: CGSize.jf.screenWidth(), height: CGSize.jf.screenHeight())) 97 | self.backgroundColor = .black 98 | self.layer.cornerRadius = self.config.enableDynamicIsLand ? 17 : 10 99 | } 100 | 101 | func configSubview() { 102 | self.addSubview(self.verStackView) 103 | self.verStackView.translatesAutoresizingMaskIntoConstraints = false 104 | self.verStackView.addConstraints([ 105 | NSLayoutConstraint(item: self.titleLabel, attribute: .width, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .width, multiplier: 1, constant: CGSize.jf.screenWidth() - 30 - self.config.contentInset.left - self.config.contentInset.right), 106 | ]) 107 | self.addConstraints( 108 | [ 109 | NSLayoutConstraint(item: self.verStackView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0), 110 | NSLayoutConstraint(item: self.verStackView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: self.config.enableDynamicIsLand ? 34 / 2 : 0), 111 | ] 112 | ) 113 | if let title = self.config.title { 114 | self.titleLabel.text = title 115 | self.titleLabel.isHidden = false 116 | } 117 | 118 | if let assetIconType = self.config.assetIcon, let result = assetIconType.getImageName(), let image = self.getBundleImage(with: result.name, imageType: result.imageType) { 119 | self.iconImgView.image = image 120 | self.iconImgView.isHidden = false 121 | } 122 | 123 | self.layoutIfNeeded() 124 | let titleSize = self.titleLabel.frame.size 125 | let iconSize = self.iconImgView.frame.size 126 | var height: CGFloat = self.config.contentInset.bottom + self.config.contentInset.top + (self.config.enableDynamicIsLand ? 34 : 0) 127 | let horInset = CGFloat(self.config.contentInset.left + self.config.contentInset.right) 128 | let dynamicisLandSize = 120.0 + 20.0 + (iconSize == .zero ? 20 : 0) 129 | var contentWidth = max(titleSize.width, iconSize.width) 130 | if iconSize.width > 0 && iconSize.width + horInset > titleSize.width { 131 | contentWidth = iconSize.width 132 | } 133 | var width = contentWidth + horInset 134 | 135 | if titleSize != .zero { 136 | height += titleSize.height 137 | if self.config.enableDynamicIsLand { 138 | self.layer.cornerRadius = height / 2 139 | } 140 | } 141 | 142 | if iconSize != .zero { 143 | height += titleSize != .zero ? self.config.itemSpacing : 0 144 | height += iconSize.height 145 | if titleSize == .zero { 146 | width = height 147 | } 148 | if self.config.enableDynamicIsLand { 149 | self.layer.cornerRadius = 20 150 | } 151 | } 152 | if self.config.enableDynamicIsLand { 153 | width = max(width, dynamicisLandSize) 154 | } 155 | self.frame = CGRect(x: 0, y: 0, width: width, height: height) 156 | if config.enableRotation { 157 | self.addRotationAnimation() 158 | } 159 | } 160 | 161 | func addRotationAnimation() { 162 | if self.iconImgView.layer.animationKeys() == nil { 163 | let baseAni = CABasicAnimation(keyPath: "transform.rotation.z") 164 | let toValue: CGFloat = .pi * 2.0 165 | baseAni.toValue = toValue 166 | baseAni.duration = 1.0 167 | baseAni.isCumulative = true 168 | baseAni.repeatCount = MAXFLOAT 169 | baseAni.isRemovedOnCompletion = false 170 | self.iconImgView.layer.add(baseAni, forKey: "rotationAnimation") 171 | } 172 | } 173 | 174 | required init?(coder: NSCoder) { 175 | fatalError("init(coder:) has not been implemented") 176 | } 177 | 178 | private func getBundleImage(with imageName: String, imageType: String = "png") -> UIImage? { 179 | if let img = UIImage(named: imageName) { 180 | return img 181 | } 182 | if let path = Bundle.main.path(forResource: imageName, ofType: imageType), let image = UIImage(contentsOfFile: path) { 183 | return image 184 | } 185 | //support SPM 186 | if let bundlePath = Bundle.main.path(forResource: "JFPopup_JFPopup", ofType: "bundle") { 187 | if let imgPath = Bundle(path: bundlePath)?.path(forResource: imageName + "@2x", ofType: imageType), let image = UIImage(contentsOfFile: imgPath) { 188 | return image 189 | } 190 | return nil 191 | } 192 | guard let frameWorkPath = Bundle.main.path(forResource: "Frameworks", ofType: nil)?.appending("/JFPopup.framework") else { return nil } 193 | guard let bundlePath = Bundle(path: frameWorkPath)?.path(forResource: "JFPopup", ofType: "bundle") else { return nil } 194 | if let imgPath = Bundle(path: bundlePath)?.path(forResource: imageName + "@2x", ofType: imageType), let image = UIImage(contentsOfFile: imgPath) { 195 | return image 196 | } 197 | return nil 198 | } 199 | } 200 | 201 | public extension JFPopup where Base: JFPopupView { 202 | 203 | static func hideLoading() { 204 | 205 | let work = DispatchWorkItem { 206 | let firstTask = JFLoadingViewsQueue.first 207 | if firstTask != nil { 208 | JFLoadingViewsQueue.removeFirst() 209 | } 210 | if let v = firstTask?.popupView { 211 | v.dismissPopupView { _ in } 212 | } 213 | } 214 | 215 | if Thread.current == Thread.main { 216 | work.perform() 217 | } else { 218 | DispatchQueue.main.sync(execute: work) 219 | } 220 | 221 | } 222 | 223 | static func loading() { 224 | self.loading(hit: nil) 225 | } 226 | 227 | static func loading(hit: String?) { 228 | self.loading(hit: hit, inView: nil) 229 | } 230 | 231 | 232 | ///show loading view 233 | /// - Parameters: 234 | /// - hit: message 235 | /// - inView: only support keywindow or ontroller.view, default keywindow 236 | static func loading(hit: String?, inView: UIView?) { 237 | var options: [JFToastOption] = [ 238 | .enableAutoDismiss(false), 239 | .icon(.imageName(name: "jf_loading")), 240 | .enableRotation(true), 241 | .itemSpacing(15)] 242 | options += [.enableUserInteraction(true)] 243 | if let view = inView, !JFIsSupportDynamicIsLand { 244 | options += [.mainContainer(view)] 245 | } 246 | if let hit = hit { 247 | options += [.hit(hit)] 248 | options += [.contentInset(.init(top: 30, left: 47, bottom: 30, right: 47))] 249 | } else { 250 | options += [.contentInset(.init(top: 35, left: 35, bottom: 35, right: 35))] 251 | } 252 | JFPopupView.popup.toast { options } 253 | } 254 | 255 | static func toast(hit: String) { 256 | self.toast { 257 | [.hit(hit)] 258 | } 259 | } 260 | 261 | static func toast(hit: String, icon: JFToastAssetIconType) { 262 | self.toast { 263 | [.hit(hit),.icon(icon)] 264 | } 265 | } 266 | 267 | @discardableResult static func toast(options: () -> [JFToastOption]) -> JFPopupView? { 268 | let allOptions = options() 269 | var mainView: UIView? 270 | var config: JFPopupConfig = .dialog 271 | var toastConfig = JFToastConfig() 272 | config.bgColor = .clear 273 | config.enableUserInteraction = false 274 | config.enableAutoDismiss = true 275 | config.isDismissible = false 276 | toastConfig.enableDynamicIsLand = config.toastPosition == .dynamicIsland 277 | for option in allOptions { 278 | switch option { 279 | case .hit(let hit): 280 | toastConfig.title = hit 281 | break 282 | case .icon(let icon): 283 | toastConfig.assetIcon = icon 284 | break 285 | case .enableUserInteraction(let enable): 286 | config.enableUserInteraction = enable 287 | break 288 | case .enableAutoDismiss(let autoDismiss): 289 | config.enableAutoDismiss = autoDismiss 290 | break 291 | case .autoDismissDuration(let duration): 292 | config.autoDismissDuration = duration 293 | break 294 | case .bgColor(let bgColor): 295 | config.bgColor = bgColor 296 | break 297 | case .mainContainer(let view): 298 | mainView = view 299 | break 300 | case .withoutAnimation(let without): 301 | config.withoutAnimation = without 302 | break 303 | case .position(let pos): 304 | config.toastPosition = pos 305 | toastConfig.enableDynamicIsLand = config.toastPosition == .dynamicIsland && JFIsSupportDynamicIsLand 306 | break 307 | case .enableRotation(let enable): 308 | toastConfig.enableRotation = enable 309 | break 310 | case .contentInset(let inset): 311 | toastConfig.contentInset = inset 312 | break 313 | case .itemSpacing(let spcaing): 314 | toastConfig.itemSpacing = spcaing 315 | break 316 | } 317 | } 318 | 319 | guard toastConfig.title != nil || toastConfig.assetIcon != nil else { 320 | assert(toastConfig.title != nil || toastConfig.assetIcon != nil, "title or assetIcon only can one value nil") 321 | return nil 322 | } 323 | guard JFLoadingViewsQueue.count == 0 else { 324 | print("only can show single loading need manual dismiss) view in the same time") 325 | return nil 326 | } 327 | let popupView = self.custom(with: config, yourView: mainView) { mainContainer in 328 | JFToastView(with: toastConfig) 329 | } 330 | if config.enableAutoDismiss == false { 331 | Self.safeAppendToastTask(task: JFToastQueueTask(with: config, toastConfig: toastConfig, mainContainer: mainView, popupView: popupView)) 332 | } 333 | return popupView 334 | } 335 | 336 | private static func safeAppendToastTask(task: JFToastQueueTask) { 337 | let work = DispatchWorkItem { 338 | JFLoadingViewsQueue.append(task) 339 | } 340 | if Thread.current == Thread.main { 341 | work.perform() 342 | } else { 343 | DispatchQueue.main.sync(execute: work) 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Sources/JFPopup/Resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryFans/JFPopup/8b0227215e754218280667cd656e22abc4d62821/Sources/JFPopup/Resources/.gitkeep -------------------------------------------------------------------------------- /Sources/JFPopup/Resources/fail@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryFans/JFPopup/8b0227215e754218280667cd656e22abc4d62821/Sources/JFPopup/Resources/fail@2x.png -------------------------------------------------------------------------------- /Sources/JFPopup/Resources/jf_loading@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryFans/JFPopup/8b0227215e754218280667cd656e22abc4d62821/Sources/JFPopup/Resources/jf_loading@2x.png -------------------------------------------------------------------------------- /Sources/JFPopup/Resources/success@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryFans/JFPopup/8b0227215e754218280667cd656e22abc4d62821/Sources/JFPopup/Resources/success@2x.png -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------