Unexpected nil index path in _shouldShowMenuForCell - ios

Does anyone know what is going on with this problem? My cell stop swiping because of this.
[Assert] Unexpected nil index path in
_shouldShowMenuForCell:, this should never happen. Cell ; baseClass = UITableViewCell; frame = (0 97.5; 375 130); alpha = 0; hidden = YES;
autoresize = W; gestureRecognizers = ; layer
= >
Help !!!

I change swiping method from gestureRecognizer to ScrollView and everything works perfect.

If your cell is created by -
[[[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil] lastObject];
AND using addSubview to display it. This problem will occur.
My solution is using "addSubview:cell.contentView" BUT NO "addSubview:cell"

I already found solution even with creating swipe cell using gesture recognizer, in my case the problem ("Unexpected nil index path in _shouldShowMenuForCell:, this should never happen. ") was appearing because of this function:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subview in self.subviews.reversed() {
if subview.frame.contains(point) {
return subview
}
}
return super.hitTest(point, with: event)
}
tip: Working solution of:
Capturing touches on a subview outside the frame of its superview using hitTest:withEvent:

Related

How to iterate through all UIButtons on screen in Swift 3.0?

I am trying to do a loop to iterate through all UIButtons found on the view in order to change the position of each button. (See code below)
for case let button as UIButton in self.view.subviews {
rect = button.frame
rect.origin.x = rect.origin.x + 20
rect.origin.y = rect.origin.y + 20
button.frame = rect
}
However, when I debugged the code I found that the loop is not finding any buttons, and thus the positions remained the same. I have 9 buttons in a View found in the View Controller.
Anyone knows what's wrong with this loop?
Your code works fine for me. The implication is that your buttons are not subviews of self.view, but subviews of some view further down the hierarchy. You will have to write a recursive version of your loop.
For example:
func lookForButton(_ v: UIView) {
let subs = v.subviews
if subs.count == 0 {return}
for vv in subs {
if vv is UIButton {
print(vv) // just testing: do your real stuff here
}
lookForButton(vv)
}
}
lookForButton(self.view)
Your code is working. This:
var view = UIView()
view.addSubview(UIView())
view.addSubview(UIButton())
for case let button as UIButton in view.subviews {
print(button)
}
prints:
<UIButton: 0x7fcc9bd07320; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x60000002dca0>>
Conclusion: self.view.subviews does not contain any UIButtons.

CALayer shadow in UITableViewCell Drawn incorrectly

