Code for the Custom UIView:
Please check the video too here: https://drive.google.com/open?id=1kbrOxXWcJIi4vkiqMNer3exBr5cOWgDz
import UIKit
protocol PostAttachmentFullScreenViewDelegate: class {
func closeAttachmentFullView()
}
class PostAttachmentFullScreenView: UIView {
weak var delegate: PostAttachmentFullScreenViewDelegate?
#IBOutlet var backgroundView: UIImageView!
#IBOutlet var closeButton: UIButton!
#IBAction func closeViewAction(_ sender: Any) {
print("will call delegate to put it off")
self.delegate?.closeAttachmentFullView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let _ = commonInitialization()
backgroundView.image = UIImage(named: "ScrollImageTop1")
closeButton.isUserInteractionEnabled = true
}
override init(frame: CGRect) {
super.init(frame: frame)
let _ = commonInitialization()
backgroundView.image = UIImage(named: "ScrollImageTop1")
closeButton.isUserInteractionEnabled = true
}
func commonInitialization() -> UIView
{
let bundle = Bundle.init(for: type(of: self))
let nib = UINib(nibName: "PostAttachmentFullScreenView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
return view
}
}
usage in ViewController (I am defining an instance of the custom view and putting it inside the Scroll View):
var frame = CGRect(x:0, y:0, width:0, height:0)
let blue = PostAttachmentFullScreenView()
blue.delegate = self
blue.isUserInteractionEnabled = true
blue.backgroundColor = UIColor.blue
blue.backgroundView.image = fileAttachments[1]
frame.origin.x = attachmentsScrollView.frame.size.width * CGFloat (0)
frame.size = attachmentsScrollView.frame.size
blue.frame = frame
attachmentsScrollView.addSubview(blue)
extension NewPostViewController : PostAttachmentFullScreenViewDelegate
{
func closeAttachmentFullView() {
print("hiding attachments view")
attachmentSuperView.isHidden = true
}
}
To my surprise it doesn't even print - "will call delegate to put it off".
I am not able to understand what's wrong here. Please help me understand the issue and correct it. Thank you.
You are mixing programmatic approach and xib approach.
As you have added IBOultet and IBAction that means you are using xib for the UIView.
In that scenario you have to load the UIView xib when initialising the view.
Add an extension for UIView in your project:
extension UIView {
class func fromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
}
}
when you are initialising your view add it like this :
let blue : PostAttachmentFullScreenView = UIView.fromNib()
blue.delegate = self
blue.isUserInteractionEnabled = true
blue.backgroundColor = UIColor.blue
blue.backgroundView.image = fileAttachments[1]
frame.origin.x = attachmentsScrollView.frame.size.width * CGFloat (0)
frame.size = attachmentsScrollView.frame.size
blue.frame = frame
attachmentsScrollView.addSubview(blue)
and the delegate and button action methods will work.
you missed this :
You never set the target/action on your button. Somewhere you need to call addTarget(_:action:for:) to set the target/action on the button. Also, what connects the button to your PostAttachmentFullScreenView as an outlet?
This might be an obvious one but for me (Xcode 10.1) adding all missing UI constraints to the UIButton in question (at least 4 constraints) fixed the error for me in my custom view:
Make sure you add enough constraints (typically 4 constraints) or enough to have all warnings regarding missing constraints removed. After doing this and attaching the button with Ctrl + drag from View to corresponding swift code, the click was being detected and working properly.
Hope this helps.
I'm working with Xcode 10.0 beta 4 and Swift 4 and I'm trying to add a UIGestureRecognizers specifically a UISwipeGestureRecognizer for both left and right swipes to a UIView that has been added as a subview to my main View Controller via a nib but unfortunately the gesture doesn't seem to be recognized when I actually perform the action in simulator. Here's a snippet of what is in my viewDidLoad():
//Add gesture recognizer for when the calendar is swiped right.
let grSwipeRight = UISwipeGestureRecognizer(target: self, action: #selector(swipeRight(sender:)))
grSwipeRight.direction = UISwipeGestureRecognizer.Direction.right
calendar.calendarDays.isUserInteractionEnabled = true
calendar.calendarDays.addGestureRecognizer(grSwipeRight)
The swipeRight action in the selector is to a function that just contains a print() call and calendar is a reference to my UIView which is the class used in my .xib
class Calendar: UIView
{
#IBOutlet var calendarDays: UICollectionView!
let nibName = "Calendar"
var contentView: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let view = self.loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
self.bringSubviewToFront(view)
contentView = view
contentView?.isUserInteractionEnabled = true
}
private func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
I setup the .xib as a UIView using Interface Builder and set the class of the view to my Calendar class and the labels and such within my .xib show up properly its just when I add any Gesture Recognizer to either the calendar(View) or objects within my calendar the gesture isn't recognized. Is this even the proper way to do something like this or should I be taking a completely different approach?
Edit:
I've included what my IB .xib and .storyboard look like if its helpful at all. Storyboard, xib
To be able to use gesture recognizers within my .xib, I used an override of awakeFromNib() as follows within my custom UIView Calendar class:
override func awakeFromNib()
{
super.awakeFromNib()
let touchTest = UITapGestureRecognizer(target: self, action: #selector(self.testTap(sender:)))
testingLabel.isUserInteractionEnabled = true
testingLabel.addGestureRecognizer(touchTest)
}
I have a drawing app. Inside my VC there are five imageViews with five colors in them. I want to be able to click on the imageView and change the stroke color. It can be easily done if I repeat myself in the viewcontroller by adding gesture Recognizers to each UIImageView and have their individual "selector" function. Such as
func redTapped() {}
func blueTapped() {}
However, I want to be able to make the code more clear by creating a custom class (ColorImageView.Swift) for these ImageViews so that when I assign the class to these buttons, they automatically gets the tap gesture and my VC automatically receives the information about which one is tapped. At the moment, I can get a "imagePressed" printed out for each image that gets assigned to my class. However, I have no way of distinguishing which one were pressed. Below are my code for ColorImageView.Swift
import Foundation
class ColorImageView: UIImageView {
private func initialize() {
let touchGesture = UITapGestureRecognizer(target: self, action: #selector(ColorImageView.imagePressed(_:)))
touchGesture.numberOfTapsRequired = 1
self.userInteractionEnabled = true
self.addGestureRecognizer(touchGesture)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func imagePressed(gestureRecognizer: UITapGestureRecognizer) {
print("image pressed \(gestureRecognizer)")
}
}
My imageView names are red.png, green.png, blue.png...etc
Thanks
You can get the tag easily.It works fine.
func imagePressed(gestureRecognizer: UITapGestureRecognizer)
{
print("image pressed \(gestureRecognizer)")
let tappedImageVIew = gestureRecognizer.view as! UIImageView
print("image pressed \(tappedImageVIew.tag)")
}
After initialisation of by subclass of UIImageView I have the following line of code:
self.userInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:"))
I created the necessary associated function :
func handleTap(gestureRecognizer: UITapGestureRecognizer) {
print("In handler")
}
On tapping on the view in question, "In handler was never printed to the console". I then removed the handler function to see if the compiler would complain about the missing function. It didn't.
I'm positively stumped. I'd truly appreciate any light people can shed on this.
Update: My class is actually a UIImageView as opposed to UIView
I was using UITapGestureRecognizer that I placed on a UILabel using Storyboard.
To get this to work I also had to place a checkmark in the block labeled: "User Interaction Enabled" in the UILabel Attributes Inspector in the Storyboard.
I discovered the answer after carefully combing through my code.
One of the parent views was created without supplying a frame:
While it's a noobish enough error to warrant deletion of this questions, odds are someone else will also have the same issue in the future...
Try this
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.view.userInteractionEnabled = true
var tapGesture = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
self.view.addGestureRecognizer(tapGesture)
}
func handleTap(sender : UIView) {
println("Tap Gesture recognized")
}
In addition to the other answers, this can be caused by adding the gesture recognizer to multiple views. Gesture recognizers are for single views only.
Reference: https://stackoverflow.com/a/5567684/6543020
I ran into this problem with programmatic views.
My UIView with the gesture recognizer had .isUserInteractionEnabled = true, but it did not respond to taps until I set .isUserInteractionEnabled = true for its parent views as well.
Most likely you add UIGestureRecognizer in wrong place. Here is working sample with UIView from storyboard. If you create your UIView dynamically then you should put this initialization in the correct constructor.
class TestView: UIView
{
override func awakeFromNib()
{
self.userInteractionEnabled = true
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:"))
}
func handleTap(gestureRecognizer: UITapGestureRecognizer)
{
println("Here")
}
}
I found the solution to this problem after lot of trial and error. So there are two solution two this
1. Either add the GestureRecognizer in viewDidLoad() and turn
userInteractionEnabled = true
2. If using computed property use lazy var instead of let to the property.
lazy var profileImageView: UIImageView = {
let iv = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
iv.image = #imageLiteral(resourceName: "gameofthrones_splash")
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleSelectProfileImageView)))
iv.isUserInteractionEnabled = true
return iv
}()
For anyone that is still having problems even with all the previous answers, make sure you are using UITapGestureRecognizer instead of UIGestureRecognizer, I kept missing this detail while trying to find what was wrong.
This Code works for me with XCode 7.0.1
import UIKit
class ImageView: UIImageView {
init(frame: CGRect, sender: Bool, myImage: UIImage) {
super.init(frame: frame)
self.image = myImage
initBorderStyle(sender)
// enable user interaction on image.
self.userInteractionEnabled = true
let gesture = UITapGestureRecognizer(target: self, action: "previewImage:")
addGestureRecognizer(gesture)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func previewImage(myGesture: UITapGestureRecognizer? = nil) {
print("i'm clicked")
}
private func initBorderStyle(sender: Bool) {
self.layer.masksToBounds = true
self.layer.cornerRadius = 8
self.layer.borderWidth = 0.5
self.layer.borderColor = getBorderColor(sender)
self.backgroundColor = getColor(sender)
}
func getBorderColor(sender: Bool) -> CGColor {
var result: CGColor
if sender {
result = UIColor(red: 0.374, green: 0.78125, blue: 0.0234375, alpha: 0.5).CGColor
} else {
result = UIColor(red: 0.3125, green: 0.6015625, blue: 0.828125, alpha: 0.5).CGColor
}
return result
}
}
Xcode 11.4 Swift 5.2
UserInteraction is enabled by default on Custom UIView
import UIKit
class YourView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
self.addGestureRecognizer(tapGesture)
}
#objc func tapped() {
// do something
}
}
If you want to be notified elsewhere that the custom UIView has been tapped, then it is convenient to use a Notification.
It is best to create this as an extension to prevent it being Stringly typed.
extension Notification.Name {
static let DidTapMyView = Notification.Name("DidTapMyView")
}
In your custom UIView when the tapped function is called
#objc func tapped() {
NotificationCenter.default.post(name: .DidTapMyView, object: self)
}
In your UIViewController where you want to listen for the Notification:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(myViewWasTapped), name: .DidTapMyView, object: nil)
}
#objc func myViewWasTapped() {
// Notified here - do something
}
Here's a list of my findings in 2021, XCode 13:
Make sure both your view and all of its superviews have set width & height constraints(this one is crucial)
Set isUserInteractionEnabled = true for your view
There's no need to set explicit frame or isUserInteractionEnabled for other super views
In addition the above (userInteractionEnabled, clipsToBounds, etc.), if you are working with a view in a child view controller be sure that you have added it as a child with myParentViewController.addChild(myChildViewController) — we've run into a couple of situations now where visible views were not firing recognizing gestures because their view controllers hadn't been added, and presumably the VCs themselves were not being retained.
It turned out that my gesture didn't work because it was in a normal class, and not a subclass of UIView or anything.
I solved the issue by creating a subclass of UIView that I had an instance of in this normal class and placed the gesture logic in that.
I solved my issue by setting the height to the UIView.
optionView.heightAnchor.constraint(equalToConstant: 18).isActive = true
I had the same issue in my programmatically created ViewController. The bug was fixed when I added a backgroundColor to the view.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing))
view.addGestureRecognizer(tap)
}
}
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...