UIScrollView disable vertical bounce only at bottom - objective-c

I've been searching for a way to disable vertical bounce for a UIScrollView but only at bottom. I still need to have the bounce at the top.
Couldn't find anything. Is this possible?
Thanks in advance!

Use the UIScrollViewDelegate method scrollViewDidScroll to check the content offset of the scrollview and more or less check if the user is trying to scroll beyond the bottom bounds of the scroll view. Then just use this to set the scroll back to the end of the scroll view. It happens so rapidly that you can't even tell that the scroll view bounced or extended beyond its bounds at all.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height) {
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentSize.height - scrollView.frame.size.height)];
}
}

To disable vertical bounce at the top in Swift:
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 {
scrollView.contentOffset.y = 0
}
}
To disable vertical bounce at the bottom in Swift:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.height {
scrollView.contentOffset.y = scrollView.contentSize.height - scrollView.bounds.height
}
}

Based on 0x7fffffff's answer I've created a class:
VerticalBounceControl.h
#import <UIKit/UIKit.h>
#interface VerticalBounceControl : NSObject
#property (nonatomic, assign) BOOL bounce;
- (id) initWithScrollView:(UIScrollView*)scrollView bounceAtTop:(BOOL)bounceAtTop bounceAtBottom:(BOOL)bounceAtBottom;
#end
VerticalBounceControl.m
#import "VerticalBounceControl.h"
#interface VerticalBounceControl ()
#property (nonatomic, retain) UIScrollView* scrollView;
#property (nonatomic, assign) BOOL bounceAtTop;
#property (nonatomic, assign) BOOL bounceAtBottom;
#end
#implementation VerticalBounceControl
- (id) initWithScrollView:(UIScrollView*)scrollView bounceAtTop:(BOOL)bounceAtTop bounceAtBottom:(BOOL)bounceAtBottom {
self = [super init];
if(self) {
self.bounce = YES;
self.scrollView = scrollView;
self.bounceAtTop = bounceAtTop;
self.bounceAtBottom = bounceAtBottom;
[self.scrollView addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];
}
return self;
}
- (void) dealloc {
[self.scrollView removeObserver:self forKeyPath:#"contentOffset"];
self.scrollView = nil;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"contentOffset"]) {
if (self.bounce && ((self.scrollView.contentOffset.y<=0 && self.bounceAtTop) || (self.scrollView.contentOffset.y>=self.scrollView.contentSize.height-1 && self.bounceAtBottom))) {
self.scrollView.bounces = YES;
} else {
self.scrollView.bounces = NO;
}
}
}
#end
that can be used without the caller being a delegate of UIScrollView like this:
VerticalBounceControl* bounceControl = [[VerticalBounceControl alloc] initWithScrollView:anyScrollView bounceAtTop:YES bounceAtBottom:NO];
This way you can have this bounce behavior for UIScrollViews that you don't want to alter their delegate (like UIWebView's one).
Again, thanks go #0x7fffffff for the solution!

Swift 3:
This works to disable vertical bounce only at the bottom:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.height {
scrollView.contentOffset.y = scrollView.contentSize.height - scrollView.bounds.height
}
}

Let's disable the bounces when scroll to the bottom.
scrollView.bounces = scrollView.contentOffset.y < scrollView.contentSize.height - scrollView.frame.height

I added fabsf() to 0x7fffffff♦'s solution to also cater for negative(downward) scroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (fabsf(scrollView.contentOffset.y) >= scrollView.contentSize.height - scrollView.frame.size.height) {
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentSize.height - scrollView.frame.size.height)];
}
}

