Control Center-like sliding view implementation in Swift - swift

I'm trying to create an interactive sliding transition on App.
The goal is to present a uiview when view controller that will be dragged from the bottom(not from the bottom edge so it won't conflict with the control center) and will partially cover the main view controller (just like the iOS control center). The transition should be interactive, i.e according to the dragging of the user.
Would be happy to hear ideas regarding available APIs.

Following code will do the simple slideView animation code tested in Xcode 8 and worked..
import UIKit
class ViewController: UIViewController,UIGestureRecognizerDelegate {
// I am using VisualView as a slideView
#IBOutlet weak var slideView: UIVisualEffectView!
//Button to open slideView
#IBOutlet weak var button: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// setting background for sideView
slideView.backgroundColor = UIColor(patternImage: UIImage(named: "original.jpg")!)
//Initial hide so it won't show.when the mainView loads...
slideView.isHidden = true
// DownSwipe to hide slideView
let downSwipe = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.handleSwipes(_:)))
downSwipe.direction = .down
view.addGestureRecognizer(downSwipe)
}
// set UIButton to open slideVie...(I am using UIBarButton)
#IBAction func sdrawButton(_ sender: AnyObject) {
self.slideView.isHidden = false
slideView.center.y += view.frame.height
UIView.animate(withDuration: 0.7, delay: 0, options: UIViewAnimationOptions.curveEaseIn, animations:{
self.slideView.center.y -= self.view.frame.height
self.button.isEnabled = false
}, completion: nil)
}
func handleSwipes(_ sender:UISwipeGestureRecognizer) {
if (sender.direction == .down) {
print("Swipe Down")
self.slideView.isHidden = true
self.slideView.center.y -= self.view.frame.height
UIView.animate(withDuration: 0.7, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations:{
self.slideView.center.y += self.view.frame.height
self.button.isEnabled = true
}, completion: nil)
}
}
}
Let me know the code works for you...

Sorry if this question is a bit old.
You can subclass a UIView and override touchesBegan(_ , with) to save the original touch location
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { print("No touch"); return }
startPoint = touch.location(in: self)
startHeight = heightConstraint.constant
slideDirection = .none
super.touchesBegan(touches, with: event)
}
}
and touchesMoved(_,with) to update the Height constraint while a finger is sliding the view.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { print("touchesMovedno touches"); return }
let endPoint = touch.location(in: self)
let diff = endPoint.y - startPoint.y
// This causes the view to move along the drag
switch anchorLocation {
case .top :
heightConstraint.constant = startHeight + diff
case .bottom :
heightConstraint.constant = startHeight - diff
}
self.layoutIfNeeded()
// Update direction
if diff == 0.0 {
self.slideDirection = .none
} else {
self.slideDirection = (diff > 0) ? .down : .up
}
super.touchesMoved(touches, with: event)
}
Override touchesEnded(_, with) to add animation if you wish
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.modifyHeightConstraints()
self.animateViewTransition(duration: animDuration, delay: animDelay, clearance: animClearance, springDampingRatio: animSpringDampingRatio, initialSpringVelocity: animInitialSpringVelocity, complete: {
self.slideDirection = .none
})
super.touchesEnded(touches, with: event)
}
I have created a component that might have the exact feature you wanted. The component includes adjustable animated bouncing. You can layout and design subviews on the view in storyboard.
Check out the link in GitHub
https://github.com/narumolp/NMPAnchorOverlayView

Related

How can I end a touch/gesture with a long hold?

