Adding UIGestureRecognizer To Subview Programmatically in Swift - ios

I currently have a subview that is created and added to the UIView in ViewDidLoad(). I am attempting to user UIGestureRecognizers to detect a tap and unhide a particular button. My current code:
override func viewDidLoad() {
super.viewDidLoad()
architectView = CustomClass(frame: self.view.bounds)
self.view.addSubview(architectView)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: "handleTap:")
gestureRecognizer.delegate = self
architectView.addGestureRecognizer(gestureRecognizer)
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
The handleTap() function is a simple test to see if the taps are being recognized. This code does not trigger the UIAlert when it is pressed? What am I missing?

I tested your code here and it does work. However, I think you might be missing to add the UIGestureRecognizerDelegate protocol to your View Controller. See below:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
var architectView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
architectView = UIView(frame: self.view.bounds)
self.view.addSubview(architectView)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: "handleTap:")
gestureRecognizer.delegate = self
architectView.addGestureRecognizer(gestureRecognizer)
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Swift 3 version code based on Raphael Silva answer:
import UIKit
class ViewController: UIViewController, UIGestureRecognizerDelegate {
var architectView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
architectView = UIView(frame: self.view.bounds)
self.view.addSubview(architectView)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTap(gestureRecognizer:)))
gestureRecognizer.delegate = self
architectView.addGestureRecognizer(gestureRecognizer)
}
func handleTap(gestureRecognizer: UIGestureRecognizer) {
let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Related

swift5 UILabel Clickable

I am learning iOS development. And I want to make UILabel clickable
Here is what I have done but with no result.
#IBOutlet weak var coordinate: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
coordinate.adjustsFontSizeToFitWidth = true
let tap = UIGestureRecognizer(target: self, action: #selector(uiLabelClicked()))
coordinate.addGestureRecognizer(tap)
coordinate.isUserInteractionEnabled = true
Below is a function that is expected to perform an action when label is clicked.
#objc func uiLabelClicked(){
let alert = UIAlertController(title: "Coordinate", message: "I have been clicked...", preferredStyle: .alert)
self.present(alert, animated: true, completion: nil)
debugPrint("clicked")
}
I apologize for my English I am using google translator.
Try this:
#IBOutlet weak var coordinate: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
coordinate.adjustsFontSizeToFitWidth = true
let tap = UITapGestureRecognizer(target: self, action: #selector(self.labelAction(_:)))
coordinate.addGestureRecognizer(tap)
coordinate.isUserInteractionEnabled = true
}
#objc func labelAction(_ sender: UIGestureRecognizer) {
let alert = UIAlertController(title: "Coordinate", message: "I have been clicked...", preferredStyle: .alert)
self.present(alert, animated: true, completion: nil)
debugPrint("clicked")
}

How do I present a View Controller with UIView, UIButton sub-classes?

So I have created this UIButton Sub-class. It's a reusable component and can be added in any view controller. I need to present an alert view controller with the tap in the button. So basically I need to find out in which view controller the button lies so I can pass a view controller as a parameter.
final class ProfileButton: UIButton {
let picker = UIImagePickerController()
lazy var shadowImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.image = UIImage(named: "download")
return imageView
}()
override public init(frame: CGRect) {
super.init(frame: frame)
setupSelf()
self.addTarget(self, action: #selector(profileButtonTapped), for: .touchUpInside)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override public func layoutSubviews() {
super.layoutSubviews()
shadowImageView.layer.cornerRadius = self.frame.width/2.70
shadowImageView.layer.masksToBounds = true
}
#objc func profileButtonTapped(){
imageHandler(presenter: )
}
#objc func imageHandler(presenter: UIViewController!){
let alertController = UIAlertController(title: "Profile Picture", message: "Please select your profile picture", preferredStyle: .alert)
let okAction = UIAlertAction(title: "Photo Library", style: .default, handler: { (action: UIAlertAction!) in
print("I am working")
})
let cameraAction = UIAlertAction(title: "Camera", style: .default, handler: { (action: UIAlertAction!) in
print("I am working")
})
alertController.addAction(okAction)
alertController.addAction(cameraAction)
presenter.present(alertController, animated: true, completion: nil)
}
I actually need to find out the view controller so I can pass it in the imageHandler(presenter: UIViewController) function.
In this case you can get the top controller using this extension
extension UIApplication {
class func getTopMostViewController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
} else {
return nil
}
}
}
#objc func imageHandler() {
//...
UIApplication.getTopMostViewController()?.present(alertController, animated: true, completion: nil)
}
You could add a weak var reference to the controller inside your UIButton. Better way would be add the handler function in the UIViewController extension and addTarget in the viewDidLoad instead of the UIButton's init(frame:). Here's an example:
final class ProfileButton: UIButton {
//Remove addTarget from init(frame:)
}
class ViewController: UIViewController {
let profileButton = ProfileButton()
override func viewDidLoad() {
super.viewDidLoad()
//...
profileButton.addTarget(self, action: #selector(imageHandler), for: .touchUpInside)
}
}
extension UIViewController {
#objc func imageHandler() {
let alertController = UIAlertController(title: "Profile Picture", message: "Please select your profile picture", preferredStyle: .alert)
let okAction = UIAlertAction(title: "Photo Library", style: .default, handler: { (action: UIAlertAction!) in
print("I am working")
})
let cameraAction = UIAlertAction(title: "Camera", style: .default, handler: { (action: UIAlertAction!) in
print("I am working")
})
alertController.addAction(okAction)
alertController.addAction(cameraAction)
present(alertController, animated: true, completion: nil)
}
}

