iOS Swift: Send action (control event) from subclass of UIImageView - ios

How can I send action (similar kind of tap event) from sub class of UIImageView to View Controller.
Here is reference answer, that I want to apply for UIImageView. (UIImageView does not have UIControl in its super class hierarchy)
Following is my code but not working as I don't know, how to implement, what I need.
class TappableImageView: UIImageView {
// Initializer methods.....
//------------------------------------------
private func addTapGesture(){
let tapOnImage = UITapGestureRecognizer(target: self, action: #selector(TappableImageView.handleTapGesture(tapGesture:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapOnImage)
}
//----------------------------------------------
#objc func handleTapGesture(tapGesture: UITapGestureRecognizer) {
// how can I send tap/click event to all view controllers, where I've used this image from this point.
}
}
Is there any alternate/other solution that may work for me?

I will recommend you to use a closure to handle taps:
class TappableImageView: UIImageView {
var handleTap: (() -> Void)? = nil
//------------------------------------------
private func addTapGesture(){
let tapOnImage = UITapGestureRecognizer(target: self, action: #selector(TappableImageView.handleTapGesture(tapGesture:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapOnImage)
}
//----------------------------------------------
#objc func handleTapGesture(tapGesture: UITapGestureRecognizer) {
handleTap?()
}
}
And then I your view controller you can use this i.e. in viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
yourTappableImageView.handleTap = {
print("an image view was tapped")
}
}
It assumes that your TappableImageView is stored in variable named yourTappableImageView.

Related

How can I detect a tap event in a child class of UIView?

I'm creating an app with swift.
I've made child classes from UIView. After making them and writing some processes there, I feel that I want them to detect touch events.
But they aren't children of UIButton.
I'd not like to force them to detect touch events using UIGestureRecognizer. Because UIGestureRecognizer needs to be used in UIViewController. I'd like to write codes of detecting touch just in the view.
Are there any ways to detect touch events just in UIView?
You can simply add the gesture to the subclass of UIView as other said, but if you want to include the gesture within the definition of the subclass and make it more modular, you can use the notification dispatch mechanism to broadcast the gesture to the registered view controller.
First, you create a name for the notification:
extension Notification.Name {
static let CustomViewTapped = Notification.Name("CustomViewTapped")
}
Then, you add the gesture to your custom view:
class CustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let tap = UIGestureRecognizer(target: self, action: #selector(tapped))
self.addGestureRecognizer(tap)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func tapped(_ sender: UIGestureRecognizer) {
NotificationCenter.default.post(name: .CustomViewTapped, object: self)
}
}
And, finally, observe the broadcast from your view controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(customViewTapped), name: .CustomViewTapped, object: nil)
let customView = CustomView()
self.view.addSubview(customView)
}
#objc func customViewTapped(_ sender: UIGestureRecognizer) {
}
}
You can add UIGestureRecognizer to UIView. Another way you can add invisible UIButton on top of your UIView.
If you use UIView subclass, you can use something following and handle tap in action closure
class TappableView: UIView {
var action: (()->())? = nil
init(frame: CGRect) {
super.init(frame: frame)
initialization()
}
func initialization() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tapGesture)
}
#objc private func tapGesture() {
action?()
}
}
class childView: UIView {
var action: (()->())? = nil
init(frame: CGRect) {
super.init(frame: frame)
initialization()
}
func initialization() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tapGesture)
}
#objc private func tapGesture() {
//Action called here
}
}
//MARK:- View Tap Handler
extension UIView {
private struct OnClickHolder {
static var _closure:()->() = {}
}
private var onClickClosure: () -> () {
get { return OnClickHolder._closure }
set { OnClickHolder._closure = newValue }
}
func onTap(closure: #escaping ()->()) {
self.onClickClosure = closure
isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(onClickAction))
addGestureRecognizer(tap)
}
#objc private func onClickAction() {
onClickClosure()
}
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView(frame: .init(x: 0, y: 0, width: 80, height: 50))
view.backgroundColor = .red
view.onTap {
print("View Tapped")
}
}

UIGesturerecognizer is not working on UIView

