UIScrollview delaysContentTouches issue - ios

I have UIScrollView loaded with UIButtons and on UIButton action I have highlighted UIImage of each UIButton.
If I don't set delaysContentTouches as NO then highlighted UIImage of UIButton will not shown if I touch up UIButton very fast. After I set delaysContentTouches property as NO then only UIButton highlighted UIImage is shown.
Now after setting delaysContentTouches property as NO for UIScrollView. I can not scroll my UIScrollView by dragging on the UIButtons. Now how can I resolve this issue.
Please give me an advise.
Thanks in advance.

Here's what works for me. Subclass UIScrollView, and implement only this method:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}
Then set delaysContentTouches = NO;
Voila! Works just like the home screen: Highlights buttons immediately, but still allows scrolling :)

I found that in iOS 8, the UIScrollView's underlying UIPanGestureRecognizer is not respecting the UIScrollView's delaysContentTouches property. I consider this an iOS 8 bug. Here's my workaround:
theScrollView.panGestureRecognizer.delaysTouchesBegan = theScrollView.delaysContentTouches

OK I have resolved by implementing below method :
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
NSLog(#"touchesShouldCancelInContentView");
if ([view isKindOfClass:[UIButton class]])
return NO;
else
return YES;
}

Unable to find a satisfactory solution online so far (and it seems to be that Apple is ignoring the issue). Found a thread on Apple's developer forum with some suggestions in there that may help: UIScrollView: 'delaysContentTouches' ignored
I was able to use the workaround from this link. To summarize the workaround (I'm para-quoting here):
UIEvent objects contain a time stamp.
You can record the time stamp at the time of touchesBegan on your
embedded subview.
In touchesMoved of scrollView's subview, look at the time stamp and
location again.
If the touch has not moved very far and more than, say, 0.1 seconds
have passed, you can assume the user touched the subview and then
delayed movement.
In this case, the UIScrollView will have decided, independently, that
this is NOT a scrolling action even though it will never tell you
that.
So, you can have a local state variable to flag that this condition of
delayed movement occurred and process events received by the subview.
Here's my code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// store the timestamp
_beginDragTimeStamp = event.timestamp;
// your embedded subview's touches begin code
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
// compare and ignore drag if time passed between tap and drag is less than 0.5s
if(event.timestamp - _beginDragTimeStamp < 0.5) return;
// your drag code
}

I had same issue & same hierarchy of the views, With latest sdk , just use it :
Setting delaysContentTouches to NO for UIButton in the same UITableViewCell.
self.scrollview.delaysContentTouches = NO

Create a subclass of the UIScrollView (or UITableView, or UICollectionView, or any other UIScrollView subclass that you use).
Implement the below method:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:UIButton.class]) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
Set this subclass at xib/storyboard as a "Custom Class" class if you use the interface builder.
Unselect Delay Touch Down in a xib or set delaysContentTouches = NO in code.

Related

ios 8 - buttons in horizontal scroll view intercepting pan event - scroll does not work

I have a horizontal scroll view with a line of buttons. The scroll view will not scroll unless I do an extremely fast swipe.
If I set the buttons to userInteractionEnabled = NO, then the scrolling works as expected, but of course, then the buttons don't work at all.
This app worked fine in iOS 7 and before. It seems to be a iOS 8 "feature". I did notice that I can catch the button's drag event, but I don't know how to redirect it back to the scrollView.
I'm thinking I'll need to replace my buttons with UIViews and manage the events myself but I'd be grateful if someone has other ideas or solutions.
I found that in iOS 8, the UIScrollView's underlying UIPanGestureRecognizer is not respecting the UIScrollView's delaysContentTouches property. I consider this an iOS 8 bug. Here's my workaround:
theScrollView.panGestureRecognizer.delaysTouchesBegan = theScrollView.delaysContentTouches
I've had this problem with a UITableView which has custom UITableViewCells with UIButtons on it. I put this in my UITableview class.
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}
Solved my problem.
EDIT: Just to clearify, you can create a subclass of UIScrollview and add this to solve the problem.
This hack works for me:
UITapGestureRecognizer *nothingTap = [[UITapGestureRecognizer alloc] init];
nothingTap.delaysTouchesBegan = YES;
[_scrollView addGestureRecognizer:nothingTap];
credit: https://devforums.apple.com/thread/241467
I made a subclass of UIScrollView to fix this issue. You only need this method in it:
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
UITouch *touch = [touches anyObject];
if(touch.phase == UITouchPhaseMoved)
{
return NO;
}
else
{
return [super touchesShouldBegin:touches withEvent:event inContentView:view];
}
}
Then just remember to set the class to your subclass in the storyboard if you're using one and you're good to go.
I have same issue and I solved it by creating a subclass of UIScrollView and set it's cancelContentTouches value to TRUE and its working fine.
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}
Hope it will work for you.

