UIPanGestureRecognizer doesn't trigger action - ios

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!

Related

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

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)
}
}

Swift addSubview() on views created using init(repeating:count) doesn't work

Here is a ViewController that creates 4 subviews using init(repeating:count).
In viewDidLoad I add them as subviews and set their frames. When I run the application only the last view is added.
class ViewController: UIViewController {
let subviews = [UIView].init(repeating: UIView(), count: 4)
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<subviews.count {
self.view.addSubview(subviews[i])
self.subviews[i].backgroundColor = UIColor.red
self.subviews[i].frame = CGRect(x: CGFloat(i) * 35, y: 30, width: 30, height: 30)
}
}
}
Here's the same code except instead of using init(repeating:count) I use
a closure. This works fine-- all subviews are added.
class ViewController: UIViewController {
let subviews: [UIView] = {
var subviews = [UIView]()
for i in 0..<4 {
subviews.append(UIView())
}
return subviews
}()
override func viewDidLoad() {
//same as above...
}
}
You’ve put the same instance of UIView in your array four times. Your viewDidLoad just ends up moving that single view around. You need to create four separate instances of UIView.
let subviews = (0 ..< 4).map({ _ in UIView() })

Tap Gesture Recognizer not received in custom UIView embedded in super view

I am trying to create a custom UIView/Scrollview named MyScrollView that contains a few labels (UILabel), and these labels receive tap gestures/events in order to respond to user's selections .
In order to make the tap event work on the UILabels, I make sure they all have userIteractionEnabled = true and I created a delegate as below:
protocol MyScrollViewDelegate {
func labelClicked(recognizer: UITapGestureRecognizer)
}
The custom UIView is being used in ScrollViewController that I created, this ScrollViewController implements the delegate method as well:
import UIKit
import Neon
class ScrollViewController: UIViewController, MyScrollViewDelegate {
var curQuestion: IPQuestion?
var type: QuestionViewType?
var lastClickedLabelTag: Int = 0 //
init(type: QuestionViewType, question: IPQuestion) {
super.init(nibName: nil, bundle: nil)
self.curQuestion = question
self.type = type
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func loadView() {
view = MyScrollView(delegate: self, q: curQuestion!)
view.userInteractionEnabled = true
}
}
// implementations for MyScrollViewDelegate
extension ScrollViewController {
func labelTitleArray() -> [String]? {
print("labelTitleArray called in implemented delegate")
return ["Comments", "Answers"]
}
func labelClicked(recognizer: UITapGestureRecognizer) {
print("labelClicked called in implemented delegate")
let controller = parentViewController as? ParentViewController
controller?.labelClicked(recognizer)
lastClickedLabelTag = recognizer.view!.tag
}
}
// MARK: - handle parent's ViewController event
extension QuestionDetailViewController {
func updateActiveLabelsColor(index: Int) {
print("updating active labels color: \(index)")
if let view = view as? MyScrollView {
for label in (view.titleScroll.subviews[0].subviews as? [UILabel])! {
if label.tag == index {
label.transform = CGAffineTransformMakeScale(1.1,1.1)
label.textColor = UIColor.purpleColor()
}
else {
label.transform = CGAffineTransformMakeScale(1,1)
label.textColor = UIColor.blackColor()
}
}
}
}
}
This above ScrollViewController is added, as a child view controller to the parent view controller, and positioned to the top part of the parent's view:
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
self.view.backgroundColor = UIColor.whiteColor()
addChildViewController(scrollViewController) // added as a child view controller here
view.addSubview(scrollViewController.view) // here .view is MyScrollView
scrollViewController.view.userInteractionEnabled = true
scrollViewController.view.anchorToEdge(.Top, padding: 0, width: view.frame.size.width, height: 100)
}
The app can load everything up in the view, but the tap gesture/events are not passed down to the labels in the custom MyScrollView. For this, I did some google search and have read Event Delivery: Responder Chain on Apple Developer website and did a hit test as well. The hitTest function below can be triggered in the MyScrollView:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
print("hit test started, point: \(point), event: \(event)")
return self
}
My observations with the hitTest is that the touchesBegan() and touchesEnded() methods are triggered in the view only when the hitTest function is there. Without hitTest, both functions do not get called with taps.
but no luck getting the UILabel to respond to Tap Gestures. So I am reaching out to experts on SO here. Thanks for helping!
I think I found out the reason why the UILabel did not respond to tapping after much struggle: the .addGestureRecognizer() method to the label was run in the init() method of my custom UIView component, which is wrong, because the view/label may not have been rendered yet. Instead, I moved that code to the lifecycle method layoutSubviews(), and everything started to work well:
var lastLabel: UILabel? = nil
for i in 0..<scrollTitleArr.count {
let label = UILabel()
label.text = scrollTitleArr[i] ?? "nothing"
print("label: \(label.text)")
label.font = UIFont(name: "System", size: 15)
label.textColor = (i == 0) ? MaterialColor.grey.lighten2 : MaterialColor.grey.darken2
label.transform = (i == 0) ? CGAffineTransformMakeScale(1.1, 1.1) : CGAffineTransformMakeScale(0.9, 0.9)
label.sizeToFit()
label.tag = i // for tracking the label by tag number
label.userInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.labelClicked(_:))))
titleContainer.addSubview(label)
if lastLabel == nil {
label.anchorInCorner(.TopLeft, xPad: 0, yPad: 0, width: 85, height: 40)
// label.anchorToEdge(.Left, padding: 2, width: 85, height: 40)
} else {
label.align(.ToTheRightMatchingTop, relativeTo: lastLabel!, padding: labelHorizontalGap, width: 85, height: 40)
}
lastLabel = label
}
In addition, I don't need to implement any of the UIGestureRecognizer delegate methods and I don't need to make the container view or the scroll view userInteractionEnabled. More importantly, when embedding the custom UIView to a superview, I configured its size and set clipsToBounds = true.
I guess I should have read more UIView documentation on the Apple Developer website. Hope this will help someone like me in the future! Thanks to all!
You have to set the property userInteractionEnabled = YES.
For some reason, my simulator was frozen or something when the tap gesture recognizer wasn't working. So, when I restarted the app, then it all worked again. I don't know if this applies here, but that was the fix for me.