When I am tapping on UIView but Gesture is not working.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
I just want to perform this action on UIView tap.
You may check in the debug view hierarchy if anything with alpha = 0 is overlapping your viewForSHadow.
You have to implement as follows:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_sender:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
...
}
Your code is more than enough to have working UIGestureRecognizer, you should check some other stuff like, is there something else that can consume the user interaction. And also to check if you use
isUserInteractionEnabled = false
to some parent view of viewForSHadow.
You have to use UIGestureRecognizerDelegate
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tapRecognizer.delegate = self
blackView.addGestureRecognizer(tapRecognizer)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return true
}
#objc func handleDismiss() {
print("handleDismiss")
}
Reference
Don't forget to set the UIGestureRecognizerDelegate in your class
You need to mention numberOfTapsRequired property of the UITapGestureRecognizer.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_sender:)))
tap.numberOfTapsRequired = 1
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTapGesture(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
// Why are you hiding this view **viewForSHadow**
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
Also, make sure you are not doing viewForSHadow.isUserInteractionEnabled = false anywhere in the code.
First of all you code doesn't compile. The handleTap(_:) signature must be like:
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
Secondly, you need to first try with the minimal code in a separate project. And by minimal code I mean what you've added in the question.
class ViewController: UIViewController {
#IBOutlet weak var viewForSHadow: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
}
Try with just the above code and see if you can get it working. The above code is working well at my end without any delegate or numberOfTaps.

UITapGesture doesn't work as expected on Popup class

I create a Popup class to use in my app and I want to add a UITapGestureRecognizer to the black layer opacity, when the user touch outside of the popup this close automatically. But the gesture was not recognized. I show you my code of the Popup class
class Popup {
let supView : UIView!
let blackVoile = UIView()
init(superView viewToInsert : UIView){
self.supView = viewToInsert
build()
}
private func build(){
blackVoile.frame = supView.bounds
blackVoile.layer.backgroundColor = UIColor.black.cgColor
blackVoile.isUserInteractionEnabled = true
let closeGesture = UITapGestureRecognizer(target: self, action: #selector(self.close))
blackVoile.addGestureRecognizer(closeGesture)
}
func show(){
supView.addSubview(blackVoile)
}
#objc func close(){
print("close function")
self.blackVoile.removeFromSuperview()
}
}
The close func was never called. And there is no other over layer above the blackVoile UIView
This is when I called my class :
let newPopup = Popup(superView : self.view)
newPopup.show()
I'm beginner so, maybe we can't add gesture to a class who are not have an UIView instance?
Problem with your opacity. If we make any opacity to zero then that view consider as a hidden. So, your tapGesture not working.
Update
var newPopup : Popup!
override func viewDidLoad() {
super.viewDidLoad()
newPopup = Popup(superView : self.view)
newPopup.show()
}
Your supView also needs to be userInteractionEnabled.

How to identify which subView has been pressed in Swift?

I have a built a UIView class called SetView. In its initializer I create multiple subviews and later in ViewController I want to determine which subView has been pressed. in my viewDidLoad method I iterate through all the subviews add them to a class array of UIView called mySubViews and it my getIndex method, I am trying to retrieve the value which is always retrieved as nil. I suppose that it is my main view that is passed as a sender rather than particular subviews but I don't know how to pass specific subviews since #selector does not accept argument. I would appreciate any suggestions on how I could determine which subview was pressed to update features of a given subview.
override func viewDidLoad() {
super.viewDidLoad()
for view in setView.subviews {
mySubViews.append(view)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
}
}
#objc func getIndex(_ sender:UIView) {
print(mySubViews.index(of: sender))
}
The sender should be the gesture recognizer. Then give your views a tag and set the same tag for your gesture recognizer. Then you can get the view with viewWithTag.
Or with your array it could be like
override func viewDidLoad() {
super.viewDidLoad()
var index = 0
for view in setView.subviews {
mySubViews.append(view)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.tag = index
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
index += 1
}
}
#objc func getIndex(_ sender: UITapGestureRecognizer) {
print(mySubViews[sender.tag])
}
The sender in your target method is a gesture recognizer. The fact that it is only interpreted as UIView will always return nil in your call.
Try the following:
#objc func getIndex(_ sender: UIGestureRecognizer) {
print(mySubViews.index(of: sender.view))
}
Still I would prefer you would use a single gesture recognizer on the super view. Then you can check the hit view by checking if the gesture recognizer was within the view bounds:
#objc func getIndex(_ sender: UIGestureRecognizer) {
let allViewsAtGestureLocation = mySubViews.filter { $0.bounds.contains(sender.location(in: $0)) }
let firstHitView = mySubViews.first(where: { $0.bounds.contains(sender.location(in: $0)) })
}
I assume you would need the second one. From it you can again find an index.
Maybe try something like this:
for view in setView.subviews {
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(getIndex(_:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
mySubViews.addSubview(view)
}
Don't use append if you're trying to add it in, and add it after you assign the gesture.

I am defining gesture recognizers in an ImageView subclass but calling a method in the ViewController crashes with "unrecognized selector"

I am defining gesture recognizers in an ImageView subclass but calling a method in the ViewController crashes with "unrecognized selector". The gesture works if I define the gestures in the VC, but I'm trying to put the code in the custom view class to cut down on clutter.
Is this possible? This is what I'm doing:
class DetailPhotoImageView: UIImageView {
func setupGestures(){
let tapped = UITapGestureRecognizer(target: self, action: #selector(TripDetailVC.tapped(_:)))
tapped.numberOfTapsRequired = 2
addGestureRecognizer(tapped)
}
}
And then in the VC I have this function and call imageView.setupGestures()
func tapped(_ gesture:UIGestureRecognizer) {
if let tapGesture = gesture as? UITapGestureRecognizer {
switch tapGesture.numberOfTapsRequired {
case 2:
print("Worked")
default:
break
}
}
}
So going off of Randall Wang's suggestion, they are right, you should could also go about this another, space-saving way. Pass the viewController as a parameter and set the it as the target:
class DetailPhotoImageView: UIImageView, UIGestureRecognizerDelegate {
func setupGestures(_ target: UIViewController) {
guard let aTarget = target as? MyViewController else { return }
let tap = UITapGestureRecognizer(target: aTarget, action: #selector(aTarget.someAction(_:)))
tap.delegate = self
tap.numberOfTapsRequired = taps
addGestureRecognizer(tap)
}
}
And in your viewController class:
detailPhoto.setupGestures(self)
Make a protocol for your UIImageView that takes in the gesture and set your ViewController as the delegate (having the ImageView as a parameter as well is just good practice.. or common):
protocol DetailPhotoDelegate {
func detailPhoto(_ detailPhoto: DetailPhotoImageView, actionFor gesture: UITapGestureRecognizer)
}
Add a delegate variable to you UIImageView subclass and preform your protocol function when the view is tapped. Also, make sure your UIImageView is a UIGestureRecognizerDelegate.
class DetailPhotoImageView: UIImageView, UIGestureRecognizerDelegate {
var delegate: DetailPhotoDelegate?
func setupGestures() {
let tap = UITapGestureRecognizer(target: self, action: #selector(someAction(_:)))
tap.delegate = self
tap.numberOfTapsRequired = taps
addGestureRecognizer(tap)
}
func someAction(_ guesture: UITapGestureRecognizer) {
print("tap - subclass func")
guard let aDelegate = aDelegate else { assertionFailure(); return }
delegate.detailPhoto(self, actionFor: gesture)
}
}
Then add the protocol to your ViewController, set it as your DetailPhotoImageView's delegate somewhere prior to the action being implemented (I did it in viewDidLoad() for the example), and implement the protocol method however you wish:
class ViewController: UIViewController, DetailPhotoDelegate {
#IBAction weak var detailPhoto: DetailPhotoImageView!
override func viewDidLoad() {
super.viewDidLoad()
detailPhoto.delegate = self
}
// Mark: DetailPhotoDelegate
func detailPhoto(_ detailPhoto: DetailPhotoImageView, actionFor guesture: UITapGestureRecognizer) {
switch guesture.numberOfTapsRequired {
case 1: print("1 tap"); return
case 2: print("2 taps"); return
default: print("\(guesture.numberOfTapsRequired) taps"); return
}
}
I think target shouldn't be self

Resources