diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b45ff..32d6941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # CHANGELOG +### v2.0 +- Realistic animation like Facebook Messenger #24, #26 +- Redesign of ProxyDelegate #25, #27 +- Prepare Migration Guide +- Create unavailable.swift for migration from 1.x + ### v1.1.1 - Fixed crash problem using WKWebView's scrollview. #18 @@ -7,8 +13,8 @@ - Edge shadow #9 - CHANGELOG.md -### v1.0 #7 -Major Update. It is stable version. +### v1.0 +Major Update. It is stable version. #7 #### Added - Blur background effect #3 diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 3213c46..9aaebdf 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 4D5A351C1DDB21F100ED9D54 /* PullToDismiss.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4D5A351A1DDB21F100ED9D54 /* PullToDismiss.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4D5A35211DDBFD7200ED9D54 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5A35201DDBFD7200ED9D54 /* Config.swift */; }; 4D9D97C91DF0751400C10C87 /* img.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4D9D97C81DF0751400C10C87 /* img.jpg */; }; + 4DB1C4991E6EF167008103D1 /* SampleWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB1C4981E6EF167008103D1 /* SampleWebViewController.swift */; }; 4DC885401DDDF13200B282FB /* SampleTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC8853F1DDDF13200B282FB /* SampleTableViewController.swift */; }; 4DDE88051DDAA1DC00154B59 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DDE88041DDAA1DC00154B59 /* AppDelegate.swift */; }; 4DDE88071DDAA1DC00154B59 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DDE88061DDAA1DC00154B59 /* ViewController.swift */; }; @@ -46,6 +47,7 @@ 4D5A351A1DDB21F100ED9D54 /* PullToDismiss.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PullToDismiss.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4D5A35201DDBFD7200ED9D54 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 4D9D97C81DF0751400C10C87 /* img.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = img.jpg; sourceTree = ""; }; + 4DB1C4981E6EF167008103D1 /* SampleWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleWebViewController.swift; sourceTree = ""; }; 4DC8853F1DDDF13200B282FB /* SampleTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleTableViewController.swift; sourceTree = ""; }; 4DDE88011DDAA1DC00154B59 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4DDE88041DDAA1DC00154B59 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -103,6 +105,7 @@ 4D19BAD51DDDE49A0027383B /* SampleCollectionLayout.swift */, 4D19BAD71DDDE8050027383B /* SampleCustomViewController.swift */, 4D19BAD91DDDE80F0027383B /* SampleCustomViewController.storyboard */, + 4DB1C4981E6EF167008103D1 /* SampleWebViewController.swift */, ); path = Demo; sourceTree = ""; @@ -188,6 +191,7 @@ 4DE54A291DDD4B4100568F7B /* UIColor+Hex.swift in Sources */, 4DDE88051DDAA1DC00154B59 /* AppDelegate.swift in Sources */, 4D19BAD41DDDE1DD0027383B /* SampleCollectionViewController.swift in Sources */, + 4DB1C4991E6EF167008103D1 /* SampleWebViewController.swift in Sources */, 4D19BAD81DDDE8050027383B /* SampleCustomViewController.swift in Sources */, 4D5A35211DDBFD7200ED9D54 /* Config.swift in Sources */, 4DC885401DDDF13200B282FB /* SampleTableViewController.swift in Sources */, diff --git a/Demo/Demo/Base.lproj/Main.storyboard b/Demo/Demo/Base.lproj/Main.storyboard index e9537d2..9c9ea5d 100644 --- a/Demo/Demo/Base.lproj/Main.storyboard +++ b/Demo/Demo/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -26,10 +26,10 @@ + - - + + @@ -83,75 +95,75 @@ - diff --git a/Demo/Demo/SampleCollectionLayout.swift b/Demo/Demo/SampleCollectionLayout.swift index 8639c79..9753208 100644 --- a/Demo/Demo/SampleCollectionLayout.swift +++ b/Demo/Demo/SampleCollectionLayout.swift @@ -14,8 +14,6 @@ class SampleCollectionLayout: UICollectionViewFlowLayout { minimumLineSpacing = 0 minimumInteritemSpacing = 0 sectionInset = .zero - let side = UIScreen.main.bounds.width / 4 - itemSize = CGSize(width: side, height: side) } required init?(coder aDecoder: NSCoder) { diff --git a/Demo/Demo/SampleCollectionViewController.swift b/Demo/Demo/SampleCollectionViewController.swift index d9df79f..fa87146 100644 --- a/Demo/Demo/SampleCollectionViewController.swift +++ b/Demo/Demo/SampleCollectionViewController.swift @@ -28,7 +28,7 @@ class SampleCollectionViewController: UICollectionViewController { navigationController?.navigationBar.setValue(UIBarPosition.topAttached.rawValue, forKey: "barPosition") pullToDismiss = PullToDismiss(scrollView: collectionView!) Config.shared.adaptSetting(pullToDismiss: pullToDismiss) - pullToDismiss?.delegateProxy = self + pullToDismiss?.delegate = self view.backgroundColor = .white } @@ -51,5 +51,11 @@ class SampleCollectionViewController: UICollectionViewController { alert.addAction(UIAlertAction(title: "ok", style: .default, handler: nil)) self.present(alert, animated: true, completion: nil) } +} +extension SampleCollectionViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let side = UIScreen.main.bounds.width / 6 + return CGSize(width: side, height: side) + } } diff --git a/Demo/Demo/SampleCustomViewController.swift b/Demo/Demo/SampleCustomViewController.swift index 00a2204..26f7ab8 100644 --- a/Demo/Demo/SampleCustomViewController.swift +++ b/Demo/Demo/SampleCustomViewController.swift @@ -24,7 +24,7 @@ class SampleCustomViewController: UIViewController { pullToDismiss = PullToDismiss(scrollView: tableView, viewController: self, navigationBar: coverView) Config.shared.adaptSetting(pullToDismiss: pullToDismiss) tableView.dataSource = self - pullToDismiss?.delegateProxy = self + pullToDismiss?.delegate = self } @IBAction func close(_: UIButton) { diff --git a/Demo/Demo/SampleTableViewController.swift b/Demo/Demo/SampleTableViewController.swift index e06f93b..db96dfc 100644 --- a/Demo/Demo/SampleTableViewController.swift +++ b/Demo/Demo/SampleTableViewController.swift @@ -30,7 +30,7 @@ class SampleTableViewController: UITableViewController { pullToDismiss?.dismissAction = { [weak self] in self?.dismiss(nil) } - pullToDismiss?.delegateProxy = self + pullToDismiss?.delegate = self } var disissBlock: (() -> Void)? @@ -60,7 +60,6 @@ class SampleTableViewController: UITableViewController { } override func scrollViewDidScroll(_ scrollView: UIScrollView) { -// print("\(scrollView.contentOffset.y)") - } - + print("\(scrollView.contentOffset.y)") + } } diff --git a/Demo/Demo/SampleWebViewController.swift b/Demo/Demo/SampleWebViewController.swift new file mode 100644 index 0000000..4361357 --- /dev/null +++ b/Demo/Demo/SampleWebViewController.swift @@ -0,0 +1,48 @@ +// +// SampleWebViewController.swift +// Demo +// +// Created by Suguru Kishimoto on 3/7/17. +// Copyright © 2017 Suguru Kishimoto. All rights reserved. +// + +import UIKit +import WebKit +import PullToDismiss + +class SampleWebViewController: UIViewController { + + private lazy var webView: WKWebView = WKWebView(frame: .zero) + private var pullToDismiss: PullToDismiss? + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(webView) + + webView.translatesAutoresizingMaskIntoConstraints = false + let attributes: [NSLayoutAttribute] = [.top, .left, .right, .bottom] + let constraints: [NSLayoutConstraint] = attributes.map { attribute in + NSLayoutConstraint(item: webView, attribute: attribute, relatedBy: .equal, toItem: view, attribute: attribute, multiplier: 1.0, constant: 0.0) + } + + view.addConstraints(constraints) + + pullToDismiss = PullToDismiss(scrollView: webView.scrollView) + Config.shared.adaptSetting(pullToDismiss: pullToDismiss) + pullToDismiss?.delegate = self + view.backgroundColor = .white + + webView.load(URLRequest(url: URL(string: "https://www.google.co.jp")!)) + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + } + +} + +extension SampleWebViewController: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + print("!!!!!!!!! \(scrollView.contentOffset)") + } +} diff --git a/Demo/Demo/ViewController.swift b/Demo/Demo/ViewController.swift index 2d7b089..93fe422 100644 --- a/Demo/Demo/ViewController.swift +++ b/Demo/Demo/ViewController.swift @@ -15,6 +15,7 @@ class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet private weak var demoButton2: UIButton! @IBOutlet private weak var demoButton3: UIButton! @IBOutlet private weak var demoButton4: UIButton! + @IBOutlet private weak var demoButton5: UIButton! @IBOutlet private weak var backgroundSwitch: UISegmentedControl! @IBOutlet private weak var colorTextField: UITextField! @IBOutlet private weak var currentColorView: UIView! @@ -27,7 +28,7 @@ class ViewController: UIViewController, UITextFieldDelegate { override func viewDidLoad() { super.viewDidLoad() - let buttons: [UIButton] = [demoButton1, demoButton2, demoButton3, demoButton4] + let buttons: [UIButton] = [demoButton1, demoButton2, demoButton3, demoButton4, demoButton5] buttons.forEach { $0.layer.cornerRadius = $0.frame.height / 2 $0.clipsToBounds = true @@ -144,6 +145,8 @@ class ViewController: UIViewController, UITextFieldDelegate { self?.blurSampleImageView.alpha = 1.0 }) return nav + case demoButton5: + return UINavigationController(rootViewController: SampleWebViewController()) default: fatalError() } diff --git a/Documents/PullToDismiss2MigrationGuide.md b/Documents/PullToDismiss2MigrationGuide.md new file mode 100644 index 0000000..64aa4d4 --- /dev/null +++ b/Documents/PullToDismiss2MigrationGuide.md @@ -0,0 +1,73 @@ +# PullToDismiss 2 Migration Guide + +PullToDismiss 2.0 has several breaking changes. + +## change delegateProxy to delegate + +- Aefore 2.0 + +```swift +pullToDismiss?.delegateProxy = self +``` + +- 2.0〜 + +```swift +pullToDismiss?.delegate = self +``` + +## Custom class (subclass) is no longer needed +Since PullToDismiss 2.0, all scroll view's delegate is available without creating custom class. + +- Before 2.0 + +```swift +import PullToDismiss + +class CustomPullToDismiss: PullToDismiss { + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return tableViewDelegate?.tableView?(tableView, heightForRowAt: indexPath) ?? 44.0 + } +} + +class SampleViewController: UIViewController { + @IBOutlet private weak var tableView: UITableView! + private var pullToDismiss: PullToDismiss? + override func viewDidLoad() { + super.viewDidLoad() + pullToDismiss = CustomPullToDismiss(scrollView: tableView) + pullToDismiss?.delegateProxy = self + } +} + +extension SampleViewController: UITableViewDelegate { + // called by CustomPullToDismiss + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return indexPath.section == 0 ? 44.0 : 60.0 + } + + // ... +} +``` + +- 2.0〜 + +```swift +import PullToDismiss + +class SampleViewController: UIViewController { + @IBOutlet private weak var tableView: UITableView! + private var pullToDismiss: PullToDismiss? + override func viewDidLoad() { + super.viewDidLoad() + pullToDismiss = PullToDismiss(scrollView: tableView) + pullToDismiss?.delegate = self + } +} + +extension SampleViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return indexPath.section == 0 ? 44.0 : 60.0 + } +} +``` diff --git a/PullToDismiss.xcodeproj/project.pbxproj b/PullToDismiss.xcodeproj/project.pbxproj index 26af6ef..fcf6a58 100644 --- a/PullToDismiss.xcodeproj/project.pbxproj +++ b/PullToDismiss.xcodeproj/project.pbxproj @@ -7,9 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 4D22AC5C1E6E36B20006EDA0 /* DelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D22AC5A1E6E36B20006EDA0 /* DelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4D22AC5D1E6E36B20006EDA0 /* DelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D22AC5B1E6E36B20006EDA0 /* DelegateProxy.m */; }; + 4D2B82BA1E73E835008DD411 /* Unavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D2B82B91E73E835008DD411 /* Unavailable.swift */; }; 4D2F15C31DF2D570000A5172 /* BackgroundEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D2F15C21DF2D570000A5172 /* BackgroundEffect.swift */; }; 4D5A35191DDB21DB00ED9D54 /* PullToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5A35181DDB21DB00ED9D54 /* PullToDismiss.swift */; }; - 4D5A351F1DDBF18B00ED9D54 /* PullToDismiss+Proxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5A351E1DDBF18B00ED9D54 /* PullToDismiss+Proxy.swift */; }; + 4D63E6A01E6E38F100FEA3D0 /* ScrollViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D63E69F1E6E38F100FEA3D0 /* ScrollViewDelegateProxy.swift */; }; 4D870AAB1DDAA0CD00575A28 /* PullToDismiss.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D870AA11DDAA0CD00575A28 /* PullToDismiss.framework */; }; 4D870AB01DDAA0CD00575A28 /* PullToDismissTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D870AAF1DDAA0CD00575A28 /* PullToDismissTests.swift */; }; 4D870AB21DDAA0CD00575A28 /* PullToDismiss.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D870AA41DDAA0CD00575A28 /* PullToDismiss.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -29,9 +32,12 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 4D22AC5A1E6E36B20006EDA0 /* DelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DelegateProxy.h; sourceTree = ""; }; + 4D22AC5B1E6E36B20006EDA0 /* DelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DelegateProxy.m; sourceTree = ""; }; + 4D2B82B91E73E835008DD411 /* Unavailable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unavailable.swift; sourceTree = ""; }; 4D2F15C21DF2D570000A5172 /* BackgroundEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundEffect.swift; sourceTree = ""; }; 4D5A35181DDB21DB00ED9D54 /* PullToDismiss.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PullToDismiss.swift; sourceTree = ""; }; - 4D5A351E1DDBF18B00ED9D54 /* PullToDismiss+Proxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PullToDismiss+Proxy.swift"; sourceTree = ""; }; + 4D63E69F1E6E38F100FEA3D0 /* ScrollViewDelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewDelegateProxy.swift; sourceTree = ""; }; 4D870AA11DDAA0CD00575A28 /* PullToDismiss.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PullToDismiss.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4D870AA41DDAA0CD00575A28 /* PullToDismiss.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PullToDismiss.h; sourceTree = ""; }; 4D870AA51DDAA0CD00575A28 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -67,10 +73,13 @@ children = ( 4D5A35181DDB21DB00ED9D54 /* PullToDismiss.swift */, 4D2F15C21DF2D570000A5172 /* BackgroundEffect.swift */, - 4D5A351E1DDBF18B00ED9D54 /* PullToDismiss+Proxy.swift */, 4D9D97C41DF058C700C10C87 /* CustomBlurView.swift */, 4DBDB74E1E1F69770068AA1F /* EdgeShadow.swift */, 4DBDB7501E1F6FFA0068AA1F /* UIView+EdgeShadow.swift */, + 4D22AC5A1E6E36B20006EDA0 /* DelegateProxy.h */, + 4D22AC5B1E6E36B20006EDA0 /* DelegateProxy.m */, + 4D63E69F1E6E38F100FEA3D0 /* ScrollViewDelegateProxy.swift */, + 4D2B82B91E73E835008DD411 /* Unavailable.swift */, ); path = Sources; sourceTree = ""; @@ -119,6 +128,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 4D22AC5C1E6E36B20006EDA0 /* DelegateProxy.h in Headers */, 4D870AB21DDAA0CD00575A28 /* PullToDismiss.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -176,6 +186,7 @@ 4D870AA01DDAA0CD00575A28 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = KW3G83N6K6; + LastSwiftMigration = 0820; ProvisioningStyle = Automatic; }; 4D870AA91DDAA0CD00575A28 = { @@ -242,12 +253,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4D2B82BA1E73E835008DD411 /* Unavailable.swift in Sources */, 4DBDB74F1E1F69770068AA1F /* EdgeShadow.swift in Sources */, 4D5A35191DDB21DB00ED9D54 /* PullToDismiss.swift in Sources */, - 4D5A351F1DDBF18B00ED9D54 /* PullToDismiss+Proxy.swift in Sources */, 4DBDB7511E1F6FFA0068AA1F /* UIView+EdgeShadow.swift in Sources */, 4D2F15C31DF2D570000A5172 /* BackgroundEffect.swift in Sources */, + 4D22AC5D1E6E36B20006EDA0 /* DelegateProxy.m in Sources */, 4D9D97C51DF058C700C10C87 /* CustomBlurView.swift in Sources */, + 4D63E6A01E6E38F100FEA3D0 /* ScrollViewDelegateProxy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -374,6 +387,7 @@ 4D870AB61DDAA0CD00575A28 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = KW3G83N6K6; @@ -386,6 +400,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "-.PullToDismiss"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0.1; }; name = Debug; @@ -393,6 +409,7 @@ 4D870AB71DDAA0CD00575A28 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = KW3G83N6K6; @@ -405,6 +422,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "-.PullToDismiss"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 3.0.1; }; name = Release; diff --git a/PullToDismiss/PullToDismiss.h b/PullToDismiss/PullToDismiss.h index 873734a..ae0ef1b 100644 --- a/PullToDismiss/PullToDismiss.h +++ b/PullToDismiss/PullToDismiss.h @@ -15,5 +15,6 @@ FOUNDATION_EXPORT double PullToDismissVersionNumber; FOUNDATION_EXPORT const unsigned char PullToDismissVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import +#import "DelegateProxy.h" diff --git a/README.md b/README.md index 53c650b..40e8b24 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # PullToDismiss -PullToDismiss provides dismiss modal viewcontroller function by pulling scrollview or navigationbar with smooth and rich background effect. +PullToDismiss provides dismiss modal viewcontroller function like Facebook Messenger by pulling scrollview or navigationbar with smooth and rich background effect. [![GitHub release](https://img.shields.io/github/release/sgr-ksmt/PullToDismiss.svg)](https://github.com/sgr-ksmt/PullToDismiss/releases) ![Language](https://img.shields.io/badge/language-Swift%203-orange.svg) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods](https://img.shields.io/badge/Cocoa%20Pods-✓-4BC51D.svg?style=flat)](https://cocoapods.org/pods/PullToDismiss) -[![CocoaPodsDL](https://img.shields.io/cocoapods/dt/PullToDismiss.svg)]() +[![CocoaPodsDL](https://img.shields.io/cocoapods/dt/PullToDismiss.svg)](https://cocoapods.org/pods/PullToDismiss) [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/matteocrippa/awesome-swift#ui) @@ -19,13 +19,15 @@ PullToDismiss provides dismiss modal viewcontroller function by pulling scrollvi - [Appetize.io Demo](https://appetize.io/app/hett44vca458r9artkbq0awxrc?device=iphone7&scale=75&orientation=portrait&osVersion=10.0) ## Feature -- Easy to use! -- Support all scroll views. (UIScrollView, UITableView, UICollectionView) +- Support all scroll views. (UIScrollView, UITableView, UICollectionView, UIWebView, WKWebView) - Customizable. (dismiss background color, alpha, height percentage of dismiss) - Available in UIViewController, UINavigationController. - Automatically add pan gesture to navigation bar. - Blur effect support. +### Migration guide +If you update from 1.x to 2.0, see [migration guide](Documents/PullToDismiss2MigrationGuide.md) if needed. + ## Usage ### Getting Started (1) Setup `PullToDismiss` @@ -58,7 +60,7 @@ self.present(nav, animated: true, completion: nil) ### Use `(UIScrollView|UITableView|UICollectionView)Delegate` -If you want to use delegate, set `delegateProxy`. +You can use all scroll view's delegate by set `pullToDismiss.delegate`. ```swift import PullToDismiss @@ -69,58 +71,21 @@ class SampleViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() pullToDismiss = PullToDismiss(scrollView: tableView) - pullToDismiss.delegateProxy = self + pullToDismiss?.delegate = self } } extension SampleViewController: UITableViewDelegate { - // called by PullToDismiss func scrollViewDidScroll(_ scrollView: UIScrollView) { // ... } - // called by PullToDismiss func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // ... } } ``` -#### Advanced -PullToDismiss defines major `UIScrollView|UITableView|UICollectionViewDelegate` methods. -But some delegate method aren't defined. -If you want to use other methods, override PullToDismiss and define delegate method you want to. - -```swift -import PullToDismiss - -class CustomPullToDismiss: PullToDismiss { - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return tableViewDelegate?.tableView?(tableView, heightForRowAt: indexPath) ?? 44.0 - } -} - -class SampleViewController: UIViewController { - @IBOutlet private weak var tableView: UITableView! - private var pullToDismiss: PullToDismiss? - override func viewDidLoad() { - super.viewDidLoad() - pullToDismiss = CustomPullToDismiss(scrollView: tableView) - pullToDismiss.delegateProxy = self - } -} - -extension SampleViewController: UITableViewDelegate { - // called by CustomPullToDismiss - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return indexPath.section == 0 ? 44.0 : 60.0 - } - - // ... -} - -``` - ### Customize You can customize backgroundEffect, dismissableHeightPercentage: @@ -170,7 +135,7 @@ pullToDismiss?.dismissableHeightPercentage = 0.5 - Add the following to your *Cartfile*: ```bash -github "sgr-ksmt/PullToDismiss" ~> 1.0 +github "sgr-ksmt/PullToDismiss" ~> 2.0 ``` - Run `carthage update` @@ -184,7 +149,7 @@ github "sgr-ksmt/PullToDismiss" ~> 1.0 it, simply add the following line to your Podfile: ```ruby -pod 'PullToDismiss' ~> 1.0 +pod 'PullToDismiss', '~> 2.0' ``` and run `pod install` diff --git a/Sources/DelegateProxy.h b/Sources/DelegateProxy.h new file mode 100644 index 0000000..a509d5d --- /dev/null +++ b/Sources/DelegateProxy.h @@ -0,0 +1,13 @@ +// +// DelegateProxy.h +// PullToDismiss +// +// Created by Suguru Kishimoto on 3/7/17. +// Copyright © 2017 Suguru Kishimoto. All rights reserved. +// + +#import + +@interface DelegateProxy : NSObject +- (nonnull instancetype)initWithDelegates:(NSArray * __nonnull)delegates NS_REFINED_FOR_SWIFT; +@end diff --git a/Sources/DelegateProxy.m b/Sources/DelegateProxy.m new file mode 100644 index 0000000..dcedcc3 --- /dev/null +++ b/Sources/DelegateProxy.m @@ -0,0 +1,59 @@ +// +// DelegateProxy.m +// PullToDismiss +// +// Created by Suguru Kishimoto on 3/7/17. +// Copyright © 2017 Suguru Kishimoto. All rights reserved. +// + +#import "DelegateProxy.h" + +@interface DelegateProxy () +@property (nonnull, nonatomic, strong) NSHashTable *delegates; +@end + +@implementation DelegateProxy + +- (instancetype)initWithDelegates:(NSArray *)delegates { + self = [super init]; + if (self != nil) { + self.delegates = [NSHashTable weakObjectsHashTable]; + for (id delegate in delegates) { + if (![delegate isKindOfClass:[NSObject class]]) { + continue; + } + if ([delegate isKindOfClass:[NSNull class]]) { + continue; + } + [self.delegates addObject:delegate]; + } + } + return self; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + for (NSObject *delegate in self.delegates) { + if ([delegate respondsToSelector:aSelector]) { + return [delegate methodSignatureForSelector:aSelector]; + } + } + return nil; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation { + for (NSObject *delegate in self.delegates) { + if ([delegate respondsToSelector:anInvocation.selector]) { + [anInvocation invokeWithTarget:delegate]; + } + } +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + for (NSObject *delegate in self.delegates) { + if ([delegate respondsToSelector:aSelector]) { + return YES; + } + } + return NO; +} +@end diff --git a/Sources/PullToDismiss+Proxy.swift b/Sources/PullToDismiss+Proxy.swift deleted file mode 100644 index 235c086..0000000 --- a/Sources/PullToDismiss+Proxy.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// PullToDismiss+Proxy.swift -// PullToDismiss -// -// Created by Suguru Kishimoto on 11/16/16. -// Copyright © 2016 Suguru Kishimoto. All rights reserved. -// - -import Foundation - -extension PullToDismiss: UITableViewDelegate { - public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - tableViewDelegate?.tableView?(tableView, willDisplay: cell, forRowAt: indexPath) - } - - public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { - tableViewDelegate?.tableView?(tableView, willDisplayHeaderView: view, forSection: section) - } - - public func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { - tableViewDelegate?.tableView?(tableView, willDisplayFooterView: view, forSection: section) - } - - public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { - tableViewDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath) - } - - public func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) { - tableViewDelegate?.tableView?(tableView, didEndDisplayingHeaderView: view, forSection: section) - } - - public func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) { - tableViewDelegate?.tableView?(tableView, didEndDisplayingFooterView: view, forSection: section) - } - - public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { - return tableViewDelegate?.tableView?(tableView, shouldHighlightRowAt: indexPath) ?? true - } - - public func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) { - tableViewDelegate?.tableView?(tableView, didHighlightRowAt: indexPath) - } - - public func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) { - tableViewDelegate?.tableView?(tableView, didUnhighlightRowAt: indexPath) - } - - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableViewDelegate?.tableView?(tableView, didSelectRowAt: indexPath) - } - - public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { - tableViewDelegate?.tableView?(tableView, didDeselectRowAt: indexPath) - } -} - -extension PullToDismiss: UICollectionViewDelegate { - public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { - return collectionViewDelegate?.collectionView?(collectionView, shouldHighlightItemAt: indexPath) ?? true - } - - public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, didHighlightItemAt: indexPath) - } - - public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, didUnhighlightItemAt: indexPath) - } - - public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { - return collectionViewDelegate?.collectionView?(collectionView, shouldSelectItemAt: indexPath) ?? true - } - - public func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool { - return collectionViewDelegate?.collectionView?(collectionView, shouldDeselectItemAt: indexPath) ?? true - } - - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, didSelectItemAt: indexPath) - } - - public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, didDeselectItemAt: indexPath) - } - - - public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, willDisplay: cell, forItemAt: indexPath) - } - - public func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, willDisplaySupplementaryView: view, forElementKind: elementKind, at: indexPath) - } - - public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath) - } - - public func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) { - collectionViewDelegate?.collectionView?(collectionView, didEndDisplayingSupplementaryView: view, forElementOfKind: elementKind, at: indexPath) - } -} diff --git a/Sources/PullToDismiss.swift b/Sources/PullToDismiss.swift index 5640f89..3c5b9ed 100644 --- a/Sources/PullToDismiss.swift +++ b/Sources/PullToDismiss.swift @@ -10,17 +10,25 @@ import Foundation import UIKit open class PullToDismiss: NSObject { - + public struct Defaults { private init() {} public static let dismissableHeightPercentage: CGFloat = 0.33 } - + open var backgroundEffect: BackgroundEffect? = ShadowEffect.default open var edgeShadow: EdgeShadow? = EdgeShadow.default - + public var dismissAction: (() -> Void)? - public weak var delegateProxy: AnyObject? + public weak var delegate: UIScrollViewDelegate? { + didSet { + var delegates: [UIScrollViewDelegate] = [self] + if let delegate = delegate { + delegates.append(delegate) + } + proxy = ScrollViewDelegateProxy(delegates: delegates) + } + } public var dismissableHeightPercentage: CGFloat = Defaults.dismissableHeightPercentage { didSet { dismissableHeightPercentage = min(max(0.0, dismissableHeightPercentage), 1.0) @@ -31,8 +39,15 @@ open class PullToDismiss: NSObject { fileprivate var dragging: Bool = false fileprivate var previousContentOffsetY: CGFloat = 0.0 fileprivate weak var viewController: UIViewController? - + private var __scrollView: UIScrollView? + + private var proxy: ScrollViewDelegateProxy? { + didSet { + __scrollView?.delegate = proxy + } + } + private var panGesture: UIPanGestureRecognizer? private var backgroundView: UIView? private var navigationBarHeight: CGFloat = 0.0 @@ -44,11 +59,11 @@ open class PullToDismiss: NSObject { } self.init(scrollView: scrollView, viewController: viewController) } - + public init(scrollView: UIScrollView, viewController: UIViewController, navigationBar: UIView? = nil) { super.init() - scrollView.delegate = self __scrollView = scrollView + proxy = ScrollViewDelegateProxy(delegates: [self]) self.viewController = viewController if let navigationBar = navigationBar ?? viewController.navigationController?.navigationBar { let gesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) @@ -62,25 +77,26 @@ open class PullToDismiss: NSObject { if let panGesture = panGesture { panGesture.view?.removeGestureRecognizer(panGesture) } - + + proxy = nil __scrollView?.delegate = nil __scrollView = nil } - + fileprivate var targetViewController: UIViewController? { return viewController?.navigationController ?? viewController } - + private var haveShadowEffect: Bool { return backgroundEffect != nil || edgeShadow != nil } - + fileprivate func dismiss() { targetViewController?.dismiss(animated: true, completion: nil) } - + // MARK: - shadow view - + private func makeBackgroundView() { deleteBackgroundView() guard let backgroundEffect = backgroundEffect else { @@ -88,7 +104,7 @@ open class PullToDismiss: NSObject { } let backgroundView = backgroundEffect.makeBackgroundView() backgroundView.frame = targetViewController?.view.bounds ?? .zero - + switch backgroundEffect.target { case .targetViewController: targetViewController?.view.addSubview(backgroundView) @@ -101,28 +117,28 @@ open class PullToDismiss: NSObject { self.backgroundView = backgroundView } - + private func updateBackgroundView(rate: CGFloat) { guard let backgroundEffect = backgroundEffect else { return } - + backgroundEffect.applyEffect(view: backgroundView, rate: rate) } - + private func deleteBackgroundView() { backgroundView?.removeFromSuperview() backgroundView = nil targetViewController?.view.clipsToBounds = true } - + private func resetBackgroundView() { guard let backgroundEffect = backgroundEffect else { return } backgroundEffect.applyEffect(view: backgroundView, rate: 1.0) } - + @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { switch gesture.state { case .began: @@ -132,12 +148,12 @@ open class PullToDismiss: NSObject { updateViewPosition(offset: diff) gesture.setTranslation(.zero, in: gesture.view) case .ended: - finishDragging() + finishDragging(withVelocity: .zero) default: break } } - + fileprivate func startDragging() { targetViewController?.view.layer.removeAllAnimations() backgroundView?.layer.removeAllAnimations() @@ -148,7 +164,7 @@ open class PullToDismiss: NSObject { targetViewController?.view.clipsToBounds = false } } - + fileprivate func updateViewPosition(offset: CGFloat) { var addOffset: CGFloat = offset // avoid statusbar gone @@ -160,7 +176,7 @@ open class PullToDismiss: NSObject { if case .some(.targetViewController) = backgroundEffect?.target { backgroundView?.frame.origin.y = -(targetViewController?.view.frame.origin.y ?? 0.0) } - + let targetViewOriginY: CGFloat = targetViewController?.view.frame.origin.y ?? 0.0 let targetViewHeight: CGFloat = targetViewController?.view.frame.height ?? 0.0 let rate: CGFloat = (1.0 - (targetViewOriginY / (targetViewHeight * dismissableHeightPercentage))) @@ -168,13 +184,14 @@ open class PullToDismiss: NSObject { updateBackgroundView(rate: rate) targetViewController?.view.updateEdgeShadow(edgeShadow, rate: rate) } - - fileprivate func finishDragging() { + + fileprivate func finishDragging(withVelocity velocity: CGPoint) { let originY = targetViewController?.view.frame.origin.y ?? 0.0 let dismissableHeight = (targetViewController?.view.frame.height ?? 0.0) * dismissableHeightPercentage - if originY > dismissableHeight { + if originY > dismissableHeight || originY > 0 && velocity.y < 0 { deleteBackgroundView() targetViewController?.view.detachEdgeShadow() + proxy = nil _ = dismissAction?() ?? dismiss() } else if originY != 0.0 { UIView.perform(.delete, on: [], options: [.allowUserInteraction], animations: { [weak self] in @@ -192,7 +209,7 @@ open class PullToDismiss: NSObject { } viewPositionY = 0.0 } - + private static func viewControllerFromScrollView(_ scrollView: UIScrollView) -> UIViewController? { var responder: UIResponder? = scrollView while let r = responder { @@ -203,24 +220,15 @@ open class PullToDismiss: NSObject { } return nil } - - // MARK: - delegates - - public weak var scrollViewDelegate: UIScrollViewDelegate? { - return delegateProxy as? UIScrollViewDelegate - } - - public weak var tableViewDelegate: UITableViewDelegate? { - return delegateProxy as? UITableViewDelegate - } - - public weak var collectionViewDelegate: UICollectionViewDelegate? { - return delegateProxy as? UICollectionViewDelegate - } - - public weak var collectionViewDelegateFlowLayout: UICollectionViewDelegateFlowLayout? { - return delegateProxy as? UICollectionViewDelegateFlowLayout - } +} + +extension PullToDismiss: UITableViewDelegate { +} + +extension PullToDismiss: UICollectionViewDelegate { +} + +extension PullToDismiss: UICollectionViewDelegateFlowLayout { } extension PullToDismiss: UIScrollViewDelegate { @@ -233,60 +241,17 @@ extension PullToDismiss: UIScrollViewDelegate { } previousContentOffsetY = scrollView.contentOffset.y } - scrollViewDelegate?.scrollViewDidScroll?(scrollView) - } - - public func scrollViewDidZoom(_ scrollView: UIScrollView) { - scrollViewDelegate?.scrollViewDidZoom?(scrollView) } - + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { startDragging() dragging = true previousContentOffsetY = scrollView.contentOffset.y - scrollViewDelegate?.scrollViewWillBeginDragging?(scrollView) } - + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - scrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset) - } - - public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - finishDragging() + finishDragging(withVelocity: velocity) dragging = false previousContentOffsetY = 0.0 - scrollViewDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) - } - - public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { - scrollViewDelegate?.scrollViewWillBeginDecelerating?(scrollView) - } - - public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - scrollViewDelegate?.scrollViewDidEndDecelerating?(scrollView) - } - - public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { - scrollViewDelegate?.scrollViewDidEndScrollingAnimation?(scrollView) - } - - public func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return scrollViewDelegate?.viewForZooming?(in: scrollView) - } - - public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { - scrollViewDelegate?.scrollViewWillBeginZooming?(scrollView, with: view) - } - - public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { - scrollViewDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale) - } - - public func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { - return scrollViewDelegate?.scrollViewShouldScrollToTop?(scrollView) ?? true - } - - public func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { - scrollViewDelegate?.scrollViewDidScrollToTop?(scrollView) } } diff --git a/Sources/ScrollViewDelegateProxy.swift b/Sources/ScrollViewDelegateProxy.swift new file mode 100644 index 0000000..2cc4b37 --- /dev/null +++ b/Sources/ScrollViewDelegateProxy.swift @@ -0,0 +1,16 @@ +// +// ScrollViewDelegateProxy.swift +// PullToDismiss +// +// Created by Suguru Kishimoto on 3/7/17. +// Copyright © 2017 Suguru Kishimoto. All rights reserved. +// + +import Foundation +import UIKit + +class ScrollViewDelegateProxy: DelegateProxy, UIScrollViewDelegate { + @nonobjc convenience init(delegates: [UIScrollViewDelegate]) { + self.init(__delegates: delegates) + } +} diff --git a/Sources/Unavailable.swift b/Sources/Unavailable.swift new file mode 100644 index 0000000..e80abb4 --- /dev/null +++ b/Sources/Unavailable.swift @@ -0,0 +1,36 @@ +// +// Unavailable.swift +// PullToDismiss +// +// Created by Suguru Kishimoto on 3/11/17. +// Copyright © 2017 Suguru Kishimoto. All rights reserved. +// + +import Foundation + +extension PullToDismiss { + @available(*, unavailable, renamed: "delegate") + public weak var delegateProxy: AnyObject? { + fatalError("\(#function) is no longer available") + } + + @available(*, unavailable, message: "unavailable") + public weak var scrollViewDelegate: UIScrollViewDelegate? { + fatalError("\(#function) is no longer available") + } + + @available(*, unavailable, message: "unavailable") + public weak var tableViewDelegate: UITableViewDelegate? { + fatalError("\(#function) is no longer available") + } + + @available(*, unavailable, message: "unavailable") + public weak var collectionViewDelegate: UICollectionViewDelegate? { + fatalError("\(#function) is no longer available") + } + + @available(*, unavailable, message: "unavailable") + public weak var collectionViewDelegateFlowLayout: UICollectionViewDelegateFlowLayout? { + fatalError("\(#function) is no longer available") + } +}