If I create a GestureRecognizer handler through InterfaceBuilder, I get a method that looks like this:
#IBAction func Tap(tap: UITapGestureRecognizer) {
var touchLocation = tap.locationInView(self.view)
imageView.transform.tx = touchLocation.x
imageView.transform.ty = touchLocation.y
}
//works great
If I manually create a GestureRecognizer, I have to create a function with no arguments, like this:
let tap = UITapGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
let imageSize = CGSize(width: 100, height: 100)
var iView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
self.view.addSubview(iView)
let image2 = UIImage(named: "square.png");
iView.image = image2
iView.userInteractionEnabled = true
iView.addGestureRecognizer(tap)
tap.addTarget(self, action: "Tap")
}
func Tap( //why can't I put an argument in here?// ){
let tapAlert = UIAlertController(title: "Tap Pressed", message: "You just tapped", preferredStyle: .Alert)
tapAlert.addAction(UIAlertAction(title: "OK", style: .Destructive, handler: nil))
self.presentViewController(tapAlert, animated: true, completion: nil)
}
//works but I've lost access to the sending object
If I add an argument to the Tap handler
func Tap(sender: UITapGestureRecognizer){
}
//crashes: [tapHandler]: unrecognized selector sent to instance
, it crashes on the Tap. I'd prefer to have a method with access to the sender object. Is there a way to manually create a GestureRecognizer handler and have access to the sender object, the same way you can when you do it through the Interface Builder?
Selector names are something that Swift inherits from Objective C. In Objective C a selector with single argument has name selector: as opposite to just selector, which means - no arguments. Change the name of the action in addTarget from Tap to Tap: and it should work then.
Related
i have 3 UITextField. when i set the resignFirstResponder for the text field, for every key press the key board goes down and key board pops up when we enter. which means for each letter pressed the key board disappears
I tried creating an outlet for the text field as below but the print statement is executed but the key board is not getting disappeared when focus lost or moved to the next text field
#IBAction func done(_ sender: UITextField) {
print("Text field done$$$$$$$$$$$$$$$")
sender.endEditing(true)
sender.resignFirstResponder()
// print("After Resign")
}
tried the below one also:
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:))))
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//Textfield
let textfield = UITextField()
textfield.setShadowTextField(username, "user-name")
textfield.setShadowTextField(useremail, "email")
textfield.setShadowTextField(userPhone, "phone")
//Hide Keyboard
textName.resignFirstResponder()
textEmail.resignFirstResponder()
userPhone.resignFirstResponder()
//
name = textName.text ?? ""
emailphone = textEmail.text ?? ""
//Button
if isFirstTimeSubView == true {
button?.setSemiButtonLeft(btnFemaleSelector, shadowViewFemale)
button?.setGradientButton(btnFemaleSelector, startColor: "0d5e90", endColor: "8ec67d")
button?.setSemiButtonRight(btnMaleSelector, shadowViewMale)
button?.setGradientButton(btnMaleSelector, startColor: "FFFFFF", endColor: "FFFFFF")
isFirstTimeSubView = false
}
button?.setRoundButton(btnSubmit, shadowView)
button?.setGradientButton(btnSubmit, startColor: "0d5e90", endColor: "8ec67d")
//textNam.sendAction("resignFirstResponder", to:nil, from:nil, forEvent:nil)
// self.userText.delegate = selftextName.resignFirstResponder()
}
extension UITextField {
func setShadowTextField(_ textfield: UITextField?, _ imagename: String?) {
// set color & border
textfield?.borderStyle = .none
textfield?.backgroundColor = UIColor.white
// set corner
textfield?.layer.cornerRadius = (textfield?.frame.size.height ?? 0.0) / 2
//set shadow
textfield?.layer.shadowOpacity = 0.4
textfield?.layer.shadowRadius = 15
textfield?.layer.shadowOffset = CGSize(width: 5, height: 10)
textfield?.layer.shadowColor = UIColor.gray.cgColor
// set icon & Placeholder position
let UIViewController = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: textfield?.frame.size.height ?? 0.0))
let icon = UIImageView(image: UIImage(named: imagename ?? ""))
icon.frame = CGRect(x: 0, y: 0, width: UIViewController.frame.size.width / 4, height: UIViewController.frame.size.height / 4)
icon.center = UIViewController.center
UIViewController.addSubview(icon)
textfield?.leftView = UIViewController
textfield?.leftViewMode = .always
}
}
The question is a little unclear but if you want the keyboard to go down when you've pressed the done button, you should implement the UITextFieldDelegate on your view controller and during the textFieldShouldReturn(_:) method call resignFirstResponder from there:
class myVC: UIViewController, UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool{
return true
}
}
and don't forget to set the delegate in the viewDidLoad for your VC:
override func viewDidLoad() {
super.viewDidLoad()
/// Text field delegate handling
textFieldFirstName.delegate = self
}
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619603-textfieldshouldreturn?language=objc
By calling resignFirstResponder in viewDidLayoutSubviews, you are resigning the responder every time the viewDidLayoutSubviews is called, i.e. When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. So I presume the keyboard will be changing the bounds, causing viewDidLayoutSubviews to fire after each keypress and creating the behaviour you are seeing
I am having a no-show menuController and I have checked all of the suggestions in previous questions. It turns out the imageView I have implemented a UILongPressGestureRecognizer on, to show the menu, is returning False on calling .becomeFirstResponder just before setting up the menu controller.
I am coding in swift 4 and can't figure out how to make the imageView return True to calling .becomeFirstResponder. Help!
/*********************************************************/
override func viewDidLoad() {
super.viewDidLoad()
// long tap to show menu that enables deletion of the image.
imageView_1.isUserInteractionEnabled = true
let longPressRecogniser = UILongPressGestureRecognizer(target: self, action: #selector(longPressOnImage(_:)))
//longPressRecogniser.numberOfTapsRequired = 1
//longPressRecogniser.numberOfTouchesRequired = 1
longPressRecogniser.minimumPressDuration = 0.5
imageView_1.addGestureRecognizer(longPressRecogniser)
imageView_1.image = placeHolderImage_1
imageView_2.image = placeHolderImage_2
}
/*********************************************************/
#IBAction func longPressOnImage(_ gestureRecognizer: UILongPressGestureRecognizer) {
print(#function)
if gestureRecognizer.state == .began {
//print("gestureRecognizer.state == .began")
self.tappedView = gestureRecognizer.view!
if tappedView.canResignFirstResponder {
print("can resign first responder")
}
if tappedView.becomeFirstResponder() {
print("returned TRUE to becomeFirstResponder")
} else {
print("returned FALSE to becomeFirstResponder")
}
// Configure the shared menu controller
let menuController = UIMenuController.shared
// Configure the menu item to display
// Create a "delete" menu item
let deleteImage = UIMenuItem(title: "Delete", action: #selector(deleteImage_1))
menuController.menuItems = [deleteImage]
// Set the location of the menu in the view.
let location = gestureRecognizer.location(in: tappedView)
print("location = ", location)
let menuLocation = CGRect(x: location.x, y: location.y, width: 2, height: 2)
menuController.setTargetRect(menuLocation, in: tappedView)
//update the menu settings to force it to display my custom items
menuController.update()
// Show the menu.
menuController.setMenuVisible(true, animated: true)
print("menu should be visible now")
}
}
/*********************************************************/
#objc func deleteImage_1() {
print(#function)
}
My caveman debugging print statements output:
longPressOnImage
can resign first responder
returned FALSE to becomeFirstResponder
location = (207.0, 82.0)
menu should be visible now
Create a custom imageView class and override "canBecomeFirstResponder" property like this:
class ResponsiveImage : UIImageView{
override var canBecomeFirstResponder: Bool{
return true
}
}
Use this ResponsiveImage type and your code will work :)
Thank you to adri. Your answer is the solution to my problem.
I had read in other posts to similar questions about overriding var canBecomeFirstResponder but either overlooked it or it wasn't made explicit that a custom UIImageView class needs to be created.
Just to make it clear to newbies like me, the class of the imageView in storyBoard and its #IBOutlet in its viewController must typed as ResponsiveImage. If only one of these is changed a type casting error is reported.
Many thanks for ending my hours of frustration! :-)
I want to add a Progress bar to a tableViewController.
I have one function called HelpersFunctions which do all the calculation.
The function doCalculation is responsible for the calculation.
So, I add the following notification to doCalculation as follow:
NotificationCenter.default.post(name: .return_progress, object: self)
for i in 1...n1 {
//Do all the calculation
}
So, once I reach NotificationCenter.default.post, it will move to a Tableview Controller called CreateNewElementVC
now, inside the ViewDidLoad, I added the following line:
//progress
NotificationCenter.default.addObserver(self, selector: #selector(showProgress), name: .return_progress, object: nil)
In the same swift file, I added the following:
let container_elementProperty: ProgressBarView = {
let view = ProgressBarView()
view.backgroundColor = UIColor(white: 0, alpha: 0.5)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
#objc func showProgress() {
if(progressCounter > 1.0){timer.invalidate()}
print("Step 1")
container_elementProperty.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
container_elementProperty.backgroundColor = UIColor(white: 0, alpha: 0.5)
container_elementProperty.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
let queue = DispatchQueue(label: "queue1", qos: .userInteractive)
queue.async {
print("Step 2")
self.view.addSubview(self.container_elementProperty)
}
//view.addSubview(container_elementProperty)
print("Step 3")
container_elementProperty.progress = progressCounter
progressCounter = progressCounter + progressIncrement
let x1: Float = Float(start_Counting)
let x2: Float = Float(End_Counting)
let xx: Float = x1 / x2 * 100
print("Start at: \(xx) %)")
}
So, first I put all the required data in the CreateNewElementVC, then there is a button called run to do all the calculation and then it will move to another TableViewController with all the result.
So while I am inside the function doCalculation, the progress bar should appear .
In fact, the Progress bar container_elementProperty (UIview) appeared just after the calculation is completed which make the progress bar is useless.
Any idea how to make the View called container_elementProperty UIView to be seen ?
I am close to solve this issue as I can see the progress in the stack as below image, I just want to show this on the screen before completing the calculation.
Why I am not able to put the view on the screen while doing the calculation as you can see that step 2 ran first.
The warning related to this issue is: UIView.addSubview(_:) must be used from main thread only.
A Sample Project can be checked on this link at github.
Appreciate any kind of support.
To simulate a progress, such as a network request you cannot simply do a for loop in the main thread, you should use GCD.
To update the progress of an ongoing action use Delegation, not KVO.
Using global variables makes your code flawed, avoid it!
This is a working version of your code.
# Write By Tushar on 11/02/2021#
import UIKit
class ViewController: UIViewController {
let totalValue:Float = 80.0
var firstNumber:Int? = 0
#IBOutlet weak var progressBar: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
progressBar.trackTintColor = .white
progressBar.tintColor = .red
// Do any additional setup after loading the view.
progressBar.setProgress(0.0, animated: false)
}
func setProgress(firstValue:Float){
let processValue = firstValue/totalValue
progressBar.setProgress(processValue, animated: true)
}
#IBAction func btnResetClick(_ sender: UIButton) {
print("Reset Button Clicked")
firstNumber = 0
progressBar.setProgress(0.0, animated: false)
}
#IBAction func btnShowProgressClick(_ sender: UIButton) {
guard let presentValue = firstNumber else { return }
let newValue = presentValue + 1
firstNumber = newValue
setProgress(firstValue: Float(firstNumber!))
}
}
Each tap adds an object on my scene, but I want to add it only once and then disable the Tap gesture. I looked everywhere but none of them are working with my code. Can someone help me with this? Either limiting the tap to 1 only or disabling it. I tried adding the tap gesture as Outlet and then setting .isEnabled = false but it is still not working.
class ARScene: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var sceneView: ARSCNView!
var tap : UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
let configuration = ARWorldTrackingConfiguration()
self.sceneView.session.run(configuration)
// Set the view's delegate
sceneView.delegate = self
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
// let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
//sceneView.scene = scene
registerGestureRecognizer()
}
func registerGestureRecognizer(){
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
#objc func handleTap(gestureRecognizer: UIGestureRecognizer){
//let touchLocation = gestureRecognizer.location(in: sceneView)
let sceneLocation = gestureRecognizer.view as! ARSCNView
let touchLocation = gestureRecognizer.location(in: sceneLocation)
let hitResult = self.sceneView.hitTest(touchLocation, types: [.existingPlaneUsingExtent, .estimatedHorizontalPlane])
if hitResult.count > 0 {
guard let hitTestResult = hitResult.first else{
return
}
let node = SCNNode()
let scene = SCNScene(named: "art.scnassets/bucket/Bucket2.scn")
let nodeArray = scene!.rootNode.childNodes
for childNode in nodeArray{
node.addChildNode(childNode as SCNNode)
}
let worldPos = hitTestResult.worldTransform
node.scale = SCNVector3Make(0.009,0.009,0.009);
node.position = SCNVector3(x: 0, y: worldPos.columns.3.y, z: -1.4)
sceneView.scene.rootNode.addChildNode(node)
tap.isEnabled = false //NOT WORKING, I want to stop tap gesture here
}
}
In order to disable your tapGesture you need to have reference to the same gesture instance you assign tap to.
There are two ways either create global instance so you can change its properties like Enable/Disable from anywhere
Or access the variable from the action/method for that gesture.
Create the global varibale for your tap
like
var tap: UITapGestureRecognizer! // global varibale
func registerGestureRecognizer(){
tap = UITapGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
tap.numberOfTapsRequired = 1
sceneView.addGestureRecognizer(tap)
}
and then disable this in taphandle
tap.isEnabled = false // disable the gesture
Or
2 Update your handle methods to
#objc func handleTap(withTapRecognizer tapGesture: UITapGestureRecognizer) {
........
tapGesture.isEnabled = false // use the instance variable and disable the gesture this will disable the gesture from which its been called
}
For you to access the gesture in the function, you need to initialize it using
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:)))
Then in your handleTap method, you can this at the end of the function
gestureRecognizer.view?.removeGestureRecognizer(gestureRecognizer)
I am trying to add custom behaviour to my buttons. If clicked once - one action is performed. If clicked twice another one is performed.
I saw such a solution in this answer and I have tried all possible combinations:
clickOnce.shouldBeRequiredToFailByGestureRecognizer(clickTwice)
clickTwice.shouldBeRequiredToFailByGestureRecognizer(clickOnce)
clickOnce.shouldRequireFailureOfGestureRecognizer(clickTwice)
clickTwice.shouldRequireFailureOfGestureRecognizer(clickOnce)
clickOnce.canPreventGestureRecognizer(clickTwice)
clickOnce.canBePreventedByGestureRecognizer(clickTwice)
clickTwice.canPreventGestureRecognizer(clickOnce)
clickTwice.canBePreventedByGestureRecognizer(clickOnce)
But nothing worked for me.
And here's the full code:
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let buttonTestOne = NSButton(frame: CGRect(x: 500, y: 500, width: 100, height: 500))
let clickOnceForTestOne = NSClickGestureRecognizer(target: buttonTestOne, action: #selector(ViewController.clickOneTime(_:)))
clickOnceForTestOne.numberOfClicksRequired = 1
buttonTestOne.addGestureRecognizer(clickOnceForTestOne)
self.view.addSubview(buttonTestOne)
let arrTestButtons = ["One", "Two", "Three"]
var x = 0
var y = 0
for item in arrTestButtons{
let buttonNew = NSButton(frame: CGRect(x: x, y: y, width: 100, height: 100))
x = x + 120
y = y + 120
buttonNew.attributedTitle = NSAttributedString(string: item)
let clickOnce = NSClickGestureRecognizer(target: self, action: #selector(ViewController.clickOneTime(_:)))
clickOnce.numberOfClicksRequired = 1
buttonNew.addGestureRecognizer(clickOnce)
let clickTwice = NSClickGestureRecognizer(target: self, action: #selector(ViewController.clickTwoTimes(_:)))
clickTwice.numberOfClicksRequired = 2
buttonNew.addGestureRecognizer(clickTwice)
clickOnce.shouldBeRequiredToFailByGestureRecognizer(clickTwice)
clickTwice.shouldBeRequiredToFailByGestureRecognizer(clickOnce)
clickOnce.shouldRequireFailureOfGestureRecognizer(clickTwice)
clickTwice.shouldRequireFailureOfGestureRecognizer(clickOnce)
clickOnce.canPreventGestureRecognizer(clickTwice)
clickOnce.canBePreventedByGestureRecognizer(clickTwice)
clickTwice.canPreventGestureRecognizer(clickOnce)
clickTwice.canBePreventedByGestureRecognizer(clickOnce)
self.view.addSubview(buttonNew)
}
}
func clickOneTime(g:NSClickGestureRecognizer){
if g.state == .Ended {
Swift.print("single click")
}
}
func clickTwoTimes(g: NSClickGestureRecognizer){
if g.state == .Ended {
Swift.print("DOUBLE CLICK!")
}
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
What am I doing wrong? I think the mistake must be pretty simple, I am either calling from the wrong place, or somewhat like that, but I can't understand.
Here is the log file, which is the same for all combinations:
Clicked once:
single click
As expected
Clicked twice:
single click
DOUBLE CLICK!
Which starts both single and double click.
I have read the documentation. And I've tried all the combinations, because couldn't find a solution.
I have also tried with the logs. So Swift.print(clickOnce.shouldBeRequiredToFailByGestureRecognizer(clickTwice)) and one of these, but it gives me false.
I was trying to solve this myself; it turns out that the solution is relatively straightforward.
Use a gesture recogniser for the double-click (here, I've added them to the view, but the same principle should work for buttons)
let doubleClickRecognizer = NSClickGestureRecognizer(target: self, action: #selector(ViewController.clickTwice))
doubleClickRecognizer.numberOfClicksRequired = 2
self.view?.addGestureRecognizer(doubleClickRecognizer)
Add
#objc func clickTwice() {
print("double click")
}
and then handle single clicks through
override func mouseDown(with event: NSEvent) {
print("mouse down; once")
}
(or appropriate equivalent).
NSGestureRecognizer has
var delaysPrimaryMouseButtonEvents: Bool
which by default is set to true, so with this combination, it waits until the doubleClickRecognizer has handled the double-click before going up the responder chain to see who else is interested in a mouseclick.
Two gesture recognisers have the same problem as handling single and double clicks in mouseDown: the single click fires first. In order to overcome this, you need to override
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool
This is not a function you call, it's a delegate function that you override. So you set your viewController to conform to NSGestureRecognizerDelegate (which means it will receive delegate methods); set singleClickRecognizer.delegate = self (where self is your viewController), and implement
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
if gestureRecognizer == singleClickRecognizer && otherGestureRecognizer == doubleClickRecognizer {
return true
}
return false
}
which states that your singleClickRecognizer waits for the doubleClickRecognizer to fail before executing its selector.
Unfortunately, I have found that in SpriteKit, while this code does what it says on the tin (process a double-click first), it leads to unacceptable delays, so I'm not sure I can recommend it.
Try this:
let click = NSClickGestureRecognizer(target: self, action: #selector(clicked(_:))
click.numberOfClicksRequired = 2
Maybe you could try it this way, where "button" is your button
let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.clickTwoTimes(_:)))
let singleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.clickOneTime(_:)))
singleTapRecognizer.numberOfTapsRequired = 1
doubleTapRecognizer.numberOfTapsRequired = 2
button.addGestureRecognizer(doubleTapRecognizer)
button.addGestureRecognizer(singleTapRecognizer)
singleTapRecognizer.requireGestureRecognizerToFail(doubleTapRecognizer)