So heres my issue, the 4 orange rectangles you see on the gif are a single vertical UICollectionView = orangeCollectionView.
The Green and Purple "card" views are part of another UICollectionView = overlayCollectionView.
overlayCollectionView has 3 cells, one of which is just a blank UICollectionViewCell, the other 2 are the cards.
When the overlayCollectionView is showing the blank UICollectionViewCell, I want to be able to scroll the orangeCollectionView.
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let superr = superview else { return true}
for view in superr.subviews {
if view.isKind(of: OrangeCollectionView.self) {
view.point(inside: point, with: event)
return false
}
}
return true
}
This allows me to scroll the orangeCollectionView HOWEVER this doesn't actually work to fix my issue. I need to be able to scroll left and right to show the cards, however this blocks all touches becuase the point always falls on the OrangeCollectionView.
How can I check to see if they are scrolling left/right to show the cards? Otherwise if they are on the blank cell, scroll the orangeViewController up and down.
Had this issue as well... Didn't find a nice way to do it, but this works.
First, you need to access the scroll view delegate method scrollViewDidScroll(). There you call:
if scrollView == overlayScrollView {
if scrollView.contentOffset.x == self.view.frame.width { // don't know which coordinate you need
self.overlayScrollView.alpa = 0
}
}
After that, you add a blank view onto of the orange collection view. The view's alpha is 0 (I think, maybe the color is just clear; try it out if it works).
In the code, you then add a UISwipeGestureRecognizer to the view you just created and and detect whether there's a swipe to the left or to the right.
By detecting the direction of that swipe, you can simply change the contentOffset.x axis of your overlayScrollView to 0 or self.view.frame.width * 2.
Sorry I can't provide my working sample code, I'm answering from my mobile. It's not the proper solution, but when I made a food app for a big client it worked perfectly and no one ever complained :)
Related
I want to show horizontal stacked cards , which moves one by one from left to right by swipe action . Once a card moves it forms a stack on the right side
I have tried seeing various libraries which straight away does not implement this. https://www.cocoacontrols.com/controls/iaccordion this one came close it has vertical swipe and is in objective C. Can you point me in the right direction to get this done ?
Card-like stacks are very easy to make but can be hard to perfect when it comes to some effects. Basically each of your views has one of 3 possible positions; on initial stack, on target stack, user is dragging it.
So the simplest implementation is to define 2 centers for your views and have 2 arrays of views. At the beginning all the views are in the first array. All views in first array should be centered to first center and in second array centered to the second center. Now you need a pan gesture. When a gesture starts you should check if it starts on the left stack if it does simply remove it from first array and begin changing its center. Like
func onPanGesture(_ sender: UIGestureRecognizer) {
switch sender.state {
case .began:
if leftStackView.bounds.contains(sender.location(in: leftStackView)), viewsInLeftStack.count > 0 {
pannedView = viewsInLeftStack.removeLast()
}
case .changed:
pannedView?.center = sender.location(in: self.view)
}
}
Now when finger is lifted you should check if card should be put back or moved to the other stack. So another case for gesture recognizer must be added:
case .ended, .cancelled:
if let pannedView = pannedView {
if(abs(pannedView.center.x-leftStackView.center.x) > abs(pannedView.center.x-rightStackView.center.x)) {
// Closer to the right one horizontally
viewsInLeftStack.append(pannedView)
pannedView.center = leftStackView.center
} else {
// Closer to the left one horizontally
viewsInRightStack.append(pannedView)
pannedView.center = rightStackView.center
}
}
In basics that is all you need. But then comes making the component nice. For instance when card is released it should animate to its deck:
UIView.animate(withDuration: 0.3) {
pannedView.center = self.rightStackView.center
}
Then cards may be a bit displaced on both of decks which is easiest to apply some transform. They may even rotate a bit as you drag them... But this is all up to you what you want to implement.
I have a UIScrollView that works when swiping left or right, however I've reduced the size of the scrollView so, now display area doesn't fully occupy the superview's frame, and swiping works only within the frame of the scroll view.
I would like to be able to scroll vertically even when swiping up and down outside the horizontal bounds of the narrowed scroll view.
It was recommended that I use a gesture recognizer, but that's beyond my current familiarity with iOS and could use more specific advice or a bit more guidance to get started with that.
There is a simpler approach then use a Gesture Recognizer =]
You can setup the superview of the scroll view (which is BIGGER...) to pass the touches to the scroll view. It's working M-A-G-I-C-A-L-Y =]
First, select the view that will pass all it's touches to the scroll view. if your parent view is already ok with that you may use it. otherwise you should consider add a new view in the size that you want that will catch touches.
Now create a new class (I'll use swift for the example)
class TestView: UIView {
#IBOutlet weak var Scroller: UIScrollView!
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if (view == self) {
return Scroller
}
return view
}
}
Nice! now as you can see we added an outlet of the scroller. so use interface builder, select the new view and set it's class to "TestView" in the identity inspector (Or to the name that you'll use for your custom class).
After you set the class and your view is still selected go to connections inspector and connect "Scroller" to your scroll view on the storyboard. All connected properly =]
That's it!! no gesture recognizer needed!!
The new view will pass all it's touches to the scroll view and it'll behave just like you pan in it =]
In my answer I used that answer
EDIT: I improved the code now, it wasn't working as expected before, now it catches only when in needs and not every touch in the app as before
Search for a component called SwipeGestureRecognizer :
Grab it and drop it on top of the View (use the hierarchy to make sure
you drop it on it, if you drop it on another element this code will not work):
Select one of the SwipeGestureRecognizer in the hierarchy and go to its attribute page. Change Swipe to Right.
Make sure the other recogniser has the Swipe attribute to Left
Select UIScrollView and uncheck Scrolling enabled
Connect detectSwipe() (see source code below) to both recognizers.
--
#IBAction func detectSwipe (_ sender: UISwipeGestureRecognizer) {
if (currentPage < MAX_PAGE && sender.direction == UISwipeGestureRecognizerDirection.left) {
moveScrollView(direction: 1)
}
if (currentPage > MIN_PAGE && sender.direction == UISwipeGestureRecognizerDirection.right) {
moveScrollView(direction: -1)
}
}
func moveScrollView(direction: Int) {
currentPage = currentPage + direction
let point: CGPoint = CGPoint(x: scrollView.frame.size.width * CGFloat(currentPage), y: 0.0)
scrollView.setContentOffset(point, animated: true)
// Create a animation to increase the actual icon on screen
UIView.animate(withDuration: 0.4) {
self.images[self.currentPage].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.4)
for x in 0 ..< self.images.count {
if (x != self.currentPage) {
self.images[x].transform = CGAffineTransform.identity
}
}
}
}
Refer to https://github.com/alxsnchez/scrollViewSwipeGestureRecognizer for more
I don't have time for detailed answer but:
In storyboard drag a pan gesture recognizer on the scroll view's superview... Connect it's action with your view controller and in this action change the scroll view position by using the properties from the gesture recognizer that you got as parameter
Tip: when connecting the action change parameter type from Any to UIPanGestureRecognizer in the combo box
please don't see this answer as recommendation to use this approach in your problem, I don't know if that's the best way, I'm just helping you to try it
I have this table view which in some cases contains few rows, even one.
What’s wrong with it is that even if I have one row, it allows me to scroll down and the row gets hidden at top.
It practically almost disappears from screen, as if there would be somethingto show below it.
I can’t disable scrolling because I have pull down to refresh.
Any ideas if there is a setting I am missing? Or how I could not allow scroll down if I do not have enough rows to cover the whole screen?
Actually, your case is kind of tricky, because:
The first I thought that the solution will be myTableView.alwaysBounceVertical = false
That's will do the job for you, but the problem in your case that you have a UIRefreshControl() and setting alwaysBounceVertical to false will disable scrolling to top for displaying the refreshController.
So, it should be done manually, as follows:
1- Implement the scrollViewDidScroll method from UIScrollViewDelegate.
2- check the scrolling direction in it.
3- if the scrolling direction goes down, check if content size of the tableView is more than its height, i.e check if tableView contains cell more than its height.
4- if the output of step 3 is false, disable scrolling, else, enable scrolling.
5- add dispatch_after to re-enable tableView scrolling.
It goes like this (Note: Swift 2 code.):
private var lastContentOffset: CGFloat = 0
// 1
func scrollViewDidScroll(scrollView: UIScrollView) {
// 2
if (self.lastContentOffset > scrollView.contentOffset.y) {
print("scrolling up")
}
else if (self.lastContentOffset < scrollView.contentOffset.y) {
print("scrolling down")
// 3 and 4
myTableView.scrollEnabled = myTableView.contentSize.height > myTableView.frame.size.height ? true : false
// 5
// delaying is half a second
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC) * 500), dispatch_get_main_queue(), { () -> Void in
self.myTableView.scrollEnabled = true
})
}
}
Hope this is a good solution for your case.
My layout is currently like this:
View
-- View
-- Vertical ScrollView
------ View
--------- Horizontal Paginated ScrollView
--------- View
------------- Horizontal ScrollView -- not working properly
See this image for view hierarchy screenshot from xcode:
Using Swift.
I am adding subviews dynamically to this "Size Select Scroll View"
Two Issues:
After adding views, there is no margin between the subviews. Each subview's coord. are like this: (10.0, 0.0, 44.0, 44.0), (54.0, 0.0, 44.0, 44.0), (98.0, 0.0, 44.0, 44.0), (142.0, 0.0, 44.0, 44.0) etc.
But the appearance is like this without the 10 points gap between each subview: http://i.stack.imgur.com/mCGRW.png
Scrolling horizontally is a pain. It only works on maybe 1/4 height from top of the scrollview area and very difficult to scroll. How do i layout subviews so that this scrollview is properly scrollable?
Note: I am explicitly setting content size of size scrollview to more than required so that i can see the scrolling.
I found out the issue. According to the Apple Docs the touch events will be passed to a subview only if it lies entirely in its parent.
In my case, the scrollview was going out of bounds of its parent view ( Details View), because of which touch events were weird. I increased the parent view's size to fit the scrollview and it works fine now.
From the docs (https://developer.apple.com/library/content/qa/qa2013/qa1812.html):
The most common cause of this problem is because your view is located outside the bounds of its parent view. When your application receives a touch event, a hit-testing process is initiated to determine which view should receive the event. The process starts with the root of the view hierarchy, typically the application's window, and searches through the subviews in front to back order until it finds the frontmost view under the touch. That view becomes the hit-test view and receives the touch event. Each view involved in this process first tests if the event location is within its bounds. Only after the test is successful, does the view pass the event to the subviews for further hit-testing. So if your view is under the touch but located outside its parent view's bounds, the parent view will fail to test the event location and won't pass the touch event to your view.
This is always a challenge in iOS.
There are various solutions which unfortunately depend on the exact situation.
Here's a drop-in solution which is often the right solution.
/*
So, PASS any touch to the NEXT view, BUT ALSO if any of OUR
subviews are buttons, etc, then THOSE should ALSO work normally. :/
*/
import UIKit
class Passthrough: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return subviews.contains(where: {
!$0.isHidden
&& $0.isUserInteractionEnabled
&& $0.point(inside: self.convert(point, to: $0), with: event)
})
}
}
(Of course, you can also just drop the call in to some class, eg
class SomeListOrWhatever: UICollectionView, .. {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
print("MIGHT AS WELL TRY THIS")
return subviews.contains(where: {
!$0.isHidden
&& $0.isUserInteractionEnabled
&& $0.point(inside: self.convert(point, to: $0), with: event)
})
}
Even if you "don't totally understand what the problem is", this is "one of" the solutions!
For example, this is (usually!) the precise solution to the exact issue quoted from the doco by #kishorer747
It's definitely a real nuisance in iOS.
In my app I'm trying to implement custom table view scrolling. I'm trying to achieve something similar to Volvo ocean race app, which does it very well.
Basically when user scrolls, middle cell gets higher and current high cell gets smaller
My original idea was playing with cell's frame in scrollViewDidScroll method for example
func scrollViewDidScroll(scrollView: UIScrollView) {
if(self.tblViewPhotos.contentOffset.y > 0)
{
var thisCell = self.tblViewPhotos.cellForRowAtIndexPath(selectedIndexPath) as PhotoCell
var offset = self.tblViewPhotos.contentOffset.y;
thisCell.frame = CGRectMake(thisCell.frame.origin.x, thisCell.frame.origin.y, thisCell.frame.size.width, 250-offset);
}
}
however I'm getting very buggy results (frame changing suddenly when swiping fast etc). I would be glad, if someone can point me to right direction