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.
Related
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:
I have a UIScrollView with an image, and the image is in a container view. The view hierarchy looks like this:
UIScrollView
UIView
UIImageView
Panning works fine, but when I use the pinch gesture to zoom, the image changes size, but doesn't move relative to the origin, so the point centred underneath the two fingers moves as the gesture progresses. This makes it very difficult to zoom in on a particular point, since it's sliding away as you're zooming.
Description of the views involved:
<MyApp.MyView: 0x7ff6fd827e00; baseClass = UIScrollView; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x61800004a4d0>; layer = <CALayer: 0x618000023420>; contentOffset: {684, 487.5}; contentSize: {1731.4584832023011, 1154.3056554682007}>
<UIView: 0x7ff6fbe0c4d0; frame = (0 0; 1728 1152); transform = [2.2028669620902912, 0, 0, 2.2028669620902912, 0, 0]; layer = <CALayer: 0x6180000234c0>>
<UIImageView: 0x7ff6fbe09cd0; frame = (0 0; 1728 1152); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x618000023340>>
I have the UIImageView inside the UIView wrapper because I will have other views overlaid on the image which need to zoom/scale with the image.
There's very little of my own code running during the zoom. The UIScrollViewDelegate returns the UIView (the parent of the UIImageView), and configure the scroll view:
minimumZoomScale = 0.2
maximumZoomScale = 3.0
contentSize = image.size
Well, I've made a simple project that use your structure of views. And I didn't face with the problems. Possibly, you should check this things:are auto layout constraints setting right way is your parent view zooming correctly is mode of UIImageView fitting your needs Hope this may help
adBannerView.removeFromSuperview() isn't working on my scene - when you press a button the ad should disappear but it doesn't.
if i print the adBannerView from inside the button pressed block it prints
<ADBannerView: 0x15e54b910; frame = (0 0; 414 50); clipsToBounds = YES; hidden = YES; gestureRecognizers = <NSArray: 0x17024cfc0>; layer = <CALayer: 0x170234640>>
so hidden = yes, but it is still shown?
Be sure that you're triggering the method on the main thread, try this:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
adBannerView.removeFromSuperview()
})
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.
I have a subview UIbutton with frame (4,8),(13,13).
Touch A is at position (4,16) relative to superview, but gets sent to the superview, even though its within the button bounds. In window coordinates this is (49,131).
Touch B occurs one pixel left with window coordinates of (48,131) but gets sent to the subview button, even though it's out of bounds. The view reports a (-1,7) position in the UIButton.
Touch A
<UITouch: 0x7b2ddeb0> phase: Ended tap count: 1
window: <DebugWindow: 0x791357f0; baseClass = UIWindow; frame = (0 0; 320 480); layer = <UIWindowLayer: 0x791358d0>>
view: <QueueOverlayCellView: 0x7b1ca320; frame = (2 2; 269 30); tag = 1; layer = <CALayer: 0x7b1ca3a0>>
location in window: {49, 131} previous location in window: {49, 131}
location in view: {4, 16} previous location in view: {4, 16}
Touch B
<UITouch: 0x7b442e90> phase: Ended tap count: 1
window: <DebugWindow: 0x791357f0; baseClass = UIWindow; frame = (0 0; 320 480); layer = <UIWindowLayer: 0x791358d0>>
view: <TableButton: 0x7b1cada0; baseClass = UIButton; frame = (4 8; 13 13); opaque = NO; layer = <CALayer: 0x7b1cae40>>
location in window: {48, 130} previous location in window: {48, 130}
location in view: {-1, 7} previous location in view: {-1, 7}
The superview does not implement a custom pointInside method.
How is it that the touch out of bounds of the subview can be forwarded to the subview and the touch that is in bounds of the subview is forwarded to the superview?
Not a solution, but a workaround to my own question.
This strange behavior appears to occur with buttons that are too small, in this case its 13x13. Expanding the button size (and using contentInsent for image placement), increased the touchable area (in this case expanding it to 43 pixels wide) to about half the button.