I'm using a UIScrollerView and I found that it tends to "snap" back to a previous position when I scroll it.
I would like to simulate the behavior if the UItableView's scroller, that stays in the position where the user released his finger.
The properties set:
[scroller setCanCancelContentTouches:NO];
scroller.clipsToBounds = YES;
[scroller setScrollEnabled:YES];
scroller.userInteractionEnabled = YES;
scroller.alwaysBounceHorizontal = YES;
scroller.alwaysBounceVertical= NO;
scroller.showsVerticalScrollIndicator = NO;
scroller.scrollsToTop = NO;
scroller.bounces = NO;
scroller.pagingEnabled = NO;
There a few possible reasons for the snap back.
One could be that you have pagingEnabled set to YES which means that when the user lets go their finger, it will snap to the closest page boundary.
Another is that you have the contentSize set incorrectly. This is not the problem if you can actually scroll to the top and bottom and leave it there without it bouncing away.
- (void)scrollViewDidScroll:(UIScrollView *)sender {
int scrollDirection;
if (lastContentOffset > self.scrollView.contentOffset.x){
scrollDirection = RIGHT;
NSLog(#"right");
}
else if (lastContentOffset < self.scrollView.contentOffset.x) {
scrollDirection = LEFT;
NSLog(#"left");
lastContentOffset = self.scrollView.contentOffset.x;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
lastContentOffset = self.scrollView.contentOffset.x;
}
Related
How would I go about disabling a UIButton if the UIScrollView has scrolled more than a certain amount?
this is what I've been trying. Perhaps it's the wrong scrollViewDidScroll: delegate method.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_scrollView.contentOffset.y >= 100) {
mapLaunchButton.enabled = NO;
}
}
thanks for any help
Simple! You'll need to create a variable to store the starting position of the scroll view though. It should be a CGPoint. Set it to the scroll view's content offset in scrollViewWillBeginDragging: (where the scroll view starts moving) and then do comparison in scrollViewDidScroll similarly to how you were doing it before.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
startingPoint = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y >= startingPoint.y + 100.0f) {
mapLaunchButton.enabled = NO;
}
}
Keep in mind you may need to modify the values I've provided slightly depending on the starting position of the scroll view, and the direction in which you'd like to monitor the changes.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_scrollView.contentOffset.y >= 100) {
mapLaunchButton.enabled = NO;
}
else {
mapLaunchButton.enabled = YES;
}
}
The code is OK, but you have to add the delegate for the scrollView
- (void)viewDidLoad {
[super viewDidLoad];
// do whatever
...
// Add the delegate for the scrollview
[_scrollView setDelegate:self];
}
I want to create UIScrollView with scrolling buttons.So when user press left arrow button, scroll must scroll properly.
The issue is: when I click button 3 times quickly scroll can't scroll properly (because of many calls of scrollRectToVisible). May be I can stop current animation before next animation?
P.S. If I set [self scrollScrollViewToIndex:index animated:NO] everything works properly, but I need animation
Here is my code:
- (void)scrollScrollViewToIndex:(int)index animated:(BOOL)animated
{
NSLog(#"scrolled to index: %d", index);
CGFloat offsetX = CGRectGetWidth(_scrollMain.frame) * index;
CGRect scrollRect = CGRectMake(offsetX, 0, CGRectGetWidth(_scrollMain.frame), CGRectGetHeight(_scrollMain.frame));
[_scrollMain scrollRectToVisible:scrollRect animated:animated];
// [self.scrollMain setContentOffset:CGPointMake(offsetX, 0) animated:animated];
}
- (IBAction)leftArrowPressed:(id)sender
{
int indexOfVoucher = [_arrayVouchers indexOfObject:_voucher];
indexOfVoucher--;
self.voucher = [_arrayVouchers objectAtIndex:indexOfVoucher];
[self updateViewWithVoucherWithScrolling:YES];
}
- (IBAction)rightArrowPressed:(id)sender
{
int indexOfVoucher = [_arrayVouchers indexOfObject:_voucher];
indexOfVoucher++;
self.voucher = [_arrayVouchers objectAtIndex:indexOfVoucher];
[self updateViewWithVoucherWithScrolling:YES];
}
- (void)updateViewWithVoucherWithScrolling:(BOOL)withScrolling
{
int indexOfVoucher = [_arrayVouchers indexOfObject:_voucher];
_leftArrowButton.hidden = _rightArrowButton.hidden = NO;
if (indexOfVoucher == 0)
{
_leftArrowButton.hidden = YES;
}
else if (indexOfVoucher == [_arrayVouchers count] - 1)
{
self.rightArrowButton.hidden = YES;
}
if (withScrolling)
{
[self scrollScrollViewToIndex:indexOfVoucher animated:YES];
}
}
update:
working code according to Mar0ux's advice
- (void)scrollScrollViewToIndex:(int)index animated:(BOOL)animated
{
NSLog(#"scrolled to index: %d", index);
CGFloat offsetX = CGRectGetWidth(_scrollMain.frame) * index;
if (animated)
{
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState //Multiple options
animations:^ {
// [self.scrollMain setContentOffset:CGPointMake(offsetX, 0) animated:NO];
CGRect scrollRect = CGRectMake(offsetX, 0, CGRectGetWidth(_scrollMain.frame), CGRectGetHeight(_scrollMain.frame));
[_scrollMain scrollRectToVisible:scrollRect animated:NO];
}
completion:^ (BOOL finished) {
}];
}
else
{
CGRect scrollRect = CGRectMake(offsetX, 0, CGRectGetWidth(_scrollMain.frame), CGRectGetHeight(_scrollMain.frame));
[_scrollMain scrollRectToVisible:scrollRect animated:NO];
}
}
You can always animate the contentOffset property yourself and use UIViewAnimationOptionBeginFromCurrentState option. As soon as the second animation begins, the first will end, and by using current state option, the second animation will start from where the first left off.
A few suggestions:
1) do you really want the user hammering on the button while its scrolling? If so then I suggest that your UI design may need redesign.
2) when you perturb the UI in an action method, its best to post other UI actions by dispatching a block with the code to the main queue - the button hiliting will look better.
3) in your specific case, you could in the action method disable the button, then re-enable it when the scrolling has stopped.
The implementation of pinch zoom is been done by using UIScrollView and putting 'UIImageView` inside it, through following code:
-(void)viewDidLoad{
slideShowImageViewScrollView.maximumZoomScale = 4.0;
slideShowImageViewScrollView.minimumZoomScale = 1.0;
slideShowImageViewScrollView.clipsToBounds = YES;
slideShowImageViewScrollView.delegate = self;
slideShowImageViewScrollView.scrollEnabled = NO;
}
Then after checking zoomScale scrolling is been enabled as:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if (scrollView.zoomScale!=1.0)
{
slideShowImageViewScrollView.scrollEnabled = YES;
}
else
{
slideShowImageViewScrollView.scrollEnabled = NO;
}
}
And here is the view that is for zooming in, in scroll view
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return slideShowImageView;
}
Now using timer the image of UIImageView is been changed after a time period, by following code:
- (void) changeImageSlide
{
if (imageCounter>=totalNoOfImages-1)
{
imageCounter=0;
}
NSString *str = [NSString stringWithFormat:#"%#.jpg",[copyOf_myGlobleArrayOfImageIds objectAtIndex:imageCounter]];
mainSlideShowImageView setImage:[UIImage imageNamed:str]];
[mainSlideShowImageView setTag:[[copyOf_myGlobleArrayOfImageIds objectAtIndex:imageCounter] intValue]];
imageCounter++;
}
Only thing problamatic here is when the next image is loaded that is also, 'zoomed in', we want image in normal mode.
Can we achieve it(by coding it for zoom out in our changeImageSlide method or any where)?
If yes, then how?
If no, then is there any alternative?
Thank You.
As you load the new image, set the zoomScale of the scrollView to 1.0.
[slideShowImageViewScrollView setZoomScale:1.0 animated:NO];
You can set the zoom scale on the scrollview to reset it to 1.0. You could even store zoom levels against images so that it preserves the individual zoom setting per image.
What's the simplest way to have a scroll view (with pagingEnabled set to YES) have a page width set to something other than the scroll view's bounds?
Let me give an example. Suppose I have a scroll view with 10 items, each 150 pixels wide, and my scroll view is 300 pixels wide. If I start with views 1 and 2 visible and scroll horizontally to the right, I want the next "page" to show items 2 and 3. If I scroll one more page to the right, I would see items 3 and 4.
Has anyone done this? If not, what strategy would you use?
Alexander Repty just blogged about this very topic: http://blog.proculo.de/archives/180-Paging-enabled-UIScrollView-With-Previews.html. Short answer: it's not as simple as just changing the width of the 'page', but it's not too hard.
Mike,
use the approach from my blog that Ben linked you to. Make the UIScrollView half the size of the surrounding view (i.e. 150px) and move it to the left instead of leaving it centered.
That should give you exactly the behaviour you wanted.
Cheers,
Alex
It can surely be done with the help of scrollview delegate methods & content-offset.
Below is the detailed code for the same.
int start;
int end;
int k=0;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView1
{
pageControlUsed = NO;
start = scrollView.contentOffset.x;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView1 willDecelerate:(BOOL)decelerate
{
end = scrollView.contentOffset.x;
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView1{
int diff = end-start;
if (diff>0)
{
k=k+150;
[scrollView setContentOffset:CGPointMake(k, 0) animated:YES];
}
else {
k=k-150;
[scrollView setContentOffset:CGPointMake(k, 0) animated:YES];
}
}
Simply override scrollViewWillEndDragging
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
CGFloat pageWidth = 160;
targetContentOffset->x = pageWidth * (int)(targetContentOffset->x / pageWidth);
}
UIScrollView *album = [[UIScrollView alloc] init];
album.translatesAutoresizingMaskIntoConstraints = NO;
album.pagingEnabled = YES;
album.clipsToBounds = NO;
album.showsHorizontalScrollIndicator = NO;
album.contentInset = UIEdgeInsetsMake(0, -50, 0, -50);
[view addSubview:album];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-50-[_album(220)]" options:0 metrics:metrics views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_album(300)]" options:0 metrics:metrics views:views]];
this works fine on iOS 7.1~ 8.2
I'm using a UITextView to roughly replicate the SMS text box above the keyboard. I'm using a UITextView instead of a field so that it can expand with multiple lines.
The problem is that, in my UITextView, the correction suggestions pop up below the text, causing them to be partially obscured by the keyboard.
In the SMS app, the suggestions pop up above the text. The placement does not appear to be a property of UITextView, or UITextInputTraits.
Any idea how to replicate this behavior? Thanks!
The problem is that the Keyboard is implemented as a separate UIWindow, rather than as a view within the main UIWindow, so layout with it is tricky. Here are some pointers in the right direction:
Hunt through the application's -windows property to find the private UITextEffectsWindow window and figure out its frame. This is the keyboard
Hunt through the TextView's subviews to find the private UIAutocorrectInlinePrompt view. This is the autocorrect bubble.
Move that subview into a separate wrapper view (added to the TextView) and then move that wrapper view so it's above the above-mentioned keyboard window.
You'll notice two mentions of "private" above. That carries all the relevant caveats. I have no idea why Apple has allowed the problem to persist when even their apps have had to work around it.
By doing the search for the UIAutocorrectInlinePrompt in an overridden or swizzled layoutSubViews it is possible to alter the layout of the correction so that it appears above. You can do this without calling any private APIs by looking for the subs views of particular classes positioned in a way you'd expect them. This example works out which view is which, checks to see that the correction is not already above the text and moves the correction above, and draws it on the window so that it is not bounded by the UITextView itself. Obviously if apple change the underlying implementation then this will fail to move correction. Add this to your overriden or swizzled layoutSubViews implementation.
- (void) moveSpellingCorrection {
for (UIView *view in self.subviews)
{
if ([[[view class] description] isEqualToString:#"UIAutocorrectInlinePrompt"])
{
UIView *correctionShadowView = nil; // [view correctionShadowView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectShadowView"])
{
correctionShadowView = subview;
break;
}
}
if (correctionShadowView)
{
UIView *typedTextView = nil; //[view typedTextView];
UIView *correctionView = nil; //[view correctionView];
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] isEqualToString:#"UIAutocorrectTextView"])
{
if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
{
correctionView = subview;
}
else
{
typedTextView = subview;
}
}
}
if (correctionView && typedTextView)
{
CGRect textRect = [typedTextView frame];
CGRect correctionRect = [correctionView frame];
if (textRect.origin.y < correctionRect.origin.y)
{
CGAffineTransform moveUp = CGAffineTransformMakeTranslation(0,-50.0);
[correctionView setTransform: moveUp];
[correctionShadowView setTransform: moveUp];
CGRect windowPos = [self convertRect: view.frame toView: nil ];
[view removeFromSuperview];
[self.window addSubview: view];
view.frame = windowPos;
}
}
}
}
}
}
Actually doing
textview.scrollEnabled = NO;
will set the bubble on top of the text... the caveat is that you lose scrolling, in my case it wasn't a problem due to havinng a textfield only for input purposes with character limit
Actually, the keyboard simply uses the result of -[UITextInput textInputView] to determine where to put the correction view (and to ask if your view supports correction). So all you need to do is this:
- (UIView *)textInputView {
for (UIWindow *window in [UIApplication sharedApplication].windows) {
if ([window isKindOfClass:NSClassFromString(#"UITextEffectsWindow")] &&
window != self.window) {
return window;
}
}
// Fallback just in case the UITextEffectsWindow has not yet been created.
return self;
}
Note that you'll likely also need to update -[UITextInput firstRectForRange:] to use the coordinate system of the window / device, so you can do this:
- (CGRect)firstRectForRange:(CoreTextTokenTextRange *)range {
CGRect firstRect = [self firstRectForRangeInternal:range];
return [self convertRect:firstRect toView:[self textInputView]];
}
(In the above context, self is a class that implements UITextInput).
If the bottom of your UITextView clears the keyboard, you should be able to just resize your UITextView to be tall enough to see the corrections. The corrections themselves don't display outside of the UITextView's frame.
If you want to mimic what you are getting in the SMS app (corrections above), you'll probably have to roll your own.
Putting the below method, adjustAutocorrectPromptView in layoutSubviews worked for me in portrait and landscape. I have a category that provides the bottom and top methods on view but you get the idea.
NSArray * subviewsWithDescription(UIView *view, NSString *description)
{
return [view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"class.description == '%#'", description]]];
}
- (void) adjustAutocorrectPromptView;
{
UIView *autocorrectPromptView = [subviewsWithDescription(self, #"UIAutocorrectInlinePrompt") lastObject];
if (! autocorrectPromptView)
{
return;
}
UIView *correctionShadowView = [subviewsWithDescription(autocorrectPromptView, #"UIAutocorrectShadowView") lastObject];
if (! correctionShadowView)
{
return;
}
UIView *typedTextView = nil; //[view typedTextView];
UIView *correctionView = nil; //[view correctionView];
for (UIView *subview in subviewsWithDescription(autocorrectPromptView, #"UIAutocorrectTextView"))
{
if (CGRectContainsRect(correctionShadowView.frame,subview.frame))
{
correctionView = subview;
}
else
{
typedTextView = subview;
}
}
if (correctionView && typedTextView)
{
if (typedTextView.top < correctionView.top)
{
correctionView.bottom = typedTextView.top;
correctionShadowView.center = correctionView.center;
}
}
}
Make sure your view controller delegate is listening to the notification when the keyboard pops up so that you resize your UITextView so that the keyboard doesn't obscure the UITextView. Then your correction won't be obscured by the keyboard. See:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/12641-uitextview-scroll-while-editing.html
Here is a copy of the code from that page in case the original link is broken:
// the amount of vertical shift upwards keep the Notes text view visible as the keyboard appears
#define kOFFSET_FOR_KEYBOARD 140.0
// the duration of the animation for the view shift
#define kVerticalOffsetAnimationDuration 0.50
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
}
- (IBAction)backgroundClick:(id)sender
{
[latitudeField resignFirstResponder];
[longitudeField resignFirstResponder];
[notesField resignFirstResponder];
if (viewShifted)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:kVerticalOffsetAnimationDuration];
CGRect rect = self.view.frame;
rect.origin.y += kOFFSET_FOR_KEYBOARD;
rect.size.height -= kOFFSET_FOR_KEYBOARD;
self.view.frame = rect;
[UIView commitAnimations];
viewShifted = FALSE;
}
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
if (!viewShifted) { // don't shift if it's already shifted
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:kVerticalOffsetAnimationDuration];
CGRect rect = self.view.frame;
rect.origin.y -= kOFFSET_FOR_KEYBOARD;
rect.size.height += kOFFSET_FOR_KEYBOARD;
self.view.frame = rect;
[UIView commitAnimations];
viewShifted = TRUE;
}
return YES;
}