From 08846bbc09f03a065779dc20dd66975732152c7c Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 24 Sep 2019 12:11:10 -0400 Subject: [PATCH] Animate dimming view opacity based on sheet height --- SheetImagePicker.xcodeproj/project.pbxproj | 4 +++ SheetImagePicker/MathHelpers.swift | 30 +++++++++++++++++++ .../SheetContainerViewController.swift | 29 +++++++----------- 3 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 SheetImagePicker/MathHelpers.swift diff --git a/SheetImagePicker.xcodeproj/project.pbxproj b/SheetImagePicker.xcodeproj/project.pbxproj index cb2722e..0157842 100644 --- a/SheetImagePicker.xcodeproj/project.pbxproj +++ b/SheetImagePicker.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ D610D2F6233945C0009EB06A /* SheetImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D610D2D1233945AF009EB06A /* SheetImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D610D2FD23394E00009EB06A /* SheetContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D610D2FC23394E00009EB06A /* SheetContainerViewController.swift */; }; D610D2FF23395975009EB06A /* Detent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D610D2FE23395975009EB06A /* Detent.swift */; }; + D6B61D63233A748300809DE7 /* MathHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B61D62233A748300809DE7 /* MathHelpers.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -58,6 +59,7 @@ D610D2F0233945BB009EB06A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D610D2FC23394E00009EB06A /* SheetContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetContainerViewController.swift; sourceTree = ""; }; D610D2FE23395975009EB06A /* Detent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Detent.swift; sourceTree = ""; }; + D6B61D62233A748300809DE7 /* MathHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MathHelpers.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -102,6 +104,7 @@ isa = PBXGroup; children = ( D610D2D4233945AF009EB06A /* SheetImagePicker.h */, + D6B61D62233A748300809DE7 /* MathHelpers.swift */, D610D2FC23394E00009EB06A /* SheetContainerViewController.swift */, D610D2FE23395975009EB06A /* Detent.swift */, D610D2D5233945AF009EB06A /* Info.plist */, @@ -246,6 +249,7 @@ files = ( D610D2FD23394E00009EB06A /* SheetContainerViewController.swift in Sources */, D610D2FF23395975009EB06A /* Detent.swift in Sources */, + D6B61D63233A748300809DE7 /* MathHelpers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SheetImagePicker/MathHelpers.swift b/SheetImagePicker/MathHelpers.swift new file mode 100644 index 0000000..3595250 --- /dev/null +++ b/SheetImagePicker/MathHelpers.swift @@ -0,0 +1,30 @@ +// +// MathHelpers.swift +// SheetImagePicker +// +// Created by Shadowfacts on 9/24/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import UIKit + +func clamp(_ value: CGFloat, from: CGFloat, to: CGFloat) -> CGFloat { + if value < from { + return from + } else if value > to { + return to + } else { + return value + } +} + +func smoothstep(value: CGFloat, from: CGFloat, to: CGFloat) -> CGFloat { + let x = clamp((value - from) / (to - from), from: 0, to: 1) + // 3x^2 - 2x^3 + return 3 * pow(x, 2) - 2 * pow(x, 3) +} + +func lerp(_ value: CGFloat, min: CGFloat, max: CGFloat, from: CGFloat, to: CGFloat) -> CGFloat { + let value = clamp((value - min) / (max - min), from: 0, to: 1) + return value * (to - from) + from +} diff --git a/SheetImagePicker/SheetContainerViewController.swift b/SheetImagePicker/SheetContainerViewController.swift index 82d6146..c84c7e8 100644 --- a/SheetImagePicker/SheetContainerViewController.swift +++ b/SheetImagePicker/SheetContainerViewController.swift @@ -31,6 +31,9 @@ public class SheetContainerViewController: UIViewController { var topDetent: (detent: Detent, offset: CGFloat) { return detents.map { ($0, $0.offset(in: view)) }.min(by: { $0.1 < $1.1 })! } + var bottomDetent: (detent: Detent, offset: CGFloat) { + return detents.map { ($0, $0.offset(in: view)) }.max(by: { $0.1 < $1.1 })! + } public var minimumDetentJumpVelocity: CGFloat = 500 public var maximumStretchDistance: CGFloat = 15 @@ -39,6 +42,8 @@ public class SheetContainerViewController: UIViewController { lazy var initialConstant: CGFloat = view.bounds.height / 2 var dimmingView: UIView! + public var minimumDimmingAlpha: CGFloat = 0 + public var maximumDimmingAlpha: CGFloat = 0.75 public init(content: UIViewController) { self.content = content @@ -56,7 +61,7 @@ public class SheetContainerViewController: UIViewController { dimmingView = UIView() dimmingView.translatesAutoresizingMaskIntoConstraints = false dimmingView.backgroundColor = .systemGray - dimmingView.alpha = 0.5 + dimmingView.alpha = (maximumDimmingAlpha - minimumDimmingAlpha) / 2 view.addSubview(dimmingView) NSLayoutConstraint.activate([ dimmingView.leadingAnchor.constraint(equalTo: view.leadingAnchor), @@ -88,28 +93,15 @@ public class SheetContainerViewController: UIViewController { case .changed: let translation = recognizer.translation(in: content.view) var realOffset = initialConstant + translation.y - if realOffset < topDetent.offset { - func clamp(_ value: CGFloat, from: CGFloat, to: CGFloat) -> CGFloat { - if value < from { - return from - } else if value > to { - return to - } else { - return value - } - } - func smoothstep(value: CGFloat, from: CGFloat, to: CGFloat) -> CGFloat { - let x = clamp((value - from) / (to - from), from: 0, to: 1) - // 3x^2 - 2x^3 - return 3 * pow(x, 2) - 2 * pow(x, 3) - } - let topOffset = topDetent.offset + let topOffset = topDetent.offset + if realOffset < topOffset { let smoothed = smoothstep(value: realOffset, from: topOffset, to: 0) realOffset = topOffset - smoothed * maximumStretchDistance } topConstraint.constant = realOffset - + dimmingView.alpha = lerp(realOffset, min: topOffset, max: bottomDetent.offset, from: maximumDimmingAlpha, to: minimumDimmingAlpha) + case .ended: let velocity = recognizer.velocity(in: view) @@ -130,6 +122,7 @@ public class SheetContainerViewController: UIViewController { UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: springVelocity, animations: { self.view.layoutIfNeeded() + self.dimmingView.alpha = lerp(springToDetent.1, min: self.topDetent.offset, max: self.bottomDetent.offset, from: self.maximumDimmingAlpha, to: self.minimumDimmingAlpha) }, completion: { (finished) in self.delegate?.sheetContainer(self, didSnapToDetent: springToDetent.0) })