I have been looking a solution to disable bottom bounce only for a scrollview with wider contentSize width than device since I am using paging. None of those work for me but I found a way to do it. I thought I should share this since I wasted two days just to do this.
I am using Swift 4 but this applies to objective c if you know how to convert this which should be easy if you have experience with both.
This works in a scrollview with device screen frame as frame.
Add variable to be aware for scrollview's current page.
var currentPage : Int!
Set initial value
currentPage = 0
Update currentPage after scrolling
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let width = scrollView.frame.size.width
let page = (scrollView.contentOffset.x + (0.5 * width)) / width
currentPage = Int(page)
}
Filter bottom scrolling and update contentOffset
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if CGFloat(currentPage) * screenWidth < scrollView.contentOffset.x || CGFloat(currentPage) * screenWidth < scrollView.contentOffset.y || (((CGFloat(currentPage) * screenWidth - scrollView.contentOffset.x) >= 0) && scrollView.contentOffset.y > 0){
scrollView.contentOffset.y = 0
}
}
CGFloat(currentPage) * screenWidth < scrollView.contentOffset.x || CGFloat(currentPage) * screenWidth < scrollView.contentOffset.y will filter bottom left and right scrolling
(((CGFloat(currentPage) * screenWidth - scrollView.contentOffset.x) >= 0) && scrollView.contentOffset.y > 0) will filter bottom mid scrolling
scrollView.contentOffset.y = 0 will stop it from scrolling

Related

NSPopover show relative to NSStatusItem

This is my code:
if #available(OSX 10.10, *) {
if let b = statusItem.button {
popover.showRelativeToRect(b.bounds, ofView: b, preferredEdge: .MinY)
}
} else {
}
The else block is for OS X Mavericks because NSStatusItem.button is not available. Is there a simple way of showing the popover relative to the status item? If not, is it possible to show the popover in the center of the screen instead without the arrow?
before you had access to the statusitem button you had to provide your own view. Then all works the same
to retain the original behaviour, draw a custom view that looks like a status item ;)
e.g.
#interface DDQuickMenuStatusItemView : NSView
#property(weak) NSStatusItem *item;
//...
#end
#implementation DDQuickMenuStatusItemView
//...
- (void)drawRect:(NSRect)dirtyRect {
NSImage *image = nil;
if(self.item) {
[self.item drawStatusBarBackgroundInRect:self.bounds withHighlight:NO];
image = self.item.image;
}
if(image) {
NSRect r = self.bounds;
r.size = [image size];
r = [self.class centerRect:r inRect:self.bounds];
r = [self centerScanRect:r];
[image drawInRect:r fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1];
}
}
#pragma mark -
+ (CGRect)centerRect:(CGRect)rect inRect:(CGRect)inRect
{
CGRect result = rect;
result.origin.x = inRect.origin.x + (inRect.size.width - result.size.width)*0.5f;
result.origin.y = inRect.origin.y + (inRect.size.height - result.size.height)*0.5f;
return result;
}
#end
Note that the view is a sample and not production ready ;)

scrollViewDidScroll not called -- scrollViewDidEndDecelerating is called

I have a custom view replacing the keyboard and I'm trying to get it to scroll offscreen when the user scrolls down.
My original scrollViewDelegate methods worked EXCEPT there was a delay between user scrolling and view animation because I was using scrollViewDidEndDecelerating and it took about 1 second for this to be called after the user started scrolling.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.isUserScrolling = YES;
// Save to tell if scrolling up or down
initialContentOffset = scrollView.contentOffset.y;
previousContentDelta = 0.f;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
self.isUserScrolling = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.isUserScrolling = NO;
// If scrolled down
CGFloat prevDelta = previousContentDelta;
CGFloat delta = scrollView.contentOffset.y - initialContentOffset;
if (delta > 0.f && prevDelta <= 0.f) {
} else if (delta < 0.f && prevDelta >= 0.f) {
[self animateKeyBoardSpace:[self rectForKeyboardSpace:NO] curve:UIViewAnimationCurveEaseInOut duration:0.25];
}
previousContentDelta = delta;
}
So I am trying to now check for downward scrolling in scrollViewDidScroll and call animateKeyBoardSpace there like so:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// ScrollDirection scrollDirection;
if (lastContentOffset > scrollView.contentOffset.y) {
[self animateKeyBoardSpace:[self rectForKeyboardSpace:NO] curve:UIViewAnimationCurveEaseInOut duration:0.25];
}
else if (lastContentOffset < scrollView.contentOffset.y) {
}
lastContentOffset = scrollView.contentOffset.x;
}
HOWEVER, scrollViewDidScroll isn't even getting called. It's in the same tableViewController, the delegate is set, and the other delegate methods are getting called.
I was working in the superclass and I had a stub for scrollViewDidScroll in the subclass from some time ago. This was capturing the delegate call.
I just had to delete the subclass stub.

