The problem you're experiencing is due to your definition of the panGestureRecognizer
variable in the class definition here:
//Here I create the reference to the recognizer
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
You can initialize it this way, but it seems like self
is not setup when this variable is created. So your action is never registered.
There are a couple ways you can fix this using your code, you can continue to initialize it as an instance variable, but you'll need to setup your target/action in your initialize()
function
let panGestureRecognizer = UIPanGestureRecognizer()
func initialize() {
// add your target/action here
panGestureRecognizer.addTarget(self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
addGestureRecognizer(panGestureRecognizer)
}
Or you can simply initialize your gesture recognizer in your initialize function and not use an instance variable
func initialize() {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
addGestureRecognizer(panGestureRecognizer)
}
My original answer
Here's a solution that works using constraints with a view defined in the storyboard or by manipulating the frame directly.
Example with Constraints
import UIKit
// Delegate protocol for managing constraint updates if needed
protocol MorphDelegate: class {
// tells the delegate to change its size constraints
func morph(x: CGFloat, y: CGFloat)
}
class ExpandableView: UIView {
var delegate: MorphDelegate?
init() {
// frame is set later if needed by creator when using this init method
super.init(frame: CGRect.zero)
configureGestureRecognizers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureGestureRecognizers()
}
override init(frame: CGRect) {
super.init(frame: frame)
configureGestureRecognizers()
}
// setup UIPanGestureRecognizer
internal func configureGestureRecognizers() {
let panGR = UIPanGestureRecognizer.init(target: self, action: #selector(didPan(_:)))
addGestureRecognizer(panGR)
}
@objc func didPan(_ panGR: UIPanGestureRecognizer) {
// get the translation
let translation = panGR.translation(in: self).applying(transform)
if let delegate = delegate {
// tell delegate to change the constraints using this translation
delegate.morph(x: translation.x, y: translation.y)
} else {
// If you want the view to expand/contract in opposite direction
// of drag then swap the + and - in the 2 lines below
let newOriginY = frame.origin.y + translation.y
let newHeight = frame.size.height - translation.y
// expand self via frame manipulation
let newFrame = CGRect(x: frame.origin.x, y: newOriginY, width: frame.size.width, height: newHeight)
frame = newFrame
}
// reset translation
panGR.setTranslation(CGPoint.zero, in: self)
}
}
If you want to use this class by defining it in the storyboard and manipulating it's constraints you'd go about it like this.
First define your view in the storyboard and constrain it's width and height. For the demo I constrained it's x position to the view center and it's bottom to the SafeArea.bottom
Create an IBOutlet for the view, as well as it's height constraint to your ViewController file.
I set the background color to blue for this example.
In the view controller I defined a function to setup the view (currently only sets the delegate for constraint manipulation callbacks) and then defined an extension to handle delegate calls for updating constraints.
class ViewController: UIViewController {
@IBOutlet weak var expandingView: ExpandableView!
@IBOutlet weak var constraint_expViewHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// call to configure our expanding view
configureExpandingView()
}
// this sets the delegate for an expanding view defined in the storyboard
func configureExpandingView() {
expandingView.delegate = self
}
}
// setup the delegate callback to handle constraint manipulation
extension ViewController: MorphDelegate {
func morph(x: CGFloat, y: CGFloat) {
// this will update the view's height based on the amount
// you drag your finger in the view. You can '+=' below if
// you want to reverse the expanding behavior based on pan
// movements.
constraint_expViewHeight.constant -= y
}
}
Doing it this way via constraints gives me this when I run the project:
Example with frame manipulation
To use this and manipulate the height using just the frame property the view controller might implement the view creation something like this:
override func viewDidLoad() {
super.viewDidLoad()
setupExpandingView()
}
func setupExpandingView() {
let newView = ExpandableView()
newView.backgroundColor = .red
newView.frame = CGRect(x: 20, y: 200, width: 100, height: 100)
view.addSubview(newView)
}
Using just frame manipulation I get this: