UIScrollView Delegate methods clashing - iphone

I am using the following two UIScrollView delegate methods to call another method in my UIViewController:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// do something
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
// do something
}
I've tried various different ways of calling //do something but either end up with both not being called, or both delegate methods being called, calling //do something twice in certain situations. For example:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if(![scrollView isDecelerating] && ![scrollView isDragging]){
//do something
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if(!decelerate){
//do something
}
}
So with the above if I scroll and let it slow to a stop, it calls scrollViewDidEndDecelerating:, but if I scroll and stop it with a tap it calls both scrollViewDidEndDragging: and scrollViewDidEndDecelerating:
I want it to call one or the other... is there something I can do with scrollViewDidEndDecelerating: and the scrollView object to stop this double method call?

The scroll view delegate does its job. You can not prevent it from happening. But with a simple logic you can achieve what you are trying to do.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
dragged = YES;
// do something
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (!dragged) {
// do something
}
dragged = NO;
}

// very simple
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if(!decelerate){
// Do something
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// Do something
}

This seems to work. Close to EmptyStack's answer
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (!dragged) {
//do something
}
dragged = NO;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if(!decelerate){
dragged = YES;
//do something
} else {
dragged = NO;
}
}

Related

why UIScrollView doesnt call it's Delegate?

the problem is I make a new UIScrollView at Xib and then link the delegate to owner..
some how the delegate doesnt called.. any clue? or simple example for UIScrollView
sorry I am a new ios developer and thank in advance..
the code :
at header
#interface stageViewController : UIViewController<UIScrollViewDelegate>
at .m
#pragma mark drag delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(#"scrollViewDidScroll");
} // any offset changes
- (void)scrollViewDidZoom:(UIScrollView *)scrollView{
NSLog(#"scrollViewDidZoom");
}
// called on start of dragging (may require some time and or distance to move)
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
NSLog(#"scrollViewWillBeginDragging");
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
NSLog(#"scrollViewWillEndDragging");
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
NSLog(#"scrollViewWillBeginDecelerating");
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSLog(#"scrollViewDidEndDecelerating");
}
Write scrollView.delegate = self; If you have made it pragmatically.
And if you have your ScrollView in XIB then ctrl+drag from your scrollView to your File's Owner and then set its delegate.

In UIScrollview restrict a particular delegate from the uiscrollview delegate list

From the list of available UIScrollview delegate list . I want to restrict a selective uiscrollview delegate function namely:
(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
I want to restrict it from calling by the compiler . can i accomplish this . Any suggestions & help are appreciated!!
As mentioned previously you can assign a tag to each of your UIScrollViews (or just the one you need), and then check for that specific tag, or you can compare the UIScrollView in your delegate methods to the one you are interested in.
With tags.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
if(scrollView.tag == 100){
//do something here
return imgView;
}
return nil;
}
Comparing scrollViews.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
if([myScrollView isEqual:scrollView]){
//do something here
return imgView;
}
return nil;
}

Two UIScrollViews, synchronous scrolling

I heve two UIScrollViews, they are on top of each other.
UIView
|
--------------------------
| |
UIScrollView1 UIScrollView2
I would like it, to work in the following way. If I scroll UIScrollView2, UIScrollView1 should also scroll by the same contentOffset. It must by done synchronously, so using scrollViewDidScroll is not an option. Do you guys have some idea, how can it be done?
Source Code
_prContentGridView = [[PRContentGridView alloc] initWithFrame:frame];
_prContentGridView.minimumZoomScale = 0.25;
_prContentGridView.maximumZoomScale = 2.0;
_prContentGridView.delegate = self;
_prBackgroundGridView = [[PRBackgroundGridView alloc] initWithFrame:frame];
[self addSubview:_prBackgroundGridView];
[self addSubview:_prContentGridView];
Delegate Method
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (_prContentGridView.scrollEnabled == YES) {
CGPoint p = CGPointMake(scrollView.contentOffset.x - _prevousContentOffsetOfContentScrollView.x, scrollView.contentOffset.y - _prevousContentOffsetOfContentScrollView.y);
[_prBackgroundGridView setContentOffset:p animated:YES];
}
}
use the UIScrollViewDelegate Protocol method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView == UIScrollView1){
UIScrollView2.contentOffset = scrollView.contentOffset;
}else{
UIScrollView1.contentOffset = scrollView.contentOffset;
}
}
You should try this Code,
first Declare IBOutlet in .h File,
IBOutlet UIScrollView *FirstScrollView;
IBOutlet UIScrollView *SecondScrollView;
then try this code,
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ([scrollView isEqual: FirstScrollView])
{
SecondScrollView.contentOffset =
CGPointMake(FirstScrollView.contentOffset.x, 0);
}
else
{
FirstScrollView.contentOffset =
CGPointMake(SecondScrollView.contentOffset.x, 0);
}
}

