I want to detect a shake gesture in a UITableViewCell, but no shake is being registered. Is there a way I can detect a shake in a UITableView cell?
This is the code I'm using now:
extension MyTableViewCell {
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
print("shake started")
}
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
print("shake stopped")
}
}
I would suggest adding your motionBegan/motionEnded methods to your view controller, and then updating the model that backs your table view to reflect the shake event and tell the table view to reload. (For example, you might add an isShaking property to an array of structs that holds the settings for your table view cells.) In your cellForRowAt() method, check the model to see if your isShaking property for that IndexPath is true, and if it is, make whatever change to your cell animation that you need.
Related
I'm new to the iOS development and Swift and I have the following problem:
I would like to capture touch events from the screen. I have no problems with doing this on regular views. Unfortunately, I encountered issues with the UIScrollView component. When user is tapping on the screen, long pressing on the screen or moving finger horizontally, then I'm able to capture touches, but when user is moving finger vertically and activating vertical scrolling at the same time, then I'm not able to capture touches.
I found a few threads on StackOverflow regarding similar issue, but solutions provided there did not resolve my problem.
Structure of my views is as follows:
+- View (top-level view)
|
|
+--- UIScrollView
|
|
+-- other views inside...
Here is my code:
class MyViewController : UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(myRecognizer)
}
}
Gesture recognizer is added into the top-level view.
I also have basic custom implementation of the UIGestureRecognizer:
class MyRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
// handle events...
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
// handle events...
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
// handle events...
}
}
What did I try to do to solve this issue?
setting parameter scrollView.canCancelContentTouches = false - didn't help
setting parameter scrollView.isScrollEnabled = false - I was able to capture all touch events, but scrollView stopped working - didn't help
adding gesture recognizer to the scrollView and removing it from the top-level view - didn't help
adding gesture recognizer both to the scrollView and the top-level view - didn't help
setting parameter scrollView.isUserInteractionEnabled = false - I was able to capture all touch events, but scrollView and all UI components stopped working - didn't help
reordering calls in MyRecognizer class: handling events first and then calling method from the upper class super.touches... - didn't help
Right now, I have no idea what else I can do to capture touch events inside the UIScrollView while user is scrolling the screen vertically.
Any suggestions or help are appreciated!
Regards,
Piotr
So UIScrollView has its own set of gestures that have cancelsTouchesInView set to true by default. Try setting this property to false for each gesture in the scrollview like so:
for gesture in scrollView.gestureRecognizers ?? [] {
gesture.cancelsTouchesInView = false
}
EDIT:
Solution to this problem is creating a separate custom UIScrollView and capture touches inside it. It is described in details in this thread: https://stackoverflow.com/a/70594220/10953499 (also linked in the comment).
I have a parent view which contains two elements. Essentially, they comprise a dropdown select. Referencing the below image: When the blue element is clicked, it shows an (initially hidden) dropdown UITableView. This UITableView is partially inside of the same parent view that also contains the blue element.
When I try to click on one of the UITableViewCells, only the first cell registers a touch event. If the table view is situated such that the first cell is partly inside of the parent, only clicks on the half of the image that is inside of the parent register.
This seems to be a hierarchy issue. I have tried:
Adjusting Z-indices
Situating the entire UITableView outside of the parent in the Storyboard hierarchy, but visually positioning it inside of it.
I'm not sure how to proceed.
EDIT:
See Andrea's answer which worked for me. I ended up overriding the point method as suggested, and did not use hitTest. However, I went with another implementation of the point method override:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if super.point(inside: point, with: event) { return true }
for subview in subviews {
let subviewPoint = subview.convert(point, from: self)
if subview.point(inside: subviewPoint, with: event) { return true }
}
return false
}
As DonMag wrote:
You cannot interact with an element that extends beyond the bounds of
its parent (superview).
At least not without overriding some methods, views have their own implementations to detect touches. Most important methods are:
func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView?
func point(inside point: CGPoint,
with event: UIEvent?) -> Bool
Those methods must be overriden, I've pasted some code on mine, is pretty old thus probably will require swift migration from 3.x to 4.x. Everything that you will add as a subview will handle touches even if is outside the bounds:
class GreedyTouchView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.clipsToBounds && !self.isHidden && self.alpha > 0.0 {
let subviews = self.subviews.reversed()
for member in subviews {
let subPoint = member.convert(point, from: self)
if let result: UIView = member.hitTest(subPoint, with:event) {
return result
}
}
}
return super.hitTest(point, with: event)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return super.point(inside: point, with: event)
}
}
But I'd like to point out that drop down menus are more Web UI related and not iOS UI related, you should use a picker instead.
This question already has answers here:
Detect shake gesture IOS Swift
(7 answers)
Closed 5 years ago.
Hi I want to identify the iPhone SHAKE when user shakes their phone, either in background mode or in foreground mode of the app.
Please assist me.
Thanks in advance.
Try something like this:
override func motionBegan(_ motion: UIEventSubtype, with event: UIEvent?) {
print("Device was shaken!")
}
The main trick is that you need to have some UIView (not UIViewController) that you want as firstResponder to receive the shake event messages. Here's the code that you can use in any UIView to get shake events:
class ShakingView: UIView {
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if event?.subtype == .motionShake {
// Put in code here to handle shake
}
if super.responds(to: #selector(UIResponder.motionEnded(_:with:))) {
super.motionEnded(motion, with: event)
}
}
override var canBecomeFirstResponder: Bool { return true }
You can easily transform any UIView (even system views) into a view that can get the shake event simply by subclassing the view with only these methods (and then selecting this new type instead of the base type in IB, or using it when allocating a view).
In the view controller, you want to set this view to become first responder:
override func viewWillAppear(_ animated: Bool) {
shakeView.becomeFirstResponder()
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
shakeView.resignFirstResponder()
super.viewWillDisappear(animated)
}
Don't forget that if you have other views that become first responder from user actions (like a search bar or text entry field) you'll also need to restore the shaking view first responder status when the other view resigns!
This method works even if you set applicationSupportsShakeToEdit to NO.
For objective c version refer link How do I detect when someone shakes an iPhone?
I have used collectionView and also implemented pull to sync (pull_Down_To_Get_Data_From_Server) in my ViewController(named : "DashboardViewController"). I tried the shake gesture
code(given below) in my DashboardViewController and its not working but similar code is working in another viewController of same app.
I used the canBecomeFirstResponder in viewDidLoad in first try and in viewDidAppear secondly but it was also useless.
Requirement:
Actualy i want to know the cases(if any) in which ".motionShake" event do not trigger directly and we have to implement another way? Also that i did not find anything fruitful
on google.
Image of storyboard may help in understanding all the utilities used
override func viewDidLoad() {
// call super
super.viewDidLoad()
self.becomeFirstResponder() // for shake gesture detection
}
// it will make first responder to detect shake motion event
override var canBecomeFirstResponder: Bool {
get {
return true
}
}
// get detection of shake motion event
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if motion == .motionShake
{
print("Shake detected")
}
}
We ran into something similar while working on Yoshi
We ended up creating an extension on UIWindow, that contained the motionBegan event and forwarded the event. Not sure what caused it, but it seemingly broke with swift 3.0.
I have a view in my storyboard that by default the alpha is set to 0. In certain cases the Swift file sets the alpha to 1. So either hidden or not. Before this view just contained 2 labels. I'm trying to add 2 buttons to the view.
For some reason the buttons aren't clickable at all. So when you tap it normally buttons change color slightly before you releasing, or while holding down on the button. But that behavior doesn't happen for some reason and the function connected to the button isn't being called at all.
It seems like an issue where something is overlapping or on top of the button. The button is totally visible and enabled and everything but not clickable. I tried Debug View Hierarchy but everything looks correct in that view.
Any ideas why this might be happening?
EDIT I tried making a class with the following code and in interface builder setting the container view to be that class.
class AnotherView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for view in self.subviews {
if view.isUserInteractionEnabled, view.point(inside: self.convert(point, to: view), with: event) {
return true
}
}
return false
}
}
Go with hitTest(_:with:) method. When we call super.hitTest(point, with: event), the super call returns nil, because user interaction is disabled. So, instead, we check if the touchPoint is on the UIButton, if it is, then we can return UIButton object. This sends message to the selector of the UIButton object.
class AnotherView: UIView {
#IBOutlet weak var button:UIButton!
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if self.button.frame.contains(point) {
return button
}
return view
}
#IBAction func buttnTapped(sender:UIButton) {
}
}