Several UITextFields in a UIScrollView, covered by the keyboard

I have several UITextFields in a UIScrollView. When I edit one of them and the keyboard pops-up, the field is covered by the keyboard.
I would like to scroll up the view, I thought this was automatically done by iOS but it seems not to be the case.
I'm currently using this method to scroll the view, but it doesn't work very well.
- (void)scrollToView:(UIView *)view
{
CGRect viewFrame = [[edit scrollView] convertRect:[view frame] fromView:[view superview]];
CGRect finalFrame = CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.width, (viewFrame.size.height + (inputAccessory.frame.size.height) + 4.0));
[[edit scrollView] scrollRectToVisible:finalFrame animated:YES];
}
thanks
In my case, what I do is reduce the size of the scrollView in the Editing Did Begin event of the UITextField, like this:
- (IBAction)didEnterInTextField:(id)sender
{
[sender becomeFirstResponder];
// Resize the scroll view to reduce the keyboard height
CGRect scrollViewFrame = self.scrollView.frame;
if (scrollViewFrame.size.height > 300) {
scrollViewFrame.size.height -= 216;
self.scrollView.frame = scrollViewFrame;
}
// Scroll the view to see the text field
UITextField *selectedTextField = (UITextField *)sender;
float yPosition = selectedTextField.frame.origin.y - 60;
float bottomPositon = self.scrollView.contentSize.height - self.scrollView.bounds.size.height;
if (yPosition > bottomPositon) {
yPosition = bottomPositon;
}
[self.scrollView setContentOffset:CGPointMake(0, yPosition) animated:YES];
}
Try this sample code TPKeyboardAvoiding
Its easy to implement. Just drag and drog the custom classes and change the Custom class from UIScrollview to TPKeyboardAvoidingScrollView in the xib
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
set the frame of scroll view in these two methods.. when keyboards shows or hides.
or set the scrollRectToVisible of scroll view
- (void)scrollViewToCenterOfScreen:(UIView *)theView
{
CGFloat viewCenterY = theView.center.y;
CGFloat availableHeight;
CGFloat y;
if(!isReturned)
{
if (UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad)
{
availableHeight = 1080;
}
else
{
availableHeight = 220;
}
}
else
{
if (UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad)
{
availableHeight = 1400;
}
else
{
availableHeight = 530;
}
}
y = viewCenterY - availableHeight / 2.0;
if (y < 0) {
y = 0;
}
[sclView setContentOffset:CGPointMake(0, y) animated:YES];
}
Call this method in textfield or textview begin editing.It will works.
Here is nice tutorial on same
http://www.iphonesampleapps.com/2009/12/adjust-uitextfield-hidden-behind-keyboard-with-uiscrollview/
Hope it helps you

How to eliminate the left and right margins of a grouped UITableView

