Swift - How to add tap gesture to array of UIViews? - ios

Looking to add a tap gesture to an array of UIViews - without success. Tap seems not to be recognised at this stage.
In the code (extract) below:
Have a series of PlayingCardViews (each a UIView) showing on the main view.
Brought together as an array: cardView.
Need to be able to tap each PlayingCardView independently (and then to be able to identify which one was tapped).
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
for index in cardView.indices {
cardView[index].isUserInteractionEnabled = true
cardView[index].addGestureRecognizer(tap)
cardView[index].tag = index
}
}
#objc func tapCard (sender: UITapGestureRecognizer) {
if sender.state == .ended {
let cardNumber = sender.view.tag
print("View tapped !")
}
}

You need
#objc func tapCard (sender: UITapGestureRecognizer) {
let clickedView = cardView[sender.view!.tag]
print("View tapped !" , clickedView )
}
No need to check state here as the method with this gesture type is called only once , also every view should have a separate tap so create it inside the for - loop
for index in cardView.indices {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))

I will not recommend the selected answer. Because creating an array of tapGesture doesn't make sense to me in the loop. Better to add gesture within PlaycardView.
Instead, such layout should be designed using UICollectionView. If in case you need to custom layout and you wanted to use scrollView or even UIView, then the better approach is to create single Gesture Recognizer and add to the superview.
Using tap gesture, you can get the location of tap and then you can get the selectedView using that location.
Please refer to below example:
import UIKit
class PlayCardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.red
}
}
class SingleTapGestureForMultiView: UIViewController {
var viewArray: [UIView]!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: UIScreen.main.bounds)
view.addSubview(scrollView)
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(tapGetsure(_:)))
scrollView.addGestureRecognizer(tapGesture)
addSubviews()
}
func addSubviews() {
var subView: PlayCardView
let width = UIScreen.main.bounds.width;
let height = UIScreen.main.bounds.height;
let spacing: CGFloat = 8.0
let noOfViewsInARow = 3
let viewWidth = (width - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
let viewHeight = (height - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
var yCordinate = spacing
var xCordinate = spacing
for index in 0..<20 {
subView = PlayCardView(frame: CGRect(x: xCordinate, y: yCordinate, width: viewWidth, height: viewHeight))
subView.tag = index
xCordinate += viewWidth + spacing
if xCordinate > width {
xCordinate = spacing
yCordinate += viewHeight + spacing
}
scrollView.addSubview(subView)
}
scrollView.contentSize = CGSize(width: width, height: yCordinate)
}
#objc
func tapGetsure(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: scrollView)
print("location = \(location)")
var locationInView = CGPoint.zero
let subViews = scrollView.subviews
for subView in subViews {
//check if it subclass of PlayCardView
locationInView = subView.convert(location, from: scrollView)
if subView.isKind(of: PlayCardView.self) {
if subView.point(inside: locationInView, with: nil) {
// this view contains that point
print("Subview at \(subView.tag) tapped");
break;
}
}
}
}
}

You can try to pass the view controller as parameter to the views so they can call a function on parent view controller from the view. To reduce memory you can use protocols. e.x
protocol testViewControllerDelegate: class {
func viewTapped(view: UIView)
}
class testClass: testViewControllerDelegate {
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
for cardView in self.cardView {
cardView.fatherVC = self
}
}
func viewTapped(view: UIView) {
// the view that tapped is passed ass parameter
}
}
class PlayingCardView: UIView {
weak var fatherVC: testViewControllerDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let gr = UITapGestureRecognizer(target: self, action: #selector(self.viewDidTap))
self.addGestureRecognizer(gr)
}
#objc func viewDidTap() {
fatherVC?.viewTapped(view: self)
}
}

Related

How can I create a UIView that performs an action on tap but also passes the tap event to the views behind it?

