Reputation: 159
I am trying to imitate the long press of the keyboard suggesting a letter for UIButton control. What I am trying to do is long press on UIButton, after keep pressing on the button 3 new buttons shows and select one of these 3 new buttons. just like the Keyboard letter suggestion.
How can I do this? any idea? Thank you
Upvotes: 0
Views: 540
Reputation: 543
This is quite old, but as I had the same problem I will present my solution for it.
I have my keyboard laid out and created by class Keyboard, derived from UIView Each of the letter buttons is of class LetterButton, derived from UIButton. Keyboard implements a protocol which handles KeyPressed events from the buttons.
For each button of the main keyboard a UILongPressGestureRecognizer is applied:
let longTouchRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(onButtonLongPressed))
longTouchRecognizer.cancelsTouchesInView = false
button.addGestureRecognizer(longTouchRecognizer)
button.delegate = self
with
@objc func onButtonLongPressed (_ sender: UIGestureRecognizer)
{
if (sender.state == .began)
{
guard let tag = sender.view?.tag else { return }
createPopupView(button: buttons[tag])
}
}
It is essential to set cancelsTouchesInView to false, else we wouldn't receive any further events !
On a longtouch on a button a popupview is created with one or more buttons above the button touched. We can swipe to these buttons directly from the button touched.
Implementation of LetterButton class:
class LetterButton : UIButton
{
var delegate : LetterButtonDelegate?
var isInside = false
This is called from Keyboard class:
func setIsInside(val: Bool)
{
if (val)
{
if (!isInside)
{
setBackgroundColor(UIColor.lightGray, for: .normal)
}
}
else
{
if (!isInside)
{
setBackgroundColor(UIColor.white, for: .normal)
}
}
isInside = val
}
Actually I only need the button here for the Keyboard class
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first
{
let point = touch.location(in: self)
delegate?.onBegan(button: self, point: point)
}
super.touchesBegan(touches, with: event)
}
As long as we move the touch outside of the button, the movement information will be sent to the Keyboard class:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first
{
let point = touch.location(in: self)
if !bounds.contains(point)
{
delegate?.onMoved(point: convert(point, to: superview))
return
}
}
super.touchesMoved(touches, with: event)
}
This is where the letter of the button is actually processed
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
if let touch = touches.first
{
let point = touch.location(in: self)
delegate?.onEnded(point: convert(point, to: superview))
}
super.touchesEnded(touches, with: event)
}
}
As we see, the LetterButton class establishes the following protocol which need to be implemented by the Keyboard class:
protocol LetterButtonDelegate
{
func onBegan(button: LetterButton, point: CGPoint)
func onMoved(point: CGPoint)
func onEnded(point: CGPoint)
}
The implementation of the protocol within Keyboard class is as follows:
The button which got initially touched is stored here
func onBegan(button: LetterButton, point: CGPoint)
{
buttonPressed = button
}
Processes background color change for buttons we swipe over
func onMoved(point: CGPoint)
{
let _ = findPopupButton(point: point)
}
Processing of the touch ending
func onEnded(point: CGPoint)
{
// Check if touch ended on a popup button
if let button = findPopupButton(point: point)
{
// yes, let the keyboard process the key
delegate?.KeyPressed(key: button.title(for: .normal)!)
button.setIsInside(val: false)
// remove popupbuttons
popupView?.removeFromSuperview()
popupView = nil
}
else
{
// remove popup buttons if touch ended anywhere else
if popupView != nil
{
popupView!.removeFromSuperview()
popupView = nil
}
// buttons is an array of all normal keyboard buttons
// we use it to check if the button, where the touch ended is the same where the touch began
for button in buttons
{
if (button.frame.contains(point))
{
if (button.button.tag == buttonPressed?.tag)
{
// Still on same button, process the key
delegate?.KeyPressed(key: button.button.title(for: .normal)!) break
}
}
}
}
}
// Let's see if we are moving within the bounds of a popup button
func findPopupButton (point: CGPoint) -> LetterButton?
{
var result : LetterButton? = nil
if (popupView != nil)
{
if (popupView!.frame.contains(point))
{
for sub in popupView!.subviews
{
if (sub.isKind(of: LetterButton.self))
{
let button = sub as! LetterButton
let frame = popupView!.convert(button.frame, to: self)
if (frame.contains(point))
{
button.setIsInside(val: true)
result = button
}
else
{
button.setIsInside(val: false)
}
}
}
}
}
return result
}
Upvotes: 1
Reputation: 14235
You have a complete set of events on UIControl (UIButton is its subclass) to handle all the touch events that you need:
enum {
UIControlEventTouchDown = 1 << 0,
UIControlEventTouchDownRepeat = 1 << 1,
UIControlEventTouchDragInside = 1 << 2,
UIControlEventTouchDragOutside = 1 << 3,
UIControlEventTouchDragEnter = 1 << 4,
UIControlEventTouchDragExit = 1 << 5,
UIControlEventTouchUpInside = 1 << 6,
UIControlEventTouchUpOutside = 1 << 7,
UIControlEventTouchCancel = 1 << 8,
UIControlEventValueChanged = 1 << 12,
UIControlEventEditingDidBegin = 1 << 16,
UIControlEventEditingChanged = 1 << 17,
UIControlEventEditingDidEnd = 1 << 18,
UIControlEventEditingDidEndOnExit = 1 << 19,
UIControlEventAllTouchEvents = 0x00000FFF,
UIControlEventAllEditingEvents = 0x000F0000,
UIControlEventApplicationReserved = 0x0F000000,
UIControlEventSystemReserved = 0xF0000000,
UIControlEventAllEvents = 0xFFFFFFFF
};
UIControlEventTouchDown
will fire when you touch the buttonUIControlEventTouchDownRepeat
will fire if you continue holding the button (notice that this event will fire many times, so you should handle only the first one) - here you should display the popoverUIControlEventTouchDragExit
will fire when you drag the finger out from the button - here you should hide the popoverUIControlEventTouchDragEnter
will fire when you drag the finger into the button - here you should display the popoverUIControlEventTouchUpInside
, UIControlEventTouchUpOutside
and UIControlEventTouchCancel
will fire when you lift the finger from the button - here you should hide the popoverUPDATE
You will have some logic to implement to handle dragging the finger inside the popover (because then you will drag out from the button) though.
Upvotes: 1
Reputation: 13860
You have UIControl
just for that UILongPressGestureRecognizer
And you can set the time of the press by minimumPressDuration
property
If you want this behavior on UIButton
you have to add this gesture recognizer to the button and handle the first calling (single tap).
Upvotes: 1