diff --git a/Assets/animations.gif b/Assets/animations.gif new file mode 100644 index 0000000..9bdb122 Binary files /dev/null and b/Assets/animations.gif differ diff --git a/Assets/rubberBand.gif b/Assets/rubberBand.gif new file mode 100644 index 0000000..b23253b Binary files /dev/null and b/Assets/rubberBand.gif differ diff --git a/Assets/scroll.gif b/Assets/scroll.gif new file mode 100644 index 0000000..cacfd11 Binary files /dev/null and b/Assets/scroll.gif differ diff --git a/Assets/scrollToTranslation.gif b/Assets/scrollToTranslation.gif new file mode 100644 index 0000000..ccc8cc5 Binary files /dev/null and b/Assets/scrollToTranslation.gif differ diff --git a/CHANGELOG.md b/CHANGELOG.md index de43720..dfce739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ -## 1.0.0 (December 2018) +## 1.0.0 (5 December 2018) First release diff --git a/Example/OverlayContainer.xcodeproj/project.pbxproj b/Example/OverlayContainer.xcodeproj/project.pbxproj index 6aeb042..64167a5 100644 --- a/Example/OverlayContainer.xcodeproj/project.pbxproj +++ b/Example/OverlayContainer.xcodeproj/project.pbxproj @@ -16,8 +16,8 @@ E71AC95421B04CBE00445A23 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95321B04CBE00445A23 /* UIView+Constraints.swift */; }; E71AC95621B04CFE00445A23 /* ShortcutsLikeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95521B04CFE00445A23 /* ShortcutsLikeViewController.swift */; }; E71AC95821B04D2E00445A23 /* StackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95721B04D2E00445A23 /* StackViewController.swift */; }; - E71AC95A21B04E3F00445A23 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95921B04E3F00445A23 /* DetailViewController.swift */; }; - E71AC95C21B04E4B00445A23 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95B21B04E4B00445A23 /* MasterViewController.swift */; }; + E71AC95A21B04E3F00445A23 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95921B04E3F00445A23 /* SearchViewController.swift */; }; + E71AC95C21B04E4B00445A23 /* MapsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95B21B04E4B00445A23 /* MapsViewController.swift */; }; E71AC95E21B04EC400445A23 /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = E71AC95D21B04EC400445A23 /* UIViewController+Children.swift */; }; E733097821B12DF00028009E /* MapsLikeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E733097721B12DF00028009E /* MapsLikeViewController.swift */; }; E733097A21B12E120028009E /* MapsLikeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E733097921B12E120028009E /* MapsLikeViewController.xib */; }; @@ -57,8 +57,8 @@ E71AC95321B04CBE00445A23 /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; E71AC95521B04CFE00445A23 /* ShortcutsLikeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsLikeViewController.swift; sourceTree = ""; }; E71AC95721B04D2E00445A23 /* StackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackViewController.swift; sourceTree = ""; }; - E71AC95921B04E3F00445A23 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; - E71AC95B21B04E4B00445A23 /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; + E71AC95921B04E3F00445A23 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + E71AC95B21B04E4B00445A23 /* MapsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapsViewController.swift; sourceTree = ""; }; E71AC95D21B04EC400445A23 /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = ""; }; E733097721B12DF00028009E /* MapsLikeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapsLikeViewController.swift; sourceTree = ""; }; E733097921B12E120028009E /* MapsLikeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapsLikeViewController.xib; sourceTree = ""; }; @@ -133,8 +133,8 @@ children = ( E71AC95221B04CA600445A23 /* Extension */, 607FACD51AFB9204008FA782 /* AppDelegate.swift */, - E71AC95B21B04E4B00445A23 /* MasterViewController.swift */, - E71AC95921B04E3F00445A23 /* DetailViewController.swift */, + E71AC95B21B04E4B00445A23 /* MapsViewController.swift */, + E71AC95921B04E3F00445A23 /* SearchViewController.swift */, E7E5C0AC21B168850052AD78 /* DetailHeaderView.swift */, E7E5C0AE21B1692A0052AD78 /* DetailHeaderView.xib */, E74AD21F21B5948B00BE644D /* Backdrop View */, @@ -415,7 +415,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E71AC95C21B04E4B00445A23 /* MasterViewController.swift in Sources */, + E71AC95C21B04E4B00445A23 /* MapsViewController.swift in Sources */, E71AC95621B04CFE00445A23 /* ShortcutsLikeViewController.swift in Sources */, E71AC95E21B04EC400445A23 /* UIViewController+Children.swift in Sources */, E7E5C0AD21B168850052AD78 /* DetailHeaderView.swift in Sources */, @@ -424,7 +424,7 @@ 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, E71AC95421B04CBE00445A23 /* UIView+Constraints.swift in Sources */, E74AD22121B594A400BE644D /* BackdropExampleViewController.swift in Sources */, - E71AC95A21B04E3F00445A23 /* DetailViewController.swift in Sources */, + E71AC95A21B04E3F00445A23 /* SearchViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/OverlayContainer/BackdropExampleViewController.swift b/Example/OverlayContainer/BackdropExampleViewController.swift index 2ebff69..ef6a42d 100644 --- a/Example/OverlayContainer/BackdropExampleViewController.swift +++ b/Example/OverlayContainer/BackdropExampleViewController.swift @@ -23,17 +23,17 @@ class BackdropExampleViewController: UIViewController { } private let backdropViewController = BackdropViewController() - private let detailViewController = DetailViewController() - private let masterViewController = MasterViewController() + private let searchViewController = SearchViewController() + private let mapsViewController = MapsViewController() override func viewDidLoad() { super.viewDidLoad() let overlayController = OverlayContainerViewController() overlayController.delegate = self - overlayController.viewControllers = [detailViewController] + overlayController.viewControllers = [searchViewController] let stackController = StackViewController() stackController.viewControllers = [ - masterViewController, + mapsViewController, backdropViewController, overlayController ] @@ -84,14 +84,14 @@ extension BackdropExampleViewController: OverlayContainerViewControllerDelegate func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, scrollViewDrivingOverlay overlayViewController: UIViewController) -> UIScrollView? { - return (overlayViewController as? DetailViewController)?.tableView + return (overlayViewController as? SearchViewController)?.tableView } func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, shouldStartDraggingOverlay overlayViewController: UIViewController, at point: CGPoint, in coordinateSpace: UICoordinateSpace) -> Bool { - guard let header = (overlayViewController as? DetailViewController)?.header else { + guard let header = (overlayViewController as? SearchViewController)?.header else { return false } return header.bounds.contains(coordinateSpace.convert(point, to: header)) diff --git a/Example/OverlayContainer/MapsLikeViewController.swift b/Example/OverlayContainer/MapsLikeViewController.swift index 570dab2..ff434d5 100644 --- a/Example/OverlayContainer/MapsLikeViewController.swift +++ b/Example/OverlayContainer/MapsLikeViewController.swift @@ -29,9 +29,9 @@ class MapsLikeViewController: UIViewController { super.viewDidLoad() let overlayController = OverlayContainerViewController() overlayController.delegate = self - overlayController.viewControllers = [DetailViewController()] + overlayController.viewControllers = [SearchViewController()] addChild(overlayController, in: overlayContainerView) - addChild(MasterViewController(), in: backgroundView) + addChild(MapsViewController(), in: backgroundView) } override func viewWillLayoutSubviews() { @@ -88,14 +88,14 @@ extension MapsLikeViewController: OverlayContainerViewControllerDelegate { func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, scrollViewDrivingOverlay overlayViewController: UIViewController) -> UIScrollView? { - return (overlayViewController as? DetailViewController)?.tableView + return (overlayViewController as? SearchViewController)?.tableView } func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, shouldStartDraggingOverlay overlayViewController: UIViewController, at point: CGPoint, in coordinateSpace: UICoordinateSpace) -> Bool { - guard let header = (overlayViewController as? DetailViewController)?.header else { + guard let header = (overlayViewController as? SearchViewController)?.header else { return false } let convertedPoint = coordinateSpace.convert(point, to: header) diff --git a/Example/OverlayContainer/MasterViewController.swift b/Example/OverlayContainer/MapsViewController.swift similarity index 80% rename from Example/OverlayContainer/MasterViewController.swift rename to Example/OverlayContainer/MapsViewController.swift index 14b6422..d918d03 100644 --- a/Example/OverlayContainer/MasterViewController.swift +++ b/Example/OverlayContainer/MapsViewController.swift @@ -1,5 +1,5 @@ // -// MasterViewController.swift +// MapsViewController.swift // OverlayContainer_Example // // Created by Gaétan Zanella on 29/11/2018. @@ -9,7 +9,7 @@ import MapKit import UIKit -class MasterViewController: UIViewController { +class MapsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let map = MKMapView() diff --git a/Example/OverlayContainer/DetailViewController.swift b/Example/OverlayContainer/SearchViewController.swift similarity index 85% rename from Example/OverlayContainer/DetailViewController.swift rename to Example/OverlayContainer/SearchViewController.swift index 7c9d047..ddb3068 100644 --- a/Example/OverlayContainer/DetailViewController.swift +++ b/Example/OverlayContainer/SearchViewController.swift @@ -1,5 +1,5 @@ // -// DetailViewController.swift +// SearchViewController.swift // OverlayContainer_Example // // Created by Gaétan Zanella on 29/11/2018. @@ -8,9 +8,9 @@ import UIKit -class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { +class SearchViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - let header = Bundle.main.loadNibNamed("DetailHeaderView", owner: nil, options: nil)![0] as! UIView + let header = Bundle.main.loadNibNamed("DetailHeaderView", owner: self, options: nil)![0] as! UIView let tableView = UITableView() // MARK: - UIViewController @@ -18,14 +18,15 @@ class DetailViewController: UIViewController, UITableViewDataSource, UITableView override func loadView() { view = UIView() setUpView() - title = "Detail" + title = "Search" } // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .default, reuseIdentifier: "cell") - cell.textLabel?.text = "\(indexPath.row)" + cell.textLabel?.text = "Row \(indexPath.row)" + cell.textLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) return cell } diff --git a/Example/OverlayContainer/ShortcutsLikeViewController.swift b/Example/OverlayContainer/ShortcutsLikeViewController.swift index 125d769..1fd7820 100644 --- a/Example/OverlayContainer/ShortcutsLikeViewController.swift +++ b/Example/OverlayContainer/ShortcutsLikeViewController.swift @@ -15,8 +15,8 @@ class ShortcutsLikeViewController: UIViewController { case minimum, medium, maximum } - private let detailViewController = DetailViewController() - private let masterViewController = MasterViewController() + private let searchViewController = SearchViewController() + private let mapsViewController = MapsViewController() private var sizeClass: UIUserInterfaceSizeClass = .unspecified private var needsSetup = true @@ -53,10 +53,10 @@ class ShortcutsLikeViewController: UIViewController { case .compact: let overlayController = OverlayContainerViewController() overlayController.delegate = self - overlayController.viewControllers = [detailViewController] + overlayController.viewControllers = [searchViewController] let stackController = StackViewController() stackController.viewControllers = [ - masterViewController, + mapsViewController, overlayController ] addChild(stackController, in: view) @@ -64,11 +64,11 @@ class ShortcutsLikeViewController: UIViewController { let splitController = UISplitViewController() // (gz) 2018-12-03 Both `OverlayContainerViewController` & `StackViewController` disable autorizing mask. // whereas `UINavigationController` & `UISplitViewController` need it. - detailViewController.view.translatesAutoresizingMaskIntoConstraints = true - masterViewController.view.translatesAutoresizingMaskIntoConstraints = true + searchViewController.view.translatesAutoresizingMaskIntoConstraints = true + mapsViewController.view.translatesAutoresizingMaskIntoConstraints = true splitController.viewControllers = [ - UINavigationController(rootViewController: detailViewController), - masterViewController + UINavigationController(rootViewController: searchViewController), + mapsViewController ] splitController.preferredDisplayMode = .allVisible addChild(splitController, in: view) @@ -101,14 +101,14 @@ extension ShortcutsLikeViewController: OverlayContainerViewControllerDelegate { func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, scrollViewDrivingOverlay overlayViewController: UIViewController) -> UIScrollView? { - return (overlayViewController as? DetailViewController)?.tableView + return (overlayViewController as? SearchViewController)?.tableView } func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, shouldStartDraggingOverlay overlayViewController: UIViewController, at point: CGPoint, in coordinateSpace: UICoordinateSpace) -> Bool { - guard let header = (overlayViewController as? DetailViewController)?.header else { + guard let header = (overlayViewController as? SearchViewController)?.header else { return false } return header.bounds.contains(coordinateSpace.convert(point, to: header)) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index c89c851..2d8a7cf 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - OverlayContainer (0.0.1) + - OverlayContainer (1.0.0) DEPENDENCIES: - OverlayContainer (from `../`) @@ -9,7 +9,7 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - OverlayContainer: 526c12c01273c1fdbbc292a647dabb8830cd35ac + OverlayContainer: 10314f21257c1f4630e025838e3ff0214413f8b2 PODFILE CHECKSUM: efc38d9f3da2bf7f56757ffe850db9ee5c85cbc7 diff --git a/OverlayContainer.podspec b/OverlayContainer.podspec index dbd8bba..61383c2 100644 --- a/OverlayContainer.podspec +++ b/OverlayContainer.podspec @@ -8,22 +8,16 @@ Pod::Spec.new do |s| s.name = 'OverlayContainer' - s.version = '0.0.1' + s.version = '1.0.0' s.summary = 'OverlayContainer is a UI library which makes it easier to develop overlay based interfaces.' s.swift_version = "4.2" -# This description is used to generate tags and improve search results. -# * Think: What does it do? Why did you write it? What is the focus? -# * Try to keep it short, snappy and to the point. -# * Write the description between the DESC delimiters below. -# * Finally, don't worry about the indent, CocoaPods strips it! - s.description = <<-DESC OverlayContainer is a UI library written in Swift. It makes it easier to develop overlay based interfaces, such as the one presented in the Apple Maps, Stocks or Shortcuts apps. The main component of the library is the `OverlayContainerViewController`. It defines an area where a view controller can be dragged up and down, hidding or revealing the content underneath it. DESC - s.homepage = 'https://github.com/gaetanzanella/OverlayContainer' + s.homepage = 'https://github.com/applidium/ADOverlayContainer' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'gaetanzanella' => 'gaetan.zanella@fabernovel.com' } s.source = { :git => 'https://github.com/applidium/ADOverlayContainer.git', :tag => s.version.to_s } diff --git a/OverlayContainer/Classes/Internal/ScrollViewOverlayTranslationDriver.swift b/OverlayContainer/Classes/Internal/ScrollViewOverlayTranslationDriver.swift index 981b1a7..1e6e243 100644 --- a/OverlayContainer/Classes/Internal/ScrollViewOverlayTranslationDriver.swift +++ b/OverlayContainer/Classes/Internal/ScrollViewOverlayTranslationDriver.swift @@ -26,6 +26,7 @@ class ScrollViewOverlayTranslationDriver: OverlayTranslationDriver, OverlayScrol self.translationController = translationController self.scrollView = scrollView scrollViewDelegateProxy.forward(to: self, delegateInvocationsFrom: scrollView) + lastContentOffsetWhileScrolling = scrollView.contentOffset } // MARK: - OverlayScrollViewDelegate diff --git a/OverlayContainer/Classes/Internal/Views/OverlayContainerView.swift b/OverlayContainer/Classes/Internal/Views/OverlayContainerView.swift index 5dbdf2a..25514a3 100644 --- a/OverlayContainer/Classes/Internal/Views/OverlayContainerView.swift +++ b/OverlayContainer/Classes/Internal/Views/OverlayContainerView.swift @@ -7,4 +7,4 @@ import Foundation -class OverlayContainerView: UIView {} +class OverlayContainerView: PassThroughView {} diff --git a/OverlayContainer/Classes/Internal/Views/OverlayTranslationView.swift b/OverlayContainer/Classes/Internal/Views/OverlayTranslationView.swift index 45d3704..6d5bc7f 100644 --- a/OverlayContainer/Classes/Internal/Views/OverlayTranslationView.swift +++ b/OverlayContainer/Classes/Internal/Views/OverlayTranslationView.swift @@ -7,4 +7,4 @@ import Foundation -class OverlayTranslationView: UIView {} +class OverlayTranslationView: PassThroughView {} diff --git a/OverlayContainer/Classes/OverlayContainerViewController.swift b/OverlayContainer/Classes/OverlayContainerViewController.swift index 4ef6d1a..5bb7931 100644 --- a/OverlayContainer/Classes/OverlayContainerViewController.swift +++ b/OverlayContainer/Classes/OverlayContainerViewController.swift @@ -47,8 +47,17 @@ public class OverlayContainerViewController: UIViewController { return viewControllers.last } + /// The scroll view managing the overlay translation. + public weak var drivingScrollView: UIScrollView? { + didSet { + guard drivingScrollView !== oldValue else { return } + guard isViewLoaded else { return } + loadTranslationDrivers() + } + } + /// The overlay container's style. - public private(set) var style: OverlayStyle + public let style: OverlayStyle private lazy var overlayPanGesture: OverlayTranslationGestureRecognizer = self.makePanGesture() private lazy var overlayContainerView = OverlayContainerView() @@ -164,36 +173,44 @@ public class OverlayContainerViewController: UIViewController { private func loadOverlayViews() { viewControllers.forEach { addChild($0, in: overlayContainerView) } + loadTranslationController() loadTranslationDrivers() } - private func loadTranslationDrivers() { + private func loadTranslationController() { guard let translationHeightConstraint = translationHeightConstraint, let overlayController = topViewController else { - return + return } - let controller = HeightContrainstOverlayTranslationController( + translationController = HeightContrainstOverlayTranslationController( translationHeightConstraint: translationHeightConstraint, overlayViewController: overlayController, configuration: configuration ) - controller.delegate = self + translationController?.delegate = self + } + + private func loadTranslationDrivers() { + guard let translationController = translationController, + let overlayController = topViewController else { + return + } var drivers: [OverlayTranslationDriver] = [] let panGestureDriver = PanGestureOverlayTranslationDriver( - translationController: controller, + translationController: translationController, panGestureRecognizer: overlayPanGesture ) drivers.append(panGestureDriver) - if let scrollView = configuration.scrollView(drivingOverlay: overlayController) { + let scrollView = drivingScrollView ?? configuration.scrollView(drivingOverlay: overlayController) + if let scrollView = scrollView { overlayPanGesture.drivingScrollView = scrollView let driver = ScrollViewOverlayTranslationDriver( - translationController: controller, + translationController: translationController, scrollView: scrollView ) drivers.append(driver) } translationDrivers = drivers - translationController = controller } private func setUpPanGesture() { diff --git a/README.md b/README.md index b111a6b..2f1b42c 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # OverlayContainer -[![CI Status](https://img.shields.io/travis/gaetanzanella/OverlayContainer.svg?style=flat)](https://travis-ci.org/gaetanzanella/OverlayContainer) [![Version](https://img.shields.io/cocoapods/v/OverlayContainer.svg?style=flat)](https://cocoapods.org/pods/OverlayContainer) [![License](https://img.shields.io/cocoapods/l/OverlayContainer.svg?style=flat)](https://cocoapods.org/pods/OverlayContainer) [![Platform](https://img.shields.io/cocoapods/p/OverlayContainer.svg?style=flat)](https://cocoapods.org/pods/OverlayContainer) -OverlayContainer is a UI library written in Swift. It makes it easier to master overlay based interfaces, such as the one presented in the Apple Maps, Stocks or Shortcuts apps. -![Shortcuts](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/shortcuts.gif) +OverlayContainer is a UI library written in Swift. It makes it easier to develop overlay based interfaces, such as the one presented in the Apple Maps, Stocks or Shortcuts apps. + +![scroll](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/scroll.gif) - +___ - [Features](#features) - [Requirements](#requirements) @@ -22,21 +22,19 @@ OverlayContainer is a UI library written in Swift. It makes it easier to master - [Examples](#examples) - [Advanced Usage](#advanced-usage) - [Backdrop view](#backdrop-usage) + - [Safe Area](#safe-area) - [Custom Translation](#custom-translation) - - [Custom Translation Animations](#custom-animation) + - [Custom Translation Animations](#custom-translation-animations) - [Author](#author) - [License](#license) - - ## Features -- [x] UIKit like API -- [x] Scroll view translation support -- [x] Transition between scroll & translation -- [x] Rubber band effect -- [x] Number of notches customizable -- [x] Translation animations customizable +- [x] Unlimited notches +- [x] Adapts to any custom layouts +- [x] Fluid transitions between scroll & translation : it perfectly mimics the overlay presented in the Shotcuts app +- [x] Includes a rubber band effect +- [x] Animations and target notch policy fully customizable ## Requirements @@ -85,16 +83,16 @@ class StackViewController: UIViewController { A startup sequence might look like this : ```swift -let contentController = MasterViewController() -let overlayController = DetailViewController() +let mapsController = MapsViewController() +let searchController = SearchViewController() let containerController = OverlayContainerViewController() containerController.delegate = self -containerController.viewControllers = [overlayController] +containerController.viewControllers = [searchController] let stackController = StackViewController() stackController.viewControllers = [ - contentController, + mapsController, containerController ] window?.rootViewController = stackController @@ -127,9 +125,11 @@ func overlayContainerViewController(_ containerViewController: OverlayContainerV } } ``` + ### Overlay style The overlay style defines how the overlay view controllers will be constrained in the `OverlayContainerViewController`. + ```swift enum OverlayStyle { case flexibleHeight // default @@ -138,13 +138,14 @@ enum OverlayStyle { let overlayContainer = OverlayContainerViewController(style: .rigid) ``` + * flexibleHeight ![flexibleHeight](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/flexibleHeight.gif) The overlay view controller will not be height-constrained. It will grow and shrink as the user drags it up and down. -**It is specifically designed for overlays containing scroll views. Be careful to always provide a minimum height higher than the intrinsic content of your overlay.** +It is specifically designed for overlays containing scroll views. **Be careful to always provide a minimum height higher than the intrinsic content of your overlay.** * rigid @@ -152,21 +153,33 @@ The overlay view controller will not be height-constrained. It will grow and shr The overlay view controller will be constrained with a height equal to the highest notch. The overlay won't be fully visible until the user drags it up to this notch. -**As described in the [WWDC "UIKit: Apps for Every Size and Shape" video](https://masterer.apple.com/videos/play/wwdc2018-235/?time=328), be careful when using this style. Do not use the `safeAreaLayoutGuide` to arrange your overlay.** - ### Scroll view support The container view controller can coordinate the scrolling of a scroll view with the overlay translation. + +![scrollToTranslation](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/scrollToTranslation.gif) + +Use the associated delegate method : + ```swift func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, scrollViewDrivingOverlay overlayViewController: UIViewController) -> UIScrollView? { return (overlayViewController as? DetailViewController)?.tableView } ``` + +Or directly set the container property : + +```swift +let containerController = OverlayContainerViewController() +containerController.drivingScrollView = myScrollView +``` + ### Pan gesture support The container view controller detects pan gestures on its own view. Use the dedicated delegate method to check that the specified starting pan gesture location corresponds to a grabbable view in your custom overlay. + ```swift func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, shouldStartDraggingOverlay overlayViewController: UIViewController, @@ -185,6 +198,7 @@ func overlayContainerViewController(_ containerViewController: OverlayContainerV * Maps Like: A custom layout which adapts its hierachy on rotations. ![Maps](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/maps.gif) + * Shortcuts: A custom layout which adapts its hierachy on trait collection changes : Moving from a `UISplitViewController` on regular environment to a simple `StackViewController` on compact environment. Visualize it on iPad Pro. ![Shortcuts](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/shortcuts.gif) @@ -215,12 +229,35 @@ func overlayContainerViewController(_ containerViewController: OverlayContainerV } ``` +### Safe Area + +Be careful when using safe areas. As described in the [WWDC "UIKit: Apps for Every Size and Shape" video](https://masterer.apple.com/videos/play/wwdc2018-235/?time=328), the safe area insets will not be updated if your views exceeds the screen bounds. This is specially the case when using the `OverlayStyle.flexibleHeight`. + +The simpliest way to handle the safe area correctly is to compute your notch heights using the `safeAreaInsets` provided by the container and avoid the `safeAreaLayoutGuide` bottom anchor in your overlay : + +``` +func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, + heightForNotchAt index: Int, + availableSpace: CGFloat) -> CGFloat { + let bottomInset = containerViewController.view.safeAreaInsets.bottom + switch OverlayNotch.allCases[index] { + + // ... + + case .minimum: + return bottomInset + 100 + } +} +``` + ### Custom Translation Adopt `OverlayTranslationFunction` to modify the relation between the user's finger translation and the actual overlay translation. By default, the overlay container uses a `RubberBandOverlayTranslationFunction` that provides a rubber band effect. +![rubberBand](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/rubberBand.gif) + ```swift func overlayContainerViewController(_ containerViewController: OverlayContainerViewController, overlayTranslationFunctionForOverlay overlayViewController: UIViewController) -> OverlayTranslationFunction? { @@ -235,7 +272,10 @@ func overlayContainerViewController(_ containerViewController: OverlayContainerV Adopt `OverlayTranslationTargetNotchPolicy` & `OverlayAnimatedTransitioning` protocols to define where the overlay should go once the user's touch is released and how to animate the translation. -By default, the overlay container uses a `SpringOverlayTranslationAnimationController` that mimics the behavior of a spring. +By default, the overlay container uses a `SpringOverlayTranslationAnimationController` that mimics the behavior of a spring. +The associated target notch policy `RushingForwardTargetNotchPolicy` will always try to go forward is the user's finger reachs a certain velocity. It might also decide to skip some notches if the user goes too fast. + +![animations](https://github.com/applidium/ADOverlayContainer/blob/master/Assets/animations.gif) ```swift