Unverified Commit 007fadb1 authored by Zach Knox's avatar Zach Knox
Browse files

Swift 4 Swift 4 Swift 4 Swift 4

also lots of pod updates
parent 7f38b8b6
# Uncomment this line to define a global platform for your project
# platform :ios, '8.0'
# Uncomment this line if you're using Swift
platform :ios, '11.0'
use_frameworks!
target 'WhatsOpen' do
pod 'RealmSwift'
pod 'ObjectMapper', '~> 2.2'
pod 'ObjectMapper', '~> 3.0'
#pod 'Segmentio', '~> 2.1'
pod 'DeckTransition', '~> 1.0'
pod 'DeckTransition', '~> 1.4.0'
end
......
PODS:
- DeckTransition (1.2.0)
- ObjectMapper (2.2.8)
- DeckTransition (1.4.0)
- ObjectMapper (3.0.0)
- Realm (2.10.1):
- Realm/Headers (= 2.10.1)
- Realm/Headers (2.10.1)
......@@ -8,16 +8,16 @@ PODS:
- Realm (= 2.10.1)
DEPENDENCIES:
- DeckTransition (~> 1.0)
- ObjectMapper (~> 2.2)
- DeckTransition (~> 1.4.0)
- ObjectMapper (~> 3.0)
- RealmSwift
SPEC CHECKSUMS:
DeckTransition: 23a2c7bdb24bf740a460da72cbb25f9dd28d0a51
ObjectMapper: 3d571bb5af471c779e1160828cd9ad5c4ef90958
DeckTransition: d678005a9b35c2745fe8b683c39c41f840ad69d7
ObjectMapper: 92230db59bf8f341a5c3a3cf0b9fbdde3cf0d87f
Realm: fc7a317a5c2c9ba91f5f235ede4e2ea76e9eba0c
RealmSwift: 505ed6c15942a2e76f5cfa78a8667cfa997ee75b
PODFILE CHECKSUM: 84f488897b7d96947925a9d04edb67bb66a2cde9
PODFILE CHECKSUM: 5595b2ebe2214e0d557d3eac7bdc9dbb3b2edf20
COCOAPODS: 1.3.1
......@@ -15,7 +15,7 @@ Hereʼs a GIF showing it in action.
## Requirements
- Swift 3
- Swift 4
- iOS 9 or later
## Installation
......@@ -108,14 +108,35 @@ func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
```
### Snapshots
For a variety of reasons, and especially because of iOS 11's safe area layout, DeckTransition uses a snapshot of your presenting view controller's view instead of using the view directly. This view is automatically updated whenever the frame is resized.
However, there can be some cases where you might want to update the snapshot view by yourself, and this can be achieved using the following one line snippet:
```swift
(presentationController as? DeckSnapshotUpdater)?.requestPresentedViewSnapshotUpdate()
```
All this does is request the presentation controller to update the snapshot.
You can also choose to update snapshot directly from the presenting view controller, as follows:
```swift
(presentedViewController?.presentationController as? DeckSnapshotUpdater)?.requestPresentedViewSnapshotUpdate()
```
It's worth noting that updating the snapshot is an expensive process and should only be used if necessary, for example if you are updating your entire app's theme.
## Apps Using DeckTransition
- [Petty](https://zachsim.one/projects/petty) by [Zach Simone](https://twitter.com/zachsimone)
- [Bitbook](https://bitbookapp.com) by [Sammy Gutierrez](https://sammygutierrez.com)
Feel free to submit a PR if you’re using this library in your apps
## Author
Written by Harshil Shah. You can [find me on Twitter](https://twitter.com/HarshilShah1910) if you have any suggestions, requests, or just want to talk!
Written by [Harshil Shah](https://twitter.com/HarshilShah1910)
## License
......
......@@ -13,74 +13,40 @@ final class DeckDismissingAnimationController: NSObject, UIViewControllerAnimate
// MARK:- Private variables
private let duration: TimeInterval?
private let animation: (() -> ())?
private let completion: ((Bool) -> ())?
// MARK:- Initializers
init(duration: TimeInterval?, animation: (() -> ())?, completion: ((Bool) -> ())?) {
init(duration: TimeInterval?) {
self.duration = duration
self.animation = animation
self.completion = completion
}
// MARK:- UIViewControllerAnimatedTransitioning
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let presentingViewController = transitionContext.viewController(forKey: .to)!
let presentedViewController = transitionContext.viewController(forKey: .from)!
let containerView = transitionContext.containerView
let roundedViewForPresentingView = RoundedView()
roundedViewForPresentingView.cornerRadius = Constants.cornerRadius
roundedViewForPresentingView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(roundedViewForPresentingView)
/// The presentedViewController throughout this library refers to the
/// card view controller which is presented in the Deck style, and so
/// for consistency, even through it's the view controller that we are
/// transitioning `.from` in the context of the dismissal animation and
/// should thus be the `presentingViewController`, it's referred to as
/// the `presentedViewController` here
/// At the end of the transition the rounded view has to have the same
/// frame as the presentingView, except with a height equal to the
/// cornerRadius
let finalFrameForPresentingView = transitionContext.finalFrame(for: presentingViewController)
let finalFrameForRoundedViewForPresentingView = CGRect(
x: finalFrameForPresentingView.origin.x,
y: finalFrameForPresentingView.origin.y,
width: finalFrameForPresentingView.width,
height: Constants.cornerRadius)
roundedViewForPresentingView.frame = finalFrameForRoundedViewForPresentingView
guard let presentedViewController = transitionContext.viewController(forKey: .from) else {
return
}
let scale: CGFloat = 1 - (ManualLayout.presentingViewTopInset * 2 / finalFrameForPresentingView.height)
let containerView = transitionContext.containerView
/// The rounded view needs to be scaled by the same amount as the
/// presentingView, and also translated down by the same amount.
/// Scaling happens with respect to the frame's center, so a
/// translate-scale-translate needs to be done to ensure that the
/// scaling is performed with respect to the top edge so it still lines
/// up with the top edge of the presentingView
let transformForRoundedViewForPresentingView = CGAffineTransform.identity
.translatedBy(x: 0, y: ManualLayout.presentingViewTopInset)
.translatedBy(x: 0, y: -finalFrameForRoundedViewForPresentingView.height / 2)
.scaledBy(x: scale, y: scale)
.translatedBy(x: 0, y: finalFrameForRoundedViewForPresentingView.height / 2)
roundedViewForPresentingView.transform = transformForRoundedViewForPresentingView
let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
let offScreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: .curveEaseOut,
animations: { [weak self] in
roundedViewForPresentingView.transform = .identity
presentingViewController.view.alpha = 1
presentingViewController.view.transform = .identity
presentedViewController.view.frame = offScreenFrame
self?.animation?()
}, completion: { [weak self] finished in
roundedViewForPresentingView.removeFromSuperview()
animations: {
presentedViewController.view.frame = offscreenFrame
}, completion: { finished in
transitionContext.completeTransition(finished)
self?.completion?(finished)
})
}
......
......@@ -13,87 +13,34 @@ final class DeckPresentingAnimationController: NSObject, UIViewControllerAnimate
// MARK:- Private variables
private let duration: TimeInterval?
private let animation: (() -> ())?
private let completion: ((Bool) -> ())?
// MARK:- Initializers
init(duration: TimeInterval?, animation: (() -> ())?, completion: ((Bool) -> ())?) {
init(duration: TimeInterval?) {
self.duration = duration
self.animation = animation
self.completion = completion
}
// MARK:- UIViewControllerAnimatedTransitioning
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let presentingViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let presentedViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
guard let presentedViewController = transitionContext.viewController(forKey: .to) else {
return
}
let containerView = transitionContext.containerView
let scale: CGFloat = 1 - (ManualLayout.presentingViewTopInset * 2 / presentingViewController.view.frame.height)
let roundedViewForPresentingView = RoundedView()
roundedViewForPresentingView.cornerRadius = Constants.cornerRadius
roundedViewForPresentingView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(roundedViewForPresentingView)
/// Initially the rounded view has the same frame as the presentingView,
/// except with a height equal to the cornerRadius
let initialFrameForRoundedViewForPresentingView = CGRect(
x: presentingViewController.view.frame.origin.x,
y: presentingViewController.view.frame.origin.y,
width: presentingViewController.view.frame.width,
height: Constants.cornerRadius)
roundedViewForPresentingView.frame = initialFrameForRoundedViewForPresentingView
/// The rounded view needs to be scaled by the same amount as the
/// presentingView, and also translated down by the same amount.
/// Scaling happens with respect to the frame's center, so a
/// translate-scale-translate needs to be done to ensure that the
/// scaling is performed with respect to the top edge so it still lines
/// up with the top edge of the presentingView
let transformForRoundedViewForPresentingView = CGAffineTransform.identity
.translatedBy(x: 0, y: ManualLayout.presentingViewTopInset)
.translatedBy(x: 0, y: -initialFrameForRoundedViewForPresentingView.height / 2)
.scaledBy(x: scale, y: scale)
.translatedBy(x: 0, y: initialFrameForRoundedViewForPresentingView.height / 2)
containerView.addSubview(presentedViewController.view)
presentedViewController.view.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
let roundedViewForPresentedView = RoundedView()
containerView.addSubview(roundedViewForPresentedView)
roundedViewForPresentedView.frame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: Constants.cornerRadius)
let finalFrameForPresentedView = transitionContext.finalFrame(for: presentedViewController)
let finalFrameForRoundedViewForPresentedView = CGRect(
x: finalFrameForPresentedView.origin.x,
y: finalFrameForPresentedView.origin.y,
width: finalFrameForPresentedView.width,
height: Constants.cornerRadius)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: .curveEaseOut,
animations: { [weak self] in
presentingViewController.view.transform = CGAffineTransform(scaleX: scale, y: scale)
presentingViewController.view.alpha = Constants.alphaForPresentingView
roundedViewForPresentingView.cornerRadius = Constants.cornerRadius
roundedViewForPresentingView.transform = transformForRoundedViewForPresentingView
animations: {
presentedViewController.view.frame = finalFrameForPresentedView
roundedViewForPresentedView.frame = finalFrameForRoundedViewForPresentedView
self?.animation?()
}, completion: { [weak self] finished in
roundedViewForPresentingView.removeFromSuperview()
roundedViewForPresentedView.removeFromSuperview()
}, completion: { finished in
transitionContext.completeTransition(finished)
self?.completion?(finished)
}
)
}
......
......@@ -62,17 +62,11 @@ public final class DeckTransitioningDelegate: NSObject, UIViewControllerTransiti
// MARK:- UIViewControllerTransitioningDelegate
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DeckPresentingAnimationController(
duration: presentDuration,
animation: presentAnimation,
completion: presentCompletion)
return DeckPresentingAnimationController(duration: presentDuration)
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DeckDismissingAnimationController(
duration: dismissDuration,
animation: dismissAnimation,
completion: dismissCompletion)
return DeckDismissingAnimationController(duration: dismissDuration)
}
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
......
......@@ -12,19 +12,27 @@ final class CornerView: UIView {
// MARK:- Public variables
var cornerRadius: CGFloat = 0 {
didSet {
updateCornerShape()
}
var cornerRadius: CGFloat {
get { return cornerLayer.radius }
set { cornerLayer.radius = newValue }
}
var corner: Corner? {
didSet { updateCornerShape() }
get { return cornerLayer.corner }
set { cornerLayer.corner = newValue }
}
// MARK:- Private variables
private let cornerShapeLayer = CAShapeLayer()
private var cornerLayer: CornerLayer {
return layer as! CornerLayer
}
// MARK:- Layer override
override class var layerClass: AnyClass {
return CornerLayer.self
}
// MARK:- Initializers
......@@ -44,27 +52,80 @@ final class CornerView: UIView {
}
private func setup() {
layer.addSublayer(cornerShapeLayer)
cornerShapeLayer.fillColor = UIColor.black.cgColor
cornerLayer.fillColor = UIColor.black.cgColor
}
}
private final class CornerLayer: CAShapeLayer {
// MARK:- Public variables
override var frame: CGRect {
didSet { setNeedsDisplay() }
}
@NSManaged var radius: CGFloat
var corner: Corner? {
didSet { setNeedsDisplay() }
}
// MARK:- UIView methods
// MARK:- Private variables
private static let radiusKey = "radius"
// MARK:- Animation overrides
override static func needsDisplay(forKey key: String) -> Bool {
guard key == radiusKey else {
return super.needsDisplay(forKey: key)
}
return true
}
override func action(forKey event: String) -> CAAction? {
/// As best as I can tell, the only way to get all the properties
/// related to the ongoing transition is to just copy them from the
/// animation that is created for any random animatable property
///
/// https://stackoverflow.com/questions/14192816/create-a-custom-animatable-property
guard event == CornerLayer.radiusKey,
let action = super.action(forKey: "backgroundColor") as? CAAnimation
else {
return super.action(forKey: event)
}
let animation = CABasicAnimation(keyPath: CornerLayer.radiusKey)
animation.fromValue = presentation()?.value(forKey: event) ?? radius
animation.duration = action.duration
animation.speed = action.speed
animation.timeOffset = action.timeOffset
animation.repeatCount = action.repeatCount
animation.repeatDuration = action.repeatDuration
animation.autoreverses = action.autoreverses
animation.fillMode = action.fillMode
animation.timingFunction = action.timingFunction
animation.delegate = action.delegate
return animation
}
// MARK:- CALayer methods
override func layoutSubviews() {
super.layoutSubviews()
cornerShapeLayer.frame = bounds
updateCornerShape()
override func display() {
self.path = currentPath()
}
// MARK:- Private methods
private func updateCornerShape() {
private func currentPath() -> CGPath? {
guard let corner = corner else {
cornerShapeLayer.path = nil
return
return nil
}
let side = cornerRadius
let side = presentation()?.radius ?? radius
let size = CGSize(width: side, height: side)
let targetRect = CGRect(withCorner: corner, at: bounds.getCorner(corner), size: size)
......@@ -114,9 +175,7 @@ final class CornerView: UIView {
}
}()
cornerShapeLayer.path = path.cgPath
return path.cgPath
}
}
......@@ -13,7 +13,10 @@ final class RoundedView: UIView {
// MARK:- Public variables
public var cornerRadius = Constants.cornerRadius {
didSet { layoutSubviews() }
didSet {
leftCorner.cornerRadius = cornerRadius
rightCorner.cornerRadius = cornerRadius
}
}
// MARK:- Private variables
......@@ -42,6 +45,9 @@ final class RoundedView: UIView {
leftCorner.corner = .topLeft
rightCorner.corner = .topRight
leftCorner.cornerRadius = cornerRadius
rightCorner.cornerRadius = cornerRadius
leftCorner.translatesAutoresizingMaskIntoConstraints = false
rightCorner.translatesAutoresizingMaskIntoConstraints = false
......@@ -49,16 +55,13 @@ final class RoundedView: UIView {
addSubview(rightCorner)
}
// MARK:- UUView methods
// MARK:- UIView methods
override func layoutSubviews() {
super.layoutSubviews()
leftCorner.frame = bounds
rightCorner.frame = bounds
leftCorner.cornerRadius = cornerRadius
rightCorner.cornerRadius = cornerRadius
}
}
PODS:
- DeckTransition (1.2.0)
- ObjectMapper (2.2.8)
- DeckTransition (1.4.0)
- ObjectMapper (3.0.0)
- Realm (2.10.1):
- Realm/Headers (= 2.10.1)
- Realm/Headers (2.10.1)
......@@ -8,16 +8,16 @@ PODS:
- Realm (= 2.10.1)
DEPENDENCIES:
- DeckTransition (~> 1.0)
- ObjectMapper (~> 2.2)
- DeckTransition (~> 1.4.0)
- ObjectMapper (~> 3.0)
- RealmSwift
SPEC CHECKSUMS:
DeckTransition: 23a2c7bdb24bf740a460da72cbb25f9dd28d0a51
ObjectMapper: 3d571bb5af471c779e1160828cd9ad5c4ef90958
DeckTransition: d678005a9b35c2745fe8b683c39c41f840ad69d7
ObjectMapper: 92230db59bf8f341a5c3a3cf0b9fbdde3cf0d87f
Realm: fc7a317a5c2c9ba91f5f235ede4e2ea76e9eba0c
RealmSwift: 505ed6c15942a2e76f5cfa78a8667cfa997ee75b
PODFILE CHECKSUM: 84f488897b7d96947925a9d04edb67bb66a2cde9
PODFILE CHECKSUM: 5595b2ebe2214e0d557d3eac7bdc9dbb3b2edf20
COCOAPODS: 1.3.1
......@@ -36,7 +36,7 @@ open class HexColorTransform: TransformType {
if let rgba = value as? String {
if rgba.hasPrefix("#") {
let index = rgba.characters.index(rgba.startIndex, offsetBy: 1)
let hex = rgba.substring(from: index)
let hex = String(rgba[index...])
return getColor(hex: hex)
} else {
return getColor(hex: rgba)
......
......@@ -28,14 +28,20 @@
import Foundation
public extension DateFormatter {
public convenience init(withFormat format : String, locale : String) {
self.init()
self.locale = Locale(identifier: locale)
dateFormat = format
}
}
open class ISO8601DateTransform: DateFormatterTransform {
static let reusableISODateFormatter = DateFormatter(withFormat: "yyyy-MM-dd'T'HH:mm:ssZZZZZ", locale: "en_US_POSIX")
public init() {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
super.init(dateFormatter: formatter)
super.init(dateFormatter: ISO8601DateTransform.reusableISODateFormatter)
}
}
......@@ -108,7 +108,8 @@ public extension Map {
guard let jsonArray = currentValue as? [Any] else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '[Any]'", file: file, function: function, line: line)
}
return try jsonArray.enumerated().map { i, JSONObject -> T in
return try jsonArray.map { JSONObject -> T in
return try Mapper<T>(context: context).mapOrFail(JSONObject: JSONObject)
}
}
......@@ -119,9 +120,10 @@ public extension Map {
guard let jsonArray = currentValue as? [Any] else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '[Any]'", file: file, function: function, line: line)
}
return try jsonArray.enumerated().map { i, json -> Transform.Object in
return try jsonArray.map { json -> Transform.Object in
guard let object = transform.transformFromJSON(json) else {
throw MapError(key: "\(key)[\(i)]", currentValue: json, reason: "Cannot transform to '\(Transform.Object.self)' using \(transform)", file: file, function: function, line: line)
throw MapError(key: "\(key)", currentValue: json, reason: "Cannot transform to '\(Transform.Object.self)' using \(transform)", file: file, function: function, line: line)
}
return object
}
......@@ -183,16 +185,16 @@ public extension Map {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '[[Any]]'",
file: file, function: function, line: line)
}
return try json2DArray.enumerated().map { i, jsonArray in
try jsonArray.enumerated().map { j, json -> Transform.Object in
return try json2DArray.map { jsonArray in
try jsonArray.map { json -> Transform.Object in
guard let object = transform.transformFromJSON(json) else {