I am applying shadow to a UITableViewCell using CALayer.
Here's my code:
- (void)addShadowToView:(UIView *)view
{
// shadow
view.layer.shadowColor = [[UIColor colorWithWhite:0.0f alpha:0.1f] CGColor];
view.layer.shadowOpacity = 1.0f;
view.layer.shadowOffset = CGSizeMake(0.0f, 3.0f);
view.layer.shadowRadius = 6.0f;
CGRect shadowFrame = view.layer.bounds;
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
view.layer.shadowPath = shadowPath;
}
The issue is that for some tableviewcells, the shadow does not span the entire width of the cell. For some cells it would be correct, for others it would be faulty. I do notice that the rotation of the device also affects it, and reloading of the tableview data sometimes solves it.
What is the best way to mitigate this issue (and with that I don't mean to reload the whole tableview on each rotation etc.)?
Example bottom of cell where shadow is correctly applied:
Bottom of cell in same tableview after scrolling down (shadow only applied for first 75% of width):
Edit: I have noticed the issue is caused from these lines of code:
CGRect shadowFrame = view.layer.bounds;
CGPathRef shadowPath = [UIBezierPath bezierPathWithRect:shadowFrame].CGPath;
view.layer.shadowPath = shadowPath;
If I leave them out, everything is fine. But I've been told there is certain performance benefit when using this. Somehow the shadow is not correctly applied to new dimensions after rotating..
You can override the setter for you're cell's frame and call addShadowToView:. You can optimize this more by storing your cell's size and only updating the shadow path when the size changes for example:
#property (nonatomic, assign) CGSize size;
And
- (void) setFrame:(CGRect)frame
{
[super setFrame:frame];
// Need to check make sure this subview has been initialized
if(self.subviewThatNeedsShadow != nil && !CGSizeEqualToSize(self.size,_frame.size)
{
[self addShadowToView: self.subviewThatNeedsShadow];
}
}
The easiest solution is to add the shadow to the UITableViewCell's contentView (vs the layer for the cell's backing view). Since the cell's bounds change on scroll, if you add the shadow to the root view then you would have to update the shadow's path on each scroll event which would be costly and not necessary.
You're definitely correct re: the performance hit by not explicitly setting the shadowPath though. If you don't have any animated content within the cell, I'd also recommend rasterizing it to further improve performance.
EDIT: You must also ensure that when you set the shadow path that the contentView's bounds are in their 'final' position. If the size of the cell is later modified, this will result in the contentView's bounds changing and thus an incorrect shadowPath. The solution to this is to update the path in the UITableViewCell's -layoutSubviews method.
Here the concern is not the parent view frame where your working here concern is its sublayer and its size which should be changed when layout changes. You can override the below method which will help you to setup correct frame on layout changing.
public override void LayoutSublayersOfLayer(CALayer layer)
{
base.LayoutSublayersOfLayer(layer);
if (layer.Name == "gradient")
{
layer.Frame = view.Layer.Frame;
}
}
In above code view is the where you added sublayer. If you are playing with multiple layers in same view than you can use the identifier name property to work on particular layer.
Thanks for #beyowulf's answer gave me clues in override UIView frame get and set
In my case, I would like to make shadow stick with the other subview in subclass tableView cell.
Swift 5
// TargetView old size
var lastSize: CGSize = .zero
// Override frame in subclass tableView cell
override var frame: CGRect {
get {
super.frame
}
set {
super.frame = newValue
if targetView != nil {
// Compared targetView size with old one.
if lastSize != targetView.frame.size {
/* Update the other subview's shadow path or layer frame here */
lastSize = targetView.frame.size
}
}
}
}
It works for me.

2 UIPageViewControllers gestures conflict

I've got 2 UIPageViewControllers one inside other, both with horizontal scroll. One fullscreen, containing all the user info, another - photo gallery for this user. Behavior: when i swipe all the user photos, it swipes the fullscreen. But sometimes, i can't swipe photos, seems like this gesture is blocked, and it swipes only the first pager. But it unblocks when i make back swipe gesture. Here is a video, of what i'm talking about: https://youtu.be/Hr7tDKNv15A Help me find error causing it, at now I can't imagine how i need to debug that.
Overrided hitTest of container view, which stores inner pager:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if pointInside(point, withEvent: event) {
print("Inside")
print("Self view:\(self)")
print("Self subviewsview:\(self.subviews)")
print("Self subviewsview of subview:\(self.subviews[0].subviews)")
return self.subviews[0].subviews[0]
} else {
print("Outside")
return nil
}
}
That's my output, when i touch photos:
Inside
Self view:<armeniaApp.debugGesture: 0x7f7f8be46e60; frame = (0 0; 400 400); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7f7f8be1e7c0>>
Self subviewsview:[<_UIPageViewControllerContentView: 0x7f7f8be920a0; frame = (0 0; 400 400); clipsToBounds = YES; opaque = NO; autoresize = W+H; layer = <CALayer: 0x7f7f89ff1e00>>]
Self subviewsview of subview:[<_UIQueuingScrollView: 0x7f7f8a836e00; frame = (0 0; 400 400); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x7f7f8be81db0>; layer = <CALayer: 0x7f7f8be522b0>; contentOffset: {400, 0}; contentSize: {1200, 400}>]
So the gesture that i need stores in UIQueuingScrollView but what i need to do next? return self.subviews[0].subviews[0] doesn't help
I'd suggest taking a look at requireGestureRecognizerToFail: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/#//apple_ref/occ/instm/UIGestureRecognizer/requireGestureRecognizerToFail:
It seems like UIKit is getting confused as to which page controller to pass the gestures on to as they will both be listening for the same thing.
I'd imagine you want the outer page view controller's gesture recognisers (accessible as an array property: self.pageViewController.gestureRecognizers) to require the gesture recognisers of the inner page view controller to fail. That way the swipe between photos will take precedence, but if there are no further photos to swipe to, you should be able to swipe between profiles.