I basically want to have an invisible UIView that catches taps on it (and performs some action) but also passes through this tap event to the views behind it. I want to make it some generic class that inherits from UIView.
You can propagate touch events through the responder chain:
class PassTouchView: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// do what you want here
print("View was touched!")
// propagate up the responder chain (most likely the superview)
self.next?.touchesBegan(touches, with: event)
}
}
Trying to do this with Gesture Recognizers is a bit different. If you want to do that, you probably want to use protocol/delegate pattern.
Edit
Quick example of protocol/delegate pattern when the "covering view" is using a UITapGestureRecognizer.
The protocol:
protocol PassTapDelegate {
func passMeTheTap(_ recognizer: UITapGestureRecognizer)
}
A custom "pass the tap" view:
class PassTapView: UIView {
var ptDelegate: PassTapDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
let t = UITapGestureRecognizer(target: self, action: #selector(self.gotTap(_:)))
addGestureRecognizer(t)
}
#objc func gotTap(_ recognizer: UITapGestureRecognizer) -> Void {
print("Got Tap in PassTap view")
ptDelegate?.passMeTheTap(recognizer)
}
}
A sample view controller showing how to use it:
class PassTappingViewController: UIViewController, PassTapDelegate {
var btnsArray: [UIButton] = []
override func viewDidLoad() {
super.viewDidLoad()
let colors: [UIColor] = [
.systemBlue, .systemGreen, .systemOrange, .systemTeal,
]
let centers: [CGPoint] = [
CGPoint(x: -75.0, y: -75.0),
CGPoint(x: 75.0, y: -75.0),
CGPoint(x: -75.0, y: 75.0),
CGPoint(x: 75.0, y: 75.0),
]
var x: CGFloat = 0
var y: CGFloat = 0
var i: Int = 1
for (pt, c) in zip(centers, colors) {
let b = UIButton()
b.backgroundColor = c
b.setTitle("Button \(i)", for: [])
b.translatesAutoresizingMaskIntoConstraints = false
btnsArray.append(b)
view.addSubview(b)
// all buttons are 120 x 120
b.widthAnchor.constraint(equalToConstant: 120.0).isActive = true
b.heightAnchor.constraint(equalTo: b.widthAnchor).isActive = true
x = pt.x
y = pt.y
// uncomment these two lines to see the tap passing through
// to multiple overlapping buttons
//x = -75.0
//y = -75.0 + CGFloat(i) * 40.0
b.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: x).isActive = true
b.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: y).isActive = true
i += 1
}
// add a red PassTapView
let passTapView: PassTapView = PassTapView()
passTapView.translatesAutoresizingMaskIntoConstraints = false
passTapView.backgroundColor = .systemRed
view.addSubview(passTapView)
// center it so it's covering portions of the buttons
NSLayoutConstraint.activate([
passTapView.widthAnchor.constraint(equalToConstant: 120.0),
passTapView.heightAnchor.constraint(equalTo: passTapView.widthAnchor),
passTapView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
passTapView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// add action for each button
btnsArray.forEach { b in
b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
}
// set self as the custom delegate for passTapView
passTapView.ptDelegate = self
}
#objc func btnTapped(_ sender: Any?) -> Void {
// unwrap the sender as a button and get its title
guard let b = sender as? UIButton,
let t = b.currentTitle
else {
return
}
print("Button \(t) was tapped!")
}
func passMeTheTap(_ recognizer: UITapGestureRecognizer) {
// loop through buttons array to see if tap from
// passTapView is inside any of them
btnsArray.forEach { b in
if b.bounds.contains(recognizer.location(in: b)) {
self.btnTapped(b)
}
}
}
}
The code will create 4 buttons in a grid, with a PassTapView overlaid on top so it covers part of each button:
Tapping the Red view where it covers part of a button will "pass the tap" gesture to the delegate, which will trigger the .touchUpInside action if the tap location is contained in a button.
If we un-comment two lines in the controller (see the comments), it will lay out the buttons in a stack so they overlap each other:
Now, tapping the left side of the red view will trigger the .touchUpInside action for multiple buttons because the tap gesture location will fall inside more than one of them.

UIPanGestureRecognizer doesn't trigger action

