Circular Transition, change initial circle size - ios

I have a circular transition you can find in the code below. This transition creates an extending circle with the next view controller inside.
It works fine, but I've been trying to increase the circle size on start for a while now.
To be exact, currently on execution the circle will be drawn from zero, but I want it to have an initial size which appears immediately, from where it starts extending.
How can I change the initial size of the circle?
import UIKit
class CircularTransition: NSObject {
var circle = UIView()
var startingPoint = CGPoint.zero {
didSet {
circle.center = startingPoint
}
}
var circleColor = UIColor.white
var duration = 0.2
enum CircularTransitionMode:Int {
case present, dismiss, pop
}
var transitionMode:CircularTransitionMode = .present
}
extension CircularTransition:UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
let viewCenter = presentedView.center
let viewSize = presentedView.frame.size
circle = UIView()
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
circle.backgroundColor = circleColor
circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
containerView.addSubview(circle)
presentedView.center = startingPoint
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.alpha = 0
containerView.addSubview(presentedView)
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform.identity
presentedView.transform = CGAffineTransform.identity
presentedView.alpha = 1
presentedView.center = viewCenter
}, completion: { (success:Bool) in
transitionContext.completeTransition(success)
})
}
}else{
let transitionModeKey = (transitionMode == .pop) ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
if let returningView = transitionContext.view(forKey: transitionModeKey) {
let viewCenter = returningView.center
let viewSize = returningView.frame.size
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.center = self.startingPoint
returningView.alpha = 0
if self.transitionMode == .pop {
containerView.insertSubview(returningView, belowSubview: returningView)
containerView.insertSubview(self.circle, belowSubview: returningView)
}
}, completion: { (success:Bool) in
returningView.center = viewCenter
returningView.removeFromSuperview()
self.circle.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
}
func frameForCircle (withViewCenter viewCenter:CGPoint, size viewSize:CGSize, startPoint:CGPoint) -> CGRect {
let xLength = fmax(startPoint.x, viewSize.width - startPoint.x)
let yLength = fmax(startPoint.y, viewSize.height - startPoint.y)
let offestVector = sqrt(xLength * xLength + yLength * yLength) * 2
let size = CGSize(width: offestVector, height: offestVector)
return CGRect(origin: CGPoint.zero, size: size)
}
}

Related

Cloud moving animation in Swift? Is it possible?

We are following this tutorial to http://www.invasivecode.com/weblog/core-animation-scroll-layer-cascrolllayer
As per their tutorial animation will stop based on the layer bounds reached. How to do the cloud moving animation by using core animation or in Imageview?
Is it possible?
Gif or Lottie animation not needed here, we excepting through code.
Note that we have tried their code and its working but lack is there as i mentioned above.
How to keep on moving animation like cloud does?
Think twice before devoting this question.
Code:
class ViewController: UIViewController {
var translation: CGFloat = 0.0
public var currentWidth : CGFloat = {
let width = UIScreen.main.bounds.width
return width
}()
public var currentHeight : CGFloat = {
let width = UIScreen.main.bounds.height
return width
}()
lazy var scrollLayer : CAScrollLayer = {
let scrollLayer = CAScrollLayer() // 8
scrollLayer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth, height: currentHeight) // 9
scrollLayer.position = CGPoint(x: self.view.bounds.size.width/2, y: self.view.bounds.size.height/2) // 10
scrollLayer.borderColor = UIColor.black.cgColor // 11
// scrollLayer.borderWidth = 5.0 // 12
scrollLayer.scrollMode = CAScrollLayerScrollMode.horizontally // 13
return scrollLayer
}()
lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(scrollLayerScroll))
if #available(iOS 15.0, *) {
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 5.0, maximum: 8.0, __preferred: 6.0)
} else {
displayLink.preferredFramesPerSecond = 5
// Fallback on earlier versions
}
return displayLink
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let img = UIImage(named: "new") { // 1
let imageSize = img.size
let layer = CALayer() // 2
layer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth * 50, height: currentHeight * 5) // 3
// layer.position = CGPoint(x: imageSize.width/2, y: imageSize.height/2) // 4
layer.contents = img.cgImage // 5
view.layer.addSublayer(scrollLayer) // 6
scrollLayer.addSublayer(layer) // 7
}
displayLink.add(to: RunLoop.current, forMode: .common)
}
#objc func scrollLayerScroll() {
let newPoint = CGPoint(x: translation, y: 0.0)
scrollLayer.scroll(newPoint)
translation += 10.0
// if translation > 1600.0 {
//// stopDisplayLink()
// }
}
func stopDisplayLink() {
displayLink.invalidate()
}
}
Here setting width too high for animating the layer extra time
layer.bounds = CGRect(x: 0.0, y: 0.0, width: currentWidth * 50, height: currentHeight * 5) // 3
We ended up with following solution by rob mayoff
Animate infinite scrolling of an image in a seamless loop
Please find the updated code.
class CloudPassingVC: UIViewController {
#IBOutlet weak var imageToAnimate: UIImageView!
var cloudLayer: CALayer? = nil
var cloudLayerAnimation: CABasicAnimation? = nil
override func viewDidLoad() {
super.viewDidLoad()
cloudScroll()
}
func cloudScroll() {
let cloudsImage = UIImage(named: "clouds")
let cloudPattern = UIColor(patternImage: cloudsImage!)
cloudLayer = CALayer()
cloudLayer!.backgroundColor = cloudPattern.cgColor
cloudLayer!.transform = CATransform3DMakeScale(1, -1, 1)
cloudLayer!.anchorPoint = CGPoint(x: 0, y: 1)
let viewSize = imageToAnimate.bounds.size
cloudLayer!.frame = CGRect(x: 0, y: 0, width: (cloudsImage?.size.width ?? 0.0) + viewSize.width, height: viewSize.height)
imageToAnimate.layer.addSublayer(cloudLayer!)
let startPoint = CGPoint.zero
let endPoint = CGPoint(x: -(cloudsImage?.size.width ?? 0.0), y: 0)
cloudLayerAnimation = CABasicAnimation(keyPath: "position")
cloudLayerAnimation!.timingFunction = CAMediaTimingFunction(name: .linear)
cloudLayerAnimation!.fromValue = NSValue(cgPoint: startPoint)
cloudLayerAnimation!.toValue = NSValue(cgPoint: endPoint)
cloudLayerAnimation!.repeatCount = 600
cloudLayerAnimation!.duration = 10.0
applyCloudLayerAnimation()
}
func applyCloudLayerAnimation() {
cloudLayer!.add(cloudLayerAnimation!, forKey: "position")
}
}