Swift UIScrollView not working with buttons [duplicate]

I have a horizontal scroll view with a line of buttons. The scroll view will not scroll unless I do an extremely fast swipe.
If I set the buttons to userInteractionEnabled = NO, then the scrolling works as expected, but of course, then the buttons don't work at all.
This app worked fine in iOS 7 and before. It seems to be a iOS 8 "feature". I did notice that I can catch the button's drag event, but I don't know how to redirect it back to the scrollView.
I'm thinking I'll need to replace my buttons with UIViews and manage the events myself but I'd be grateful if someone has other ideas or solutions.
I found that in iOS 8, the UIScrollView's underlying UIPanGestureRecognizer is not respecting the UIScrollView's delaysContentTouches property. I consider this an iOS 8 bug. Here's my workaround:
theScrollView.panGestureRecognizer.delaysTouchesBegan = theScrollView.delaysContentTouches
I've had this problem with a UITableView which has custom UITableViewCells with UIButtons on it. I put this in my UITableview class.
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}
Solved my problem.
EDIT: Just to clearify, you can create a subclass of UIScrollview and add this to solve the problem.
This hack works for me:
UITapGestureRecognizer *nothingTap = [[UITapGestureRecognizer alloc] init];
nothingTap.delaysTouchesBegan = YES;
[_scrollView addGestureRecognizer:nothingTap];
credit: https://devforums.apple.com/thread/241467
I made a subclass of UIScrollView to fix this issue. You only need this method in it:
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
UITouch *touch = [touches anyObject];
if(touch.phase == UITouchPhaseMoved)
{
return NO;
}
else
{
return [super touchesShouldBegin:touches withEvent:event inContentView:view];
}
}
Then just remember to set the class to your subclass in the storyboard if you're using one and you're good to go.
I have same issue and I solved it by creating a subclass of UIScrollView and set it's cancelContentTouches value to TRUE and its working fine.
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}
Hope it will work for you.

Draggable UIView stops posting touchesBegan after being added to UIScrollView

In Xcode 5.1 I have created a simple test app for iPhone:
The structure is: scrollView -> contentView -> imageView -> image 1000 x 1000 on the top.
And on the bottom of the single view app I have seven draggable custom UIViews.
The dragging is implemented in Tile.m with touchesXXXX methods.
My problem is: once I add a draggable tile to the contentView in my ViewController.m file - I can not drag it anymore:
- (void) handleTileMoved:(NSNotification*)notification {
Tile* tile = (Tile*)notification.object;
//return;
if (tile.superview != _scrollView && CGRectIntersectsRect(tile.frame, _scrollView.frame)) {
[tile removeFromSuperview];
[_contentView addSubview:tile];
[_contentView bringSubviewToFront:tile];
}
}
The touchesBegan isn't called for the Tile anymore as if the scrollView would mask that event.
I've searched around and there was a suggestion to extend the UIScrollView class with the following method (in my custom GameBoard.m):
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
NSLog(#"%s: %hhd", __PRETTY_FUNCTION__,
[result.superview isKindOfClass:[Tile class]]);
self.scrollEnabled = ![result.superview isKindOfClass:[Tile class]];
return result;
}
Unfortunately this doesn't help and prints 0 in debugger.
The problem is, partly, because user interactions are disabled on the content view. However, enabling user interactions disables scrolling as the view captures all touches. So here is the solution. Enable user interactions in storyboard, but subclass the content view like so:
#interface LNContentView : UIView
#end
#implementation LNContentView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
return result == self ? nil : result;
}
#end
This way, hit test passes only if the accepting view is not self, the content view.
Here is my commit:
https://github.com/LeoNatan/ios-newbie
The reason Tile views don't get touches is that scroll view's pan gesture recogniser consumes the events. What you need is, attach a UIPanGestureRecongnizer to each of your tiles and configure them as follows:
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)]; // handle drag in pan:method
[tile addGestureRecognizer:pan];
UIPanGestureRecognizer *scrollPan = self.scrollView.panGestureRecognizer;
[scrollPan requireGestureRecognizerToFail:pan];
Here you let scroll view's pan gesture recogniser know that you only wish scrolling to happen if none of the tiles are bing dragged.
I've checked the approach — it does work indeed. Regarding your code, you'll need to handle all touches in the gesture recogniser rather than Tile view because touch events may be consumed/delayed by hit-tested view's gesture recogniser before they reach the view itself. Please refer to UIGestureRecognizer documentation to learn more about the topic.
It looks as ir one of the views in the hierarchy is capturing the events.
Have a look at the section
The Responder Chain Follows a Specific Delivery Path
Of the Apple doc's here
Edit:
Sorry I was writing from memory. This is how i resolved a similar issue in an app of myself:
I use UITapGestureRecognizer in the view(s) that I want to detect the touch. Implement the following delegate method of the UITapGestureRecognizer:
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
The touches' set contains all the objects (views) that received the event.