I'd like to move a simple, small UIView around the screen and 'drop' it off at any location on the screen, but accurately. Simply lifting your finger from the screen does not have the desired effect as there is always some movement upon lifting your finger, resulting in the object not being in the required location.
What I'm looking for is some way to count down a specified number of milliseconds AFTER holding/pausing at the desired location and then have some mechanism ENDING my touches/gesture so the object is placed EXACTLY where I want it.
I've been reasonably successful with touchesBegan/Moved/Ended, but even though I called the touchesEnded method the touches never really end as I can still drag around the object on the screen and relocate it - not what I want.
import UIKit
class ViewController: UIViewController {
let greenDot : UIView = {
let greenDot = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
greenDot.backgroundColor = .green
greenDot.layer.cornerRadius = greenDot.bounds.height / 2
return greenDot
}()
var timer : Timer?
var lastTouch = Set<UITouch>()
var lastTouchEvent: UIEvent?
override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
print(touch.location(in: view))
greenDot.center = touch.location(in: view)
view.addSubview(greenDot)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
print(touch.location(in: view))
if timer != nil { timer?.invalidate() }
greenDot.center = touch.location(in: view)
view.addSubview(greenDot)
lastTouch = touches
lastTouchEvent = event
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(stopTouches), userInfo: nil, repeats: false)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
timer?.invalidate()
print("Touches Ended #: \(touch.location(in: view))")
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Cancelling touch")
}
#objc func stopTouches() {
touchesEnded(lastTouch, with: lastTouchEvent)
// touchesCancelled(lastTouch, with: lastTouchEvent)
// view.resignFirstResponder()
}
}
With UIGestures I have tried with the UILongPressGestureRecognizer, but I don't know how to 'end' with a long-press. Do I use 2 long-presses in sequence - one to start the movement and another to end the movement? I like the longPress since it is continuous and I can thus pan with it.
import UIKit
class ViewController: UIViewController {
let greenDot : UIView = {
let dot = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
dot.backgroundColor = .green
dot.layer.cornerRadius = dot.frame.width/2
return dot
}()
override func viewDidLoad() {
super.viewDidLoad()
let longPress1 = UILongPressGestureRecognizer(target: self, action: #selector(press1))
longPress1.minimumPressDuration = 0.2
view.addGestureRecognizer(longPress1)
}
#objc func press1(_ sender: UILongPressGestureRecognizer) {
let newLocation = sender.location(in: view)
greenDot.backgroundColor = .green
greenDot.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
greenDot.center = CGPoint(x: newLocation.x, y: newLocation.y - 40)
if sender.state == .ended {
greenDot.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
greenDot.center = CGPoint(x: newLocation.x, y: newLocation.y - 40)
greenDot.backgroundColor = .purple
}
view.addSubview(greenDot)
}
}
So in conclusion: I'm looking for a method to place an object, which I am moving around on the screen with my finger, accurately.
Any help in this regard will be greatly appreciated.
Intriguing how often you end up answering your own question after placing it before others.
So after many days of fumbling around I stumbled across this pointer from Apple:
https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/implementing_a_custom_gesture_recognizer/implementing_a_discrete_gesture_recognizer
In short, I have the basics of what I was looking for by creating a custom Gesture Recognizer. Thanks Apple.
So for anyone else who might be interested, following is the 'basic' code for grabbing an object, moving it around and letting a timer release it for you so that the drop is as accurate as needed. Releasing before the timer expires also works, but then some shifting might/will occur during release. Once the timer has fired and your finger is still on the screen, you will be unable to accidentally drag the object out of place.
Obviously there is still a lot of work needed before implementing in an app to mitigate errors. 'Small steps'
import UIKit
import UIKit.UIGestureRecognizerSubclass
class ObjectDropGestureRecognizer: UIGestureRecognizer {
var trackedTouch : UITouch? = nil
var intervalTime : Double = 1.0
var dropTimer : Timer?
var counter : Int = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
print(touches.count)
if touches.count != 1 {
self.state = .failed
}
if self.state == .possible {
self.state = .began
print("began...")
}
if self.trackedTouch == nil {
self.trackedTouch = touches.first
} else {
for touch in touches {
if touch != self.trackedTouch {
self.ignore(touch, for: event)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
guard let newTouch = touches.first else { return }
self.state = .changed
counter += 1
print("Changed...\(counter)")
if self.dropTimer != nil { dropTimer?.invalidate() }
dropTimer = Timer.scheduledTimer(withTimeInterval: intervalTime, repeats: false) { timer in
print("Timer fired")
print(newTouch.location(in: self.view))
self.state = .ended
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
if self.dropTimer != nil {
self.dropTimer?.invalidate()
self.state = .ended
}
self.state = .recognized
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
self.trackedTouch = nil
self.state = .cancelled
}
override func reset() {
super.reset()
self.trackedTouch = nil
self.counter = 0
}
}
class ViewController: UIViewController {
let greenDot : UIView = {
let dot = UIView(frame: CGRect(x: 50, y: 200, width: 20, height: 20))
dot.backgroundColor = .green
dot.layer.cornerRadius = dot.frame.width/2
return dot
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(greenDot)
let objectMoveGesture = ObjectDropGestureRecognizer(target: self, action: #selector(tapAction(_:)))
objectMoveGesture.intervalTime = 0.8
view.addGestureRecognizer(objectMoveGesture)
}
#objc func tapAction(_ touch: ObjectDropGestureRecognizer){
let newLocation = touch.location(in: view)
greenDot.center = CGPoint(x: newLocation.x, y: newLocation.y)
}
}

draw a straight line with a starting touch point

I want my swift code to draw a straight horiziontal line. Right now it selects a point and the user extends the line anchored in the first point. I just want the user to be able to draw the line left or right. I tried alternating bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y)) but that does not seem to have any effect.
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var drawingPlace: UIImageView!
var startTouch : CGPoint?
var secondTouch : CGPoint?
var currentContext : CGContext?
var prevImage : UIImage?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemOrange
drawingPlace.backgroundColor = .gray
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startTouch = touch?.location(in: drawingPlace)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
secondTouch = touch.location(in: drawingPlace)
if(self.currentContext == nil){
UIGraphicsBeginImageContext(drawingPlace.frame.size)
self.currentContext = UIGraphicsGetCurrentContext()
}else{
self.currentContext?.clear(CGRect(x: 0, y: 0, width: drawingPlace.frame.width, height: drawingPlace.frame.height))
}
self.prevImage?.draw(in: self.drawingPlace.bounds)
let bezier = UIBezierPath()
bezier.move(to: startTouch!)
bezier.addLine(to: secondTouch!)
bezier.addLine(to: CGPoint(x:startTouch!.x, y:startTouch!.y))
bezier.close()
UIColor.blue.set()
self.currentContext?.setLineWidth(4)
self.currentContext?.addPath(bezier.cgPath)
self.currentContext?.strokePath()
let img2 = self.currentContext?.makeImage()
drawingPlace.image = UIImage.init(cgImage: img2!)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.currentContext = nil
self.prevImage = self.drawingPlace.image
}
}
If you want to draw a horizontal line, create a CGPoint whose x is the location of the touch and whose y is that of the starting point. That will result in a horizontal line.
That having been said, here are a few other of observations:
If you call UIGraphicsBeginImageContext, you must call UIGraphicsEndImageContext. You should do this within touchesMoved, not trying to hang on to the context beyond this call.
If you were to do this, we would generally use UIGraphicsImageRenderer nowadays.
Personally, I wouldn't try rendering an image for every touch. That is a pretty expensive operation. I would just add a CAShapeLayer and then update the path for that layer. Let CAShapeLayer take care of the rendering of the path.
I'm not quite sure why you are iterating through the array of touches. I would just grab one and use that.
I might suggest using predictive touches to minimize perceived lagginess.
The startTouch is actually a CGPoint, not a UITouch, so I might call it startPoint instead.
If you want to make a snapshot image, I'd do that in touchesEnded, not in touchesMoved.
For example:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var startPoint: CGPoint?
let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 4
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
return shapeLayer
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.backgroundColor = .systemOrange
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startPoint = touch?.location(in: imageView)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard var touch = touches.first else { return }
if let predicted = event?.predictedTouches(for: touch)?.last {
touch = predicted
}
updatePath(in: imageView, to: touch)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePath(in: imageView, to: touch)
let image = UIGraphicsImageRenderer(bounds: imageView.bounds).image { _ in
imageView.drawHierarchy(in: imageView.bounds, afterScreenUpdates: true)
}
shapeLayer.path = nil
imageView.image = image
}
}
private extension ViewController {
func updatePath(in view: UIView, to touch: UITouch) {
let point = touch.location(in: view)
guard view.bounds.contains(point) else { return }
let bezier = UIBezierPath()
bezier.move(to: startPoint!)
bezier.addLine(to: CGPoint(x: point.x, y: startPoint!.y))
shapeLayer.path = bezier.cgPath
}
}
This just renders the current line as a CAShapeLayer, but when the stroke is done, it creates a snapshot of the image view (permanently capturing the stroke in an image), removes the shape layer’s path, and updates the image view’s image.
But hopefully this answers the question on how to make the line horizontal.