App crashes when setting a class to UITapGestureRecognizer's target instead of 'self'

The below code works when I set 'self' as target to the instance of UITapGestureRecognizer but when I set it to 'SkinViewTransitionHelper' instance the app crashes and the Xcode doesn't show any helpful information, actually nothing at all and when the app crashes the control(the green indicator I mean) goes to AppDelegate. Also, the stack trace is empty just showing some threads that are not relevant to the crash. I did search a lot and reread the documentation of UIGestureRecognizer but did not help. What am I doing wrong? I'm coding the app in Xcode Version 6.2 (6C107a). Also, I'm testing the app in iOS simulator Version 8.2 (553.8). I also did check UITapGestureRecognizer calling another class
/**
Manages the view that contains logo and button.
*/
class LaunchView: UIView {
// MARK: Instance variables
/** Logo image view */
private var logoImageView: UIImageView!
/** x Button image view */
private var xButtonImageView: UIImageView!
// MARK: Designated Initializer
/**
Initialize LaunchView view with logo and button.
:param: frame Frame of this view
:param: logoImage Logo Image used at the top of the view
:param: xButtonImage Button Image used at the bottom of the view
*/
init(frame: CGRect, logoImage: UIImage, xButtonImage: UIImage){
super.init(frame: frame)
// Initialize the logoImageView
logoImageView = UIImageView(frame: frame)
// Set the image of logoImageView
logoImageView.image = logoImage
// Initialize the capriceButtonImage
// Note: It's kinda hard coding the (x,y) values. Look for a generic way.
xButtonImageView = UIImageView(frame: CGRect(x: frame.size.width / 4 + 20, y: frame.size.height - 200, width: xButtonImage.size.width, height: xButtonImage.size.height))
// Set the image of xButtonImageView
xButtonImageView.image = xButtonImage
// SingleTap recognizer for xButtonImageView
// Note: Find a generic solution instead of writing method name in "". It's kinda horrible; Maybe I forget to write the name of function correctly.
// Note: Still not possible in Swift. reflect() helps if we want information about properties.
// Note: https://github.com/ksm/SwiftInFlux#reflection
// FIXME: App crashes using SkinViewTransitionHelper() as target
let singleTapGestureRecognizer: UITapGestureRecognizer =
UITapGestureRecognizer(target: SkinViewTransitionHelper(), action: "handleGesture:")
// works like a charm if I remove the comment and comment above line
// let singleTapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "handleGesture:")
// Set the nunmber of taps required
singleTapGestureRecognizer.numberOfTapsRequired = 1
singleTapGestureRecognizer.numberOfTouchesRequired = 1
// Add tap gesture to xButtonImageView
xButtonImageView.addGestureRecognizer(singleTapGestureRecognizer)
xButtonImageView.userInteractionEnabled = true
// Add logoImageView to the view
self.addSubview(logoImageView)
// Add xButtonImageView to the view
self.addSubview(xButtonImageView)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// func handleGesture(gestureRecongnizer: UIGestureRecognizer) {
// if .Ended == gestureRecongnizer.state {
// T0D0: Notify LaunchViewController of gesture
// }
// }
}
/**
Handle gesture recognized by a UIView instance
*/
class SkinViewTransitionHelper {
// MARK: Gesture Handler
/**
Handle gesture recognized by a UIView instance
*/
func handleGesture(gestureRecongnizer: UIGestureRecognizer) {
println(__FUNCTION__)
if .Ended == gestureRecongnizer.state {
// TODO: Load SkinViewController
}
}
}
import UIKit
class LaunchViewViewController: UIViewController {
override func viewDidLoad() {
println(self)
println(__FUNCTION__)
super.viewDidLoad()
}
override func loadView() {
println(self)
println(__FUNCTION__)
// Set LaunchView as the view of LaunchViewViewController
self.view = LaunchView(frame: UIScreen.mainScreen().bounds, logoImage: ImagesCatalog.Logo, xButtonImage: ImagesCatalog.xButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
It crashes because target is a weak property of the UITapGestureRecognizer class and so the instance of SkinViewTransitionHelper is not retained by by the UITapGestureRecognizer.
Make a property of type SKinViewTransitionHelper and store an instance on it and set it to the UITapGestureRecognizer target.
class LaunchView {
var skinViewHelper : SKinViewTransitionHelper!
init(frame: CGRect, logoImage: UIImage, xButtonImage: UIImage){
//Your code
self.skinViewHelper = SKinViewTransitionHelper()
let singleTapGestureRecognizer: UITapGestureRecognizer =
UITapGestureRecognizer(target: self.skinViewHelper, action: "handleGesture:")
}
}
Also mark the function you want to reference as selector with #objc
class SkinViewTransitionHelper {
#objc func handleGesture(gestureRecongnizer: UIGestureRecognizer) {
println(__FUNCTION__)
if .Ended == gestureRecongnizer.state {
// TODO: Load SkinViewController
}
}
}
What happens if you instantiate SkinViewTransitionHelper a store its value into a variable (probably needs to be a class property) and then assign it as the target?
I think it's possible that you SkinViewTransitionHelper instance is deallocated after the 'target' assignment because there are no hard references to it anywhere.

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