Ignoring Touch Events of embedded UIButtons when scrolling UICollectionView

I have a custom UICollectionViewCell that has a few custom UIView objects residing inside them. Each of these UIViews has a UIButton which responds to Touch Down and Touch Up Inside linked by IBActions. Basically, I want these buttons to shrink down when pressed down and spring back to their original size when let go. I can easily accomplish this with the controls and the press down and press up works. However, the problem I am facing happens when scrolling is introduced into the mix. The UICollectionView these cells are apart of is a scrolling one. If I happen to touch a button as I start my scroll, the Touch Down event is triggered as well as the scrolling event of the UICollectionView. If I recall correctly, this was never the case pre-iOS7. When a scrolling event was started, the UIButton event wasnt fired off, I think it had to do with the delaysContentTouches. This looks to be broken or changed now. It actually still works decently on iPhone, just not on iPad. If I scroll my view on iPad, with my touch starting inside the embedded UIButton, the button will shrink and the buttons action will be fired off.
So to restate the issue as plainly as I can: Is there anyway to ignore touches on embedded UIButtons when scrolling is occurring? Touches work fine when there is no scrolling triggered, I just dont want the events to fire off if the user is indeed scrolling. Is there any workaround?
If you need any more specific details, I would be happy to help you understand.
you need to subclass scrollView (collectionView or tableView) and override
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:UIButton.class]) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
swift
override func touchesShouldCancelInContentView(view: UIView) -> Bool {
if view is UIButton {
return true
}
return super.touchesShouldCancelInContentView(view)
}
thats it now you can scroll over button and not lose button tap event.
In a UICollectionView of mine, buttons inside of UICollectionViewCells registered TouchUpInside-taps even though the UICollectionView was still decelerating, which sounds like a similar problem to what you're having. I created a UIButton subclass that overrides beginTrackingWithTouch:withEvent and will return NO in case the UIScrollView it's contained in is decelerating or dragging.
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
UIView *superView = self;
while((superView = [superView superview])) {
if ([superView isKindOfClass:UIScrollView.class]) {
UIScrollView *scrollView = (UIScrollView *)superView;
if (scrollView.isDecelerating || scrollView.isDragging) {
return NO;
}
}
}
return [super beginTrackingWithTouch:touch withEvent:event];
}
The easiest thing to try that comes to mind is to check if the UIScrollView (your UICollectionView) is scrolling or dragging when the button action is triggered.
if(! self.collectionView.dragging && ! self.collectionView.decelerating)
{
// do action because we are not moving
}
Have you tried that?

UITableView inside UIScrollView not receiving first tap after scrollling