If I have a grouped UITableView, it comes with a grayish border/buffer around the edge of it. Is there any way to tell how large this buffer is, or equivalently, how big the actual white cell inside it is?
This is how you can eliminate the left and right margins of a grouped UITableView. Just subclass UITableViewCell. Note that if you want the margin to be something other than 0 just change the setFrame method to suit your needs.
//In CustomGroupedCell.h
#import <UIKit/UIKit.h>
#interface CustomGroupedCell : UITableViewCell
#property (nonatomic, weak) UITableView *myTableView;
#end
//In CustomGroupedCell.m
#import "CustomGroupedCell.h"
#implementation CustomGroupedCell
#synthesize myTableView = _myTableView;
- (void)setFrame:(CGRect)frame
{
frame = CGRectMake(- [self cellsMargin] , frame.origin.y, self.myTableView.frame.size.width + 2 *[self cellsMargin], frame.size.height);
self.contentView.frame = frame;
[super setFrame:frame];
}
- (CGFloat)cellsMargin {
// No margins for plain table views
if (self.myTableView.style == UITableViewStylePlain) {
return 0;
}
// iPhone always have 10 pixels margin
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
return 10;
}
CGFloat tableWidth = self.myTableView.frame.size.width;
// Really small table
if (tableWidth <= 20) {
return tableWidth - 10;
}
// Average table size
if (tableWidth < 400) {
return 10;
}
// Big tables have complex margin's logic
// Around 6% of table width,
// 31 <= tableWidth * 0.06 <= 45
CGFloat marginWidth = tableWidth * 0.06;
marginWidth = MAX(31, MIN(45, marginWidth));
return marginWidth;
}
#end

How do I vertically center align text in a NSTextField?

I have an NSTextField and I want to vertically center align the text in it. Basically I need the NSTextField answer of How do I vertically center UITextField Text?
Anyone got some pointers? Thanks!
You could subclass NSTextFieldCell to do what you want:
MDVerticallyCenteredTextFieldCell.h:
#import <Cocoa/Cocoa.h>
#interface MDVerticallyCenteredTextFieldCell : NSTextFieldCell {
}
#end
MDVerticallyCenteredTextFieldCell.m:
#import "MDVerticallyCenteredTextFieldCell.h"
#implementation MDVerticallyCenteredTextFieldCell
- (NSRect)adjustedFrameToVerticallyCenterText:(NSRect)frame {
// super would normally draw text at the top of the cell
NSInteger offset = floor((NSHeight(frame) -
([[self font] ascender] - [[self font] descender])) / 2);
return NSInsetRect(frame, 0.0, offset);
}
- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView
editor:(NSText *)editor delegate:(id)delegate event:(NSEvent *)event {
[super editWithFrame:[self adjustedFrameToVerticallyCenterText:aRect]
inView:controlView editor:editor delegate:delegate event:event];
}
- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView
editor:(NSText *)editor delegate:(id)delegate
start:(NSInteger)start length:(NSInteger)length {
[super selectWithFrame:[self adjustedFrameToVerticallyCenterText:aRect]
inView:controlView editor:editor delegate:delegate
start:start length:length];
}
- (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)view {
[super drawInteriorWithFrame:
[self adjustedFrameToVerticallyCenterText:frame] inView:view];
}
#end
You can then use a regular NSTextField in Interface Builder, and specify MDVerticallyCenteredTextFieldCell (or whatever you want to name it) as the custom class for the text field's text field cell (select the textfield, pause, then click the textfield again to select the cell inside the text field):
Swift 3.0 version (Create a custom subclass for NSTextFieldCell):
override func drawingRect(forBounds rect: NSRect) -> NSRect {
var newRect = super.drawingRect(forBounds: rect)
let textSize = self.cellSize(forBounds: rect)
let heightDelta = newRect.size.height - textSize.height
if heightDelta > 0 {
newRect.size.height -= heightDelta
newRect.origin.y += (heightDelta / 2)
}
return newRect
}
It's better to use boundingRectForFont and the function ceilf() when calculating possible maximum font height, because the above-mentioned solution causes text to be cut off under the baseline. So the adjustedFrameToVerticallyCenterText: will look like this
- (NSRect)adjustedFrameToVerticallyCenterText:(NSRect)rect {
CGFloat fontSize = self.font.boundingRectForFont.size.height;
NSInteger offset = floor((NSHeight(rect) - ceilf(fontSize))/2);
NSRect centeredRect = NSInsetRect(rect, 0, offset);
return centeredRect;
}