I'm trying to create a subclass of UIView in order to let expand the view with a pan over it. It should work this way: if the user makes a pan toward the top the view's height decrease, instead if the pan is toward the bottom it should increase. In order to achieve that functionality, I'm trying to add a UIPanGestureRecognizer to the view but it doesn't seem to work. I've done it this way:
The first snippet is the uiView subclass declaration
class ExpandibleView: UIView {
//Here I create the reference to the recognizer
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
//And here I set its minimumNumberOfTouches and maximumNumberOfTouches properties and add it to the view
func initialize() {
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
self.addGestureRecognizer(panGestureRecognizer)
}
//here's the function that should handle the pan but who instead doesn't seem to been called at all
#objc func handlePan(_ sender:UIPanGestureRecognizer) {
//Here's I handle the Pan
}
}
The second one instead is the implementation inside the View Controller.
class MapViewController: UIViewController {
#IBOutlet weak var profileView: ExpandibleView!
//MARK: - ViewController Delegate Methods
override func viewDidLoad() {
super.viewDidLoad()
//Here I set the View
profileView.isUserInteractionEnabled = true
profileView.initialize()
profileView.minHeight = 100
profileView.maxHeight = 190
}
}
I set inside the storyboard the view's class as the subclass I created but the recognizer doesn't trigger at all the handler.
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:
Try change this
class ExpandibleView: UIView {
func initialize() {
}
//here's the function that should handle the pan but who instead doesn't seem to been called at all
func handlePan() {
//your function
}
}
class MapViewController: UIViewController {
#IBOutlet weak var profileView: ExpandibleView!
//MARK: - ViewController Delegate Methods
override func viewDidLoad() {
super.viewDidLoad()
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
profileView.addGestureRecognizer(panGestureRecognizer)
//Here I set the View
profileView.isUserInteractionEnabled = true
profileView.initialize()
profileView.minHeight = 100
profileView.maxHeight = 190
}
#objc func handlePan(_ sender:UIPanGestureRecognizer) {
profileView.handlePan()
}
}
Or you can create a delegate for call in other view.
good luck!

Adding a menu overlay to a UICollectionView using LongPress gesture

I've got a UICollectionView that typically contains 100-200 cells that scrolls both horizontally and vertically. I am trying to provide a simple UIView popup with 3 icons when the user performs a LongPress gesture to allow them to easily navigate to a couple specific cells within the UICollectionView.
I'd like the start of the LongPress to bring up the popup, then the user would drag their finger to one of the three icons and release to select that icon.
The problem I'm having is I can't figure out how to get either the view containing the 3 icons or the icons themselves to respond to the end of the LongPress gesture.
Here's a simplification of the UIViewController:
class MyViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var myCollectionView: UICollectionView!
#IBAction func longTouch(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
self.menu!.frame = CGRect(x: sender.location(in: self.view).x - 20,
y: sender.location(in: self.view).y - 75,
width: 100, height: 100)
self.menu!.isHidden = false
}
else if sender.state == .ended {
self.menu!.isHidden = true
}
}
// Popup for long touch
var menu: UIView?
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
self.initMenu()
}
// MARK:- UICollectionViewDelegate
...
// MARK:- UICollectionViewDataSource
...
// MARK:- UIScrollViewDelegate
...
private func initMenu() -> Void {
self.menu = UIView()
self.menu!.isUserInteractionEnabled = true
self.menu!.frame = CGRect(x: 0,
y: 0,
width: 100,
height: 50)
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(menuLongPressHandler))
self.menu!.addGestureRecognizer(longPress)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
self.menu!.addGestureRecognizer(tapGesture)
// There would be ImageView icons here as well, which I've left out for simplicity
self.menu!.isHidden = true
}
func menuLongPressHandler() {
print("Long Press")
}
func handleTap(sender: UITapGestureRecognizer) {
print("Tap")
}
}
Neither the "Tap" or "Long Press" are printed when the LongPress ends while over the View. Any suggestions on how to get the view to capture the end of that LongPress gesture?

iOS Swift, cannot get pinch gesture to work