Brief
I am having an issue with a UITableView inside a UIScrollView. When I scroll the external scrollView, the table does not receive the willSelect/didSelect event on the first touch, but it does on the second one. What is even more strange, the cell itself gets the touches and the highlighted state, even when the delegate does not.
Detailed explanation
My view hierarchy:
UIView
- UIScrollView (outerscroll)
- Some other views and buttons
- UITableView (tableView)
Inside the scroll view I have some extra views that get expanded/closed dynamically. The table view needs to get "fixed" on top, together with some other elements of the view, so that is why I created this layout, that allows me to easily move elements in a similar way than Apple recommends by the use of transformations when the scroll happens.
The table View is transformed with a translation effect when the outerscroll moves like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.outerScrollView) {
CGFloat tableOffset = scrollView.contentOffset.y - self.fixedHeaderFrame.origin.y;
if (tableOffset > 0) {
self.tableView.contentOffset = CGPointMake(0, tableOffset);
self.tableView.transform = CGAffineTransformMakeTranslation(0, tableOffset);
}
else {
self.tableView.contentOffset = CGPointMake(0, 0);
self.tableView.transform = CGAffineTransformIdentity;
}
// Other similar transformations are done here, but not involving the table
}
In my cell, if I implement these methods:
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
NSLog(#"selected");
}
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
NSLog(#"highlighted");
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
NSLog(#"touchesBegan");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
NSLog(#"touchesEnded");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
NSLog(#"touchesCancelled");
}
Y can see this output when fails (first tap):
2014-02-10 13:04:40.940 MyOrderApp[5588:70b] highlighted
2014-02-10 13:04:40.940 MyOrderApp[5588:70b] touchesBegan
2014-02-10 13:04:40.978 MyOrderApp[5588:70b] touchesEnded
And this one when works (second tap):
2014-02-10 13:05:30.359 MyOrderApp[5588:70b] highlighted
2014-02-10 13:05:30.360 MyOrderApp[5588:70b] touchesBegan
2014-02-10 13:05:30.487 MyOrderApp[5588:70b] touchesEnded
2014-02-10 13:05:30.498 MyOrderApp[5588:70b] expanded
No other frame change, animation or any other view interaction is done between the first and the second tap. Also, only when scrolling large amounts the bug appears, but with scrollings of just a few pixels everything keeps working as expected.
I experimented changing some properties as well, but with no luck. Some of the things I did:
Remove userInteractionEnabled from views other than the scroll and table
Add a call to setNeedsLayout on the table, scroll and main view when scrollViewDidScroll occurs.
Remove the transformations from the table (still happens)
I have seen some comments about the unexpected behaviour of embedding UITableViews inside UIScrollViews but I can not see such a warn in the official documentation by Apple, so I am expecting it to work.
The app is iOS7+ only.
Questions
Has anyone experienced similar issues? Why is this and how can I solve it? I think that I could be able to intercept the tap gesture on the cell and pass it with a custom delegate or similar, but I would like the table to receive the proper events and so my UITableViewDelegate receives it as expected.
Updates
I tried disabling cell reuse as suggested in a comment but it still happens in the same way.
leave the inner UITableView's scrollEnabled property set as YES. this lets the inner UITableView know to handle scroll-related touches on the UIScrollView correctly.
From Apple Documentation, you shouldn't embed a UITableViewinside a UIScrollView.
Important: You should not embed UIWebView or UITableView objects in
UIScrollView objects. If you do so, unexpected behavior can result
because touch events for the two objects can be mixed up and wrongly
handled.
Your problem is really related to what your UIScrollView does.
But if it's just to hide the tableview when needed (that was my case), you can just move the UITableView in its superview.
I wrote a small example here : https://github.com/rvirin/SoundCloud/
I ran into this same problem and figured out a solution!!
You need to set the delaysTouchesBegan to true on your scrollview so that the scrollview sends its failed scrolled-gesture (i.e. the tap) to its children.
var delaysTouchesBegan: Bool -
A Boolean value determining whether the receiver delays sending touches in a begin phase to its view.
When the value of the property is YES, the window suspends delivery of
touch objects in the UITouchPhaseBegan phase to the view. If the
gesture recognizer subsequently recognizes its gesture, these touch
objects are discarded. If the gesture recognizer, however, does not
recognize its gesture, the window delivers these objects to the view
in a touchesBegan:withEvent: message (and possibly a follow-up
touchesMoved:withEvent: message to inform it of the touches’ current
locations).
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/index.html#//apple_ref/occ/instp/UIGestureRecognizer/delaysTouchesBegan
But there's a catch...it doesn't work if you do it directly on the scrollview!
// Does NOT work
self.myScrollview.delaysTouchesBegan = true
Apparently this is an iOS bug where setting this property doesn't work (thank you apple). However there's a simple workaround: set the property directly on the scrollview's pan gesture. Sure enough, this worked for me perfectly:
// This works!!
self.myScrollview.panGestureRecognizer.delaysTouchesBegan = true
It seems that your UiTableView doesn't recognize your tap. Did you try to use that :
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer
{
if ([otherGestureRecognizer.view isKindOfClass:[UITableView class]]) {
return YES;
}
return NO;
}
Note from apple:
called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other. return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)
note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
Hope that will help.
Gesture recognizers won't work correctly for two embedded scroll views or subclasses.
Try a workaround:
Use transparent, custom, and overlaying everything in cell UIButton with proper tag, or subclass UIButton and add a index path property and overwrite each time in reused cell.
Add this button as a property to your custom cell.
Add target for desired UIControlEvent (one or more) that points to your UITableViewDelegate protocol adopting class.
Disable selecting in IB, and manually manage the selection from code.
This solution requires attention for cases of single/multi selection.
I've encountered a UITableView with scrollEnabled being NO within a UIScrollView in some legacy code. I have not been able to change the existing hierarchy easily nor enable scrolling, but come up with the the following workaround for the first tap problem:
#interface YourOwnTableView : UITableView
#end
#implementation YourOwnTableView
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
// Note that this is a hack and it can stop working at some point.
// Looks like a table view with scrollEnabled being NO does not handle cancellation cleanly,
// so let's repeat begin/end touch sequence here hoping it'll reset its own internal state properly
// but won't trigger cell selection (the touch passed is in its cancelled phase, perhaps there is a part
// of code inside which actually checks it)
[super touchesBegan:touches withEvent:event];
[super touchesEnded:touches withEvent:event];
}
#end
Again, this is just a workaround working in my specific case. Having a table view within a scroll view is still a wrong thing.
I would recommend to look for options like not letting your cell to be in highlighted state when you are actually scrolling the outer scroll view which is very easy to handle and is the recommended way. You can do this just by taking a boolean and toggling it in the below method
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
The scrollview is trying to figure out whether the user's intention is to scroll or not, so it's delaying the initial touch on purpose. You can turn this off by setting delaysContentTouches to NO.
I have the same problem with nested UITableView and have found a work-around for this:
innerTableView.scrollEnabled = YES;
innerTableView.alwaysBounceVertical = NO;
You'll need to set the height of the inner table view to match with the total height of its cells so that it'll not scroll when user scrolling the outer view.
Hope this helps.
My mistake was implementing the delegate method:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
instead of the one I meant to implement:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Hence only being called on the second cell being tapped, because that was when the first cell would be de selected. Stupid mistake made with the help of autocomplete. Just a thought for those of you who may wander here not realizing you've made the same mistake too.
Drop a UIButton over your UITableViewCell and create the outlet as "btnRowSelect".
In your view controller put this code in cellForRowAtIndexPath
cell.btnRowSelect.tag = indexPath.row
cell.btnRowSelect.addTarget(self, action: Selector("rowSelect:"), forControlEvents: .TouchUpInside)
Add this function to your viewController as well-
func rowSelect (sender:UIButton) {
// "sendet.tag" give you the selected row
// do whatever you want to do in didSelectRowAtIndexPath
}
This function "rowSelect" will work as didSelectRowAtIndexPath where
you get the row"indexPath.row" as "sender.tag"
As other answers say you shouldn't put a tableview in a scrollview. A UITableView inherits from UIScrollView anyway so I guess that's where things get confusing. What I always do in this situation is:
1) Subclass UITableViewController and include a property UIView *headView.
2) In the parent UIViewController create all the top stuff in a container UIView
3) Initialise your custom UITableView and add the tableView's view to the view controller full size
[self.view addSubview: self.myTableView.view];
4) Set the headView to be your UIView gubbins
self.tableView.headView = myHeadViewGubbins.
5) In the tableViewController method
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger *)section;
Do:
if ( section == 0 ) {
return self.headView;
}
Now you have a table view with a bunch of other shizzle at the top.
Enjoy!
That it, if touch table view it will work properly. also with scroll view in same view controller also.
tableview.scrollEnabled = true;
I have the same issue, Then refer to "Nesting Scroll Views" as lxx said.
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html
An example of cross directional scrolling can be found in the Stocks application. The top view is a table view, but the bottom view is a horizontal scroll view configured using paging mode. While two of its three subviews are custom views, the third view (that contains the news articles) is a UITableView (a subclass of UIScrollView) that is a subview of the horizontal scroll view. After you scroll horizontally to the news view, you can then scroll its contents vertically.
It is work

Resources