UITableView Scroll event

I want to detect if mytable view has been scrolled, I tried all touch events like this one:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
//my code
}
but it seems that all touch events don't response to scroll but they response only when cells are touched, moved,...etc
Is there a way to detect scroll event of UITableView ?
If you implement the UITableViewDelegate protocol, you can also implement one of the UIScrollViewDelegate methods:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
or
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
For example, if you have a property called tableView:
// ... setting up the table view here ...
self.tableView.delegate = self;
// ...
// Somewhere in your implementation file:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(#"Will begin dragging");
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"Did Scroll");
}
This is because UITableViewDelegate conforms to UIScrollViewDelegate, as can be seen in the documentation or in the header file.
If you have more than one table views as asked by Solidus, you can cast the scrollview from the callback to tableview as UITableView is derived from UIScrollView and then compare with the tableviews to find the source tableview.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
UITableView* fromTableView = (UITableView*) scrollView;
UITableView* targetTableView = nil;
if (fromTableView == self.leftTable) {
targetTableView = self.leftTable;
} else {
targetTableView = self.rightTable;
}
...
}
These are the methods from UITableViewDelegate using Swift to detect when an UITableView will scroll or did scroll:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
}

How can I loop through all subviews of a UIView, and their subviews and their subviews

How can I loop through all subviews of a UIView, and their subviews and their subviews?
Use recursion:
// UIView+HierarchyLogging.h
#interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy;
#end
// UIView+HierarchyLogging.m
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy
{
NSLog(#"%#", self);
for (UIView *subview in self.subviews)
{
[subview logViewHierarchy];
}
}
#end
// In your implementation
[myView logViewHierarchy];
Well here is my solution using recursion and a wrapper(category/extension) for the UIView class.
// UIView+viewRecursion.h
#interface UIView (viewRecursion)
- (NSMutableArray*) allSubViews;
#end
// UIView+viewRecursion.m
#implementation UIView (viewRecursion)
- (NSMutableArray*)allSubViews
{
NSMutableArray *arr=[[[NSMutableArray alloc] init] autorelease];
[arr addObject:self];
for (UIView *subview in self.subviews)
{
[arr addObjectsFromArray:(NSArray*)[subview allSubViews]];
}
return arr;
}
#end
Usage : Now you should be looping through all the sub views and manipulate them as needed.
//disable all text fields
for(UIView *v in [self.view allSubViews])
{
if([v isKindOfClass:[UITextField class]])
{
((UITextField*)v).enabled=NO;
}
}
Here's another Swift implementation:
extension UIView {
var allSubviews: [UIView] {
return self.subviews.flatMap { [$0] + $0.allSubviews }
}
}
A solution in Swift 3 that gives all subviews without including the view itself:
extension UIView {
var allSubViews : [UIView] {
var array = [self.subviews].flatMap {$0}
array.forEach { array.append(contentsOf: $0.allSubViews) }
return array
}
}
I tag everything when it's created. Then it's easy to find any subview.
view = [aView viewWithTag:tag];
Just found an interesting way to do this through the debugger:
http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/
references this Apple Technote:
https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT
Just make sure your debugger is paused (either set a break point of pause it manually) and you can ask for the recursiveDescription.
Here is an example with actual view looping and breaking functionality.
Swift:
extension UIView {
func loopViewHierarchy(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
var stop = false
block(self, &stop)
if !stop {
self.subviews.forEach { $0.loopViewHierarchy(block: block) }
}
}
}
Call example:
mainView.loopViewHierarchy { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Reversed looping:
extension UIView {
func loopViewHierarchyReversed(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
for i in stride(from: self.highestViewLevel(view: self), through: 1, by: -1) {
let stop = self.loopView(view: self, level: i, block: block)
if stop {
break
}
}
}
private func loopView(view: UIView, level: Int, block: (_ view: UIView, _ stop: inout Bool) -> ()) -> Bool {
if level == 1 {
var stop = false
block(view, &stop)
return stop
} else if level > 1 {
for subview in view.subviews.reversed() {
let stop = self.loopView(view: subview, level: level - 1, block: block)
if stop {
return stop
}
}
}
return false
}
private func highestViewLevel(view: UIView) -> Int {
var highestLevelForView = 0
for subview in view.subviews.reversed() {
let highestLevelForSubview = self.highestViewLevel(view: subview)
highestLevelForView = max(highestLevelForView, highestLevelForSubview)
}
return highestLevelForView + 1
}
}
Call example:
mainView.loopViewHierarchyReversed { (view, stop) in
if view is UIButton {
/// use the view
stop = true
}
}
Objective-C:
typedef void(^ViewBlock)(UIView* view, BOOL* stop);
#interface UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block;
#end
#implementation UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block {
BOOL stop = NO;
if (block) {
block(self, &stop);
}
if (!stop) {
for (UIView* subview in self.subviews) {
[subview loopViewHierarchy:block];
}
}
}
#end
Call example:
[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
if ([view isKindOfClass:[UIButton class]]) {
/// use the view
*stop = YES;
}
}];
With the help of Ole Begemann. I added a few lines to incorporate the block concept into it.
UIView+HierarchyLogging.h
typedef void (^ViewActionBlock_t)(UIView *);
#interface UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction;
#end
UIView+HierarchyLogging.m
#implementation UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction {
//view action block - freedom to the caller
viewAction(self);
for (UIView *subview in self.subviews) {
[subview logViewHierarchy:viewAction];
}
}
#end
Using the HierarchyLogging category in your ViewController. You are now have freedom to what you need to do.
void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
if ([view isKindOfClass:[UIButton class]]) {
NSLog(#"%#", view);
}
};
[self.view logViewHierarchy: ViewActionBlock];
Need not create any new function. Just do it when debugging with Xcode.
Set a breakpoint in a view controller, and make the app pause at this breakpoint.
Right click the empty area and press "Add Expression..." in Xcode's Watch window.
Input this line:
(NSString*)[self->_view recursiveDescription]
If the value is too long, right click it and choose "Print Description of ...". You will see all subviews of self.view in the console window. Change self->_view to something else if you don't want to see subviews of self.view.
Done! No gdb!
Here is a recursive code:-
for (UIView *subViews in yourView.subviews) {
[self removSubviews:subViews];
}
-(void)removSubviews:(UIView *)subView
{
if (subView.subviews.count>0) {
for (UIView *subViews in subView.subviews) {
[self removSubviews:subViews];
}
}
else
{
NSLog(#"%i",subView.subviews.count);
[subView removeFromSuperview];
}
}
By the way, I made an open source project to help with this sort of task. It's really easy, and uses Objective-C 2.0 blocks to execute code on all views in a hierarchy.
https://github.com/egold/UIViewRecursion
Example:
-(void)makeAllSubviewsGreen
{
[self.view runBlockOnAllSubviews:^(UIView *view) {
view.backgroundColor = [UIColor greenColor];
}];
}
Here is a variation on Ole Begemann's answer above, which adds indentation to illustrate the hierarchy:
// UIView+HierarchyLogging.h
#interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces;
#end
// UIView+HierarchyLogging.m
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces {
if (whiteSpaces == nil) {
whiteSpaces = [NSString string];
}
NSLog(#"%#%#", whiteSpaces, self);
NSString *adjustedWhiteSpaces = [whiteSpaces stringByAppendingFormat:#" "];
for (UIView *subview in self.subviews) {
[subview logViewHierarchy:adjustedWhiteSpaces];
}
}
#end
The code posted in this answer traverses all windows and all views and all of their subviews. It was used to dump a printout of the view hierarchy to NSLog but you can use it as a basis for any traversal of the view hierarchy. It uses a recursive C function to traverse the view tree.
I wrote a category some time back to debug some views.
IIRC, the posted code is the one that worked. If not, it will point you in the right direction. Use at own risk, etc.
This displays the hierarchy level as well
#implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(int)level
{
NSLog(#"%d - %#", level, self);
for (UIView *subview in self.subviews)
{
[subview logViewHierarchy:(level+1)];
}
}
#end
Wish I'd found this page first, but if (for some reason) you want to do this non-recursively, not in a Category, and with more lines of code
I think all of the answers using recursion (except for the debugger option) used categories. If you don't need/want a category, you can just use a instance method. For instance, if you need to get an array of all labels in your view hierarchy, you could do this.
#interface MyViewController ()
#property (nonatomic, retain) NSMutableArray* labelsArray;
#end
#implementation MyViewController
- (void)recursiveFindLabelsInView:(UIView*)inView
{
for (UIView *view in inView.subviews)
{
if([view isKindOfClass:[UILabel class]])
[self.labelsArray addObject: view];
else
[self recursiveFindLabelsInView:view];
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.labelsArray = [[NSMutableArray alloc] init];
[self recursiveFindLabelsInView:self.view];
for (UILabel *lbl in self.labelsArray)
{
//Do something with labels
}
}
The method below creates one or more mutable arrays, then loops through the subviews of the input view. In doing so it adds the initial subview then queries as to whether there are any subviews of that subview. If true, it calls itself again. It does so until the all the views of the hierarchy have been added.
-(NSArray *)allSubs:(UIView *)view {
NSMutableArray * ma = [NSMutableArray new];
for (UIView * sub in view.subviews){
[ma addObject:sub];
if (sub.subviews){
[ma addObjectsFromArray:[self allSubs:sub]];
}
}
return ma;
}
Call using:
NSArray * subviews = [self allSubs:someView];