I have two instances of UIScrollView, and I want them to zoom at the same time.
Anyone have any experience doing that?
I'm using the NSNotificationCenter to tell my object when to zoom. Initially I thought I could somehow get at the currently visible rect, and just call zoomToRect:, but I don't see a way to do that. What I have now is setting the zoomScale and contentOffset properties. It looks like this:
- (void)registerForZoomNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveZoomNotification:)
name:ZOOM_NOTIFICATION_IDENTIFIER
object:nil];
}
- (void)receiveZoomNotification:(NSNotification*)notification {
UIScrollView *currentScrollView = (UIScrollView*)[notification object];
// zoomLevel
[(UIScrollView*)self.view setZoomScale:currentScrollView.zoomScale animated:NO];
// contentOffset
[(UIScrollView*)self.view setContentOffset:currentScrollView.contentOffset animated:NO];
}
#pragma mark -
#pragma mark UIScrollViewDelegate
- (void)scrollViewDidZoom:(UIScrollView *)pageScrollView {
[[NSNotificationCenter defaultCenter] postNotificationName:ZOOM_NOTIFICATION_IDENTIFIER object:pageScrollView];
}
It's not working though, and seems terribly erratic. Ideas anyone? Should I be taking a different approach?
EDIT: I should clarify that both scroll views are not visible at the same time. It's not important that they scroll at the EXACT same time, only that ones scroll view is at the same zoom level (and visible rect) as the other after scrolling completes.
An easier way is to implement the UIScrollViewDelegate Protocol for your UIView control which manage the 2 UIScrollView2
in your .h file just add in the #interface declaration
#interface yourUIViewControll : UIViewControll <UIScrollViewDelegate> {
UIScrollView *aUIScrollView;
UIScrollView *bUIScrollView;
}
this way now you can use all the methods you need when user scroll or zoom one of the 2 UIScrollView
so, for example, you wanna know when one is zooming or scrolling and wanna let the other too zoom and scroll you need these 2 in particular
in .m:
// called when a UIScrollView is zooming:
- (void)scrollViewDidZoom:(UIScrollView *)zoomViewInUse{
// just to test in log window:
// NSLog(#"changing zoom... scrollViewInUse.zoomScale: %.5f", zoomViewInUse.zoomScale);
//force both UIScrollViews to zoom at the new value
aUIScrollView.zoomScale = zoomViewInUse.zoomScale;
bUIScrollView.zoomScale = zoomViewInUse.zoomScale;
}
// called when a UIScrollView is scrolling:
- (void)scrollViewDidScroll:(UIScrollView *)scrollViewInUse{
// just to test in log window:
// NSLog(#"scrollViewInUse..contentOffset.x:%.1f", scrollViewInUse.contentOffset.y);
//force both UIScrollViews to scroll at the new value
aUIScrollView.contentOffset = scrollViewInUse.contentOffset;
bUIScrollView.contentOffset = scrollViewInUse.contentOffset;
}
The erratic behavior I witnessed was because I wasn't sending a notification when I scrolled, only zoomed. When I added the following additional delegate method, everything worked correctly:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[[NSNotificationCenter defaultCenter] postNotificationName:ZOOM_NOTIFICATION_IDENTIFIER object:scrollView];
}
Related
I wanted my UIView to animate to a new position when the keyboard was shown, so used the UIKeyboardWillShowNotification and UIKeyboardWillChangeFrameNotification. The problem is that when the device is rotated without the keyboard, the view has autoresizing and rotates as it should - it looks perfect.
Unfortunately, with the keyboard displayed, rotating the device sends those notifications and thus performing a UIView animation in response gives it an odd animation. It could best be described as looking like it jumps into a new position and is then anchored round by a corner to the new orientation. Perhaps you know what I'm talking about.
Is there any way for me to detect when the device is rotating or otherwise deal with the problem when rotating when the keyboard is being shown?
For orientation-change detection use the UIDeviceOrientationDidChangeNotification.
I guess the following code will help you..
#property(nonatomic,strong)BOOL keyBoardShow;
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeShown:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
[super viewDidLoad];
}
-(void)keyboardWillBeShown:(NSNotification *)aNotification {
keyBoardShow = YES;
}
-(void)keyboardWillBeHidden:(NSNotification *)aNotification {
keyBoardShow = NO;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (keyBoardShow) {
// Do the needful when the keyboard is shown
}else{
}
}
Just set bool value in keyboardWillBeShown and keyboardWillBeHidden delegate methods. And do your uiview position in willRotateToInterfaceOrientation.
Instead of relying on the built-in notifications, why don't you instead rely on what's triggering the keyboard showing in the first place? I'm guessing the keyboard pops up when a particular UITextField becomes the first responder. You should be able to use that.
Register a delegate for that UITextField and implement -textFieldDidBeginEditing:. The delegate should be a view controller. Then, to keep things loosely coupled, post your own notification from the delegate and have your view registered for that.
It's a bit more work, but it gives you much better control.
- (void)textFieldDidBeginEditing:(UITextField *)textField
Apple documentation: UITextFieldDelegate Protocol
I am using a UIWebView to show a PDF. I need to track some UIScrollView events, so I set the built-in UIScrollView like this:
for(UIScrollView *s in webView.subviews) {
s.delegate = self;
}
But the problem is that when I do this, I lose the ability to pinch to zoom (like I could before because the UIWebView's Scale Pages to Fit property was set). How come this happens? What can I do to fix it? I went ahead and implemented the UIScrollView shouldZoom method, but when I pass back the scrollView as the view to zoom on, it zooms from the corner, not where my fingers are.
Does anybody know a solution to this problem? How can I have the UIScrollView delegate set, and still retain natural zooming ability?
Thanks!
You are messing with some UIWebView internals. This feels like a bad hack, but I think you could forward all the UIScrollViewDelegate methods back to UIWebView in addition to doing whatever you need to do.
Also, UIWebView has many subviews. You should check and override the delegate just for the UIScrollView.
for (UIView *subview in webViews.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
subview.delegate = self;
}
}
Either handle the following events in your delegate:
– viewForZoomingInScrollView:
– scrollViewWillBeginZooming:withView:
– scrollViewDidEndZooming:withView:atScale:
– scrollViewDidZoom:
or save the original delegate
origDelegate = s.delegate;
s.delegate = self;
and forward the calls, e.g:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return [origDelegate viewForZoomingInScrollView:scrollView];
}
I'm using UIScrollView's canceling touch ability with canCancelContentTouches.
However, I 'd like the uiscrollview to attempt to cancel touch when it detected horizontal dragging(not vertical).
(Hope solution would be available under < iOS 3.13)
Thank you
Implement the UIScrollViewDelegate and then use something like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[scrollView setContentOffset: CGPointMake(0, scrollView.contentOffset.y)];
}
Another way would be having a UIScrollView which is smaller or equal to the size of its parent view and with a disabled "Always bounce horizontal".
The safest and most successful method I've found to constrain the movement of a scroll view is to subclass UIScrollView and override setContentOffset:animated: and setContentOffset: methods (code below).
The advantage of overriding these methods is that it directly alters the requested contentOffset before any of the UIKit code starts to act on it, avoiding any of the side effects that can occur when modifying the contentOffset in scrollViewDidScroll: or other UIScrollViewDelegate methods.
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
// restrict movement to horizontal only
CGPoint newOffset = CGPointMake(contentOffset.x, 0);
[super setContentOffset:newOffset animated:animated];
}
- (void)setContentOffset:(CGPoint)contentOffset {
// restrict movement to horizontal only
CGPoint newOffset = CGPointMake(contentOffset.x, 0);
[super setContentOffset:newOffset];
}
i guess i would use the method scrollViewWillBeginDragging
found in the UIScrollViewDelegate
and inside i could control if user is going horizontally or not...
scrollViewWillBeginDragging:
Tells the delegate when the scroll view is about to start scrolling the content.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
Parameters
scrollView
The scroll-view object that is about to scroll the content view.
Discussion
The delegate might not receive this message until dragging has occurred over a small distance.
Availability
Available in iOS 2.0 and later.
I am looking for a way to create a scrollViewIsZooming method that is called while zooming is occuring. Does anyone know of a way to do this?
I am wanting to use it to keep the content centered in the scrollView while zooming. If I use the scrollViewDidEndZooming method, the content snaps back to the center after zooming is finished.
Thanks!
There's a zooming propriety on UIScrollView :
#property(nonatomic, readonly, getter=isZooming) BOOL zooming
If you check it in each
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
call it should work.
What about viewForZoomingInScrollView:
If you put a pinch gesture recogniser in the view then connect it to an action you can control zooming that way. I have an action "doPinch" and an outlet "pinchRecognizer" that I use like this:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.zoomableImage;
}
Then
- (IBAction)doPinch:(id)sender
{
NSLog(#"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}
Might be easier. Note that scrollView is my outlet connected to the scroll view.
I have a UITextView included in a UITableViewCell. The layout is correct when the views are initially displayed, but once I click in the UITextView it automatically scrolls up a bit and the top half of the characters on the first line becomes invisible.
This image is when the UITextView is not active:
UITextView not active http://gerodt.homeip.net/uitextview-notactive.png
And this one is when I clicked in the UITextView to make it active:
UITextView active http://gerodt.homeip.net/uitextview-active.png
I do not the UITextView to scroll up at all, it should simple stay fixed. How can I achieve this? I already tried several settings in Interface Builder, but no luck so far.
Any suggestions are appreciated.
Gero
UITextView is a subclass of UIScrollView, so it has a configurable contentInset property. Unfortunately, if you try to change contentInset on a UITextView instance, the bottom edge inset always gets reset to 32. I've run into this before with short UITextView frames and found this to be an issue. I suspect this is what is causing your problem, but you should check the contentInset of your textview in the debugger to be sure.
The workaround/solution is simple: subclass UITextView and override the contentInset method so that it always returns UIEdgeInsetZero. Try this:
//
// BCTextView
//
// UITextView seems to automatically be resetting the contentInset
// bottom margin to 32.0f, causing strange scroll behavior in our small
// textView. Maybe there is a setting for this, but it seems like odd behavior.
// override contentInset to always be zero.
//
#interface BCZeroEdgeTextView : UITextView
#end
#implementation BCZeroEdgeTextView
- (UIEdgeInsets) contentInset { return UIEdgeInsetsZero; }
#end
This is how UITextView behaves according to Apple's engineer this is intended and UITextView is meant for text that are at least a few lines in height. There is no work around to this, use a UITextField instead or increase your UITextView to at least 3 lines in height.
You can also just do:
textView.contentInset=UIEdgeInsetsZero;
in your delegate file.
UITextView is a subclass of UIScrollView, so the answer involves the contentOffset property, which is what is being changed, not the insets or the content size. If the scroll position is correct when the view first appears, then you can store the content offset for later recall.
YourViewController.h snipped
#interface YourViewController : UIViewController <UITextViewDelegate, UIScrollViewDelegate>
#property(nonatomic, weak) IBOutlet UITextView *textView;
#end
YourViewController.m snippet
#implementation YourViewController {
#private
BOOL _freezeScrolling;
CGFloat _lastContentOffsetY;
}
// UITextViewDelegate
- (void)textViewDidBeginEditing:(UITextView *)textView {
// tell the view to hold the scrolling
_freezeScrolling = YES;
_lastContentOffsetY = self.textView.contentOffset.y;
}
// UITextViewDelegate
- (void)textViewDidEndEditing:(UITextView *)textView {
_freezeScrolling = NO;
}
// UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_freezeScrolling) {
// prevent the scroll view from actually scrolling when we don't want it to
[self repositionScrollView:scrollView newOffset:CGPointMake(scrollView.contentOffset.x, _lastContentOffsetY)];
}
}
// UIScrollViewDelegate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// scroll prevention should only be a given scroll event and turned back off afterward
_freezeScrolling = NO;
}
// UIScrollViewDelegate
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
// when the layout is redrawn, scrolling animates. this ensures that we are freeing the view to scroll
_freezeScrolling = NO;
}
/**
This method allows for changing of the content offset for a UIScrollView without triggering the scrollViewDidScroll: delegate method.
*/
- (void)repositionScrollView:(UIScrollView *)scrollView newOffset:(CGPoint)offset {
CGRect scrollBounds = scrollView.bounds;
scrollBounds.origin = offset;
scrollView.bounds = scrollBounds;
}
What's also important to note in the code sample above is the last method. Calling any sort of setContentOffset: will actually trigger scrolling, which results in calling scrollViewDidScroll:. So calling setContentOffset: results in an infinite loop. Setting the scroll bounds is the workaround for this.
In a nutshell, we tell the view controller to prevent the UITextView from scrolling when we detect that the user has selected the text for editing. We also store the current content offset (since we know that the position is what we want). If the UITextView tries to scroll, then we hold the content offset in place until the scroll has stopped (which triggers either scrollViewDidEndDecelerating: or scrollViewDidEndScrollingAnimation:). We also unfreeze the scrolling when the user is done editing.
Remember, this is a basic example, so you'll need to tweak the code based on the exact behavior you want.
I was experiencing a similar issue with undesired UITextView scrolling. I finally managed to fix it by resetting the contentSize at the end of my keyboardDidShow:
- (void)keyboardDidShow:(NSNotification *)notification {
textView.contentSize = CGSizeZero;
}
You also will need to register for the keyboard notification, like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
In my case I didn't want any scrolling since I was resetting the frame to the height of the textView's contentSize when textViewDidChange (growing textview inside a UIScrollView).
Try putting in Redraw on the textview instead of Scale to Fill. You still might have to capture the delegate and keep the content offset but it should at least prevent the jump to point (0,0). Also Autoresizes subview must be turned off. It was jumping to top of textview every time on me too and this solved that problem.