How to set the button's action from another view controller

how can I create reusable view controller (let's call it "reusableVC") acting like UIAlertController. ReusableVC have "ok" button, that will act depending from where resuableVC called. I know about delegates and NotificationCenter. Just wondering can we pass what "ok" button should do when creating reusableVC, like this:
reusableVC.addAction(UIAlertAction(title: "", style: .default, handler: { (action) in
// some code
}))
If you only need one OK button you may use this solution, otherwise, you can still find interest in this pattern.
class ReusableVC{
var onOKPressed: ( () -> () )?
// Create all your other things and don't forget that you should call onOKPressed() whenever user pushed that OK button
}
class ViewController{
func setupReusableVC(){
let reusableVC = ReusableVC()
reusableVC.onOKPressed = {
print("ok pressed")
}
}
}
The action handler is just a closure. You can declare it everywhere.
In the reusable view controller add a property
var customAction : ((UIAlertAction) -> Void)?
and pass the property as handler
reusableVC.addAction(UIAlertAction(title: "", style: .default, handler: customAction))
In the source view controller create the action
let action : ((UIAlertAction) -> Void)? = { action in
// do something
}
and pass it in perform(segue
Create a UIViewController Extension to include Alert Functionality
extension UIViewController{
open func hideKeyBoardOnTap(){
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.view.addGestureRecognizer(tap)
}
#objc private func dismissKeyboard(){
self.view.endEditing(true)
}
open func showAlertWithOK(_ title: String = "Alert!",message: String = "Please take appropriate action"){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okButton = UIAlertAction(title: "Ok", style: .default, handler:{ (alertAction) in
self.okAction()
})
alert.addAction(okButton)
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}
}
open func showAlertWithOkAndCancel(_ title: String = "Alert!",_ message: String = "Please take appropriate action", _ firstButtonTitle: String = "Ok", _ firstButtonStyle: UIAlertActionStyle = .default, _ secondButtonTitle: String = "Cancel",_ secondButtonStyle: UIAlertActionStyle = .cancel){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okButton = UIAlertAction(title: firstButtonTitle, style: firstButtonStyle, handler:{ (alertAction) in
self.okAction()
})
let cancelButton = UIAlertAction(title: secondButtonTitle, style: secondButtonStyle, handler: { (alertAction) in
self.cancelAction()
})
alert.addAction(okButton)
alert.addAction(cancelButton)
self.present(alert, animated: true, completion: nil)
}
#objc private func okAction(){
self.dismiss(animated: true, completion: nil)
}
#objc private func cancelAction(){
self.dismiss(animated: true, completion: nil)
}
}
How to Use
func didReceiveError(_ error: CFNetworkErrors) {
var message = error.message
self.showAlertWithOK("Error", message: message)
}
func didEndWebserviceCall() {
self.showAlertWithOK(message: "didEndWebserviceCall")
}
Advantages:
You can access alert using self(which is your viewcontroller in this case)
Code reusability
Clean code.
protocol TapEventDelegate: protocol {
func buttonTap()
}
class ClassWhereDoYouWantToCatchTheEvent: TapEventDelegate {
func buttonTap() {
print("caught!")
}
}
class YourViewControllerClass {
weak var tapEventDelegate: TapEventDelegate?
reusableVC.addAction(UIAlertAction(title: "", style: .default, handler: { (action) in
tapEventDelegate?.buttonTap()
}))
}
to bind your class with YourViewControllerClass and ClassWhereDoYouWantToCatchTheEvent use somewhere at view controller initialization:
classWhereDoYouWantToCatchTheEvent.tapEventHandler = yourViewControllerClass
You can create custom UIViewController class and pass the addAction closure and then you can call that closure on the OK button tap from your CustomAlertController.
final class CustomAlertController: UIViewController {
var actionHandler: (() -> Void)?
lazy var okButton: UIButton = {
let button = UIButton()
button.backgroundColor = .black
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("OK", for: .normal)
button.addTarget(self, action: #selector(CustomAlertController.didTapOkButton(_:)), for: .touchUpInside)
button.layer.cornerRadius = 10
return button
}()
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
addActionButton()
}
private func addActionButton() {
view.addSubview(okButton)
NSLayoutConstraint.activate([
okButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 50),
okButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -50),
okButton.heightAnchor.constraint(equalToConstant: 50),
okButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 100)
])
}
public func addAction(title: String, handler: #escaping (() -> Void) {
okButton.setTitle(title, for: .normal)
actionHandler = handler
}
#objc func didTapOkButton(_ button: UIButton) {
actionHandler?()
dismiss(animated: true)
}
}
Then you can present CustomAlertController from your ViewController class and add action like below
class ViewController: UIViewController {
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alertController = CustomAlertController()
alertController.addAction(title: "OK", handler: { [unowned self] in
self.view.backgroundColor = .blue
print("OK button tapped")
})
present(alertController, animated: true)
}
}

