So what I have is I have a UITapGestureRecognizer for a Tap on the view (lets call that base view) and a UILongPressGestureRecognizer on that same base view. When the user taps the view action one is dispatched and if the user holds down on the view another view pops up. This behavior works nicely.
What I want to do is that the user does not have to separately tap the popup. Instead I want the user to just "pull up" on the popup and activate an action.
I tried to use a UIPanGesture on the popup but that did not work.
Edit
I dragged the long press gesture recognizer on to the view (actually a key in a custom keyboard). This is the code
#IBAction func popupPressed(_ sender: UITapGestureRecognizer) {
//Todo code for popupKey
}
#IBAction func keyPressedLong(_ sender: UILongPressGestureRecognizer) {
switch sender.state {
case .began:
popupTint = UIView(frame: self.frame)
popupTint!.backgroundColor = UIColor.black
popupTint!.alpha = 0.6
self.view.addSubview(popupTint!)
popup = UIView(frame: sender.view!.frame)
popup.frame = CGRect(x: popup.frame.origin.x, y: sender.view!.frame.origin.y - popup.frame.height, width: popup.frame.width, height: popup.frame.height)
popupKey.layer.cornerRadius = 5
popupKey.clipsToBounds = true
let popUpTap = UITapGestureRecognizer(target: self, action: #selector(self.popupPressed(tapGesture:)))
popupKey.addGestureRecognizer(popUpTap)
self.view.addSubview(popup)
default:
break
}
}
Any Ideas ?
Thanks a lot in advance.
UILongPressGestureRecognizer is continuous so you could use the location in the base view to move the popup & check if the movement threshold was met.
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.didLongPress(gesture:)))
self.baseView.addGestureRecognizer(longPress)
#objc func didLongPress(gesture: UILongPressGestureRecognizer) {
if self.popUpView.bounds.contains(gesture.location(in: self.popUpView)) {
// perform action
}
}
I hope this answers your question.
Related
In my app, I am displaying a popover that shows a custom UIView allowing the user to select a color using various sliders. I also want to implement an 'eyedropper' tool that the user can tap and hold then drag around to choose a color from anything visible in the app. Inside my custom UIView I added a UIPanGestureRecognizer to my button that points to the handlePan method:
var eyedropperStartLocation = CGPoint.zero
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
// self is a custom UIView that contains my color selection
// sliders and is placed inside a UITableView that's in
// the popover.
let translation = recognizer.translation(in: self)
if let view = recognizer.view {
switch recognizer.state {
case .began:
eyedropperStartLocation = view.center
case .ended, .failed, .cancelled:
view.center = eyedropperStartLocation
return
default: break
}
view.center = CGPoint(x: view.center.x + translation.x,
y: view.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, in: self)
}
}
I can drag the button around and it changes location, however I have two issues:
The eyedropper button isn't always in front of other items, even inside the popover or the custom UIView inside the popover
The eyedropper button disappears when outside the bounds of the popover
How can I get the button to be visible all the time, including outside the popover? I'll want to detect where the user lets go of it within the app so I can determine what color it was on.
I figured out how to do this so I'll answer my own question. Instead of moving around the view/button that's inside the popup, I create a new UIImageView and add it to the application's Window, letting it span the whole application. The original button stays where it is - you could easily change the state on it to make it look different, or hide it if you wanted to.
You could also use Interface Builder to tie to #IBActions, but I just did everything in code. The clickButton method kicks things off but calculating location in the window and putting it on the screen. The handlePan method does the translation and lets you move it around.
All code below is swift 4 and works in XCode 9.4.1 (assuming I didn't introduce any typos):
// Call this from all your init methods so it will always happen
func commonInit() {
let panner = UIPanGestureRecognizer(target: self, action: #selector(handlePan(recognizer:)))
theButton.addGestureRecognizer(panner)
theButton.addTarget(self, action: #selector(clickButton), for: .touchDown)
eyedropperButton.addTarget(self, action: #selector(unclickButton), for: .touchUpInside)
eyedropperButton.addTarget(self, action: #selector(unclickButton), for: .touchUpOutside)
}
var startLocation = CGPoint.zero
lazy var floatingView: UIImageView = {
let view = UIImageView(image: UIImage(named: "imagename"))
view.backgroundColor = UIColor.blue
return view
}()
// When the user clicks button - we create the image and put it on the screen
// this makes the action seem faster vs waiting for the panning action to kick in
#objc func clickButton() {
guard let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window else { return }
// We ask the button what it's bounds are within it's own coordinate system and convert that to the
// Window's coordinate system and set the frame on the floating view. This makes the new view overlap the
// button exactly.
floatingView.frame = theButton.convert(theButton.bounds, to: nil)
window.addSubview(floatingView)
// Save the location so we can translate it as part of the pan actions
startLocation = floatingView.center
}
// This is here to handle the case where the user didn't move enough to kick in the panGestureRecognizer and cancel the action
#objc func unclickButton() {
floatingView.removeFromSuperview()
}
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self)
switch recognizer.state {
case .ended, .failed, .cancelled:
doSomething()
floatingView.removeFromSuperview()
return
default: break
}
// This section is called for any pan state except .ended, .failed and .cancelled
floatingView.center = CGPoint(x: floatingView.center.x + translation.x,
y: floatingView.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, in: self)
}
I have an image view on scroll view. I added more than one sub views on image view.
->UIScrollView
-->UIImageView
--->UIViews
I want to add gesture recognizer to subviews. But It doesn't work. Where is my mistake? function handleTap() not triggered.
func addSubViewOnImageView(mPoint:CGPoint, mSize: CGSize){
let rect = CGRect(origin: mPoint, size: mSize)
let sView = UIView(frame: rect)
sView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
tap.delegate = self
sView.addGestureRecognizer(tap)
imageView.addSubview(sView)
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("tapped any sub view")
}
This property is inherited from the UIView parent class. This class changes the default value of this property to NO.
enable the user interaction for your imageview, by default its false , for more info you get the info from apple document
imageView.isUserInteractionEnabled = true
just check
yourImageView.isUserInteractionEnabled = true
is there or not?
An Alternate if you don't want to do
yourImageView.isUserInteractionEnabled = true
Just make your UIView class a subclass of UIControl from storyboard refer from screenshot.
it will act as an UIButton no need to write the code for adding an tap gesturejust add an action and likenormally we do for UIImageView`
I have an image, I convert it into a 360 panoramic image, using metal (https://github.com/ejeinc/MetalScope). How can I add a button on the door (see the screenshot) so that by clicking on it, it would go to the next controller with a different panoramic image (another room)
github project : https://github.com/Mahnach/MetalRender
You can add a tap gesture recogniser to the image, then get the point where image is tapped. If it is tapped near the door, perform segue to the next controller. If you're not sure where the door area is, you can print out the touch point & see where on the image you're tapping.
override func viewDidLoad() {
super.viewDidLoad()
//Create Tap Gesture
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
//Enable image user interaction
self.imageView.isUserInteractionEnabled = true
//Add Tap gesture to the image
self.imageView.addGestureRecognizer(tapGestureRecognizer)
}
#objc func tapAction(_ sender: UITapGestureRecognizer){
//Get the touch point
let touchPoint = sender.location(in: self.imageView)
//Set the door area
let doorArea = CGRect(x: 200.0, y: 100.0, width: 75.0, height: 100.0)
//Then check if touch point is near door
if doorArea.contains(touchPoint){
//Peform segue
performSegue(withIdentifier: "nextScene", sender: nil)
}
}
I want to remove UIView from screen after user tap something except that view. (to visualize it for you I will upload sketch of my view)
And I want to remove blue UIView after user tap on something except buttons in this view. What should I use?
EDIT:
In blue UIView are two buttons and I want to remove that view when user tap on background image
I did what #yerpy told me to do but it isn't working.
func test(gestureRecognizer: UITapGestureRecognizer) {
print("test")
}
func setUpBackgroundImageView() {
self.view.addSubview(backgroundImageView)
backgroundImageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
backgroundImageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
backgroundImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
backgroundImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let tap = UITapGestureRecognizer(target: self, action: #selector(test(gestureRecognizer:)))
backgroundImageView.addGestureRecognizer(tap)
tap.delegate = self
}
And I also add shouldReceiveTouch function to UIGestureRecognizerDelegate. What am I doing wrong?
Add UIGestureRecognizer to the super view :
As you said you have image view as a background.
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped(gestureRecognizer:)))
imageView.addGestureRecognizer(tapRecognizer)
tapRecognizer.delegate = self
Adding target action :
func tapped(gestureRecognizer: UITapGestureRecognizer) {
// Remove the blue view.
}
And then inside UITapGestureRecognizerDelegate :
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.superview!.superclass! .isSubclass(of: UIButton.self) {
return false
}
return true
}
}
Hope it helps !
EDIT
Make sure that user can touch on the view by enabling : self.view.userInteractionEnabled = true
1- Add a view below your view, let call it overlay (gray one)
2- Add your container view with all your buttons inside (green one)
3- Add a tap gesture to the overlay (drag tap to overlay view)
4- Create a #IBAction of the tap to the viewcontroller
5- Write code to hide your green view inside the #IBAction
Image
Image
you can use UITapGestureRecognizer for that view
override func viewDidLoad() {
super.viewDidLoad()
// Add "tap" press gesture recognizer
let tap = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
tap.numberOfTapsRequired = 1
backgroundimage.addGestureRecognizer(tap)
}
// called by gesture recognizer
func tapHandler(gesture: UITapGestureRecognizer) {
// handle touch down and touch up events separately
if gesture.state == .began {
} else if gesture.state == .ended {
}
}
You can
subclass with that background view, and implement an inherited method -beganTouches:(there is another para but I forget what it is)
Add a UITapGestureRecognizer to that view
Add a giant button that covers the entire screen under all the buttons. Anytime the user presses on the giant button underneath the smaller buttons, do what you want it to do.
Not the most elegant but it works :P
override func viewDidAppear(animated: Bool) {
view.userInteractionEnabled = true
let pinchGesture:UIPinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: "pinchGesture")
view.addGestureRecognizer(pinchGesture)
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissKeyboard")
view.addGestureRecognizer(tap)
}
func dismissKeyboard() {
view.endEditing(true)
}
func pinchGesture(){
self.performSegueWithIdentifier("trick1Segue", sender: self)
}
In my iOS app, i want to transition to a different view controller when a pinch gesture is performed on the screen.
The tap gesture is to dismiss the keyboard when tapped outside the keyboard area
While running the app, I get an error message:
"Attempting to present < ** > on < ** > while a presentation is in progress"
The new view controller appears but opens twice, with a very short time difference. Looked up a lot of blogs but couldn't find a solution, please help!!!
The problem is that pinchGesture can be called multiple times. You should add a property to your viewController to keep track of the fact that you already have acted upon the pinch gesture:
var segueInProcess = false
func pinchGesture() {
if !segueInProcess {
self.performSegueWithIdentifier("trick1Segue", sender: self)
segueInProcess = true
}
}
Gesture recognizers are called multiple times with different states. What you can do is check the state property of the UIPinchGestureRecognizer in pinchGesture().