iOS 13 breaks UIViewPropertyAnimator transition

We have a broken transition in WeatherKit only reproducible on iOS 13 beta. We're unsure if this is an UIKit bug or we're doing something awfully wrong.
With an array of UIViewPropertyAnimator working before iOS 13, ever since iOS 13 (through all of the betas) the animation frame is not updating correctly. For example, I have an UIViewPropertyAnimator called labelAnimator which animates a label to some specific CGRect, that CGRect is not respected and the label animates somewhere else as shown in the video.
Curious enough, if I mess around with the order of the transitions in the array, the bottom sheet works fine and the only one that animates wrong is the temperature label.
Here's the code that animates that whole view:
class MainView: UIViewController {
var panGesture = UIPanGestureRecognizer()
var tapGesture = UITapGestureRecognizer()
let animationDuration: TimeInterval = 0.75
var diff: CGFloat = 150
#IBOutlet weak var gradientView: GradientView!
#IBOutlet weak var detailedViewContainer: UIView!
#IBOutlet weak var blurView: UIVisualEffectView!
override func viewDidLoad() {
self.panGesture.addTarget(self, action: #selector(MainView.handlePanGesture(gesture:)))
self.detailedViewContainer.addGestureRecognizer(self.panGesture)
self.tapGesture.addTarget(self, action: #selector(MainView.handleTapGesture(gesture:)))
self.detailedViewContainer.addGestureRecognizer(self.tapGesture)
}
enum PanelState {
case expanded
case collapsed
}
var nextState: PanelState {
return panelIsVisible ? .collapsed : .expanded
}
var panelIsVisible: Bool = false
var runningAnimations = [UIViewPropertyAnimator]()
var animationProgressWhenInterrupted: CGFloat = 0.0
#objc func handleTapGesture(gesture: UITapGestureRecognizer) {
switch gesture.state {
case .ended:
tapAnimation()
default: break
}
}
#objc func tapAnimation(){
self.panGesture.isEnabled = false
self.tapGesture.isEnabled = false
startInteractiveTransition(state: nextState, duration: animationDuration)
updateInteractiveTransition(fractionComplete: 0)
let linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.8, y: -0.16), controlPoint2: CGPoint(x: 0.22, y: 1.18))
continueInteractiveTransition(timingParameters: linearTiming){
self.panGesture.isEnabled = true
self.tapGesture.isEnabled = true
}
}
#objc func handlePanGesture(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
if !panelIsVisible ? gesture.velocity(in: nil).y < 0 : gesture.velocity(in: nil).y > 0 {
startInteractiveTransition(state: nextState, duration: animationDuration)
}
case .changed:
let translation = gesture.translation(in: self.detailedViewContainer)
var fractionComplete = (translation.y / view.bounds.height * 2)
fractionComplete = !panelIsVisible ? -fractionComplete : fractionComplete
updateInteractiveTransition(fractionComplete: fractionComplete)
case .ended:
let linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.8, y: -0.16), controlPoint2: CGPoint(x: 0.22, y: 1.18))
continueInteractiveTransition(timingParameters: linearTiming) {
self.panGesture.isEnabled = true
self.tapGesture.isEnabled = true
}
NotificationCenter.default.post(name: .resetHeaders, object: nil)
NotificationCenter.default.post(name: .disableScrolling, object: nil, userInfo: ["isDisabled": nextState == .collapsed])
default:
break
}
}
// MARK: - Animations
func animateTransitionIfNeeded(state: PanelState, duration: TimeInterval) {
if runningAnimations.isEmpty {
// MARK: Frame
var linearTiming = UICubicTimingParameters(animationCurve: .easeOut)
linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.1, y: 0.1), controlPoint2: CGPoint(x: 0.1, y: 0.1))
let frameAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
frameAnimator.addAnimations {
switch state {
case .expanded:
self.detailedViewContainer.frame = CGRect(x: 0, y: self.diff, width: self.view.bounds.width, height: self.view.bounds.height - self.diff)
case .collapsed:
self.detailedViewContainer.frame = CGRect(x: 0, y: self.view.bounds.height - self.view.safeAreaInsets.bottom - 165, width: self.view.bounds.width, height: 200)
}
}
// MARK: Arrow
let arrowAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
arrowAnimator.addAnimations {
switch state {
case .expanded:
self.leftArrowPath.transform = CGAffineTransform(rotationAngle: 15 * CGFloat.pi / 180)
self.rightArrowPath.transform = CGAffineTransform(rotationAngle: 15 * -CGFloat.pi / 180)
case .collapsed:
self.leftArrowPath.transform = CGAffineTransform(rotationAngle: 15 * -CGFloat.pi / 180)
self.rightArrowPath.transform = CGAffineTransform(rotationAngle: 15 * CGFloat.pi / 180)
}
self.leftArrowPath.center.y = self.detailedViewContainer.frame.origin.y + 15
self.rightArrowPath.center.y = self.detailedViewContainer.frame.origin.y + 15
}
// MARK: Scale
let radiusAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
radiusAnimator.addAnimations{
switch state {
case .expanded:
self.gradientView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
self.gradientView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
self.gradientView.layer.cornerRadius = dataS.hasTopNotch ? 20 : 14
case .collapsed:
self.gradientView.transform = CGAffineTransform.identity
self.gradientView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
self.gradientView.layer.cornerRadius = 0
}
}
// MARK: Blur
let blurTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.5, y: 0.25), controlPoint2: CGPoint(x: 0.5, y: 0.75))
let blurAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: blurTiming)
blurAnimator.addAnimations {
switch state {
case .expanded:
self.blurView.effect = UIBlurEffect(style: .dark)
case .collapsed:
self.blurView.effect = nil
}
}
// MARK: Text
let textAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
textAnimator.addAnimations({
switch state{
case .expanded:
self.tempLabel.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
self.tempLabel.frame = CGRect(origin: CGPoint(x: 15, y: self.diff / 2 - self.tempLabel.frame.height / 2), size: self.tempLabel.frame.size)
self.descriptionLabel.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
self.descriptionLabel.alpha = 0
self.descriptionLabel.transform = CGAffineTransform(translationX: 0, y: -100)
self.summaryLabel.frame = CGRect(origin: CGPoint(x: self.blurView.contentView.center.x, y: 10), size: self.summaryLabel.frame.size)
case .collapsed:
self.descriptionLabel.transform = CGAffineTransform.identity
self.descriptionLabel.alpha = 1
self.tempLabel.transform = CGAffineTransform.identity
self.tempLabel.frame = CGRect(origin: CGPoint(x: 15, y: self.view.frame.height / 2 - self.tempLabel.frame.height / 2 - 30), size: self.tempLabel.frame.size)
self.summaryLabel.frame = CGRect(origin: CGPoint(x: self.blurView.contentView.center.x, y: self.tempLabel.center.y - self.summaryLabel.frame.height / 2), size: self.summaryLabel.frame.size)
}
}, delayFactor: 0.0)
let summaryLabelTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.05, y: 0.95), controlPoint2: CGPoint(x: 0.15, y: 0.95))
let summaryLabelTimingReverse = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.95, y: 0.5), controlPoint2: CGPoint(x: 0.85, y: 0.05))
// MARK: Summary Label
let summaryLabelAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: state == .collapsed ? summaryLabelTiming : summaryLabelTimingReverse)
summaryLabelAnimator.addAnimations {
switch state{
case .expanded:
self.summaryLabel.alpha = 1
case .collapsed:
self.summaryLabel.alpha = 0
}
}
radiusAnimator.startAnimation()
runningAnimations.append(radiusAnimator)
blurAnimator.scrubsLinearly = false
blurAnimator.startAnimation()
runningAnimations.append(blurAnimator)
summaryLabelAnimator.scrubsLinearly = false
summaryLabelAnimator.startAnimation()
runningAnimations.append(summaryLabelAnimator)
frameAnimator.startAnimation()
runningAnimations.append(frameAnimator)
textAnimator.startAnimation()
textAnimator.pauseAnimation()
runningAnimations.append(textAnimator)
arrowAnimator.startAnimation()
runningAnimations.append(arrowAnimator)
// Clear animations when completed
runningAnimations.last?.addCompletion { _ in
self.runningAnimations.removeAll()
self.panelIsVisible = !self.panelIsVisible
textAnimator.startAnimation()
}
}
}
/// Called on pan .began
func startInteractiveTransition(state: PanelState, duration: TimeInterval) {
if runningAnimations.isEmpty {
animateTransitionIfNeeded(state: state, duration: duration)
for animator in runningAnimations {
animator.pauseAnimation()
animationProgressWhenInterrupted = animator.fractionComplete
}
}
let hapticSelection = SelectionFeedbackGenerator()
hapticSelection.prepare()
hapticSelection.selectionChanged()
}
/// Called on pan .changed
func updateInteractiveTransition(fractionComplete: CGFloat) {
for animator in runningAnimations {
animator.fractionComplete = fractionComplete + animationProgressWhenInterrupted
}
}
/// Called on pan .ended
func continueInteractiveTransition(timingParameters: UICubicTimingParameters? = nil, durationFactor: CGFloat = 0, completion: #escaping ()->()) {
for animator in runningAnimations {
animator.continueAnimation(withTimingParameters: timingParameters, durationFactor: durationFactor)
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + animationDuration) {
completion()
}
}
}
And here's a video of the issue in iOS 13 and how it currently works in iOS 12.
I have same issue, for me UIViewPropertyAnimator continueAnimation durationFactor parameter is the issue, whenever it is not 0, after few animation the table view goes crazy.