i have a test project that takes text from a file, adds it to a textview and displays it.
i want to add some gestures but cannot seem to make it work...
here is the relevant code:
class ViewController2: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet var textview1: UITextView!
var pinchGesture = UIPinchGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
self.textview1.userInteractionEnabled = true
self.textview1.multipleTouchEnabled = true
self.pinchGesture.delegate = self
self.pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(ViewController2.pinchRecognized(_:)))
self.view.addGestureRecognizer(self.pinchGesture)
}
#IBAction func pinchRecognized(pinch: UIPinchGestureRecognizer) {
self.textview1.addGestureRecognizer(pinchGesture)
self.textview1.transform = CGAffineTransformScale(self.textview1.transform, pinch.scale, pinch.scale)
pinch.scale = 1.0
}
any ideas? followed several tutorials but none seem to help. code is tested on actual iPhone...
thanks a lot
Edit for Solution:
#IBAction func pinchRecognized(pinch: UIPinchGestureRecognizer) {
var pinchScale = pinchGesture.scale
pinchScale = round(pinchScale * 1000) / 1000.0
if (pinchScale < 1) {
self.textview1.font = UIFont(name: self.textview1.font!.fontName, size: self.textview1.font!.pointSize - pinchScale)
pinchScale = pinchGesture.scale
} else {
self.textview1.font = UIFont(name: self.textview1.font!.fontName, size: self.textview1.font!.pointSize + pinchScale)
pinchScale = pinchGesture.scale
}
}
thanks to nishith Singh
Try adding the gesture recogniser to your textview in viewDidLoad instead of adding it in pinchRecognized. Currently you are adding the pinchGesture to your view which is behind your text view and hence will not receive the touch
var pinchGesture = UIPinchGestureRecognizer()
Use this code:
override func viewDidLoad() {
super.viewDidLoad()
self.textview1.userInteractionEnabled = true
self.textview1.multipleTouchEnabled = true
self.pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(pinchRecognized(_:)))
self.textview1.addGestureRecognizer(self.pinchGesture)
// Do any additional setup after loading the view.
}
#IBAction func pinchRecognized(_ pinch: UIPinchGestureRecognizer) {
let fontSize = self.textview1.font!.pointSize*(pinch.scale)/2
if fontSize > 12 && fontSize < 32{
textview1.font = UIFont(name: self.textview1.font!.fontName, size:fontSize)
}
}
You might have to hit and trial with the minimum and maximum font sizes as you want, right now the minimum font size is 12 and the maximum font size is 32.
let pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(self.pinchGesture))
func pinchGesture(sender: UIPinchGestureRecognizer){
sender.view?.transform = (sender.view?.transform)!.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
print("pinch gesture")
}
class ViewController2: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet var textview1: UITextView!
var pinchGesture = UIPinchGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
self.textview1.userInteractionEnabled = true
self.textview1.multipleTouchEnabled = true
self.pinchGesture.delegate = self
self.pinchGesture = UIPinchGestureRecognizer(target: self, action: "pinchRecognized:")
self.view.addGestureRecognizer(self.pinchGesture)
}
func pinchRecognized(pinch: UIPinchGestureRecognizer) {
self.textview1.addGestureRecognizer(pinchGesture)
self.textview1.transform = CGAffineTransformScale(self.textview1.transform, pinch.scale, pinch.scale)
pinch.scale = 1.0
}
Your code is actually working. But not the way you want probably.
Initially you assigned the gesture recogniser to the view of view controller.
But then inside the method you added same gesture recogniser to the UITextView.
So it should be working on UITextView. And the gesture recogniser is removed from the view controller's view. Gesture recogniser can only have one target. Pick view controller's view or textview.
You set the delegate of self.pinchGesture before initialising.
Initialise the self.pinchGesture first.
Set the delegate.
Add self.pinchGesture to self.view
self.pinchGesture.delegate = self
self.pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(ViewController2.pinchRecognized(_:)))
self.view.addGestureRecognizer(self.pinchGesture)

Swift, iboutlet and custom controls