How to prevent back button from dissapearing?

I am creating a doodle page with an "X" as the close/dismiss button.
I also have a "clear" button to remove the doodles that have been made.
My issue is that when I press clear, even the X button disappears, how do I prevent this from happening?
This is my current app.
This is my Doodle class which allows me to draw on the view.
import UIKit
class DoodleView: UIView {
var lineColor:UIColor!
var lineWidth:CGFloat!
var path:UIBezierPath!
var touchPoint:CGPoint!
var startingPoint:CGPoint!
override func layoutSubviews() {
self.clipsToBounds = true
self.isMultipleTouchEnabled = false
lineColor = UIColor.white
lineWidth = 10
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
startingPoint = touch?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
touchPoint = touch?.location(in: self)
path = UIBezierPath()
path.move(to: startingPoint)
path.addLine(to: touchPoint)
startingPoint = touchPoint
drawShapeLayer()
}
func drawShapeLayer() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(shapeLayer)
self.setNeedsDisplay()
}
func clearCanvas() {
path.removeAllPoints()
self.layer.sublayers = nil
self.setNeedsDisplay()
}
}
This is the class controlling the view controller.
import UIKit
class DoodlePageViewController: UIViewController {
#IBOutlet weak var doodleView: DoodleView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func clearDoodle(_ sender: Any) {
doodleView.clearCanvas()
}
#IBAction func CloseDoodlePage(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Without really seeing how you declared the buttons, I can only guess that when you call the function clearCanvas(), the X button is part of the sublayer in doodle view since you are setting self.layer.sublayers = nil hence making it also dissapear.
Make sure that the X button is on top of the doodle view when you create it.
You are clearing all the subviews out of your view when you call self.layer.sublayers = nil
you either need to re-add the 'x' view back into the hierarchy. I am assuming using your function drawShapeLayer() is the 'x' ??
so it would be
func clearCanvas() {
path.removeAllPoints()
self.layer.sublayers = nil
drawShapeLayer()
self.setNeedsDisplay()
}