Custom UIViewController transition doesn't next animations perform

When I use the custom UIViewControllerAnimatedTransitioning to do a custom transition from AViewController to BViewController it performs as expected, using a Cubic Animation, but then when I try to use a default iOS presentation, like a modal to transition from BViewController to CViewController, it doesn't animate at all.
Here's the code of the first transition:
class CubeTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private var direction: Direction!
convenience init(direction: Direction) {
self.init()
self.direction = direction
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else {
return
}
toView.frame = fromView.frame
containerView.backgroundColor = UIColor.white
let fromViewIntialTransform = fromView.layer.transform
fromView.layer.anchorPointZ = -((fromView.frame.size.width) / 2)
var transform: CATransform3D = CATransform3DIdentity
transform.m34 = -1.0 / 1000
transform = CATransform3DTranslate(transform, 0, 0, (fromView.layer.anchorPointZ))
fromView.layer.transform = transform
containerView.addSubview(fromView)
toView.alpha = 1
fromView.alpha = 1
let toViewIntialTransform = toView.layer.transform
toView.layer.anchorPointZ = -((toView.frame.size.width) / 2)
transform = CATransform3DIdentity
transform.m34 = -1.0 / 1000
transform = CATransform3DTranslate(transform, 0, 0, (toView.layer.anchorPointZ))
toView.layer.transform = transform
containerView.insertSubview(toView, belowSubview:fromView)
// transform toView to it's begining position
switch direction {
case .left: // t angle x y z
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 0, 1, 0)
case .right:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
case .up:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi/2), 1, 0, 0)
case .down:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 1, 0, 0)
default:
break
}
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: UIViewAnimationOptions(), animations: {
// Animate toView to go to fromView initial position,
// And the fromView to go to a new position
switch self.direction {
case .left: // t angle x y z
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
case .right:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi / 2), 0, 1, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(-Double.pi / 2), 0, 1, 0)
case .up:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi/2), 1, 0, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(Double.pi / 2), 1, 0, 0)
case .down:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 1, 0, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(-Double.pi / 2), 1, 0, 0)
default:
break
}
}, completion: {(value: Bool) in
fromView.layer.transform = fromViewIntialTransform
toView.layer.transform = toViewIntialTransform
fromView.layoutSubviews()
toView.layoutSubviews()
// This undo the transition if it's cancelled
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
The thing gets even stranger when I use another transition to perform the segue from A to B, the transition from B to C starts to working again.
Second transition:
class PopTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 0.3
var originFrame = CGRect.zero
var presenting = true
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let detailView = presenting ? toView : transitionContext.view(forKey: .from)!
let initialFrame = presenting ? originFrame : detailView.frame
let finalFrame = presenting ? detailView.frame : originFrame
let xScaleFactor = presenting
? initialFrame.width / finalFrame.width
: finalFrame.width / initialFrame.width
let yScaleFactor = presenting
? initialFrame.height / finalFrame.height
: finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
detailView.transform = scaleTransform
detailView.center = CGPoint(
x: initialFrame.midX,
y: initialFrame.midY)
}
containerView.addSubview(toView)
containerView.bringSubview(toFront: detailView)
UIView.animate(
withDuration: duration,
delay: 0.0,
animations: {
detailView.transform = self.presenting ? .identity : scaleTransform
detailView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
},
completion:{_ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
)
}
}
You are modifying toView.layer.anchorPointZ and fromView.layer.anchorPointZ so when the transition from B to C is about to happen, the anchorPointZ is messed up.
Try saving and reseting it like you do with fromViewIntialTransform and toViewIntialTransform.

