I did my Top View and Bottom View (UIViews) hide while the UITableView is scrolling. Now, I need to check if the user begin drag the UITableview to up again and back the uiviews for the initial position. I have the following code to do the first step: hidden/show while scrolling uitableview
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
if(!self.isScrollingFast) {
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
NSInteger yOffset = scrollView.contentOffset.y;
if (yOffset > 0) {
self.tabBar.frame = CGRectMake(self.tabBar.frame.origin.x, self.originalFrame.origin.y + yOffset, self.tabBar.frame.size.width, self.tabBar.frame.size.height);
self.viewTopo.frame = CGRectMake(self.viewTopo.frame.origin.x, self.originalFrameTopo.origin.y - yOffset, self.viewTopo.frame.size.width, self.viewTopo.frame.size.height);
if(self.originalFrameHidingView.origin.y - yOffset >= 0) {
self.hidingView.frame = CGRectMake(self.hidingView.frame.origin.x, self.originalFrameHidingView.origin.y - yOffset, self.hidingView.frame.size.width, self.hidingView.frame.size.height);
}
else {
self.hidingView.frame = CGRectMake(self.hidingView.frame.origin.x, -10, self.hidingView.frame.size.width, self.hidingView.frame.size.height);
}
[self.tbPertos setFrame:CGRectMake(self.tbPertos.frame.origin.x, self.hidingView.frame.origin.y + self.hidingView.frame.size.height, self.tbPertos.frame.size.width, self.tbPertos.frame.size.height)];
if(self.tbPertos.frame.size.height + self.tbPertos.frame.origin.y + yOffset <= screenHeight)
self.tbPertos.frame = CGRectMake(self.tbPertos.frame.origin.x, self.tbPertos.frame.origin.y, self.tbPertos.frame.size.width, self.tbPertos.frame.size
.height+yOffset);
else {
self.tbPertos.frame = CGRectMake(self.tbPertos.frame.origin.x, self.tbPertos.frame.origin.y, self.tbPertos.frame.size.width, screenHeight - self.tbPertos.frame.origin.y);
}
}
if (yOffset < 1) {
self.tabBar.frame = self.originalFrame;
self.viewTopo.frame = self.originalFrameTopo;
self.hidingView.frame = self.originalFrameHidingView;
self.tbPertos.frame = CGRectMake(self.tbPertos.frame.origin.x, self.hidingView.frame.origin.y + self.hidingView.frame.size.height, self.tbPertos.frame.size.width, screenHeight - self.tbPertos.frame.origin.y);
}
}
}
And there's the code which I'm trying to do the Top and Bottom View reappear when the user begin scroll up. Independently wheres the scroll offset.
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
CGPoint currentOffset = scrollView.contentOffset;
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval timeDiff = currentTime - self.lastOffsetCapture;
CGFloat distance = currentOffset.y - self.lastOffset.y;
//The multiply by 10, / 1000 isn't really necessary.......
if (distance < 0) {
if(!self.isScrollingFast) {
NSLog(#"voltar posicao normal");
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelay:1.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
self.tabBar.frame = self.originalFrame;
self.viewTopo.frame = self.originalFrameTopo;
self.hidingView.frame = self.originalFrameHidingView;
self.tbPertos.frame = self.originalFrameTbPertos;
self.isScrollingFast = YES;
[UIView commitAnimations];
}
} else {
self.isScrollingFast = NO;
}
self.lastOffset = currentOffset;
self.lastOffsetCapture = currentTime;
}
Here i implemented code for UIView Hide / Show when tableview scrolling. When tableview scrolling down then UIView is hidden and when scrolling up then UIView show. I hope it's working for you...!
Step 1:- Make one property in .h file
#property (nonatomic) CGFloat previousContentOffset;
Step 2:- Write down this code in scrollViewDidScroll Method.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat currentContentOffset = scrollView.contentOffset.y;
if (currentContentOffset > self.previousContentOffset) {
// scrolling towards the bottom
[self.subButtonView setHidden:YES];
} else if (currentContentOffset < self.previousContentOffset) {
// scrolling towards the top
[self.subButtonView setHidden:NO];
}
self.previousContentOffset = currentContentOffset;
}
Related
I am replicating Facebook/Apple News hide/show nav bar functionality. I can hide/show the nav bar but I don't know how to calculate the scroll view's content inset as the nav bar frame.origin.y changes. I want the scroll view content inset to follow nav bar's position so that it all looks smooth.
Here's what I'm doing:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat navBarTopLength = self.navigationController.topLayoutGuide.length; // 20
CGRect frame = self.navigationController.navigationBar.frame;
CGFloat size = frame.size.height - (navBarTopLength + 1);
CGFloat framePercentageHidden = ((navBarTopLength - frame.origin.y) / (frame.size.height - 1));
CGFloat scrollOffset = scrollView.contentOffset.y;
CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset;
CGFloat scrollHeight = scrollView.frame.size.height;
CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom;
if (scrollOffset <= -scrollView.contentInset.top) {
frame.origin.y = navBarTopLength; // showing nav bar
} else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) {
frame.origin.y = -size;
} else {
frame.origin.y = MIN(navBarTopLength, MAX(-size, frame.origin.y - scrollDiff));
}
CGFloat top;
if (self.previousScrollViewYOffset > scrollOffset) {
NSLog(#"scrolling down"); //scrolling down
top = self.navigationController.navigationBar.frame.size.height + frame.origin.y;
} else {
NSLog(#"scrolling up"); //scrolling up
top = (navBarTopLength + 1) + frame.origin.y;
}
[self.navigationController.navigationBar setFrame:frame];
[self updateBarButtonItems:(1 - framePercentageHidden)];
scrollView.contentInset = UIEdgeInsetsMake(top, 0, 0, 0);
self.previousScrollViewYOffset = scrollOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self stoppedScrolling];
}
- (void)updateBarButtonItems:(CGFloat)alpha {
[self.navigationItem.leftBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem *item, NSUInteger i, BOOL *stop) {
item.customView.alpha = alpha;
}];
[self.navigationItem.rightBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem *item, NSUInteger i, BOOL *stop) {
item.customView.alpha = alpha;
}];
self.navigationItem.titleView.alpha = alpha;
self.navigationController.navigationBar.tintColor =[self.navigationController.navigationBar.tintColor colorWithAlphaComponent:alpha];
}
- (void)animateNavBarTo:(CGFloat)y {
[UIView animateWithDuration:0.2
animations:^{
CGRect frame = self.navigationController.navigationBar.frame;
BOOL hide = frame.origin.y >= y;
CGFloat alpha = (hide ? 0 : 1);
frame.origin.y = y;
[self.navigationController.navigationBar setFrame:frame];
[self updateBarButtonItems:alpha];
self.navBarHidden = hide;
}];
}
- (void)stoppedScrolling {
CGRect frame = self.navigationController.navigationBar.frame;
if (frame.origin.y < 20) {
[self animateNavBarTo:-(frame.size.height - 21)];
}
}
I know that I should be changing the content inset in scrollViewDidScroll as I'm calculating a new frame for the nav bar. The way I'm currently calculating new top inset makes the whole UI shake and everything breaks shortly after with BAD ACCESS
i am using this code for zooming this is perfect but i want zoomout after this zoomin.
CGFloat s = 3;
CGPoint p = CGPointMake(150, 300);
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, s, s);
CGFloat h = self.view.frame.size.height;
CGFloat w = self.view.frame.size.width;
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
CGFloat cx = w/2-s*(p.x-w/2);
CGFloat cy = h/2-s*(p.y-h/2);
self.view.center = CGPointMake(cx, cy); //was: (w*s/2,h-h*s/2);
} completion:^(BOOL finished) {}];
Try using hitTest and scroll view delegate method i.e, viewForZoomingInScrollView as follows.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return yourScrollView;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return yourImageView;
}
You can set the selected image in another scrollview and handle the zoom.
- (void)setImage:(UIImage *)image {
[self.imageView setImage:image];
[self.imageView sizeToFit];
//add this image view as subview of scroll view here and set content size accordingly.
CGFloat zoomScale = 1.0;
if (self.imageView.frame.size.width < self.imageView.frame.size.height) {
zoomScale = (self.scrollView.frame.size.width / self.imageView.frame.size.width);
} else {
zoomScale = (self.scrollView.frame.size.height / self.imageView.frame.size.height);
}
[self.scrollView setContentSize:self.imageView.frame.size];
[self.scrollView setMinimumZoomScale:zoomScale];
[self.scrollView setMaximumZoomScale:1.0];
[self.scrollView setZoomScale:zoomScale];
[self.scrollView setContentOffset:CGPointMake((self.imageView.frame.size.width - self.scrollView.frame.size.width) / 2,
(self.imageView.frame.size.height - self.scrollView.frame.size.height) / 2)];
}
Very similar to a UIRefreshControl, I'm trying to put a UIView on top of a UITableView. Dragging down the table view should reveal the view and have it stay there. Dragging up should hide it again and then scroll the table view. When hidden the table view should scroll normally. Scrolling back to the top should either reveal the hidden view again, or snap to the hidden state. Ultimately the revealed view should contain some buttons or a segmented control. It should look and behave very similar to the OmniFocus App.
Hidden View in OmniFocus
Revealed View in OmniFocus
This is how far I got. Especially the snapping back to the hidden state when the table view scrolls back up does not work. If you time it right you'll end up stuck in the middle of top view, which is exactly not what I want.
static CGFloat const kTopViewHeight = 40;
#interface ViewController ()
#property (nonatomic, weak) UIView *topView;
#property (nonatomic, assign) CGFloat dragStartY;
#end
#implementation ViewController
#pragma mark - View Lifecycle
- (void)viewDidLoad
{
CGRect topViewFrame = CGRectMake(0.0, -kTopViewHeight, 320, kTopViewHeight);
UIView *myView = [[UIView alloc] initWithFrame:topViewFrame];
myView.backgroundColor = [UIColor greenColor]; // DEBUG
self.topView = myView;
[self.tableView addSubview:myView];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(#"scrollViewWillBeginDragging %#", NSStringFromCGPoint(scrollView.contentOffset));
self.dragStartY = scrollView.contentOffset.y;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"scrollViewDidScroll %#", NSStringFromCGPoint(scrollView.contentOffset));
if (scrollView.contentOffset.y > 0) {
// reset the inset
scrollView.contentInset = UIEdgeInsetsZero;
} else if (scrollView.contentOffset.y >= -kTopViewHeight) {
// set the inset for the section headers
scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
} else if (scrollView.contentOffset.y < -kTopViewHeight) {
// don't scroll further when the topView's height is reached
scrollView.contentInset = UIEdgeInsetsMake(-kTopViewHeight, 0, 0, 0);
scrollView.contentOffset = CGPointMake(0, -kTopViewHeight);
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSLog(#"scrollViewDidEndDragging %#", NSStringFromCGPoint(scrollView.contentOffset));
CGFloat yOffset = scrollView.contentOffset.y;
if (yOffset < 0) {
BOOL dragDown = self.dragStartY > yOffset;
CGFloat dragThreshold = 10;
if (dragDown) {
if (yOffset <= -dragThreshold) {
[self snapDown:YES scrollView:scrollView];
} else {
[self snapDown:NO scrollView:scrollView];
}
} else if (!dragDown) {
if (yOffset >= dragThreshold - kTopViewHeight) {
[self snapDown:NO scrollView:scrollView];
} else {
[self snapDown:YES scrollView:scrollView];
}
}
}
}
- (void)snapDown:(BOOL)down scrollView:(UIScrollView *)scrollView
{
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState
animations:^{
if (down) {
// snap down
scrollView.contentOffset = CGPointMake(0, -kTopViewHeight);
scrollView.contentInset = UIEdgeInsetsMake(kTopViewHeight, 0, 0, 0);
} else {
// snap up
scrollView.contentOffset = CGPointMake(0, 0);
scrollView.contentInset = UIEdgeInsetsZero;
}
}
completion:nil];
}
//paging for data you can use this spinner
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[spinner stopAnimating];
spinner.hidesWhenStopped = NO;
spinner.frame = CGRectMake(0, 0, 320, 44);
tblView.tableFooterView = spinner;
tblView.tableFooterView.hidden = YES;
#pragma mark Table pull to refresh data....
- (void)scrollViewDidEndDragging:(UIScrollView *)aScrollView
willDecelerate:(BOOL)decelerate{
CGPoint offset = aScrollView.contentOffset;
CGRect bounds = aScrollView.bounds;
CGSize size = aScrollView.contentSize;
UIEdgeInsets inset = aScrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
float reload_distance = 50;
if(y > h + reload_distance && _nextPage) {
NSLog(#"load more data");
tblView.tableFooterView.hidden = NO;
// index for new page of data will increment here
index = index + 1;
[spinner startAnimating];
[self performSelector:#selector(requestData) withObject:nil afterDelay:1.0f];
}
}
// when you request for data and wants to stop spinner
CGPoint offset = tblView.contentOffset;
// if new page is there set bool
_nextPage = YES;
// want to remove spinner
tblView.tableFooterView.hidden = YES;
[spinner stopAnimating];
[tblView setContentOffset:offset animated:NO];
I have UIScrollView which has some views including UITextView. UITextView is bigger that the screen and has some text and has scrolling disabled. If the user tap on text view I would like to scroll my main scroll view to the position of the cursor in the text view. How can I do it? I tried use selectedRange but it doesn't seem to work.
Here is my a little more sofisticated solution:
- (void)scrollView:(UIScrollView *)scrollView scrollToTextInput:(UIResponder *)responder
{
if (!responder || ![responder conformsToProtocol:#protocol(UITextInput)] || ![responder isKindOfClass:[UIView class]]) {
return;
}
UIView<UITextInput> *textInput = (UIView<UITextInput> *)responder;
UIView *nextView = textInput;
while (nextView && (nextView != scrollView))
{
nextView = [nextView superview];
}
if (!nextView)
{
// Oh, this view is not from our `scrollView`.
return;
}
CGRect cursorRect = [textInput caretRectForPosition:textInput.selectedTextRange.start];
CGPoint cursorPoint = CGPointMake(CGRectGetMidX(cursorRect), CGRectGetMidY(cursorRect));
cursorPoint = [scrollView convertPoint:cursorPoint fromView:textInput];
CGFloat contentHeight = scrollView.contentSize.height;
CGFloat containerHeight = scrollView.frame.size.height;
CGFloat topInset = scrollView.contentInset.top;
CGFloat bottomInset = scrollView.contentInset.bottom;
CGFloat verticalInsets = scrollView.contentInset.top + scrollView.contentInset.bottom;
CGFloat offsetY = cursorPoint.y - (containerHeight - verticalInsets) / 2.f;
offsetY = MIN(MAX(offsetY, topInset), contentHeight - containerHeight + verticalInsets);
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, offsetY) animated:YES];
}
I did it in such way:
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[self performSelector:#selector(scrollToCursorPosition:) withObject:textView afterDelay:0.05f];
}
- (void)scrollToCursorPosition:(UITextView *)textView
{
UITextRange *range = textView.selectedTextRange;
UITextPosition *position = range.start;
CGRect cursorRect = [textView caretRectForPosition:position];
CGPoint cursorPoint = CGPointMake(textView.frame.origin.x + cursorRect.origin.x, textView.frame.origin.y + cursorRect.origin.y);
[self setContentOffset:CGPointMake((cursorPoint.x - 10) * self.zoomScale, (cursorPoint.y - 10) * self.zoomScale) animated:NO];
}
if someone knows the better solution then please write the answer and I will accept it.
My table view cells contain a circle in an UIView, indicating a value. I want to add the UIKit Dynamics attachment behaviour to that circle in order to for it to lag a bit when scrolling.
I don't want to attach the individual cells to each other but only the circle view to the UITableViewCell. The rest of the cell should scroll as usual.
Problem: The UITableViewCell has its origin always at (0, 0). How can I add the circle to a view that actually does move when scrolling?
I finally got it to work. The UITableView moves the coordinate system of every cell and of all views contained within that cell. Therefor I needed to manually move my view inside the UITableViewCell during scrolling while still referring to the initial anchor point.
The table view controller:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
BOOL scrollingUp = '\0';
if (self.lastContentOffset > scrollView.contentOffset.y) {
scrollingUp = YES;
}
else if (self.lastContentOffset < scrollView.contentOffset.y) {
scrollingUp = NO;
}
NSInteger offset = 64; // To compensate for the navigation bar.
if (scrollingUp) {
offset = offset - scrollView.contentOffset.y;
}
else {
offset = offset + scrollView.contentOffset.y;
}
// Limit the offset so the views will not disappear during fast scrolling.
if (offset > 10) {
offset = 10;
}
else if (offset < -10) {
offset = -10;
}
// lastContentOffset is an instance variable.
self.lastContentOffset = scrollView.contentOffset.y;
for (UITableViewCell *cell in self.tableView.visibleCells) {
// Use CoreAnimation to prohibit flicker.
[UIView beginAnimations:#"Display notification" context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationBeginsFromCurrentState:YES];
cell.view.frame = CGRectMake(cell.view.frame.origin.x, offset, cell.view.frame.size.width, cell.view.frame.size.height);
[UIView commitAnimations];
[cell.dynamicAnimator updateItemUsingCurrentState:cell.view];
}
}
The table view cell:
-(void)layoutSubviews {
[super layoutSubviews];
// _view is the animated UIView.
UIDynamicItemBehavior *viewBehavior = [[UIDynamicItemBehavior alloc] initWithItems:#[_view]];
viewBehavior.elasticity = 0.9f;
UIAttachmentBehavior *attachmentBehaviorView = [[UIAttachmentBehavior alloc] initWithItem:_view attachedToAnchor:CGPointMake(_anchorView.frame.origin.x + _anchorView.frame.size.width / 2.0f, _anchorView.frame.origin.y + _anchorView.frame.size.height / 2.0f)];
attachmentBehaviorView.damping = 8.0f;
attachmentBehaviorView.frequency = 4.0f;
attachmentBehaviorView.length = 0.0f;
[_dynamicAnimator addBehavior:viewBehavior];
[_dynamicAnimator addBehavior:attachmentBehaviorView];
}
You can change the anchorPoint of UIAttachmentBehavior during -[scrollViewDidScroll:]. You may refer to the following code snippet:
- (void)viewDidLoad
{
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIAttachmentBehavior *behavior1 = [[UIAttachmentBehavior alloc] initWithItem:self.circleView
attachedToAnchor:[self tableViewAnchor]];
behavior1.length = 10.0;
behavior1.damping = 0.3;
behavior1.frequency = 2.5;
[animator addBehavior:behavior1];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
behavior1.anchorPoint = [self.tableView convertPoint:[self tableViewAnchor] toView:self.view];
}
- (CGPoint)tableViewAnchor
{
return CGPointMake(160.0, 154.0); // return your target coordination w.r.t. the table view
}
Preview: