diff --git a/Example/UBottomSheet/Cells/EmbeddedCell.swift b/Example/UBottomSheet/Cells/EmbeddedCell.swift index 2f41520..16094ee 100644 --- a/Example/UBottomSheet/Cells/EmbeddedCell.swift +++ b/Example/UBottomSheet/Cells/EmbeddedCell.swift @@ -45,9 +45,10 @@ class EmbeddedCell: UITableViewCell, UICollectionViewDataSource, UICollectionVie } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HorizontalCell", for: indexPath) as! HorizontalCell + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HorizontalCell", for: indexPath) as? HorizontalCell else { + return UICollectionViewCell() + } cell.configure(with: items[indexPath.item]) return cell } - } diff --git a/Example/UBottomSheet/DataSource/MyDataSource.swift b/Example/UBottomSheet/DataSource/MyDataSource.swift index 35be11c..5da1e9a 100644 --- a/Example/UBottomSheet/DataSource/MyDataSource.swift +++ b/Example/UBottomSheet/DataSource/MyDataSource.swift @@ -11,10 +11,10 @@ import UBottomSheet class MyDataSource: UBottomSheetCoordinatorDataSource { func sheetPositions(_ availableHeight: CGFloat) -> [CGFloat] { - return [0.2, 0.4, 0.6, 0.8].map{$0*availableHeight} + return [0.2, 0.4, 0.6, 0.8].map{ $0 * availableHeight } } func initialPosition(_ availableHeight: CGFloat) -> CGFloat { - return availableHeight*0.4 + return availableHeight * 0.4 } } diff --git a/Example/UBottomSheet/DataSource/PullToDismissDataSource.swift b/Example/UBottomSheet/DataSource/PullToDismissDataSource.swift index 0a781b3..bb8a56e 100644 --- a/Example/UBottomSheet/DataSource/PullToDismissDataSource.swift +++ b/Example/UBottomSheet/DataSource/PullToDismissDataSource.swift @@ -11,10 +11,10 @@ import UBottomSheet class PullToDismissDataSource: UBottomSheetCoordinatorDataSource { func sheetPositions(_ availableHeight: CGFloat) -> [CGFloat] { - return [0.5, 1.1].map{$0*availableHeight} /// Trick is to set bottom position to any value more than available height such as 1.1*availableHeight + return [0.5, 1.1].map{ $0 * availableHeight } /// Trick is to set bottom position to any value more than available height such as 1.1*availableHeight } func initialPosition(_ availableHeight: CGFloat) -> CGFloat { - return availableHeight*0.5 + return availableHeight * 0.5 } } diff --git a/Example/UBottomSheet/ViewControllers/AppleMapsSheetViewController/AppleMapsSheetViewController.swift b/Example/UBottomSheet/ViewControllers/AppleMapsSheetViewController/AppleMapsSheetViewController.swift index 54280fe..26b3ae3 100644 --- a/Example/UBottomSheet/ViewControllers/AppleMapsSheetViewController/AppleMapsSheetViewController.swift +++ b/Example/UBottomSheet/ViewControllers/AppleMapsSheetViewController/AppleMapsSheetViewController.swift @@ -73,10 +73,14 @@ extension AppleMapsSheetViewController: UITableViewDelegate, UITableViewDataSour func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.section { case 0: - let cell = tableView.dequeueReusableCell(withIdentifier: "EmbeddedCell", for: indexPath) as! EmbeddedCell + guard let cell = tableView.dequeueReusableCell(withIdentifier: "EmbeddedCell", for: indexPath) as? EmbeddedCell else { + return UITableViewCell() + } return cell default: - let cell = tableView.dequeueReusableCell(withIdentifier: "MapItemCell", for: indexPath) as! MapItemCell + let cell = tableView.dequeueReusableCell(withIdentifier: "MapItemCell", for: indexPath) as? MapItemCell else { + return UITableViewCell() + } let title: String let subtitle: String switch indexPath.row { @@ -102,9 +106,10 @@ extension AppleMapsSheetViewController: UITableViewDelegate, UITableViewDataSour switch indexPath.row { case 0: - let sc = UBottomSheetCoordinator(parent: sheetCoordinator!.parent) + guard let sheetCoordinator = sheetCoordinator else { return } + let sc = UBottomSheetCoordinator(parent: sheetCoordinator.parent) vc.sheetCoordinator = sc - sc.addSheet(vc, to: sheetCoordinator!.parent) + sc.addSheet(vc, to: sheetCoordinator.parent) case 1: vc.sheetCoordinator = sheetCoordinator sheetCoordinator?.addSheetChild(vc) diff --git a/Example/UBottomSheet/ViewControllers/ListViewController/ListViewController.swift b/Example/UBottomSheet/ViewControllers/ListViewController/ListViewController.swift index 342e720..67349cc 100644 --- a/Example/UBottomSheet/ViewControllers/ListViewController/ListViewController.swift +++ b/Example/UBottomSheet/ViewControllers/ListViewController/ListViewController.swift @@ -53,7 +53,9 @@ extension ListViewController: UITableViewDelegate, UITableViewDataSource{ } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "MapItemCell", for: indexPath) as! MapItemCell + guard let cell = tableView.dequeueReusableCell(withIdentifier: "MapItemCell", for: indexPath) as? MapItemCell else { + return UITableViewCell() + } let title: String let subtitle: String switch indexPath.row { @@ -76,9 +78,10 @@ extension ListViewController: UITableViewDelegate, UITableViewDataSource{ let vc = LabelViewController() switch indexPath.row { case 0: - let sc = UBottomSheetCoordinator(parent: sheetCoordinator!.parent) + guard let sheetCoordinator = sheetCoordinator else { return } + let sc = UBottomSheetCoordinator(parent: sheetCoordinator.parent) vc.sheetCoordinator = sc - sc.addSheet(vc, to: sheetCoordinator!.parent) + sc.addSheet(vc, to: sheetCoordinator.parent) case 1: vc.sheetCoordinator = sheetCoordinator sheetCoordinator?.addSheetChild(vc) diff --git a/Example/UBottomSheet/ViewControllers/MapsViewController/MapsViewController.swift b/Example/UBottomSheet/ViewControllers/MapsViewController/MapsViewController.swift index 67c0403..8a20610 100644 --- a/Example/UBottomSheet/ViewControllers/MapsViewController/MapsViewController.swift +++ b/Example/UBottomSheet/ViewControllers/MapsViewController/MapsViewController.swift @@ -10,7 +10,7 @@ import UIKit import UBottomSheet class MapsViewController: UIViewController { - var sheetCoordinator: UBottomSheetCoordinator! + var sheetCoordinator: UBottomSheetCoordinator? var backView: PassThroughView? override func viewDidLoad() { @@ -36,21 +36,21 @@ class MapsViewController: UIViewController { } - private func addBackDimmingBackView(below container: UIView){ - backView = PassThroughView() - self.view.insertSubview(backView!, belowSubview: container) - backView!.translatesAutoresizingMaskIntoConstraints = false - backView!.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true - backView!.bottomAnchor.constraint(equalTo: container.topAnchor, constant: 10).isActive = true - backView!.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true - backView!.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true + private func addBackDimmingBackView(below container: UIView) { + guard let backView = PassThroughView() else { return } + self.view.insertSubview(backView, belowSubview: container) + backView.translatesAutoresizingMaskIntoConstraints = false + backView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true + backView.bottomAnchor.constraint(equalTo: container.topAnchor, constant: 10).isActive = true + backView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true + backView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true } } -extension MapsViewController: UBottomSheetCoordinatorDelegate{ +extension MapsViewController: UBottomSheetCoordinatorDelegate { func bottomSheet(_ container: UIView?, didPresent state: SheetTranslationState) { -// self.addBackDimmingBackView(below: container!) +// self.addBackDimmingBackView(below: container!) // if later uncommented, remove the force unwrap and add a guard let for the container self.sheetCoordinator.addDropShadowIfNotExist() self.handleState(state) } diff --git a/Example/UBottomSheet/ViewControllers/SimpleViewController/SimpleViewController.swift b/Example/UBottomSheet/ViewControllers/SimpleViewController/SimpleViewController.swift index 098b92c..e64b603 100644 --- a/Example/UBottomSheet/ViewControllers/SimpleViewController/SimpleViewController.swift +++ b/Example/UBottomSheet/ViewControllers/SimpleViewController/SimpleViewController.swift @@ -10,9 +10,9 @@ import UIKit import UBottomSheet class SimpleViewController: UIViewController { - var sheetCoordinator: UBottomSheetCoordinator! + var sheetCoordinator: UBottomSheetCoordinator? - var sheetVC: DraggableItem! + var sheetVC: DraggableItem? var useNavController = false var dataSource: UBottomSheetCoordinatorDataSource? @@ -25,10 +25,10 @@ class SimpleViewController: UIViewController { guard sheetCoordinator == nil else {return} sheetCoordinator = UBottomSheetCoordinator(parent: self) - if dataSource != nil{ - sheetCoordinator.dataSource = dataSource! + if let dataSource = dataSource { + sheetCoordinator.dataSource = dataSource } - + let vc: UIViewController if useNavController{ vc = UINavigationController(rootViewController: sheetVC) diff --git a/Sources/UBottomSheet/Classes/Extensions.swift b/Sources/UBottomSheet/Classes/Extensions.swift index be6151e..682dcbe 100644 --- a/Sources/UBottomSheet/Classes/Extensions.swift +++ b/Sources/UBottomSheet/Classes/Extensions.swift @@ -62,7 +62,8 @@ extension UIView { } extension Array where Element == CGFloat { - func nearest(to x: CGFloat) -> CGFloat { - return self.reduce(self.first!) { abs($1 - x) < abs($0 - x) ? $1 : $0 } + func nearest(to x: CGFloat) -> CGFloat? { + guard let first = first else { return nil } + return self.reduce(first) { abs($1 - x) < abs($0 - x) ? $1 : $0 } } } diff --git a/Sources/UBottomSheet/Classes/UBottomSheetCoordinator.swift b/Sources/UBottomSheet/Classes/UBottomSheetCoordinator.swift index e8855cb..d613c63 100644 --- a/Sources/UBottomSheet/Classes/UBottomSheetCoordinator.swift +++ b/Sources/UBottomSheet/Classes/UBottomSheetCoordinator.swift @@ -15,10 +15,17 @@ public enum SheetTranslationState { } public class UBottomSheetCoordinator: NSObject { - public weak var parent: UIViewController! + public weak var parent: UIViewController? private var container: UIView? - public weak var dataSource: UBottomSheetCoordinatorDataSource! { + public weak var dataSource: UBottomSheetCoordinatorDataSource? { didSet { + guard let dataSource = dataSource, + let availableHeight = availableHeight + else { + minSheetPosition = nil + maxSheetPosition = nil + return + } minSheetPosition = dataSource.sheetPositions(availableHeight).min() maxSheetPosition = dataSource.sheetPositions(availableHeight).max() } @@ -35,8 +42,8 @@ public class UBottomSheetCoordinator: NSObject { ///set true if sheet view controller is embedded in a UINavigationController public var usesNavigationController: Bool = false - public var availableHeight: CGFloat { - return parent.view.frame.height + public var availableHeight: CGFloat? { + return parent?.view.frame.height } private var cornerRadius: CGFloat = 0 { @@ -70,8 +77,8 @@ public class UBottomSheetCoordinator: NSObject { self.dataSource = parent self.delegate = delegate - minSheetPosition = dataSource.sheetPositions(availableHeight).min() - maxSheetPosition = dataSource.sheetPositions(availableHeight).max() + minSheetPosition = parent.sheetPositions(parent.view.frame.height).min() + maxSheetPosition = parent.sheetPositions(parent.view.frame.height).max() } /** @@ -107,11 +114,15 @@ public class UBottomSheetCoordinator: NSObject { - parameter config: Called after container created. So you can customize the view, like shadow, corner radius, border, etc. */ public func createContainer(with config: @escaping (UIView) -> Void) { + guard let parent = parent, + let dataSource = dataSource, + let availableHeight = availableHeight + else { return } let view = PassThroughView() self.container = view config(view) - container?.pinToEdges(to: parent.view) - container?.constraint(parent, for: .top)?.constant = dataSource.sheetPositions(availableHeight)[0] + view.pinToEdges(to: parent.view) + view.constraint(parent, for: .top)?.constant = dataSource.sheetPositions(availableHeight)[0] setPosition(dataSource.initialPosition(availableHeight), animated: false) } @@ -143,22 +154,26 @@ public class UBottomSheetCoordinator: NSObject { animated: Bool = true, didContainerCreate: ((UIView) -> Void)? = nil, completion: (() -> Void)? = nil) { - self.usesNavigationController = item is UINavigationController - let container = PassThroughView() - self.container = container - parent.view.addSubview(container) - let position = dataSource.initialPosition(availableHeight) - parent.ub_add(item, in: container, animated: animated, topInset: position) { [weak self] in - guard let sSelf = self else { + self.usesNavigationController = item is UINavigationController + let container = PassThroughView() + self.container = container + parent.view.addSubview(container) + guard let dataSource = dataSource, + let availableHeight = availableHeight + else { return } + let position = dataSource.initialPosition(availableHeight) + parent.ub_add(item, in: container, animated: animated, topInset: position) { [weak self] in + guard let sSelf = self else { return } + guard let percent = sSelf.calculatePercent(at: position) else { + completion?() return - } - sSelf.delegate?.bottomSheet(container, - didPresent: .finished(position, sSelf.calculatePercent(at: position))) + } + sSelf.delegate?.bottomSheet(container, didPresent: .finished(position, percent)) completion?() - } - didContainerCreate?(container) - setPosition(dataSource.initialPosition(availableHeight), animated: false) - } + } + didContainerCreate?(container) + setPosition(dataSource.initialPosition(availableHeight), animated: false) + } /** Adds a new view child controller to the current container view @@ -167,13 +182,19 @@ public class UBottomSheetCoordinator: NSObject { - parameter completion: called upon completion of animation */ public func addSheetChild(_ item: DraggableItem, completion: ((Bool) -> Void)? = nil) { + guard let parent = parent, + let container = container, + let availableHeight = availableHeight + else { return } parent.addChild(item) - container!.addSubview(item.view) + container.addSubview(item.view) item.didMove(toParent: parent) - item.view.frame = container!.bounds.offsetBy(dx: 0, dy: availableHeight) + item.view.frame = container.bounds.offsetBy(dx: 0, dy: availableHeight) - UIView.animate(withDuration: 0.3) { - item.view.frame = self.container!.bounds + UIView.animate(withDuration: 0.3) { [weak self] in + guard let self = self else { return } + guard let container = self.container else { return } + item.view.frame = container.bounds } completion: { finished in completion?(finished) } @@ -183,6 +204,12 @@ public class UBottomSheetCoordinator: NSObject { Frame of the sheet when added. */ private func getInitialFrame() -> CGRect { + guard let parent = parent, + let dataSource = dataSource, + let availableHeight = availableHeight + else { + return CGRectMake(0, 0, 1, 1) + } let minY = parent.view.bounds.minY + dataSource.initialPosition(availableHeight) return CGRect(x: parent.view.bounds.minX, y: minY, @@ -196,23 +223,28 @@ public class UBottomSheetCoordinator: NSObject { Use ```removeDropShadow()``` to remove drop shadow. */ public func addDropShadowIfNotExist(_ config: ((UIView) -> Void)? = nil) { - guard dropShadowView == nil else { - return - } - dropShadowView = PassThroughView() - parent.view.insertSubview(dropShadowView!, belowSubview: container!) - dropShadowView?.pinToEdges(to: container!, - insets: UIEdgeInsets(top: -getInitialFrame().minY, - left: 0, - bottom: 0, - right: 0)) - - self.dropShadowView?.layer.masksToBounds = false + guard dropShadowView == nil, + let parent = parent + else { return } + let dropShadowView = PassThroughView() + self.dropShadowView = dropShadowView + guard let container = container else { return } + parent.view.insertSubview(dropShadowView, belowSubview: container) + dropShadowView.pinToEdges( + to: container, + insets: UIEdgeInsets( + top: -getInitialFrame().minY, + left: 0, + bottom: 0, + right: 0 + ) + ) + dropShadowView.layer.masksToBounds = false if config == nil { applyDefaultShadowParams() clearShadowBackground() } else { - config?(dropShadowView!) + config?(dropShadowView) } } @@ -246,6 +278,9 @@ public class UBottomSheetCoordinator: NSObject { If you are using UIVisualEffectView or transparent sheet background. You need to cut shadow part which intersects the sheet frame with this. */ private func clearShadowBackground() { + guard let parent = parent, + let availableHeight = availableHeight + else { return } let p = CGMutablePath() p.addRect(parent.view.bounds.insetBy(dx: 0, dy: -availableHeight)) p.addPath(UIBezierPath(roundedRect: getInitialFrame(), cornerRadius: cornerRadius).cgPath) @@ -266,14 +301,14 @@ public class UBottomSheetCoordinator: NSObject { /** Set sheet top constraint value to the given new y position. - + - parameter minYPosition: new y position. - parameter animated: pass true to animate sheet position change; false otherwise. */ public func setPosition(_ minYPosition: CGFloat, animated: Bool) { self.endTranslate(to: minYPosition, animated: animated) } - + /** Set sheet top constraint value to the nearest sheet positions to given y position. @@ -281,7 +316,10 @@ public class UBottomSheetCoordinator: NSObject { - parameter animated: pass true to animate sheet position change; false otherwise. */ public func setToNearest(_ pos: CGFloat, animated: Bool) { - let y = dataSource.sheetPositions(availableHeight).nearest(to: pos) + guard let dataSource = dataSource, + let availableHeight = availableHeight, + let y = dataSource.sheetPositions(availableHeight).nearest(to: pos) + else { return } setPosition(y, animated: animated) } @@ -293,7 +331,9 @@ public class UBottomSheetCoordinator: NSObject { */ public func removeSheetChild(item: T, completion: ((Bool) -> Void)? = nil) { stopTracking(item: item) - let _item = usesNavigationController ? item.navigationController! : item + guard let _item = (usesNavigationController ? item.navigationController : .some(item)) else { + return + } UIView.animate(withDuration: 0.3, animations: { _item.view.frame = _item.view.frame.offsetBy(dx: 0, dy: _item.view.frame.height) }) { (finished) in @@ -310,16 +350,20 @@ public class UBottomSheetCoordinator: NSObject { */ public func removeSheet(_ block: ((_ container: UIView?) -> Void)? = nil, completion: ((Bool) -> Void)? = nil) { + guard let container = container else { return } self.draggables.removeAll() guard block == nil else { block?(container) return } UIView.animate(withDuration: 0.3, animations: { [weak self] in - guard let sSelf = self else { + guard let sSelf = self, + let container = sSelf.container, + let parent = sSelf.parent + else { return } - sSelf.container!.frame = sSelf.container!.frame.offsetBy(dx: 0, dy: sSelf.parent.view.frame.height) + container.frame = container.frame.offsetBy(dx: 0, dy: parent.view.frame.height) }) { [weak self ] finished in self?.container?.removeFromSuperview() self?.removeDropShadow() @@ -338,7 +382,7 @@ public class UBottomSheetCoordinator: NSObject { vc == item } } - + /** Add pan gesture recognizer to the view controller. @@ -406,7 +450,7 @@ public class UBottomSheetCoordinator: NSObject { private func handlePan(_ recognizer: UIPanGestureRecognizer, scrollView: UIScrollView? = nil) { let dy = recognizer.translation(in: recognizer.view).y let vel = recognizer.velocity(in: recognizer.view) - + switch recognizer.state { case .began: lastY = 0 @@ -414,21 +458,26 @@ public class UBottomSheetCoordinator: NSObject { //set last contentOffset y value by adding 'dy' i.e. pre pan gesture happened. lastContentOffset.y = scroll.contentOffset.y + dy } - totalTranslationMinY = minSheetPosition! - totalTranslationMaxY = maxSheetPosition! + guard let minSheetPosition = minSheetPosition, + let maxSheetPosition = maxSheetPosition + else { return } + totalTranslationMinY = minSheetPosition + totalTranslationMaxY = maxSheetPosition translate(with: vel, dy: dy, scrollView: scrollView) case .changed: translate(with: vel, dy: dy, scrollView: scrollView) case .ended, .cancelled, .failed: + guard let container = container else { return } guard let scroll = scrollView else { - self.finishDragging(with: vel, position: container!.frame.minY + dy - lastY) + self.finishDragging(with: vel, position: container.frame.minY + dy - lastY) return } - let minY = container!.frame.minY + let minY = container.frame.minY + guard let minSheetPosition = self.minSheetPosition else { return } switch dragDirection(vel) { - case .up where minY - minSheetPosition! > tolerance: + case .up where minY - minSheetPosition > tolerance: scroll.setContentOffset(lastContentOffset, animated: false) self.finishDragging(with: vel, position: minY) default: @@ -449,9 +498,12 @@ public class UBottomSheetCoordinator: NSObject { - parameter scrollView: set if scrollView gesture event */ func translate(with velocity: CGPoint, dy: CGFloat, scrollView: UIScrollView? = nil) { + guard let container = container, + let minSheetPosition = minSheetPosition + else { return } if let scroll = scrollView { switch dragDirection(velocity) { - case .up where (container!.frame.minY - minSheetPosition! > tolerance): + case .up where (container.frame.minY - minSheetPosition > tolerance): applyTranslation(dy: dy - lastY) scroll.contentOffset.y = lastContentOffset.y case .down where scroll.contentOffset.y <= 0 /*&& !scroll.isDecelerating*/: @@ -474,6 +526,9 @@ public class UBottomSheetCoordinator: NSObject { - parameter point: current top y position */ private func isSheetPosition(_ point: CGFloat) -> Bool { + guard let dataSource = dataSource, + let availableHeight = availableHeight + else { return false } return dataSource.sheetPositions(availableHeight).first(where: { (p) -> Bool in abs(p - point) < tolerance }) != nil @@ -508,6 +563,9 @@ public class UBottomSheetCoordinator: NSObject { - parameter currentPosition: current top constraint value of container view */ private func filteredPositions(_ velocity: CGPoint, currentPosition: CGFloat) -> [CGFloat] { + guard let dataSource = dataSource, + let availableHeight = availableHeight + else { return [] } if velocity.y < -100 { /// dragging up let data = dataSource.sheetPositions(availableHeight).filter { (p) -> Bool in p < currentPosition @@ -533,9 +591,9 @@ public class UBottomSheetCoordinator: NSObject { } /// y translation value for top rubber banding calculation - private var totalTranslationMinY: CGFloat! + private var totalTranslationMinY: CGFloat? /// y translation value for bottom rubber banding calculation - private var totalTranslationMaxY: CGFloat! + private var totalTranslationMaxY: CGFloat? /** Drives container view according the given value @@ -543,38 +601,49 @@ public class UBottomSheetCoordinator: NSObject { - parameter dy: change in the y direction of pan gesture */ private func applyTranslation(dy: CGFloat) { + guard let dataSource = dataSource, + let container = container, + let availableHeight = availableHeight + else { return } guard dy != 0 else { return } - let topLimit = minSheetPosition! - let bottomLimit = maxSheetPosition! + guard let topLimit = minSheetPosition, + let bottomLimit = maxSheetPosition + else { return } - let oldFrame = container!.frame + let oldFrame = container.frame var newY = oldFrame.minY if hasExceededTopLimit(oldFrame.minY + dy, topLimit) { let yy = min(0 , topLimit - oldFrame.minY) - totalTranslationMinY -= (dy - yy) - totalTranslationMaxY = maxSheetPosition! + totalTranslationMinY = totalTranslationMinY.map{ $0 - (dy - yy) } + guard let maxSheetPosition = maxSheetPosition else { return } + totalTranslationMaxY = maxSheetPosition + guard let totalTranslationMinY = totalTranslationMinY else { return } newY = dataSource.rubberBandLogicTop(totalTranslationMinY, topLimit) } else if hasExceededBottomLimit(oldFrame.minY + dy, bottomLimit) { let yy = max(0 , bottomLimit - oldFrame.minY) - totalTranslationMinY = minSheetPosition! - totalTranslationMaxY += (dy - yy) + guard let minSheetPosition = minSheetPosition else { return } + totalTranslationMinY = minSheetPosition + totalTranslationMaxY = totalTranslationMaxY.map{ $0 + (dy - yy) } + guard let totalTranslationMaxY = totalTranslationMaxY else { return } newY = dataSource.rubberBandLogicBottom(totalTranslationMaxY, bottomLimit) } else { - totalTranslationMinY = minSheetPosition! - totalTranslationMaxY = maxSheetPosition! + guard let minSheetPosition = minSheetPosition, + let maxSheetPosition = maxSheetPosition + else { return } + totalTranslationMinY = minSheetPosition + totalTranslationMaxY = maxSheetPosition newY += dy } - - let height = max(availableHeight - minSheetPosition!, availableHeight - newY) + guard let minSheetPosition = self.minSheetPosition else { return } + let height = max(availableHeight - minSheetPosition, availableHeight - newY) let frame = CGRect(x: 0, y: newY, width: oldFrame.width, height: height) - container?.frame = frame - - self.delegate?.bottomSheet(self.container, - didChange: .progressing(frame.minY, calculatePercent(at: frame.minY))) + container.frame = frame + guard let percent = calculatePercent(at: frame.minY) else { return } + self.delegate?.bottomSheet(container, didChange: .progressing(frame.minY, percent)) } /** @@ -584,7 +653,7 @@ public class UBottomSheetCoordinator: NSObject { - parameter position: new top constraint value */ private func finishDragging(with velocity: CGPoint, position: CGFloat) { - let y = filteredPositions(velocity, currentPosition: position).nearest(to: position) + guard let y = filteredPositions(velocity, currentPosition: position).nearest(to: position) else { return } endTranslate(to: y, animated: true) } @@ -598,38 +667,41 @@ public class UBottomSheetCoordinator: NSObject { - parameter animated: Pass true to animate the y position change; otherwise pass false. */ private func endTranslate(to position: CGFloat, animated: Bool = false) { - guard position != 0 else { - return - } - let oldFrame = container!.frame - let height = max(availableHeight - minSheetPosition!, availableHeight - position) + guard position != 0, + let container = container, + let minSheetPosition = minSheetPosition, + let availableHeight = availableHeight + else { return } + let oldFrame = container.frame + let height = max(availableHeight - minSheetPosition, availableHeight - position) let frame = CGRect(x: 0, y: position, width: oldFrame.width, height: height) - self.delegate?.bottomSheet(self.container, - didChange: .willFinish(position, self.calculatePercent(at: position))) + guard let percentage = self.calculatePercent(at: position) else { return } + self.delegate?.bottomSheet(container, didChange: .willFinish(position, percentage)) if animated { self.lastAnimatedValue = position - dataSource.animator?.animate(animations: { + dataSource?.animator?.animate(animations: { self.delegate?.bottomSheet(self.container, finishTranslateWith: { (anim) in - anim(self.calculatePercent(at: position)) + guard let percent = self.calculatePercent(at: position) else { return } + anim(percent) }) - self.container!.frame = frame - self.parent.view.layoutIfNeeded() + self.container?.frame = frame + self.parent?.view.layoutIfNeeded() }, completion: { finished in if self.lastAnimatedValue != position { return } - self.delegate?.bottomSheet(self.container, - didChange: .finished(position, self.calculatePercent(at: position))) - if position >= self.availableHeight { + guard let percent = self.calculatePercent(at: position) else { return } + self.delegate?.bottomSheet(container, didChange: .finished(position, percent)) + if position >= availableHeight { self.removeSheet() } }) } else { - self.container!.frame = frame - self.delegate?.bottomSheet(self.container, - didChange: .finished(position, self.calculatePercent(at: position))) + container.frame = frame + guard let percentage = self.calculatePercent(at: position) else { return } + self.delegate?.bottomSheet(container, didChange: .finished(position, percentage)) } } @@ -642,8 +714,11 @@ public class UBottomSheetCoordinator: NSObject { - parameter pos: current top constraint value */ - private func calculatePercent(at pos: CGFloat) -> CGFloat { - return (availableHeight - pos) / (availableHeight - minSheetPosition!) * 100 + private func calculatePercent(at pos: CGFloat) -> CGFloat? { + guard let minSheetPosition = minSheetPosition, + let availableHeight = availableHeight + else { return nil } + return (availableHeight - pos) / (availableHeight - minSheetPosition) * 100 } /**