Detect image colour from touch location

I am trying to build a simple drawing app that can act as a virtual guest book. Ive managed to put something simple together which allows a user to draw onto a UIImage however I cannot figure out a way to erase a drawing somebody creates without using the eraser function that I create as white. I would like to set an image however I need to figure out a way that the eraser can detect the correct colour to use based on where the users finger will be when erasing to match the image.
My eraser currently updates a set of variables (red, green, blue) the erasers function is #IBAction func eraser
I've tried a few functions that I've came across which are outdated
//
// SecondViewController.swift
// Guest Book
//
// Created by Stephen Clifford on 25/07/2019.
// Copyright © 2019 Stephen Clifford. All rights reserved.
//
import Foundation
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var toolicon: UIButton!
#IBOutlet var imageView: UIImageView!
var lastPoint = CGPoint.zero
var swiped = false
var red:CGFloat = 0.00
var green:CGFloat = 0.00
var blue:CGFloat = 0.00
var alpha:CGFloat = 1
var lw:CGFloat = 2.5
var tool:UIImageView!
var isDrawing = true
override func viewDidLoad() {
super.viewDidLoad()
tool = UIImageView()
tool.frame = CGRect(x: self.view.bounds.size.width, y: self.view.bounds.height, width: 75, height: 75)
tool.image = #imageLiteral(resourceName: "pen")
self.view.addSubview(tool)
}
#IBAction func eraser(_ sender: AnyObject) {
if (isDrawing) {
(red,green,blue) = (1,1,1)
tool.image = #imageLiteral(resourceName: "eraser")
toolicon.setImage(#imageLiteral(resourceName: "pen"), for: .normal)
(lw) = (50)
} else {
(red,green,blue) = (0,0,0)
tool.image = #imageLiteral(resourceName: "pen")
toolicon.setImage(#imageLiteral(resourceName: "eraser"), for: .normal)
(lw) = (2.5)
}
isDrawing = !isDrawing
}
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
#IBAction func save(_ sender: AnyObject) {
if let image = imageView.image {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
self.imageView.image = nil
// the alert view
let alert = UIAlertController(title: "", message: "Thank you, your guestbook entry has been saved. Redirecting you the home page", preferredStyle: .alert)
self.present(alert, animated: true, completion: nil)
// change to desired number of seconds (in this case 5 seconds)
let when = DispatchTime.now() + 2.5
DispatchQueue.main.asyncAfter(deadline: when){
// your code with delay
alert.dismiss(animated: true, completion: nil)
}
delay(3.0) {
// do stuff
self.performSegue(withIdentifier: "savetocam", sender: nil)
}
}
}
#IBAction func lineSize(_ sender: AnyObject) {
if sender.tag == 8 {
(lw) = (1)
} else if sender.tag == 9 {
(lw) = (2.5)
} else if sender.tag == 10 {
(lw) = (5)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first {
lastPoint = touch.location(in: self.view)
}
}
#IBAction func colorsPicked(_ sender: AnyObject) {
if sender.tag == 0 {
(red,green,blue) = (0,0,0)
} else if sender.tag == 1 {
(red,green,blue) = (0.25,0.00,0.39)
} else if sender.tag == 2 {
(red,green,blue) = (0.65,0.62,0.75)
} else if sender.tag == 3 {
(red,green,blue) = (1.00,0.00,0.00)
} else if sender.tag == 4 {
(red,green,blue) = (0.49,0.77,0.46)
} else if sender.tag == 5 {
(red,green,blue) = (0.0,0.68,0.94)
} else if sender.tag == 6 {
(red,green,blue) = (0.96,0.56,0.34)
} else if sender.tag == 7 {
(red,green,blue) = (1.00,0.95,0.00)
}
}
#IBAction func reset(_ sender: AnyObject) {
self.imageView.image = nil
}
func drawLines(fromPoint:CGPoint,toPoint:CGPoint) {
UIGraphicsBeginImageContext(self.view.frame.size)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
let context = UIGraphicsGetCurrentContext()
context?.move(to:CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(lw)
context?.setStrokeColor(UIColor(red: red, green: green, blue: blue, alpha: alpha).cgColor)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: self.view)
drawLines(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLines(fromPoint: lastPoint, toPoint: lastPoint)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Swift 3: How do I pinch to scale and rotate UIImageView?

I am really struggling to find tutorials online as well as already answered questions (I have tried them and they don't seem to work). I have a UIImageView that I have in the centre of my view. I am currently able to tap and drag this wherever I want on screen. I want to be able to pinch to scale and rotate this view. How do I achieve this? I have tried the code for rotation below but it doesn't seem to work? Any help will be a massive help and marked as answer. Thank you guys.
import UIKit
class DraggableImage: UIImageView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.backgroundColor = .blue
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.backgroundColor = .green
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: superview)
center = CGPoint(x: position.x, y: position.y)
}
}
}
class CVController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotateAction(sender:)))
firstImageView.addGestureRecognizer(rotateGesture)
setupViews()
}
func rotateAction(sender: UIRotationGestureRecognizer) {
let rotatePoint = sender.location(in: view)
let firstImageView = view.hitTest(rotatePoint, with: nil)
firstImageView?.transform = (firstImageView?.transform.rotated(by: sender.rotation))!
sender.rotation = 0
}
let firstImageView: DraggableImage = {
let iv = DraggableImage()
iv.backgroundColor = .red
iv.isUserInteractionEnabled = true
return iv
}()
func setupViews() {
view.addSubview(firstImageView)
let firstImageWidth: CGFloat = 50
let firstImageHeight: CGFloat = 50
firstImageView.frame = CGRect(x: (view.frame.width / 2) - firstImageWidth / 2, y: (view.frame.height / 2) - firstImageWidth / 2, width: firstImageWidth, height: firstImageHeight)
}
}
You have a some problems in your code. First you need to add the UIGestureRecognizerDelegate to your view controller and make it your gesture recognizer delegate. You need also to implement shouldRecognizeSimultaneously method and return true. Second when applying the scale you need to save the transform when the pinch begins and apply the scale in top of it:
class DraggableImageView: UIImageView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
backgroundColor = .blue
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
backgroundColor = .green
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let position = touches.first?.location(in: superview){
center = position
}
}
}
class ViewController: UIViewController, UIGestureRecognizerDelegate {
var identity = CGAffineTransform.identity
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupViews()
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(scale))
let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotate))
pinchGesture.delegate = self
rotationGesture.delegate = self
view.addGestureRecognizer(pinchGesture)
view.addGestureRecognizer(rotationGesture)
}
let firstImageView: DraggableImageView = {
let iv = DraggableImageView()
iv.backgroundColor = .red
iv.isUserInteractionEnabled = true
return iv
}()
func setupViews() {
view.addSubview(firstImageView)
let firstImageWidth: CGFloat = 50
let firstImageHeight: CGFloat = 50
firstImageView.frame = CGRect(x: view.frame.midX - firstImageWidth / 2, y: view.frame.midY - firstImageWidth / 2, width: firstImageWidth, height: firstImageHeight)
}
#objc func scale(_ gesture: UIPinchGestureRecognizer) {
switch gesture.state {
case .began:
identity = firstImageView.transform
case .changed,.ended:
firstImageView.transform = identity.scaledBy(x: gesture.scale, y: gesture.scale)
case .cancelled:
break
default:
break
}
}
#objc func rotate(_ gesture: UIRotationGestureRecognizer) {
firstImageView.transform = firstImageView.transform.rotated(by: gesture.rotation)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}