Draw resizable rectangle using swift 3

How to draw resizable rectangle in UIView , i did many search on google and github and i found this one Click Here using swift 2.3 and i converted it to swift 3 .. but i can't resize rectangle after drawing it and thats the code
//
// ResizableRectangleView.swift
// DrawShapes
//
// Created by Jordan Focht on 3/9/15.
// Copyright (c) 2015 Jordan Focht. All rights reserved.
//
import Foundation
import UIKit
private let DefaultTint = UIColor(red: 0, green: 164 / 255.0, blue: 1.0, alpha: 1.0).cgColor
private let DefaultStrokeTint = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0).cgColor
private let ClearColor = UIColor.clear.cgColor
private let DefaultCircleRadius: CGFloat = 8
private let CornerTouchSize: CGFloat = 44
protocol ResizableRectangleViewDelegate : class {
func didSelectResizableRectangleView(_ view: ResizableRectangleView)
func didDeselectResizableRectangleView(_ view: ResizableRectangleView)
}
class ResizableRectangleView: UIControl {
fileprivate var borderLayer: CALayer = CALayer()
fileprivate var topLeftCircle = CALayer()
fileprivate var topRightCircle = CALayer()
fileprivate var bottomLeftCircle = CALayer()
fileprivate var bottomRightCircle = CALayer()
weak var delegate: ResizableRectangleViewDelegate?
var strokeTintColor: CGColor = DefaultStrokeTint
var circleRadius: CGFloat = DefaultCircleRadius
var nLocation : CGPoint!
override var frame: CGRect {
get {
return super.frame
}
set {
super.frame = newValue
self.updateLayers()
}
}
override var isSelected: Bool {
get {
return super.isSelected
}
set {
let changed = self.isSelected != newValue
super.isSelected = newValue
if changed {
if isSelected {
self.delegate?.didSelectResizableRectangleView(self)
} else {
self.delegate?.didDeselectResizableRectangleView(self)
}
}
}
}
func updateLayers() {
if self.layer.sublayers == nil {
self.layer.addSublayer(self.borderLayer)
self.layer.addSublayer(self.topLeftCircle)
self.layer.addSublayer(self.topRightCircle)
self.layer.addSublayer(self.bottomLeftCircle)
self.layer.addSublayer(self.bottomRightCircle)
let layers = (self.layer.sublayers ?? []) as [CALayer]
for layer in layers {
layer.contentsScale = UIScreen.main.scale
}
}
self.updateBorderLayer()
let circleFrame = self.borderLayer.frame
updateCircleLayer(topLeftCircle, center: CGPoint(x: circleFrame.origin.x, y: circleFrame.origin.y))
updateCircleLayer(topRightCircle, center: CGPoint(x: circleFrame.origin.x, y: circleFrame.maxY))
updateCircleLayer(bottomLeftCircle, center: CGPoint(x: circleFrame.maxX, y: circleFrame.origin.y))
updateCircleLayer(bottomRightCircle, center: CGPoint(x: circleFrame.maxX, y: circleFrame.maxY))
}
func borderedFrame() -> CGRect {
return self.borderLayer.frame
}
// var trackingFrameTransform: ((CGPoint) -> ())?
func moveFrame(_ originalFrame: CGRect, initialTouchLocation: CGPoint, _ location: CGPoint) {
let targetX = originalFrame.origin.x + location.x - initialTouchLocation.x
let targetY = originalFrame.origin.y + location.y - initialTouchLocation.y
let insetBounds = self.insetBounds()
self.frame.origin.x = max(insetBounds.origin.x, min(insetBounds.maxX - self.frame.width, targetX))
self.frame.origin.y = max(insetBounds.origin.y, min(insetBounds.maxY - self.frame.height, targetY))
nLocation = location
}
fileprivate func insetBounds() -> CGRect {
let inset = self.inset()
let contentBounds = (self.superview as? DrawableView)?.contentBounds ?? self.bounds
return contentBounds.insetBy(dx: -inset, dy: -inset)
}
func updateRect(_ anchor: CGPoint, initialTouchLocation: CGPoint, originalCorner: CGPoint , _ location: CGPoint) {
let insetBounds = self.insetBounds()
let locationX = max(insetBounds.origin.x, min(insetBounds.maxX, location.x))
let locationY = max(insetBounds.origin.y, min(insetBounds.maxY, location.y))
let targetX = originalCorner.x + locationX - initialTouchLocation.x
let targetY = originalCorner.y + locationY - initialTouchLocation.y
let minSize = self.inset() + circleRadius
if insetBounds.origin.x < targetX && targetX < insetBounds.maxX {
self.frame.origin.x = min(targetX, anchor.x)
self.frame.size.width = max(minSize * 2, abs(anchor.x - targetX))
}
if insetBounds.origin.y < targetY && targetY < insetBounds.maxY {
self.frame.origin.y = min(targetY, anchor.y)
self.frame.size.height = max(minSize * 2, abs(anchor.y - targetY))
}
nLocation = location
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
CATransaction.begin()
CATransaction.setDisableActions(true)
if let superview = self.superview as? DrawableView {
for view in superview.subviews {
if let view = view as? ResizableRectangleView {
if view != self {
view.isSelected = false
view.updateLayers()
}
}
}
superview.bringSubview(toFront: self)
}
let location = touch.location(in: self.superview)
nLocation = location
var anchor: CGPoint?
var corner: CGPoint?
switch (location.x, location.y) {
case (let x, let y) where x < self.frame.origin.x + CornerTouchSize && y < self.frame.origin.y + CornerTouchSize:
anchor = CGPoint(x: self.frame.maxX, y: self.frame.maxY)
corner = CGPoint(x: self.frame.minX, y: self.frame.minY)
case (let x, let y) where x < self.frame.origin.x + CornerTouchSize && y > self.frame.maxY - CornerTouchSize:
anchor = CGPoint(x: self.frame.maxX, y: self.frame.minY)
corner = CGPoint(x: self.frame.minX, y: self.frame.maxY)
case (let x, let y) where x > self.frame.maxX - CornerTouchSize && y < self.frame.origin.y + CornerTouchSize:
anchor = CGPoint(x: self.frame.minX, y: self.frame.maxY)
corner = CGPoint(x: self.frame.maxX, y: self.frame.minY)
case (let x, let y) where x > self.frame.maxX - CornerTouchSize && y > self.frame.maxY - CornerTouchSize:
anchor = CGPoint(x: self.frame.minX, y: self.frame.minY)
corner = CGPoint(x: self.frame.maxX, y: self.frame.maxY)
default:
self.moveFrame(self.frame, initialTouchLocation: location , nLocation)
}
if let anchor = anchor {
if let corner = corner {
self.didMove = true
self.isSelected = true
self.updateRect(anchor, initialTouchLocation: location, originalCorner: corner, nLocation)
self.updateLayers()
}
}
CATransaction.commit()
return true
}
var didMove = false
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
CATransaction.begin()
CATransaction.setDisableActions(true)
didMove = true
let location = touch.location(in: self.superview)
nLocation = location
//self.trackingFrameTransform?(location)
self.updateLayers()
CATransaction.commit()
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
CATransaction.begin()
CATransaction.setDisableActions(true)
if !didMove {
self.isSelected = !self.isSelected
}
didMove = false
self.updateLayers()
// self.trackingFrameTransform = nil
nLocation = nil
CATransaction.commit()
}
func updateCircleLayer(_ layer: CALayer, center: CGPoint) {
layer.isHidden = !self.isSelected
layer.frame = CGRect(x: center.x - circleRadius, y: center.y - circleRadius, width: 2 * circleRadius, height: 2 * circleRadius)
layer.backgroundColor = self.tintColor.cgColor
layer.borderColor = strokeTintColor
layer.cornerRadius = self.circleRadius
layer.borderWidth = 1
layer.setNeedsDisplay()
}
func inset() -> CGFloat {
let circleInset = (CornerTouchSize - (self.circleRadius * 2)) / 2
return self.circleRadius + circleInset
}
func updateBorderLayer() {
self.borderLayer.masksToBounds = false
self.borderLayer.borderWidth = 1
self.borderLayer.borderColor = self.tintColor.cgColor
let inset = self.inset()
self.borderLayer.frame = self.bounds.insetBy(dx: inset, dy: inset)
self.borderLayer.setNeedsDisplay()
}
}
DrawableView.swift
import Foundation
import UIKit
struct ColoredRect {
let color: UIColor
let origin: CGPoint
let size: CGSize
var width: CGFloat {
get {
return self.size.width
}
}
var height: CGFloat {
get {
return self.size.height
}
}
}
class DrawableView: UIControl {
fileprivate let colorPicker = ColorPicker()
fileprivate var currentRect: ResizableRectangleView?
fileprivate var originalLocation: CGPoint?
fileprivate var rectIsPending = false
var contentSize: CGSize?
var contentBounds: CGRect? {
get {
if let contentSize = self.contentSize {
let scale = min(self.bounds.width / contentSize.width, self.bounds.height / contentSize.height)
let scaledWidth = contentSize.width * scale
let scaledHeight = contentSize.height * scale
let x = round(0.5 * (self.bounds.width - scaledWidth))
let y = round(0.5 * (self.bounds.height - scaledHeight))
return CGRect(x: x, y: y, width: scaledWidth, height: scaledHeight)
} else {
return nil
}
}
}
var shapes: [ColoredRect] {
get {
var shapes = [ColoredRect]()
for view in self.subviews {
if let view = view as? ResizableRectangleView {
let f = view.convert(view.borderedFrame(), to: self)
let relX = min(1.0, max(0.0, f.origin.x / self.bounds.width))
let relY = min(1.0, max(0.0, f.origin.y / self.bounds.height))
let relWidth = min(1.0, max(0.0, f.width / self.bounds.width))
let relHeight = min(1.0, max(0.0, f.height / self.bounds.height))
let relOrigin = CGPoint(x: relX, y: relY)
let relSize = CGSize(width: relWidth, height: relHeight)
let rect = ColoredRect(color: view.tintColor, origin: relOrigin, size: relSize)
shapes.append(rect)
}
}
return shapes
}
set {
let shapes = newValue
for view in self.subviews {
if let view = view as? ResizableRectangleView {
view.removeFromSuperview()
}
}
self.colorPicker.alpha = 0
for shape in shapes {
let x = shape.origin.x * self.bounds.width
let y = shape.origin.y * self.bounds.height
let width = shape.width * self.bounds.width
let height = shape.height * self.bounds.height
let rectFrame = CGRect(x: x, y: y, width: width, height: height)
let view = ResizableRectangleView()
let inset = view.inset()
view.tintColor = shape.color
view.frame = rectFrame.insetBy(dx: -inset, dy: -inset)
view.delegate = self
self.addSubview(view)
}
self.bringSubview(toFront: self.colorPicker)
}
}
// override init() {
// super.init()
// self.addColorPicker()
// }
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addColorPicker()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addColorPicker()
}
override func awakeFromNib() {
super.awakeFromNib()
self.addColorPicker()
}
fileprivate func addColorPicker() {
colorPicker.delegate = self
colorPicker.alpha = 0
self.addSubview(colorPicker)
self.bringSubview(toFront: self.colorPicker)
colorPicker.frame = CGRect(x: self.bounds.width - 44, y: 0, width: 44, height: self.bounds.height)
}
override func layoutSubviews() {
super.layoutSubviews()
colorPicker.frame = CGRect(x: self.bounds.width - 44, y: 0, width: 44, height: self.bounds.height)
}
override var canBecomeFirstResponder : Bool {
return true
}
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if (motion == UIEventSubtype.motionShake) {
self.shapes = []
}
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
if let contentBounds = self.contentBounds {
if (!contentBounds.contains(location)) {
return false
}
}
rectIsPending = true
let newRect = ResizableRectangleView()
newRect.frame = CGRect(x: location.x, y: location.y, width: 1, height: 1)
newRect.tintColor = UIColor(cgColor: self.colorPicker.color)
self.currentRect = newRect
self.originalLocation = location
CATransaction.begin()
CATransaction.setDisableActions(true)
for view in self.subviews {
if let view = view as? ResizableRectangleView {
view.isSelected = false
view.updateLayers()
}
}
CATransaction.commit()
return true
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
if let currentRect = self.currentRect {
if rectIsPending {
currentRect.delegate = self
self.addSubview(currentRect)
self.bringSubview(toFront: self.colorPicker)
}
CATransaction.begin()
CATransaction.setDisableActions(true)
if let originalLocation = self.originalLocation {
let location = touch.location(in: self)
currentRect.updateRect(originalLocation, initialTouchLocation: originalLocation, originalCorner: originalLocation, location)
// currentRect.updateRect(originalLocation, initialTouchLocation: originalLocation, originalCorner: originalLocation ,location: location)
}
CATransaction.commit()
}
return super.continueTracking(touch, with: event)
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
self.currentRect = nil
self.rectIsPending = false
}
}
extension DrawableView: ColorPickerDelegate {
func colorPicker(_ picker: ColorPicker, didChangeColor color: CGColor) {
CATransaction.begin()
CATransaction.setDisableActions(true)
for view in self.subviews {
if let view = view as? ResizableRectangleView {
if view.isSelected {
view.tintColor = UIColor(cgColor: color)
view.updateLayers()
}
}
}
CATransaction.commit()
}
}
extension DrawableView: ResizableRectangleViewDelegate {
func didSelectResizableRectangleView(_ view: ResizableRectangleView) {
self.bringSubview(toFront: self.colorPicker)
if self.colorPicker.alpha == 0 {
UIView.animate(withDuration: 0.15, animations: {
self.colorPicker.alpha = 1
})
}
}
func didDeselectResizableRectangleView(_ view: ResizableRectangleView) {
self.bringSubview(toFront: self.colorPicker)
if colorPicker.alpha == 1 {
let selectionCount = self.subviews.reduce(0) {
acc, view in
if let view = view as? ResizableRectangleView {
return acc + (view.isSelected ? 1 : 0)
}
return acc
}
if selectionCount == 0 {
UIView.animate(withDuration: 0.15, animations: {
self.colorPicker.alpha = 0
})
}
}
}
}

