diff --git a/Example/Kinetic.xcodeproj/project.pbxproj b/Example/Kinetic.xcodeproj/project.pbxproj index be90054..462e93e 100644 --- a/Example/Kinetic.xcodeproj/project.pbxproj +++ b/Example/Kinetic.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 2B61D64D1F2D4A7F009C9F91 /* PropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B61D64C1F2D4A7F009C9F91 /* PropertyTests.swift */; }; 2B61D64F1F2D6837009C9F91 /* TweenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B61D64E1F2D6837009C9F91 /* TweenTests.swift */; }; 2B61D6511F2D719D009C9F91 /* TimelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B61D6501F2D719D009C9F91 /* TimelineTests.swift */; }; + 2B64C12522076DCC002B8119 /* AnchorPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B64C12422076DCC002B8119 /* AnchorPointViewController.swift */; }; 2B92960F1E46B11900056AAC /* AdditiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B9296001E46B11900056AAC /* AdditiveViewController.swift */; }; 2B9296101E46B11900056AAC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B9296011E46B11900056AAC /* AppDelegate.swift */; }; 2B9296111E46B11900056AAC /* BasicFromToTweenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B9296021E46B11900056AAC /* BasicFromToTweenViewController.swift */; }; @@ -57,6 +58,7 @@ 2B61D64C1F2D4A7F009C9F91 /* PropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyTests.swift; sourceTree = ""; }; 2B61D64E1F2D6837009C9F91 /* TweenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweenTests.swift; sourceTree = ""; }; 2B61D6501F2D719D009C9F91 /* TimelineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTests.swift; sourceTree = ""; }; + 2B64C12422076DCC002B8119 /* AnchorPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnchorPointViewController.swift; sourceTree = ""; }; 2B9296001E46B11900056AAC /* AdditiveViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdditiveViewController.swift; sourceTree = ""; }; 2B9296011E46B11900056AAC /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 2B9296021E46B11900056AAC /* BasicFromToTweenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicFromToTweenViewController.swift; sourceTree = ""; }; @@ -156,6 +158,7 @@ 2B9296011E46B11900056AAC /* AppDelegate.swift */, 2B92960E1E46B11900056AAC /* ViewController.swift */, 2B9296001E46B11900056AAC /* AdditiveViewController.swift */, + 2B64C12422076DCC002B8119 /* AnchorPointViewController.swift */, 2B9296021E46B11900056AAC /* BasicFromToTweenViewController.swift */, 2B9296031E46B11900056AAC /* BasicFromTweenViewController.swift */, 2B9296041E46B11900056AAC /* BasicTweenViewController.swift */, @@ -430,6 +433,7 @@ 2B9296101E46B11900056AAC /* AppDelegate.swift in Sources */, 2B9296191E46B11900056AAC /* SequenceViewController.swift in Sources */, 2B5FD5DD1F2ECAD10085B250 /* PathTweenViewController.swift in Sources */, + 2B64C12522076DCC002B8119 /* AnchorPointViewController.swift in Sources */, 2B92961A1E46B11900056AAC /* StaggerViewController.swift in Sources */, 2B92961C1E46B11900056AAC /* TransformViewController.swift in Sources */, 2B9296171E46B11900056AAC /* PhysicsViewController.swift in Sources */, @@ -523,7 +527,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -570,7 +574,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/Example/Kinetic/AnchorPointViewController.swift b/Example/Kinetic/AnchorPointViewController.swift new file mode 100644 index 0000000..b940de0 --- /dev/null +++ b/Example/Kinetic/AnchorPointViewController.swift @@ -0,0 +1,117 @@ +// +// AnchorPointViewController.swift +// Kinetic_Example +// +// Created by Nicholas Shipes on 2/3/19. +// Copyright © 2019 CocoaPods. All rights reserved. +// + +import UIKit +import Kinetic + +class AnchorPointViewController: ExampleViewController { + var square: UIView! + var squareContainer: UIView! + + fileprivate let pickerView = UIPickerView() + fileprivate var pickerData: [String] = [] + fileprivate var tween: Tween? + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + title = "Anchor Point" + pickerData = [ + "Top-Left", + "Top", + "Top-Right", + "Left", + "Center", + "Right", + "Bottom-Left", + "Bottom", + "Bottom-Right" + ] + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + pickerView.translatesAutoresizingMaskIntoConstraints = false + pickerView.dataSource = self + pickerView.delegate = self + view.addSubview(pickerView) + + square = UIView() + square.translatesAutoresizingMaskIntoConstraints = false + square.backgroundColor = .red + + /** + * `TransformContainerView` is a UIView subclass in Kinetic to be used when adjusting a view's anchorPoint. + * + * Need to place our animated view within a transparent container view since we will be adjusting the view's anchorPoint + * and applying a transform, which will prevent it from shifting the view's actual position within the view and allows + * us to still use AutoLayout for positioning the view + * re: https://stackoverflow.com/a/14105757 + */ + squareContainer = TransformContainerView(view: square) + view.addSubview(squareContainer) + + /* + * Since we're placing the animated square within a container view, we set position constraints on the container view + * and then width and height constraints on the actual square view, which will also size the container automatically + */ + NSLayoutConstraint.activate([squareContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 100), + squareContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor), + square.widthAnchor.constraint(equalToConstant: 100), + square.heightAnchor.constraint(equalToConstant: 100), + pickerView.topAnchor.constraint(equalTo: squareContainer.bottomAnchor, constant: 100), + pickerView.leftAnchor.constraint(equalTo: view.leftAnchor), + pickerView.rightAnchor.constraint(equalTo: view.rightAnchor), + pickerView.heightAnchor.constraint(equalToConstant: 250)]) + + let tween = square.tween().to(Rotation(Float.pi * 2)).duration(1.0).ease(Cubic.easeInOut) + animation = tween + self.tween = tween + + pickerView.selectRow(4, inComponent: 0, animated: false) + } +} + +extension AnchorPointViewController: UIPickerViewDelegate, UIPickerViewDataSource { + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return pickerData.count + } + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return pickerData[row] + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + var anchor = AnchorPoint.center + + switch row { + case 0: anchor = .topLeft + case 1: anchor = .top + case 2: anchor = .topRight + case 3: anchor = .left + case 4: anchor = .center + case 5: anchor = .right + case 6: anchor = .bottomLeft + case 7: anchor = .bottom + case 8: anchor = .bottomRight + default: anchor = .center + } + + tween?.anchor(anchor) + } +} diff --git a/Example/Kinetic/ExampleViewController.swift b/Example/Kinetic/ExampleViewController.swift index 50676e6..ee43551 100644 --- a/Example/Kinetic/ExampleViewController.swift +++ b/Example/Kinetic/ExampleViewController.swift @@ -34,6 +34,8 @@ class ExampleViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + + view.backgroundColor = .white playButton = UIButton(type: .roundedRect) playButton.translatesAutoresizingMaskIntoConstraints = false diff --git a/Example/Kinetic/ViewController.swift b/Example/Kinetic/ViewController.swift index 0bf6272..44b61bb 100644 --- a/Example/Kinetic/ViewController.swift +++ b/Example/Kinetic/ViewController.swift @@ -43,6 +43,7 @@ class ViewController: UIViewController { rows.append(GroupTweenViewController()) rows.append(SequenceViewController()) rows.append(TransformViewController()) + rows.append(AnchorPointViewController()) rows.append(AdditiveViewController()) rows.append(PhysicsViewController()) rows.append(StaggerViewController()) diff --git a/Kinetic.podspec b/Kinetic.podspec index 51ab5ed..85e67ed 100644 --- a/Kinetic.podspec +++ b/Kinetic.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |s| s.name = "Kinetic" - s.version = "1.0.1" + s.version = "1.1.0" s.summary = "A super-flexible tweening library for iOS in Swift inspired by GSAP." s.description = <<-DESC diff --git a/Pod/Classes/Animator.swift b/Pod/Classes/Animator.swift index 4a49872..1f16f8c 100644 --- a/Pod/Classes/Animator.swift +++ b/Pod/Classes/Animator.swift @@ -118,6 +118,7 @@ final public class Animator: Equatable { fileprivate (set) public var duration: Double fileprivate (set) public var key: String public var timingFunction: TimingFunction = Linear().timingFunction + public var anchorPoint: CGPoint = CGPoint(x: 0.5, y: 0.5) public var additive: Bool = true public var changed: ((Animator, Property) -> Void)? public var finished: Bool { diff --git a/Pod/Classes/Tween.swift b/Pod/Classes/Tween.swift index ca9987c..adc76e3 100644 --- a/Pod/Classes/Tween.swift +++ b/Pod/Classes/Tween.swift @@ -77,8 +77,8 @@ public class Tween: Animation { } fileprivate var propertiesByType: Dictionary = [String: FromToValue]() private(set) var animators = [String: Animator]() - fileprivate var needsPropertyPrep = false + fileprivate var anchorPoint: CGPoint = AnchorPoint.center.point() public var additive = true; @@ -88,6 +88,11 @@ public class Tween: Animation { self.target = target super.init() + self.on(.started) { [unowned self] (animation) in + guard var view = self.target as? UIView else { return } + view.anchorPoint = self.anchorPoint + } + TweenCache.session.cache(self, target: target) } @@ -145,9 +150,7 @@ public class Tween: Animation { @discardableResult open func anchorPoint(_ point: CGPoint) -> Tween { - if var view = target as? ViewType { - view.anchorPoint = point - } + anchorPoint = point return self } @@ -371,6 +374,7 @@ public class Tween: Animation { let animator = Animator(from: transformFrom, to: transformTo, duration: duration, timingFunction: timingFunction) animator.spring = spring animator.additive = false + animator.anchorPoint = anchorPoint animator.onChange({ [weak self] (animator, value) in self?.target.apply(value) }) diff --git a/Pod/Classes/Tweenable.swift b/Pod/Classes/Tweenable.swift index f6df40c..1762878 100644 --- a/Pod/Classes/Tweenable.swift +++ b/Pod/Classes/Tweenable.swift @@ -253,6 +253,23 @@ extension ViewType where Self: UIView { return layer.anchorPoint } set { + // adjust the layer's anchorPoint without moving the view + // re: https://www.hackingwithswift.com/example-code/calayer/how-to-change-a-views-anchor-point-without-moving-it + var newPoint = CGPoint(x: bounds.size.width * newValue.x, y: bounds.size.height * newValue.y) + var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y); + + newPoint = newPoint.applying(transform) + oldPoint = oldPoint.applying(transform) + + var position = layer.position + + position.x -= oldPoint.x + position.x += newPoint.x + + position.y -= oldPoint.y + position.y += newPoint.y + + layer.position = position layer.anchorPoint = newValue } } @@ -325,3 +342,23 @@ extension ViewType where Self: CALayer { } } } + +public class TransformContainerView: UIView { + + public init(view: UIView) { + super.init(frame: .zero) + + self.translatesAutoresizingMaskIntoConstraints = false + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + + NSLayoutConstraint.activate([view.topAnchor.constraint(equalTo: self.topAnchor), + self.bottomAnchor.constraint(equalTo: view.bottomAnchor), + self.leftAnchor.constraint(equalTo: view.leftAnchor), + self.rightAnchor.constraint(equalTo: view.rightAnchor)]) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +}