I have an UIView in which an UICollectionview is there. For knowing the scroll distance of UICollectionview I used scrollViewWillBeginDragging: , but it is not getting called.
Sample Code is
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
CGPoint translation = [scrollView.panGestureRecognizer translationInView:scrollView.superview];
if(translation.y > 0)
{
//dragging down
_reusableview.backgroundColor = [UIColor blackColor];
}
else
{
// dragging up
_reusableview.backgroundColor = [UIColor clearColor];
}
}
Can anyone help me please?
Hope you have added the UIscrollViewDelegate and set the UIcollectionView delegate to the class.
Then scrollViewWillBeginDragging() function will be called when collectionView is scrolled.
Inside the function you can confirm if the scrollView isKindOfClass UICollectionView.
UICollectionView is a sub class of UIScrollView. Anyone can detect the delegate methods of scroll view by keeping in mind some points.
Set the class as delegate of scroll view
You can do this in .h file and .m file
In .h file
#interface DemoViewController : UIViewController<UIScrollViewDelegate>
{
}
In .m file
#interface SplashViewController ()<UIScrollViewDelegate>
{
}
2. Make the datasource and delegate of collection view that class.
Example:
collectionView.delegate = self;
collectionView.dataSource = self;
Try out above steps.
Hope it will work for you.
Related
Below is what I want to make
What I have is ScrollView001 & ScrollView002. Layout is as below.
- View
- ScrollView001
- ScrollView002
ScrollView002, takes full screen so ScrollView001 is obviously below ScrollView002.
I am doing this for some effect i.e. when I scroll ScrollView002, I want to scroll ScrollView001 but with lower speed, which I have done successfully but the problem is when I try to click on Car for Gallery, its not clicking. If I hide ScrollView002 simply, gallery is working as usual.
Any idea how can I make underlying view as clickable?
For Gallery, what I have used is UICollectionView.
Edit 1
After scrolling, I want somewhat like below.
Fahim Parkar,
Not sure this is what you want or have I understood you wrong :) I believe you can makeuse of hitTest: to solve it :)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView && CGRectContainsPoint(ScrollView001.frame, point)) {
return ScrollView001;
}
return hitView;
}
How it Works
Whenever you tap on screen scorllView002 covering whole screen captures the touch :) Hence hitTest of ScrollView002 gets called :) If by anyway you can access the ScrollView001 (that should be easy as they are in same ViewController) you can verify if the touch point is inside ScrollView001, If yes you can return the touchView as ScrollView001.
As a result ScrollView001 will get touch rather then ScrollView002 :)
EDIT
As you have mentioned you did try my method and it still din work for you :) I decided to give a shot myself and realized it works absolutely fine. Please have a look :)
I believe this is what your situation :) I have added two scrollViews on top of each other. Small scrollView (myScrollView) being below the bigger fullscreen scrollView (TestScrollView).
Now when I tap on the screen there is no way myScrollView getting touch :)
But thats what we need so here is what I did :)
I created a subclass of ScrollView called TestScrollView and set the same for the top scrollView :)
TestScrollView.swift
import UIKit
class TestScrollView: UIScrollView {
var scrollViewBehind : UIView!
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, withEvent: event)
if hitView != nil && CGRectContainsPoint(scrollViewBehind.frame,point) {
return scrollViewBehind
}
return hitView;
}
}
I have created a variable called scrollViewBehind to hold the reference of smallerScrollView which is behind it in storyboard (I am very much aware that holding a reference like this to view behind is not a great design :) But I believe it is good enough to explain the logic )
This is my ViewController code :)
import UIKit
class ViewController: UIViewController,UIGestureRecognizerDelegate {
#IBOutlet var myScrollView: UIScrollView!
#IBOutlet var testScrollView: TestScrollView!
override func viewDidLoad() {
super.viewDidLoad()
testScrollView.scrollViewBehind = myScrollView
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap"))
tap.delegate = self
myScrollView .addGestureRecognizer(tap)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func handleTap(){
print("Me tapped")
}
}
EDIT
As per our discussion, I realized that you have a collectionView as a subView on smaller ScrollView which itself is behind the full screen scrollView :)
I have kept the answer above as it is in Swift and it also explains how to handle hitTest to handover the control to scrollView below from the scrollView above. Though it does not completely solve your question, might give a solution enough to somebody else :)
Now as per your question, when user taps, the scrollView above captures the touch and the touch should be forwarded to collectionView on top of smaller scrollView :)
So following the same approach explained above :) I created a subclass of ScrollView lets call TestScrollView (Answer is in objective C as per your requirement)
TestScrollView.h
#import <UIKit/UIKit.h>
#interface TestScrollView : UIScrollView
#property (nonatomic,strong) UICollectionView *collectionViewBehind;
#property (nonatomic,strong) UIScrollView *scrollViewBehind;
#end
TestScrollView.m
#import "TestScrollView.h"
#implementation TestScrollView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
CGPoint myPoint = [self convertPoint:point toView:self.scrollViewBehind];
if (hitView != nil && CGRectContainsPoint(self.scrollViewBehind.frame,point)) {
return self.collectionViewBehind;
}
return hitView;
}
#end
If you notice properly TestScrollView has two properties namely collectionViewBehind and scrollViewBehind this is to hold the reference of below scrollView and collectionView :)
What hitTest does is already explained above. Though, all it does is it checks the touch point if touch point is inside scrollView it returns the collectionView. What taht does is it transfers the touch event to collectionView.
Now go to storyboard select the topScrollView and set its class as TestScrollView.
In ViewController which loads these scrollViews add,
#import "ViewController.h"
#import "TestScrollView.h"
#interface ViewController ()<UICollectionViewDelegate,UICollectionViewDataSource,UIGestureRecognizerDelegate>
#property (strong, nonatomic) IBOutlet TestScrollView *testScrollView;
#property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.testScrollView.collectionViewBehind = self.collectionView;
self.testScrollView.scrollViewBehind = self.scrollView;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"test"];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tapGesture.numberOfTapsRequired = 1;
tapGesture.delegate = self;
[self.collectionView addGestureRecognizer:tapGesture];
// Do any additional setup after loading the view, typically from a nib.
}
Yes, If you have noticed you noticed it right I am adding a TapGesture recognizer to UICollectionView. Though we managed to handover the touch to UICollectionView we noticed that didSelectItemAtIndexPath was never triggered. So this is a small work around.
Though adding GestureRecognizer to collectionView is not much advisable here in this case it is still ok as we are only overriding single tap leaving all other gestures unaltered.
So now whenever user taps the scrollView above gestureRecognizer of UICollectionView triggers and calls our selector :) Now all you have to do is to figure out which cell was tapped and select it programmatically :)
-(void)handleTap:(UIGestureRecognizer *)recognizer {
[self collectionView:self.collectionView didSelectItemAtIndexPath:[self.collectionView indexPathForItemAtPoint:[recognizer locationInView:self.collectionView]]];
}
Finally enjoy the control at
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"Hi indexPath tapped is %#",indexPath);
}
Finally here is a link to the project : https://github.com/sandeeplearner/UnderlayingClickableViews
Try this
Take button as background for your car then imageview for car so that it can be clickable.. i think there i is no need to make view cickable
You should add tapgesturerecognizer on upper scrollview, then you can handle tap something like,
- (void)handleTap:(UITapGestureRecognizer *)recognizer {
// First get the tap gesture recognizers's location in the entire
// view's window
CGPoint tapPoint = [recognizer locationInView:self.view];
// Then see if it falls within one of your below images' frames
for (UIImageView* image in relevantImages) {
// If the image's coordinate system isn't already equivalent to
// self.view, convert it so it has the same coordinate system
// as the tap.
CGRect imageFrameInSuperview = [image.superview convertRect:image toView:self.view]
// If the tap in fact lies inside the image bounds,
// perform the appropriate action.
if (CGRectContainsPoint(imageFrameInSuperview, tapPoint)) {
// Perhaps call a method here to react to the image tap
[self reactToImageTap:image];
break;
}
}
}
You can consider your car or instead of imageview in your case.
Hope this will help :)
I have a UICollectionView that is used to simulate the new calendar in iOS 7. This collection view is inside a controller that has a selectedDate property. Whenever the selectedDate property is set the collection view should scroll to the date in the collection view.
The calendar controller's viewWillAppear also ensure the selected date is visible because this controller is cached and reused.
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.calendarView scrollToDate:[self selectedDate] animated:NO];
}
The problem is that the VERY first time the calendar controller is shown the scroll does not work. The contentOffset of the collection view is not updated.
My current workaround is to schedule the scroll to occur on the next run loop using
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void)
{
// Scroll to the date.
});
It looks like when the UICollectionView is not in a window you cannot scroll. Scheduling the scroll to happen on the next run loop ensure that the view has been added to the window and can be properly scrolled.
Has anyone else experienced this issue and what their workarounds?
you can always force auto-layout to layout.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.view.layoutIfNeeded()
self.collectionView.scrollToItemAtIndexPath......
}
If you are using auto layout, the issue may be that the constraints haven't set the frames yet. Try calling the scrollToDate: method in viewDidLayoutSubviews (without dispatch_after).
#interface CustomViewController ()
#property (nonatomic) BOOL isFirstTimeViewDidLayoutSubviews; // variable name could be re-factored
#property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
#end
#implementation CustomViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.isFirstTimeViewDidLayoutSubviews = YES;
}
- (void)viewDidLayoutSubviews
{
// only after layoutSubviews executes for subviews, do constraints and frames agree (WWDC 2012 video "Best Practices for Mastering Auto Layout")
if (self.isFirstTimeViewDidLayoutSubviews) {
// execute geometry-related code...
// good place to set scroll view's content offset, if its subviews are added dynamically (in code)
self.isFirstTimeViewDidLayoutSubviews = NO;
}
bilobatum's answer is correct!
I'm writing this because I don't have reputation to comment... :/
I tried bilobatum's answer in my project, and it worked perfectly!
My code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
if (currentOffset.y != 999) {
[collectionView setContentOffset:currentOffset animated:NO];
}
}
currentOsset is a CGPoint initialized with x = 0 and y = 999 values (CGPoint currentOffset = {0,999};)
In the viewWillDisappear method I save the collectionView's contentOffset in the currentOffset.
This way if I navigate to the controller that has the collectionView and I navigated to there before, I will always have the last position.
The code that will work for you:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self.calendarView scrollToDate:[self selectedDate] animated:NO];
}
Thank you bilobatum for the answer!
Using -viewDidLayoutSubviews created an infinite loop that made the solution too complicated.
Instead I just added a small delay to let the constraints be created before the scrolling:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([self.scheduleDate isThisWeek]) [self.calendarLayout performSelector:#selector(scrollToCurrentTime) withObject:nil afterDelay:1];
}
I have subclassed UIScrollView and setting it's super class as delegate as shown in below snippet, even i have set contentSize:, delegate methods are not getting called.
popView = [[PopView alloc]initWithFrame:CGRectMake(35, y, 250, 40)];
popView.answerDelegate = self;
popView.delegate = self;
popView.contentSize = CGSizeMake(750, 40);
popView.scrollEnabled = YES;
[self addSubview:popView];
One more thing, 'PopView' is subclass of UIScrollView.
if you are subclassing UIScrollView then setting its delegate to self will not do anything. a scrollview cannot be its own delegate since it doesnt implement its delegate methods itself. the view that contains the scrollview needs to be the delegate for it and must implement its delegate functions for it to work properly.
also self != super class. super == super class
If you have to adding your Scrollview using Xib then check are set delegate to file owner.and Adding .h file Like this
#interface View : UIView <UIScrollViewDelegate>
if not work yet then trying to clean your build. and then try...
I would like to know if a cell is being dragged (via the grip on the right side when a UITableView is in editing mode). I would like to change the background while it is in that state.
There is no callback unfortunately, but since the default behaviour is to adjust the 'alpha' value of the UITableViewCell you can check for that in '- (void)layoutSubview' of your UITableViewCell subclass.
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.alpha < 0.99) {
// we are most likely being dragged
} else {
// restore for not being dragged
}
}
Note that this is a bit of a hack. I noticed that this doesn't work when the table view's separatorStyle is set to 'UITableViewCellSeparatorStyleNone'.
I don't know if this is possible in the framework. But if it's not, you can try to override touchedMoved method in an UITableViewCell subclass?
You might then be able to also retrieve the state of your tableView (editing or not) in your UItableViewCell's custom subclass.
It is possible, yes!
In ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIScrollViewDelegate>
{
IBOutlet UITableView * myTable;
IBOutlet UIScrollView * myScroll;
}
In ViewController.m
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myScroll = myTable;
myScroll.delegate = self;
}
#pragma Mark UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"%f",scrollView.contentOffset.y);
}
That is it.
It's possible yes! But you have to keep track on the states yourself in the view controller. Define a member
BOOL tableDrag = NO;
In the method
scrollViewWillBeginDragging
you set tableDrag = YES; And in the method
scrollViewDidEndDragging
you set tableDraw = NO;
Now in the scrollViewDidScroll delegate method you can set the background.
If you have several scrollers then tag the scroll view so that you know which one is dragging.
Hope it helps!
I created a very simple view trying to combine a navigation controller and table view together:
.h
interface FileView : UIView {
UINavigationController * _nav;
UITableViewController * _tableView;
}
.m
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
_tableView = [[FileTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
_nav = [[UINavigationController alloc]initWithRootViewController:_tableView];
[self addSubview:_nav.view];
}
return self;
}
Then, to my surprise, when I added this view into the main window. I found the tableview simply didn't scroll, and table cells didn't respond to any events. It looked like the table view was hidden't under some glass.
Anyone knows how to solve the issue? and what step I missed to make it work?
Thanks a lot!
Try adding this line after adding the subview:
[self bringSubviewToFront:_nav.view];
Thanks, Problem solved. When creating the FileView, I need to provide a Frame, which I didn't :)