Trouble responding to a tap on an image view

Why is my UITapGestureRecognizer not triggering my function for a UIAlertAction? When I build and run my app for the iOS Simulator, my image will not recognize a tap to trigger the alert.
Any suggestions would be appreciated. Thanks, Swift friends!
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let rect = CGRect(x:20,y:20,width:150,height:100)
let imageView = UIImageView(frame:rect)
let image = UIImage(named:"image.png")
imageView.image = image
// imageView.isUserInteractionEnabled = true
self.view.addSubview(imageView)
let guesture = UITapGestureRecognizer(target: self,action:
#selector(ViewController.singleTap))
imageView.addGestureRecognizer(guesture)
}
func singleTap()
{
let alertView = UIAlertController(title: "Heading", message: "Message!", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertView.addAction(defaultAction)
self.presentViewController(alertView, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Uncommenting this line in your code
// imageView.isUserInteractionEnabled = true.
might work. Add this also
image.multipleTouchEnabled = YES;
tap.numberOfTapsRequired = 1;

UIButton not working or gets unrecognized selector sent to instance

I have an odd (at least to me) issue in that I used the pod Localize_Swift to localize my app.
I have two issues that I cannot figure out as nearly all documentation about localizing only covers text on the same vc as the localize menu or button. In my case, I have localize strings set up and across the entire app, they are in use with a .localize suffix.
First issue:
The button either does not work. The print() function never gets triggered or if it does work, appears to be randomly, I get the unrecognized selector sent to instance that lists the func changeLanguage as the cause. I commented out all of the code within the function, but still received the error.
I have also deleted the func and recreated it, but still no go.
Second issue:
How to initialize the localization across the entire app with the strings?
SettingsViewController.swift
import UIKit
import Localize_Swift
class SettingsViewController: UITableViewController
{
var availableLanguages = Localize.availableLanguages()
var actionSheet: UIAlertController!
#IBOutlet weak var switchlangButton: UIButton!
#IBOutlet weak var flagImageView: UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
let backButton:UIBarButtonItem = UIBarButtonItem(title: "Back".localized, style: UIBarButtonItemStyle.Done, target: self, action: #selector(SettingsViewController.backBtnAction(_:)))
self.navigationItem.leftBarButtonItem = backButton
configureView()
availableLanguages.removeAtIndex(0)
print(availableLanguages)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SettingsViewController.setTranslatedText), name: LCLLanguageChangeNotification, object: nil)
}
func setTranslatedText(){
switchlangButton.setTitle("Switch Language".localized(), forState: UIControlState.Normal)
flagImageView.image = UIImage(named: "flag-en".localized() + ".png")
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
#IBAction func changeLanguage(sender: AnyObject) {
print ("changeLanguageButton pressed")
actionSheet = UIAlertController(title: nil, message: "Switch Language".localized(), preferredStyle: UIAlertControllerStyle.ActionSheet)
for language in availableLanguages {
let displayName = Localize.displayNameForLanguage(language)
let languageAction = UIAlertAction(title: displayName, style: .Default, handler: {
(alert: UIAlertAction!) -> Void in
Localize.setCurrentLanguage(language)
})
actionSheet.addAction(languageAction)
}
let cancelAction = UIAlertAction(title: "Cancel".localized(), style: UIAlertActionStyle.Cancel, handler: {
(alert: UIAlertAction) -> Void in
})
actionSheet.addAction(cancelAction)
self.presentViewController(actionSheet, animated: true, completion: nil)
}
func configureView(){
switchlangButton.setTitle("Switch Language".localized(), forState: UIControlState.Normal)
flagImageView.image = UIImage(named: "flag-en".localized() + ".png")
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
#IBAction func backBtnAction(sender:UIBarButtonItem)
{
navigationController?.popViewControllerAnimated(true)
}
}
Screenshot of the Settings scene
Just an FYI for troubleshooting, the image does not work either.

Resources