gestureRecognizer not working on added subview

I'm adding a subview in a view that holds multiple gesturerecognizer however when this view is being introduced the gesture's won't fire when the user taps inside this newly added view. It's a UIBezierPath that I'm introducing however. I've read that this could be way it doesn't work. My question is, is it possible to make the added UIBezierPath transparent in a way that the device will think the view below the drawn box is being tapped. I hope it makes sense what I'm saying.
This is the code for my UIBezierPath
class drawRecognizerIndicator{
let gestureContainer: UIView!
var view_gesture: UIView!
init(container: UIView){
gestureContainer = container
}
func drawRect(image: UIImage){
let screenSize = gestureContainer.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let gestureX = (screenWidth * 0.75) * 0.5
let gestureY = (screenWidth * 0.75) * 0.5
let gestureWidth = (screenWidth * 0.75)
let gestureHeight = (screenWidth * 0.75)
if(view_gesture != nil){
if(view_gesture.isDescendantOfView(gestureContainer)){
view_gesture.removeFromSuperview()
}
}
view_gesture = UIView(frame: CGRect(
x: (screenWidth * 0.5) - gestureX,
y: (screenHeight * 0.5) - gestureY ,
width: gestureWidth,
height: gestureHeight))
view_gesture.backgroundColor = UIColor.blackColor()
view_gesture.alpha = 0
let maskPath = UIBezierPath(roundedRect: view_gesture.bounds, byRoundingCorners: .AllCorners, cornerRadii: CGSize(width: 10, height: 10))
let maskLayer = CAShapeLayer(layer: maskPath)
maskLayer.frame = view_gesture.bounds
maskLayer.path = maskPath.CGPath
view_gesture.layer.mask = maskLayer
let view_gestureSize = view_gesture.bounds
let gestureIconWidth = view_gestureSize.width
let gestureIconHeight = view_gestureSize.height
let iconX = (gestureIconWidth * 0.8) * 0.5
let iconY = (gestureIconHeight * 0.8) * 0.5
let iconWidth = gestureIconWidth * 0.8
let iconHeight = gestureIconHeight * 0.8
let image_gesture = UIImageView(frame: CGRect(
x: (gestureIconWidth * 0.5) - iconX,
y: (gestureIconHeight * 0.5) - iconY,
width: iconWidth,
height: iconHeight))
image_gesture.contentMode = UIViewContentMode.ScaleAspectFit
image_gesture.image = image
image_gesture.userInteractionEnabled = true
view_gesture.addSubview(image_gesture)
view_gesture.userInteractionEnabled = true
gestureContainer.addSubview(view_gesture)
animateGestureNotification(view_gesture)
}
func animateGestureNotification(view_gesture: UIView){
UIView.animateWithDuration(0.5, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
view_gesture.alpha = 0.4
}, completion: nil)
UIView.animateWithDuration(0.5, delay: 2, options: UIViewAnimationOptions.CurveEaseOut, animations: {
view_gesture.alpha = 0
}, completion: nil)
}
}
Check the documentation for
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer
otherGestureRecognizer: UIGestureRecognizer) -> Bool {...}
It helps to decide what happens when there are several gesture recognizers on the same view controller.
I would start by putting some print statements in there to see exactly which GRs are being called, and then decide which ones should return true.

Resources