Issue with Dropping point in Swift

I have a table view and outside the table view and i have one view which is outside the table view. I am trying to drag and drop elements from table view to Drop view. The View looks something like this.
There is a Table View as shown in the figure. What i am trying to accomplish is, when the user long presses on a row in table view i need to drag and drop the selected item into the droppable area. On long pressing i create a snapshot of the row and add as a subview, now i am trying to drag the subview into the drop area. I am not able to do this. Can anyone help me out with this issue.
var stateDropBoard = CGRectContainsPoint(ingBoardDropView.frame, touchPoint)
if(stateDropBoard)
{
print("DROPPED")
}
I am not able to get it working. Is there any way to accomplish what i am doing.
On your parent view use touches ended method. Inside this method run the test to check if user has dropped item on top of your green colour view.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [[touches anyObject] locationInView:self.view];
CGRect fingerRect = CGRectMake(location.x-5, location.y-5, 10, 10);
for(UIView *view in self.view.subviews){
CGRect subviewFrame = view.frame;
if(CGRectIntersectsRect(fingerRect, subviewFrame)){
// this is your touched view
}
}
}
Here is swift version which you asked! with little optimisation.
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first! as UITouch
let location = touch.locationInView(self.view)
let hitTest = self.targetView.hitTest(location, withEvent:event )
if (hitTest != nil) {
print("User finished touch inside your target view!")
}else{
print("User finished touch outside your target view");
}
}
Note : Make sure you have correct constraints in place for your target view.
I guess you can take it from here. Hope this helps.

Continuous vertical scrolling between UICollectionView nested in UIScrollView

