UICollectionView: Measure force on tap of cell - ios

I am working on a tiny project where I want to use the 3D touch screen pressure capabilities (e.g. touch.force).
Right now I can measure force in my ViewController and it is behaving like I want it:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if traitCollection.forceTouchCapability == UIForceTouchCapability.available {
// 3D Touch capable
let force = touch.force
print("Force: " + force.description)
}
}
}
I have one UIImageView that presents a picture. I also use an custom UICollectionView as overlay, which contains transparent cells and colored cells. What I am trying to achieve is to measure both force, and be able to remove the color from the cells when they are pressed, but I am failing to do both. I can either measure force, or change the color of a cell in the UICollectionView, but not both.
The solution as I see it is to forward touches from the UICollectionView to the next view in the hierarchy. But another solution could be to measure both force and register taps on cells from within the custom UICollectionView.
My question is, is either of my proposed solutions any good? And if so, how do I achieve them?

Apparently the override func touches... methods are part of UIView, not neccesarily UIViewControllers. This means that implementing the touchesMoved and other methods into my custom UICollectionView is perfectly possible and it solved my problem of handling touches and taps at the same time.

Related

Transfer gestures on a UIView to a UITableView in Swift - iOS

I have a layout with a UIView at the top of the page and, right below it, I have a UITableView.
What I am wanting to do is to transfer the gesture interactions on the UIView to the UITableView, so when the user makes a drag up/down on the UIView, the UITableView scrolls vertically.
I tried the following code
tableView.gestureRecognizers?.forEach { uiView.addGestureRecognizer($0) }
but it removed the gestureRecognizers from the UITableView somehow :/
Obs.: the UIView cannot be a Header of the UIScrollView
That's Tricky
What is problem ?
Your top view is not allowed to pass through view behind it...
What would be possible solutions
pass all touches to view behind it (Seems to not possible or very tough practically )
Tell window to ignore touches on top view (Easy one)
Second option is better and easy.
So What you need to do is create subclass of UIView and override
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
and return nil if you found same view on hitTest action
Here Tested and working example
class PassThroughME : UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event) == self ? nil : self
}
}
That's it now use PassThroughME either by adding class to your view in storyboard or programmatically whatever way you have added your view
Check image i have black color view with 0.7 alpha on top still i am able to scroll
Hope it is helpful

Change shape of UICollectionViewCell gesture hitbox

I have a bunch of UICollectionViewCells which look circular (imagine a circular background image). I want to detect when I tap on one of them, but currently the gesture hitbox of the cells are square-shaped corresponding to the cell height and width. As a result, tapping on the corner of the square shape, which is a bit outside of the circular look, still registers as a gesture:
I'm thinking of a way to do this while still being able to use the indexPathForItemAtPoint(point:) method i.e. I don't want to write a manual check, such as a for loop running cell.frame.containsPoint(point:).
I tried to change the borderRadius of the cell's layer, but I think the relevant field used to detect the tap is the frame, which is a CGRect...so is it even possible to achieve what I'm trying to do, or must I write the cell detection code myself? (something like, get all cells and calculate the distance to the center, and then see if the distance is within the radius)
The main problem is that my layout causes cell frames to overlap, so indexPathForItemAtPoint(point:) currently causes some cells to be favored over others when their frames overlap (most likely based on iteration order through visibleCells).
You can create a UIBezierPath and check if the tap is within that. For example in your action for a UITapGestureRecognizer you can say something like
let location = sender.location
let indexPath = collectionView.indexPathForItemAtPoint(location)
let cell = collectionView.cellForItemAtIndexPath(indexPath!)
let newLocation = cell?.convertPoint(location, fromView: collectionView)
let circlePath = UIBezierPath(ovalInRect: (cell?.bounds)!)
if circlePath.containsPoint(newLocation!)
{
//handle cell tap
}
Or in your subclass of UICollectionViewCell you can add something like:
override func hitTest(point: CGPoint, withEvent e: UIEvent?) -> UIView? {
let circlePath = UIBezierPath(ovalInRect: (self.bounds))
if circlePath.containsPoint(point)
{
return self
}
return nil
}
This will check if the tap is in the visible part of the view or pass the event to lower views. Then implement didSelectItemAtIndexPath and handle the tap event there.

Detect touch that originated from superview

I'm creating a subclass of UIView to use in a project, and will handle touches on the main view. I'd like so that when the touch (on the main view) is dragged and contacts the special UIViews, they change their background color. Using UIView's default "touchesMoved" function only detects touches that originate on the specific view. I could set up the main view to check each instance of the custom UIView, but that would be contrary to encapsulation and lead to messy code.
Any ideas?
I'm not sure this is what your after, but if I put the following code in my view controller, whose view has several RDView views in it, the color of those views will change color when I drag from the main view into the RDView.
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
var touched = self.view.hitTest((touches.anyObject() as UITouch).locationInView(self.view), withEvent: event)
if touched is RDView {
touched?.backgroundColor = UIColor.redColor()
}
}

TableHeaderView not responding to touches

I have a custom subclass of UIView, designed in IB that contains a few labels and a button.
There is an action for touchUpInside event on the button and the target is the custom view.
I am attaching this custom view to a self.tableView.tableHeaderView for a tableview in my UI.
The strange thing is this custom view is not responding to touches. I can see it nicely with all the labels and the button in side the table view, that means the table view handles and shows it correctly, however it is not responding to touches.
I checked the whole view hierarchy and all the views involved have userInteractionEnabled as YES.
If i drag some other controls into that custom view for example a switch, segmented control..they do not respond as well. It is like these controls in custom view are not registering touches.
It doesn't make any sense. I am out of ideas. Can you help to allow the touch event on the button to arrive to its parent view?
What is a "headerTableView"? Do you mean a UITableViewHeaderFooterView? Have you tried setting userInteractionEnabled on the root UITableViewHeaderFooterView?
This is a hack that will detect a button touch and trigger touchUpInside programmatically:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
super.touchesBegan(touches, with: event)
if let touch = touches.first, button.bounds.contains(touch.location(in: button)) {
button.sendActions(for: UIControl.Event.touchUpInside)
}
}
I don’t know what your real problem is because that button should work without this.

Disable UITableView scrolling on some cells

In iOS 6, I have a UITableView, with pull to refresh enabled. In the top cell, I have a couple of customs controls which the user can interactive by dragging a circular slider (see this example). See screenshot...
The control need a tag and dragging of the slider indicators need to be dragged but they can be tricky to grab as the hit seems to often be on the cell background, causing the table dragging to kick in.
I would like to disable the default scrolling of table if the tap event happens anywhere on those controls. Two options I can think off:
disable table dragging for any event within that top cell
make sure the controls handles events on an larger area, in particular in parts where they have a transparent background
Any suggestions on how to achieve either of these?
Thanks!
I used to subclass of UIControl and override these functions:
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { }
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {}
It works fine for me of this circle slider style.
If still doesn't work you can try to turn on/off your tableView scrollable in the methods above.
tableView.isScrollEnabled = false / true

Resources