resignFirstResponder when UITextField on UIScrollView is not seen - ios

I have a UITextField on which I call becomeFirstResponder when I load the ViewController. On it, I have a UIScrollView and I would like to call resignFirstResponder on the textField when I have scrolled it out of my view.
The position of UITextField may change, but the behaviour should stay the same. Any ideas on how do you implement this sort of thing? Thanks.

I would look at observing changes to your scrollView's contentOffset property. With knowledge of scrollView's bounds and contentOffset, and textField's frame you could work out if textField is visible. Something like the following should work:
CGRect scrollViewVisibleBounds = CGRectOffset(scrollView.bounds, scrollView.contentsOffset.x, scrollView.contentsOffset.y);
BOOL textFieldIsVisible = CGRectIntersectsRect(textField.frame, scrollViewVisibleBounds);
The problem you have now is how to observe changes to contentOffset. It's not really feasible to be constantly observing (i.e. through KVO), as you'll receive multiple updates per second during, for example, a deceleration animation. I'd look at setting your viewController up as a UIScrollViewDelegate and updating your textField's visibility via callbacks like scrollViewDidEndDragging: and scrollViewDidEndDecelerating.

Related

Track UIScrollView contentOffset all the time

I'm using a timer and print the contentOffset.y of a UIScrollView to track it all the time.
But the problems is UIScrollView updates its contentOffset value only after it is decelerated.
I want to know the contentOffset.y value in every millisecond of timer and how can I do that? If I cannot use contentOffset of the UIScrollView at all, is there any other way to track similar value to contentOffset.y of a scrollView?
I'm using swift. Thank you.
Instead of tracking the changes by polling with a timer, you can observe any changes to the value. UIScrollView has a delegate property that gets called for scroll events (see UIScrollViewDelegate), and contentOffset is KVO compliant.
Or you could subclass UIScrollView and try overriding the setters (either for contentOffset or bounds where origin is the the offset, I'm not sure which one, if either, the class calls internally - using the delegate is the preferred solution).

How to call scrollViewDidScroll: the same way UIScrollView does, but during custom animation?

I have a very large horizontally scrolling UIScrollView which is reusing its subviews (moves and updates them when they are out of visible area, similar like UITableView is reusing cells). This relies on scrollViewDidScroll: delegate call, which gives me actual contentOffset, and here I decide when to reuse particular subview. So far so good.
Sometimes I need to change contentOffset programatically, but with custom animation (inertia and bounce back to the final position). I can do this quite easily using core animation.
The problem is, that during custom animation scrollViewDidScroll: delegate method is not called -> I must do it manually so that subviews reusing works. I tried to call it with timer firing each 0.02 sec. Now there are two problems:
I must get UIScrollView contentOffset using [[_scrollView.layer presentationLayer] bounds].origin.x because during animation normal _scrollView.contentOffset does not change.
However info from presentationLayer is not sufficient for exact syncing - sometimes it is bit late.
problem is when the new contentOffset is far from current position. It looks like built-in UIScrollView animation is CAKeyframeAnimation, and scrollViewDidScroll should is called on key frames positions. But I am not able to get these.
If I rely on timer which is not synced with key frames, views are reused on wrong places and I can not see them at all during animation.
Can anyone shed some light on how and when exactly UIScrollView calls scrollViewDidScroll during setContentOffset:X animated:YES? Is it possible to reproduce it without breaking appstore rules?
First of all, I wouldn't use an NSTimer with a delay of 0.02 secs - that's not what timers are supposed for. Try using a CADisplayLink, it fires once per frame.
In your callback method you can - if your custom animation is running - run your own physics code and call -setContentOffset:animated: respectively. This even allows you to ease exponentially which CA won't let you.

UIScrollView: difference between setContentOffset:animated and scrollRectToVisible:animated

I have read Apple Scroll View Programming Guide for iOS but still confused about the following part:
Scrolling to a Specific Offset
Scrolling to a specific top-left location (the contentOffset property)
can be accomplished in two ways. The setContentOffset:animated: method
scrolls the content to the specified content offset. If the animated
parameter is YES, the scrolling will animate from the current position
to the specified position at a constant rate. If the animated
parameter is NO, the scrolling is immediate and no animation takes
place. In both cases, the delegate is sent a scrollViewDidScroll:
message. If animation is disabled, or if you set the content offset by
setting the contentOffset property directly, the delegate receives a
single scrollViewDidScroll: message. If animation is enabled, then the
delegate receives a series of scrollViewDidScroll: messages as the
animation is in progress. When the animation is complete, the delegate
receives a scrollViewDidEndScrollingAnimation: message.
Making a rectangle visible
It is also possible to scroll a rectangular area so that it is
visible. This is especially useful when an application needs to
display a control that is currently outside the visible area into the
visible view. The scrollRectToVisible:animated: method scrolls the
specified rectangle so that it is just visible inside the scroll view.
If the animated parameter is YES, the rectangle is scrolled into view
at a constant pace. As with setContentOffset:animated:, if animation
is disabled, the delegate is sent a single scrollViewDidScroll:
message. If animation is enabled, the delegate is sent a series of
scrollViewDidScroll: messages as animation progresses. In the case of
scrollRectToVisible:animated: the scroll view’s tracking and dragging
properties are also NO.
If animation is enabled for scrollRectToVisible:animated:, the
delegate receives a scrollViewDidEndScrollingAnimation: message,
providing notification that the scroll view has arrived at the
specified location and animation is complete.
I think it is quite similar between setContentOffset:animated and scrollRectToVisible:animated, could someone give some hints about them?
And scrollRectToVisible:animated: is similar to scrollViewDidEndDecelerating:animated: too.
Short answer:
setContentOffset:animated preserves the zoomscale.
scrollRectToVisible:animated may change it.

Animate while UIScrollView bounces

I want to do a little animation every time the UIScrollView bounces, to be exact, I want a button to move a little bit to the left and then back to it's original position. I use the scrollViewDidScroll method and check if the scrollView's contentOffset is higher than the actual content height and then call the animation. The problem is that the animation is called multiple times during the bounce if I do it like that. Is there a way to only call it once?
Have an ivar or property named something like animationActive that will keep track of animation. When you start it, set animationActive = YES; and when animation completes, set animationActive = NO;. And of course, before you start animation, check if (animationActive == YES).

how to forward UIWebView scroll to UIScrollView ? (conditionally)

I have a UIWebView inside UIScrollView.
This idea is to be able to create more reading space on screen when user scroll the webpage upwards - by scrolling the UIScrollView upwards till the toolbar is visible, and obviously when the toolbars is nomore visible actually scroll the webpage to show more content that's on the page.
IPhone Safari browser does exactly the same functionality.
see screenshot (first) for default behavior i am getting - i guess because : the scrolling message is consumed by the webview since the touch/scroll is happening directly on webview area.
what i would like to do here is programatiicaly decide when to forward the 'upward scroll' to the UIScrollivew and when not to.
Any tips on how to get around this will be so helpful. Thanks!!
The UIWebView class reference discourages embedding a UIWebView in 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.
However I suppose you still want to implement your feature ;-)
Idea 1: Don't embed the UIWebView in a UIScrollView, instead run javascript using UIWebView's stringByEvaluatingJavaScriptFromString: method, modifying the DOM to add a toolbar below. If required, you can callback objective-c code from any buttons pushed on the toolbar by registering some custom URL schemes.
Idea 2: Don't embed the UIWebView in a UIScrollView, but in a normal UIView. Building on Vignesh's suggestion, listen for your webView's inner scrollView's scrollViewDidScroll: callback via the delegate, checking the contentOffset vs. the contentSize's height each time the callback is called. Once they are equal, it means you got to the bottom. Once this happens you can animate your toolbar's frame to "enter" the containing UIView and "push" the webView's frame away.
Idea 3: Ignore Apple's recommendation, and embed the UIWebView in a UIScrollView. Building on Vignesh's suggestion, listen for your webView's inner scrollView's scrollViewDidScroll: callback via the delegate, checking the contentOffset vs. the contentSize's height each time the callback is called. Once they are equal, it means you got to the bottom. Once this happens set the userInteractionEnabled property of the webView to NO, and set it to YES on the scrollView which contains the webView and the toolbar. Hopefully the scroll will continue smoothly enough. Of course you have to listen to the containing scroll view in the same way to determine when to switch back the userInteractionEnabled.
A variation on this idea would be to just set userInteractionEnabled to NO for the webView, but set the webView's frame's height to match its contentSize, and also enlarge the contentSize of the containing scrollView accordingly.
Both variations have the drawback that in some cases you won't be able to do things such as click on links :-( But maybe that's good enough for your case. At least in the first variation it's not so bad.
You can add a searchbox and uiwebview in a UIscrollview one below another. To get the content offset when webview is scrolled you can use the following code snippet.
UIScrollView* currentScrollView;
for (UIView* subView in testWebView.subviews) {
if ([subView isKindOfClass:[UIScrollView class]]) {
currentScrollView = (UIScrollView*)subView;
currentScrollView.delegate = (id)self;
}
}
Now you can change your base scrollview offset's y value to the offset value you get from the scrollview didscroll delegate method.
Advanced ScrollView Techniques
https://developer.apple.com/videos/wwdc/2011/

Resources