While I know nested scrollViews aren't ideal, our designers provided me with this setup, so I'm doing my best to make it work. Let's begin!
View Hierarchy
UIView
UIScrollView (Vertical Scrolling Only)
UIImageView
UICollectionView #1 (Horizontal Scrolling Only)
UIImageView (different from previous UIImageView)
UICollectionView #2 (Vertical Scrolling Only)
Important Note
All my views are defined using programmatic Auto Layout. Each successive view in the UIScrollView's subview hierarchy has a y-coordinate dependency on the view that came before it.
The Problem
For the sake of simplicity, let's modify the nomenclature a bit:
_outerScrollView will refer to UIScrollView
_innerScrollView will refer to UICollectionView #2
I'd like for my _outerScrollView to route its touch event to the _innerScrollView upon reaching the bottom of its contentSize. I'd like the reverse to happen when I scroll back up.
At present, I have the following code:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat bottomEdge = [scrollView contentOffset].y + CGRectGetHeight(scrollView.frame);
if (bottomEdge >= [_outerScrollView contentSize].height) {
_outerScrollView.scrollEnabled = NO;
_innerScrollView.scrollEnabled = YES;
} else {
_outerScrollView.scrollEnabled = YES;
_innerScrollView.scrollEnabled = NO;
}
}
where the initial conditions (before any scrolling occurs) is set to:
outerScrollView.scrollEnabled = YES;
innerScrollView.scrollEnabled = NO;
What happens?
Upon touching the view, the outerScrollView scrolls until its bottom edge, and then has a rubber band effect due to _outerScrollView.bounces = YES; If I touch the view again, the innerScrollView scroll until it hits its bottom edge. On the way back up, the same rubber banding effect occurs in the reverse order. What I want to happen is have a fluid motion between the two subviews.
Obviously, this is due to the scrollEnabled conditions that are set in the conditional in the code snippet. What I'm trying to figure out is how to route the speed/velocity of one scrollView to the next scrollView upon hitting an edge.
Any assistance in this issue would be greatly appreciated.
Other Notes
This did not work for me: https://github.com/ole/OLEContainerScrollView
I am considering putting everything in the UIScrollView hierarchy (except for UICollectionView #2) inside UICollectionView #2 supplementaryView. Not sure if that would work.
Figured it out!
First:
_scrollView.pagingEnabled = YES;
Second:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == _scrollView || scrollView == _offersCollectionView) {
CGFloat offersCollectionViewPosition = _offersCollectionView.contentOffset.y;
CGFloat scrollViewBottomEdge = [scrollView contentOffset].y + CGRectGetHeight(scrollView.frame);
if (scrollViewBottomEdge >= [_scrollView contentSize].height) {
_scrollView.scrollEnabled = NO;
_offersCollectionView.scrollEnabled = YES;
} else if (offersCollectionViewPosition <= 0.0f && [_offersCollectionView isScrollEnabled]) {
[_scrollView scrollRectToVisible:[_scrollView frame] animated:YES];
_scrollView.scrollEnabled = YES;
_offersCollectionView.scrollEnabled = NO;
}
}
}
Where:
_scrollView is the _outerScrollView
_offersCollectionView is the _innerScrollView (which was UICollectionView #2 in my original post).
Here's what happens now:
When I swipe up (so the view moves down), the offersCollectionView takes over the entire view, moving the other subViews out of the view.
If I swipe down (so the views up), the rest of the subviews come back into focus with the scrollView's bounce effect.
Accepted answer didn't work for me. Here's what did:
Define a subclass of UIScrollView:
class CollaborativeScrollView: UIScrollView, UIGestureRecognizerDelegate {
var lastContentOffset: CGPoint = CGPoint(x: 0, y: 0)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer.view is CollaborativeScrollView
}
}
Since it's not possible to reroute touches to another view, the only way to ensure that the outer scrollview can continue scrolling once the inner one stops is if it had been receiving the touches the whole time. However, in order to prevent the outer one from moving while the inner one is, we have to lock it without setting its isScrollEnabled to false, otherwise it'll stop receiving the touches and won't be able to pick up where the inner one left off when we want to scroll past the inner one's top or bottom.
That's done by assigning a UIScrollViewDelegate to the scrollviews, and implementing scrollViewDidScroll(_:) as shown:
class YourViewController: UIViewController, UIScrollViewDelegate {
private var mLockOuterScrollView = false
#IBOutlet var mOuterScrollView: CollaborativeScrollView!
#IBOutlet var mInnerScrollView: CollaborativeScrollView!
enum Direction {
case none, left, right, up, down
}
func viewDidLoad() {
mOuterScrollView.delegate = self
mInnerScrollView.delegate = self
mInnerScrollView.bounces = false
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView is CollaborativeScrollView else {return}
let csv = scrollView as! CollaborativeScrollView
//determine direction of scrolling
var directionTemp: Direction?
if csv.lastContentOffset.y > csv.contentOffset.y {
directionTemp = .up
} else if csv.lastContentOffset.y < csv.contentOffset.y {
directionTemp = .down
}
guard let direction = directionTemp else {return}
//lock outer scrollview if necessary
if csv === mInnerScrollView {
let isAlreadyAllTheWayDown = (mInnerScrollView.contentOffset.y + mInnerScrollView.frame.size.height) == mInnerScrollView.contentSize.height
let isAlreadyAllTheWayUp = mInnerScrollView.contentOffset.y == 0
if (direction == .down && isAlreadyAllTheWayDown) || (direction == .up && isAlreadyAllTheWayUp) {
mLockOuterScrollView = false
} else {
mLockOuterScrollView = true
}
} else if mLockOuterScrollView {
mOuterScrollView.contentOffset = mOuterScrollView.lastContentOffset
}
csv.lastContentOffset = csv.contentOffset
}
}
And that's it. This will stop your outer scrollview from scrolling when you begin scrolling the inner one, and get it to pick up again when the inner one is scrolled all the way to one of its ends.

Resources