I may be doing something really stupid, but I don't seem to be able to use Interface Builder to connect IBOutlet variables to custom views, but only in Swift.
I've created a class called MyView, which extends from UIView. In my controller, I've got a MyView variable (declared as #IBOutlet var newView: MyView). I go into IB and drag a UIView onto the window and give it a class of MyView.
Whenever I've done similar in Objective C, I'm then able to click on the View Controller button at the top of the app window, select the variable and drag it down to the control to link the two together. When I try it in Swift, it refuses to recognise that the view is there.
If I change the class of the variable in the controller to UIView, it works fine. But not with my custom view.
Has anyone else got this problem? And is it a feature, or just my idiocy?
Code for Controller
import UIKit
class ViewController: UIViewController {
#IBOutlet var newView:MyView
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Code for view
import UIKit
class MyView: UIView {
init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect)
{
// Drawing code
}
*/
}
I've had a similar problem, and I think it's partially a caching issue and partially just an Xcode6/Swift issue. The first step I found was required was to make sure that the view controller .swift file would be loaded in the Assistant Editor when choosing "automatic".
With Xcode finding that both the files are linked I could sometimes control-drag from the view/button/etc. from the IB to the .swift file, but often had to drag from the empty circle in the gutter of the #IBOutlet var newView:MyView line to the view I wanted it to match up to.
If you can't get the file to load in the Assistant Editor then I found that doing the following would often work:
Remove the custom class from the IB view
Clean the project (cmd + K)
Close/reopen Xcode
Possibly clean again?
Add the custom class back to the view
Hope it works :)
If that seems to get you half way/nowhere add a comment and I'll see if it triggers anything else I did
In my case import UIKit was missing, after adding this line I could create an IBOutlet from Storyboard again.
I've had a similar problem to the one described in this thread. Maybe you found a solution maybe not but anybody who encounters this in the future. I've found the key is to use the "required init" function as follows:
required init(coder aDecoder: NSCoder) {
print("DrawerView: required init")
super.init(coder: aDecoder)!
screenSize = UIScreen.mainScreen().bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
self.userInteractionEnabled = true
addCustomGestureRecognizer()
}
This is the complete class of my custom view:
import UIKit
import Foundation
class DrawerView: UIView {
var screenSize: CGRect!
var screenWidth: CGFloat!
var screenHeight: CGFloat!
var drawerState: Int = 0
override init (frame : CGRect) {
print("DrawerView: main init")
super.init(frame : frame)
}
override func layoutSubviews() {
print("DrawerView: layoutSubviews")
super.layoutSubviews()
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
print("DrawerView: required init")
super.init(coder: aDecoder)!
screenSize = UIScreen.mainScreen().bounds
screenWidth = screenSize.width
screenHeight = screenSize.height
self.userInteractionEnabled = true
addCustomGestureRecognizer()
}
func addCustomGestureRecognizer (){
print("DrawerView: addCustomGestureRecognizer")
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(self.handleDrawerSwipeGesture(_:)))
swipeDown.direction = UISwipeGestureRecognizerDirection.Down
self.addGestureRecognizer(swipeDown)
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(self.handleDrawerSwipeGesture(_:)))
swipeUp.direction = UISwipeGestureRecognizerDirection.Up
self.addGestureRecognizer(swipeUp)
print("DrawerView self: \(self)")
}
func minimizeDrawer(){
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseOut, animations: {
// let height = self.bookButton.frame.size.height
// let newPosY = (self.screenHeight-64)*0.89
// print("newPosY: \(newPosY)")
self.setY(self.screenHeight*0.86)
}, completion: { finished in
self.drawerState = 0
for view in self.subviews {
if let _ = view as? UIButton {
let currentButton = view as! UIButton
currentButton.highlighted = false
} else if let _ = view as? UILabel {
let currentButton = view as! UILabel
if self.tag == 99 {
currentButton.text = "hisotry"
} else if self.tag == 999 {
currentButton.text = "results"
}
}
}
})
}
func handleDrawerSwipeGesture(gesture: UIGestureRecognizer) {
print("handleDrawerSwipeGesture: \(self.drawerState)")
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch self.drawerState{
case 0:
if swipeGesture.direction == UISwipeGestureRecognizerDirection.Down {
// nothing to be done, mini and swiping down
print("mini: !")
} else {
// mini and swiping up, should go to underneath city box
UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseOut, animations: {
let toYPos:CGFloat = 128 + 64 + 8
self.setY(toYPos)
}, completion: { finished in
self.drawerState = 1
for view in self.subviews {
if let _ = view as? UIButton {
let currentButton = view as! UIButton
currentButton.highlighted = true
} else if let _ = view as? UILabel {
let currentLabel = view as! UILabel
currentLabel.text = "close"
}
}
})
}
break;
case 1:
if swipeGesture.direction == UISwipeGestureRecognizerDirection.Down {
// open and swiping down
self.minimizeDrawer()
} else {
// open and swiping up, nothing to be done
}
break;
default:
break;
}
}
}
}
Hope this helps...

Resources